Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Revise seed config
Alexis Sellier committed 4 years ago
commit e6f4441be7b751c7c50047d11ece53f580409ce9
parent 3fcbffe307d00460dfeed7fcf57c8b7571a86ce0
17 files changed +182 -173
modified src/Profile.svelte
@@ -377,7 +377,9 @@
        </Message>
      {/await}
    {/if}
-
    <Projects {profile} {account} config={profile.config(config)} />
+
    {#if profile.seed?.valid}
+
      <Projects {profile} seed={profile.seed} {account} {config} />
+
    {/if}
  </main>

  <svelte:component this={setNameForm} entity={profile.org ?? new User(profile.address)} {config} on:close={() => setNameForm = null} />
modified src/api.ts
@@ -1,21 +1,20 @@
-
import type { Config } from '@app/config';
+
export interface Host {
+
  host: string;
+
  port: number;
+
}

export async function get(
  path: string,
  params: Record<string, any>,
-
  config: Config
+
  api: Host
): Promise<any> {
-
  if (! config.seed.api.host) {
-
    throw new Error("Seed host unavailable");
-
  }
-

  const query: Record<string, string> = {};
  for (const [key, val] of Object.entries(params)) {
    query[key] = val.toString();
  }

-
  const base = config.seed.api.host;
-
  const port = config.seed.api.port;
+
  const base = api.host;
+
  const port = api.port;
  const search = new URLSearchParams(query).toString();
  // Allow using the functionality with local runned http-api
  const isLocalhost = /^0.0.0.0$/.test(base);
modified src/base/orgs/View/Projects.svelte
@@ -8,11 +8,12 @@
  import Widget from '@app/base/projects/Widget.svelte';
  import type { Profile } from "@app/profile";
  import type { ProjectInfo, Anchor, PendingAnchor } from "@app/project";
-
  import { Seed } from "@app/base/seeds/Seed";
+
  import type { Seed } from "@app/base/seeds/Seed";
  import AnchorActions from "@app/base/profiles/AnchorActions.svelte";

  export let profile: Profile;
  export let config: Config;
+
  export let seed: Seed;
  export let account: string | null;

  let anchors: Record<string, Anchor> = {};
@@ -55,31 +56,29 @@
</style>

<div class="projects">
-
  {#if config.seed.api.host}
-
    {#await Seed.getProjects(config)}
-
      <Loading center />
-
    {:then projects}
-
      {#each projects as project}
-
        {@const anchor = anchors[project.urn]}
-
        {@const pendingAnchor = pendingAnchors[project.urn]}
-
        <div class="project">
-
          <Widget {project} {anchor} on:click={() => onClick(project)}>
-
            <span class="actions" slot="actions">
-
              {#if profile.org?.safe && account && anchor}
-
                {#if pendingAnchor} <!-- Pending anchor -->
-
                  <AnchorActions
-
                    {account} {config} anchor={pendingAnchor} safe={profile.org.safe}
-
                    on:success={() => loadAnchors()} />
-
                {/if}
+
  {#await seed.getProjects()}
+
    <Loading center />
+
  {:then projects}
+
    {#each projects as project}
+
      {@const anchor = anchors[project.urn]}
+
      {@const pendingAnchor = pendingAnchors[project.urn]}
+
      <div class="project">
+
        <Widget {project} {anchor} on:click={() => onClick(project)}>
+
          <span class="actions" slot="actions">
+
            {#if profile.org?.safe && account && anchor}
+
              {#if pendingAnchor} <!-- Pending anchor -->
+
                <AnchorActions
+
                  {account} {config} anchor={pendingAnchor} safe={profile.org.safe}
+
                  on:success={() => loadAnchors()} />
              {/if}
-
            </span>
-
          </Widget>
-
        </div>
-
      {/each}
-
    {:catch err}
-
      <Message error>
-
        <strong>Error: </strong> failed to load projects: {err.message}.
-
      </Message>
-
    {/await}
-
  {/if}
+
            {/if}
+
          </span>
+
        </Widget>
+
      </div>
+
    {/each}
+
  {:catch err}
+
    <Message error>
+
      <strong>Error: </strong> failed to load projects: {err.message}.
+
    </Message>
+
  {/await}
</div>
modified src/base/projects/Browser.svelte
@@ -23,7 +23,7 @@
  export let revision: string;
  export let path: string;

-
  let { urn, addressOrName, seed, peer, project, config, branches } = source;
+
  let { urn, addressOrName, seed, peer, project, branches } = source;

  // This is reactive to respond to path changes that don't originate from this
  // component, eg. when using the browser's "back" button.
@@ -45,8 +45,8 @@

    const isMarkdownPath = utils.isMarkdownPath(path);
    const promise = path === "/"
-
      ? proj.getReadme(urn, commit, config)
-
      : proj.getBlob(urn, commit, path, { highlight: !isMarkdownPath }, config);
+
      ? proj.getReadme(urn, commit, source.seed.api)
+
      : proj.getBlob(urn, commit, path, { highlight: !isMarkdownPath }, source.seed.api);

    state = { status: Status.Loading, path };
    state = { status: Status.Loaded, path, blob: await promise };
@@ -72,15 +72,13 @@

    if (addressOrName) {
      navigate(proj.path({ peer, urn, addressOrName, revision, path }));
-
    } else if (seed) {
-
      navigate(proj.path({ peer, urn, seed, revision, path }));
    } else {
-
      navigate(proj.path({ peer, urn, revision, path }));
+
      navigate(proj.path({ peer, urn, seed: seed.host, revision, path }));
    }
  };

  const fetchTree = async (path: string) => {
-
    return proj.getTree(urn, commit, path, config);
+
    return proj.getTree(urn, commit, path, seed.api);
  };

  const toggleMobileFileTree = () => {
modified src/base/projects/Commit.svelte
@@ -18,10 +18,8 @@

    if (addressOrName) {
      navigate(proj.path({ content, peer, urn, addressOrName, revision, path }));
-
    } else if (seed) {
-
      navigate(proj.path({ content, peer, urn, seed, revision, path }));
    } else {
-
      navigate(proj.path({ content, peer, urn, revision, path }));
+
      navigate(proj.path({ content, peer, urn, seed: seed.host, revision, path }));
    }
  };

modified src/base/projects/Header.svelte
@@ -18,7 +18,7 @@
  // If peerSelector should be showed.
  export let peerSelector: boolean;

-
  let { urn, peer, config, project, branches, peers, anchors } = source;
+
  let { urn, peer, project, branches, peers, seed, anchors } = source;

  let dropdownState: { [key: string]: boolean } = { clone: false, seed: false, branch: false, peer: false };
  function toggleDropdown(input: string) {
@@ -163,7 +163,7 @@
    <AnchorBadge {commit} {anchors}
      head={project.head} on:click={(event) => updateRevision(event.detail)} />
  </div>
-
  {#if config.seed.git.host}
+
  {#if seed.git.host}
    <span>
      <div class="clone" on:click={() => toggleDropdown("clone")}>
        Clone
@@ -175,7 +175,7 @@
        <input
          readonly
          name="clone-url"
-
          value="https://{config.seed.git.host}/{utils.parseRadicleId(urn)}.git"
+
          value="https://{seed.git.host}/{utils.parseRadicleId(urn)}.git"
        />
        <label for="clone-url"
          >Use Git to clone this repository from the URL above.</label
@@ -184,13 +184,13 @@
    </span>
  {/if}
  <span>
-
    {#if config.seed.api.host}
+
    {#if seed.api.host}
      <div
        class="stat seed"
-
        on:click={() => navigate(`/seeds/${config.seed.api.host}`)}
+
        on:click={() => navigate(`/seeds/${seed.api.host}`)}
        title="Project data is fetched from this seed"
      >
-
        <span>{config.seed.api.host}</span>
+
        <span>{seed.api.host}</span>
      </div>
    {/if}
  </span>
modified src/base/projects/History.svelte
@@ -12,7 +12,7 @@
  export let revision: string;
  export let path: string;

-
  let { urn, seed, addressOrName, peer, config, project, branches } = source;
+
  let { urn, seed, addressOrName, peer, project, branches } = source;

  // Bind content to commit history to trigger updates in parent components.
  $: [revision_,] = splitPrefixFromPath(locator, branches, project.head);
@@ -21,19 +21,17 @@

  const navigateHistory = (revision: string, content?: ProjectContent) => {
    // Replaces path with current path if none passed.
-
    if (path === undefined) path = "/";
+
    if (! path) path = "/";

    if (addressOrName) {
      navigate(proj.path({ content, peer, urn, addressOrName, revision, path }));
-
    } else if (seed) {
-
      navigate(proj.path({ content, peer, urn, seed, revision, path }));
    } else {
-
      navigate(proj.path({ content, peer, urn, revision, path }));
+
      navigate(proj.path({ content, peer, urn, seed: seed.host, revision, path }));
    }
  };

  async function fetchCommits(revision: string): Promise<GroupedCommitsHistory> {
-
    const commitsQuery = await getCommits(urn, getOid(project.head, revision, branches), config);
+
    const commitsQuery = await getCommits(urn, getOid(project.head, revision, branches), seed.api);
    return groupCommitHistory(commitsQuery);
  }
</script>
modified src/base/projects/Routes.svelte
@@ -11,11 +11,11 @@
<!-- With an Seed context -->

<Route path="/seeds/:seed/:id/*" let:params>
-
  <View {config} seed={params.seed} id={params.id} />
+
  <View {config} seedHost={params.seed} id={params.id} />
</Route>

<Route path="/seeds/:seed/:id/remotes/:peer/*" let:params>
-
  <View {config} seed={params.seed} peer={params.peer} id={params.id} />
+
  <View {config} seedHost={params.seed} peer={params.peer} id={params.id} />
</Route>

<!-- Explicit user and org context, will at some point be replaced by the generic route -->
modified src/base/projects/View.svelte
@@ -16,7 +16,7 @@

  export let id: string; // Project name or URN.
  export let addressOrName = "";
-
  export let seed = "";
+
  export let seedHost = "";
  export let peer = "";
  export let config: Config;

@@ -29,16 +29,24 @@
  let getProject = new Promise<{ profile?: Profile | null; seed?: Seed } | null>((resolve, reject) => {
    if (addressOrName) {
      Profile.get(addressOrName, ProfileType.Project, config).then(p => resolve({ profile: p })).catch(err => reject(err.message));
-
    } else if (seed) {
-
      Seed.get(config.withSeed({ host: seed })).then(s => resolve({ seed: s })).catch(err => reject(err.message));
+
    } else if (seedHost) {
+
      Seed.lookup(seedHost, config).then(s => resolve({ seed: s })).catch(err => reject(err.message));
    } else {
      resolve(null);
    }
  }).then(async (result) => {
-
    const profile = result?.profile;
-
    const seedInstance = profile?.seed ?? result?.seed;
-
    const cfg = seedInstance && seedInstance.valid ? config.withSeed(seedInstance) : config;
-
    const info = await proj.getInfo(id, cfg);
+
    if (! result) {
+
      throw new Error("Couldn't load project");
+
    }
+

+
    const profile = result.profile;
+
    const seed = result.seed || profile?.seed;
+

+
    if (! seed?.valid) {
+
      throw new Error("Couldn't load project: invalid seed");
+
    }
+

+
    const info = await proj.getInfo(id, seed.api);
    const urn = isRadicleId(id) ? id : info.urn;
    const anchors = profile ? await profile.confirmedProjectAnchors(urn, config) : [];

@@ -51,12 +59,12 @@
    if (info.delegates) {
      // Check for selected peer to override available branches.
      if (peer) {
-
        const branchesByPeer = await proj.getBranchesByPeer(urn, peer || info.delegates[0], cfg);
+
        const branchesByPeer = await proj.getBranchesByPeer(urn, peer || info.delegates[0], seed.api);
        branches = [...Object.entries(branchesByPeer.heads)];
      }
-
      peers = await proj.getPeers(urn, cfg);
+
      peers = await proj.getPeers(urn, seed.api);
    }
-
    return { urn, addressOrName, seed, peer, project: info, branches, peers, config: cfg, profile, anchors };
+
    return { urn, addressOrName, seed, peer, project: info, branches, peers, config, profile, anchors };
  });

  $: if (projectInfo) {
@@ -72,7 +80,16 @@
  }

  function updateRouteParams({ detail: newParams }: { detail: { urn: string; path: string; revision: string; peer: string; content: proj.ProjectContent } }) {
-
    let newLocation = proj.path({ addressOrName, seed, urn: newParams.urn, content: newParams.content, peer: newParams.peer, revision: newParams.revision, path: newParams.path });
+
    const newLocation = proj.path({
+
      addressOrName,
+
      seed: seedHost,
+
      urn: newParams.urn,
+
      content: newParams.content,
+
      peer: newParams.peer,
+
      revision: newParams.revision,
+
      path: newParams.path
+
    });
+

    if (newLocation !== window.location.pathname) {
      navigate(newLocation);
    }
@@ -162,7 +179,7 @@
          </a>
          <span class="divider">/</span>
        {/if}
-
        <Link to={proj.path({ urn: result.urn, addressOrName, seed })}>{result.project.name}</Link>
+
        <Link to={proj.path({ urn: result.urn, addressOrName, seed: result.seed.host })}>{result.project.name}</Link>
        {#if peer}
          <span class="divider" title={peer}>/ {formatSeedId(peer)}</span>
        {/if}
@@ -171,10 +188,10 @@
      <div class="description">{result.project.description}</div>
    </header>

-
    {#await proj.getTree(result.urn, getOid(result.project.head, revision, result.branches), "/", config) then tree}
+
    {#await proj.getTree(result.urn, getOid(result.project.head, revision, result.branches), "/", result.seed.api) then tree}
      <Header {tree} {revision} {content} {path}
        source={result}
-
        peerSelector={!!seed}
+
        peerSelector={!!seedHost}
        on:routeParamsChange={updateRouteParams} />
      <ProjectContentRoutes {tree}
        source={result}
modified src/base/registrations/registrar.ts
@@ -98,7 +98,9 @@ export async function getRegistration(name: string, config: Config, resolver?: E
  // If no seed provided profile.seed ends up being undefined
  if (seedHost && seedId) {
    try {
-
      profile.seed = new Seed(seedHost, seedId, seedGit, seedApi);
+
      profile.seed = new Seed({
+
        host: seedHost, id: seedId, git: seedGit, api: seedApi
+
      }, config);
    } catch (e: any) {
      console.debug(e, seedHost, seedId);
      profile.seed = new InvalidSeed(seedHost, seedId);
@@ -149,7 +151,7 @@ export async function getSeed(name: string, config: Config, resolver?: EnsResolv
  }

  try {
-
    return new Seed(host, id, git, api);
+
    return new Seed({ host, id, git, api }, config);
  } catch (e: any) {
    console.debug(e, host, id);
    return new InvalidSeed(id, host);
modified src/base/seeds/Routes.svelte
@@ -7,5 +7,5 @@
</script>

<Route path="/seeds/:seed" let:params>
-
  <View {config} seedAddress={params.seed}/>
+
  <View {config} host={params.seed}/>
</Route>
modified src/base/seeds/Seed.ts
@@ -19,52 +19,79 @@ export class InvalidSeed {
export class Seed {
  valid: true = true;

-
  host: string;
-
  id: string;
+
  api: { host: string; port: number };
+
  git: { host: string; port: number };
+
  link: { host: string; id: string; port: number };

-
  api?: string;
-
  git?: string;
  version?: string;

-
  constructor(host: string, id: string, git?: string | null, api?: string | null) {
-
    assert(isDomain(host), "invalid seed host");
-
    assert(/^[a-z0-9]+$/.test(id), "invalid seed id");
+
  constructor(seed: {
+
    host: string;
+
    id: string;
+
    git?: string | null;
+
    api?: string | null;
+
    version?: string | null;
+
  }, cfg: Config) {
+
    assert(isDomain(seed.host), "invalid seed host");
+
    assert(/^[a-z0-9]+$/.test(seed.id), "invalid seed id");
+

+
    seed.api && assert(isDomain(seed.api), "invalid seed api host");
+
    seed.git && assert(isDomain(seed.git), "invalid seed git host");
+

+
    // The `git` and `api` keys being more specific take
+
    // precedence over the `host`, if available.
+
    const api = seed.api ?? seed.host;
+
    const git = seed.git ?? seed.host;
+

+
    this.api = { host: api, port: cfg.seed.api.port };
+
    this.git = { host: git, port: cfg.seed.git.port };
+
    this.link = { host: seed.host, id: seed.id, port: cfg.seed.link.port };
+

+
    if (seed.version) {
+
      this.version = seed.version;
+
    }
+
  }

-
    this.host = host;
-
    this.id = id;
+
  get id(): string {
+
    return this.link.id;
+
  }

-
    if (api && isDomain(api)) {
-
      this.api = api;
-
    }
-
    if (git && isDomain(git)) {
-
      this.git = git;
-
    }
+
  get host(): string {
+
    return this.api.host;
  }

-
  static async getPeer(config: Config): Promise<{ id: string }> {
-
    return api.get("/peer", {}, config);
+
  async getPeer(): Promise<{ id: string }> {
+
    return Seed.getPeer(this.api);
  }

-
  static async getProject(urn: string, config: Config): Promise<proj.ProjectInfo> {
-
    return proj.getInfo(urn, config);
+
  async getProject(urn: string): Promise<proj.ProjectInfo> {
+
    return proj.getInfo(urn, this.api);
  }

-
  static async getProjects(config: Config): Promise<proj.ProjectInfo[]> {
-
    const result = await proj.getProjects(config);
+
  async getProjects(): Promise<proj.ProjectInfo[]> {
+
    const result = await proj.getProjects(this.api);
    return result.map((project: any) => ({ ...project, id: project.urn }));
  }

-
  static async get(config: Config): Promise<Seed> {
-
    assert(config.seed.api.host);
+
  static async getPeer({ host, port }: api.Host): Promise<{ id: string }> {
+
    return api.get("/peer", {}, { host, port });
+
  }

+
  static async getInfo({ host, port }: api.Host): Promise<{ version: string }> {
+
    return api.get("/", {}, { host, port });
+
  }
+

+
  static async lookup(hostname: string, cfg: Config): Promise<Seed> {
+
    const host = { host: hostname, port: cfg.seed.api.port };
    const [info, peer] = await Promise.all([
-
      api.get("/", {}, config),
-
      Seed.getPeer(config),
+
      Seed.getInfo(host),
+
      Seed.getPeer(host),
    ]);

-
    const seed = new Seed(config.seed.api.host, peer.id);
-
    seed.version = info.version;
-

-
    return seed;
+
    return new Seed({
+
      host: hostname,
+
      id: peer.id,
+
      version: info.version,
+
    }, cfg);
  }
}
modified src/base/seeds/View.svelte
@@ -9,14 +9,12 @@
  import * as proj from "@app/project";

  export let config: Config;
-
  export let seedAddress: string;
-

-
  config = config.withSeed({ host: seedAddress });
+
  export let host: string;

  const onProjectClick = (project: proj.ProjectInfo) => {
    navigate(proj.path({
      urn: project.urn,
-
      seed: seedAddress,
+
      seed: host,
    }));
  };
</script>
@@ -80,20 +78,20 @@
</style>

<svelte:head>
-
  <title>{seedAddress}</title>
+
  <title>{host}</title>
</svelte:head>

-
{#await Seed.get(config)}
+
{#await Seed.lookup(host, config)}
  <main class="off-centered">
    <Loading center />
  </main>
-
{:then info}
+
{:then seed}
  <main>
    <header>
      <div class="info">
        <span class="title">
          <span class="bold">
-
            🌱 {seedAddress}
+
            🌱 {host}
          </span>
        </span>
      </div>
@@ -102,27 +100,27 @@
    <div class="fields">
      <!-- Seed Address -->
      <div class="label">Address</div>
-
      {#if info.version === "0.2.0" && info.host}
-
        <SeedAddress seed={info} port={config.seed.link.port} />
+
      {#if seed.version === "0.2.0" && seed.host}
+
        <SeedAddress {seed} port={seed.link.port} />
      {:else}
        <div class="seed-address subtle">N/A</div>
        <div class="desktop" />
      {/if}
      <!-- Seed ID -->
      <div class="label">Seed ID</div>
-
      <div>{info.id}</div>
+
      <div>{seed.id}</div>
      <div class="desktop" />
      <!-- API Port -->
      <div class="label">API Port</div>
-
      <div>{config.seed.api.port}</div>
+
      <div>{seed.api.port}</div>
      <div class="desktop" />
      <!-- API Version -->
      <div class="label">Version</div>
-
      <div>{info.version}</div>
+
      <div>{seed.version}</div>
      <div class="desktop" />
    </div>
    <!-- Seed Projects -->
-
    {#await Seed.getProjects(config) then projects}
+
    {#await seed.getProjects() then projects}
      <div class="projects">
        {#each projects as project}
          <div class="project">
@@ -133,5 +131,5 @@
    {/await}
  </main>
{:catch}
-
  <NotFound title={seedAddress} subtitle="Not able to query information from this seed." />
+
  <NotFound title={host} subtitle="Not able to query information from this seed." />
{/await}
modified src/config.json
@@ -73,7 +73,6 @@
  },
  "radicle": {
    "seed": {
-
      "host": "0.0.0.0",
      "api": { "port": 8777 },
      "link": { "port": 8776 },
      "git": { "port": 80 }
modified src/config.ts
@@ -56,9 +56,9 @@ export class Config {
  };
  abi: { [contract: string]: string[] };
  seed: {
-
    api: { host?: string; port: number };
-
    git: { host?: string; port: number };
-
    link: { host?: string; id?: string; port: number };
+
    api: { port: number };
+
    git: { port: number };
+
    link: { port: number };
  };
  ceramic: {
   client: Core;
@@ -136,24 +136,6 @@ export class Config {
    this.signer = signer;
  }

-
  // Return the config with an overwritten seed URL.
-
  withSeed(seed: { host: string; id?: string; api?: string; git?: string }): Config {
-
    const cfg = {} as Config;
-
    Object.assign(cfg, this);
-

-
    // The `git` and `api` keys being more specific take
-
    // precedence over the `host`, if available.
-
    const api = seed.api ?? seed.host;
-
    const git = seed.git ?? seed.host;
-

-
    cfg.seed.api.host = api;
-
    cfg.seed.git.host = git;
-
    cfg.seed.link.host = seed.host;
-
    cfg.seed.link.id = seed.id;
-

-
    return cfg;
-
  }
-

  getWalletConnectSigner(): WalletConnectSigner {
    if (this.walletConnect.client.connected) {
      this.setSigner(this.walletConnect.signer);
modified src/profile.ts
@@ -115,15 +115,6 @@ export class Profile {
    return this.name ?? this.address;
  }

-
  // Return the profile-specific config. This sets various URLs in the config,
-
  // based on profile data.
-
  config(config: Config): Config {
-
    if (this.seed && this.seed.valid) {
-
      return config.withSeed(this.seed);
-
    }
-
    return config;
-
  }
-

  // Returns the corresponding registration form to edit a user profile.
  // We are not interested in a non-existant registry link, since we check before hand if the name exists.
  registry(config: Config): string {
modified src/project.ts
@@ -1,8 +1,8 @@
-
import type { Config } from '@app/config';
import * as api from '@app/api';
import type { Commit, CommitHeader, CommitsHistory } from '@app/commit';
import { isOid } from '@app/utils';
import type { Profile } from '@app/profile';
+
import type { Seed } from '@app/base/seeds/Seed';

export type Urn = string;
export type Peer = string;
@@ -31,11 +31,10 @@ export interface Source {
  urn: string;
  addressOrName: string;
  peer: string;
-
  config: Config;
  project: ProjectInfo;
  peers: Peer[];
  anchors: string[];
-
  seed: string;
+
  seed: Seed;
  branches: [string, string][];
  profile?: Profile | null;
}
@@ -97,8 +96,8 @@ export interface Branches {
  heads: Branch;
}

-
export async function getInfo(nameOrUrn: string, config: Config): Promise<ProjectInfo> {
-
  const info = await api.get(`projects/${nameOrUrn}`, {}, config);
+
export async function getInfo(nameOrUrn: string, host: api.Host): Promise<ProjectInfo> {
+
  const info = await api.get(`projects/${nameOrUrn}`, {}, host);

  return {
    ...info,
@@ -106,34 +105,34 @@ export async function getInfo(nameOrUrn: string, config: Config): Promise<Projec
  };
}

-
export async function getCommits(urn: string, commit: string, config: Config): Promise<CommitsHistory> {
-
  return api.get(`projects/${urn}/commits?from=${commit}`, {}, config);
+
export async function getCommits(urn: string, commit: string, host: api.Host): Promise<CommitsHistory> {
+
  return api.get(`projects/${urn}/commits?from=${commit}`, {}, host);
}

-
export async function getCommit(urn: string, commit: string, config: Config): Promise<Commit> {
-
  return api.get(`projects/${urn}/commits/${commit}`, {}, config);
+
export async function getCommit(urn: string, commit: string, host: api.Host): Promise<Commit> {
+
  return api.get(`projects/${urn}/commits/${commit}`, {}, host);
}

-
export async function getProjects(config: Config): Promise<ProjectInfo[]> {
-
  return api.get("projects", {}, config);
+
export async function getProjects(host: api.Host): Promise<ProjectInfo[]> {
+
  return api.get("projects", {}, host);
}

-
export async function getBranchesByPeer(urn: string, peer: string, config: Config): Promise<Branches> {
-
  return api.get(`projects/${urn}/remotes/${peer}`, {}, config);
+
export async function getBranchesByPeer(urn: string, peer: string, host: api.Host): Promise<Branches> {
+
  return api.get(`projects/${urn}/remotes/${peer}`, {}, host);
}

-
export async function getPeers(urn: string, config: Config): Promise<Peer[]> {
-
  return api.get(`projects/${urn}/remotes`, {}, config);
+
export async function getPeers(urn: string, host: api.Host): Promise<Peer[]> {
+
  return api.get(`projects/${urn}/remotes`, {}, host);
}

export async function getTree(
  urn: string,
  commit: string,
  path: string,
-
  config: Config
+
  host: api.Host
): Promise<Tree> {
  if (path === "/") path = "";
-
  return api.get(`projects/${urn}/tree/${commit}/${path}`, {}, config);
+
  return api.get(`projects/${urn}/tree/${commit}/${path}`, {}, host);
}

export async function getBlob(
@@ -141,17 +140,17 @@ export async function getBlob(
  commit: string,
  path: string,
  options: { highlight: boolean },
-
  config: Config
+
  host: api.Host
): Promise<Blob> {
-
  return api.get(`projects/${urn}/blob/${commit}/${path}`, options, config);
+
  return api.get(`projects/${urn}/blob/${commit}/${path}`, options, host);
}

export async function getReadme(
  urn: string,
  commit: string,
-
  config: Config
+
  host: api.Host
): Promise<Blob> {
-
  return api.get(`projects/${urn}/readme/${commit}`, {}, config);
+
  return api.get(`projects/${urn}/readme/${commit}`, {}, host);
}

export function path(