Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Refactor projects
Sebastian Martinez committed 4 years ago
commit ee507215fb85eafb94a91a320abb29f4c43f7150
parent 935044ac4c3ebbcc5a1299f8d87fc802e0675b42
10 files changed +217 -217
modified src/base/projects/BranchSelector.spec.ts
@@ -5,6 +5,7 @@ import { styles } from "@test/support/index";
const defaultProps = {
  project: {
    head: "e678629cd37c770c640a2cd997fc76303c815772",
+
    urn: "rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio",
    name: "nakamoto",
    description: "Privacy-preserving Bitcoin light-client implementation in Rust",
    defaultBranch: "master",
modified src/base/projects/Browser.svelte
@@ -17,15 +17,13 @@
      { status: Status.Loading; path: string }
    | { status: Status.Loaded; path: string; blob: proj.Blob };

-
  export let source: proj.Source;
+
  export let project: proj.Project;
  export let tree: proj.Tree;
  export let browserStore: Readable<proj.Browser>;

-
  const { urn, project } = source;
-

  $: browser = $browserStore;
  $: path = browser.path || "/";
-
  $: revision = browser.revision || source.branches[project.head];
+
  $: revision = browser.revision || project.branches[project.head];

  // When the component is loaded the first time, the blob is yet to be loaded.
  let state: State = { status: Status.Loading, path };
@@ -39,8 +37,8 @@

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

    state = { status: Status.Loading, path };
    state = { status: Status.Loaded, path, blob: await promise };
@@ -59,19 +57,19 @@
    mobileFileTree = false;

    if (path) {
-
      proj.navigateTo({ path: newPath, revision }, source);
+
      project.navigateTo({ path: newPath, revision });
    }
  };

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

  const toggleMobileFileTree = () => {
    mobileFileTree = !mobileFileTree;
  };

-
  $: commit = proj.getOid(revision, source.branches) || project.head;
+
  $: commit = proj.getOid(revision, project.branches) || project.head;
  $: getBlob = loadBlob(path);
  $: loadingPath = state.status == Status.Loading ? state.path : null;
</script>
modified src/base/projects/Commit.svelte
@@ -4,16 +4,15 @@
  import { formatCommitTime } from "@app/commit";
  import { formatCommit } from "@app/utils";

-
  export let source: proj.Source;
+
  export let project: proj.Project;
  export let commit: string;

-
  const { seed, urn } = source;
  const onBrowse = (event: { detail: string }) => {
-
    proj.navigateTo({
+
    project.navigateTo({
      content: proj.ProjectContent.Tree,
      revision: commit,
      path: event.detail
-
    }, source);
+
    });
  };
</script>

@@ -61,7 +60,7 @@
  }
</style>

-
{#await proj.getCommit(urn, commit, seed.api) then commit}
+
{#await project.getCommit(commit) then commit}
  <div class="commit">
    <header>
      <div class="summary">
modified src/base/projects/Header.svelte
@@ -2,20 +2,19 @@
  import type { Writable } from 'svelte/store';
  import { navigate } from 'svelte-routing';
  import * as utils from '@app/utils';
-
  import * as proj from '@app/project';
-
  import { Browser, ProjectContent, Source } from '@app/project';
+
  import { Browser, ProjectContent, Project } from '@app/project';
  import AnchorBadge from '@app/base/profiles/AnchorBadge.svelte';
  import type { Tree } from "@app/project";
  import BranchSelector from './BranchSelector.svelte';
  import PeerSelector from './PeerSelector.svelte';

-
  export let source: Source;
+
  export let project: Project;
  export let tree: Tree;
  export let commit: string;
  export let browserStore: Writable<Browser>;
  export let peerSelector: boolean; // If peerSelector should be showed.

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

  $: browser = $browserStore;
  $: revision = browser.revision || commit;
@@ -31,19 +30,19 @@

  // Switches between the browser and commit view.
  const toggleContent = (input: ProjectContent) => {
-
    proj.navigateTo({
+
    project.navigateTo({
      content: content === input ? ProjectContent.Tree : input
-
    }, source);
+
    });
  };

  const updatePeer = (peer: string) => {
    dropdownState.peer = false;
-
    proj.navigateTo({ peer, revision: null }, source);
+
    project.navigateTo({ peer, revision: null });
  };

  const updateRevision = (revision: string) => {
    dropdownState.branch = false;
-
    proj.navigateTo({ revision }, source);
+
    project.navigateTo({ revision });
  };
</script>

@@ -157,7 +156,7 @@
      bind:peersDropdown={dropdownState.peer}
      on:peerChanged={(event) => updatePeer(event.detail)} />
  {/if}
-
  <BranchSelector branches={source.branches} {project} {revision} {toggleDropdown}
+
  <BranchSelector {branches} {project} {revision} {toggleDropdown}
    bind:branchesDropdown={dropdownState.branch}
    on:branchChanged={(event) => updateRevision(event.detail)} />
  <div class="anchor">
modified src/base/projects/History.svelte
@@ -1,20 +1,18 @@
<script lang="ts">
  import CommitTeaser from "./Commit/CommitTeaser.svelte";
-
  import { getCommits, Source, ProjectContent } from "@app/project";
-
  import * as proj from "@app/project";
+
  import type { Project } from "@app/project";
+
  import { ProjectContent } from "@app/project";
  import Loading from "@app/Loading.svelte";
  import { groupCommitHistory, GroupedCommitsHistory } from "@app/commit";

-
  export let source: Source;
+
  export let project: Project;
  export let commit: string;

-
  let { urn, seed } = source;
-

  const navigateHistory = (revision: string, content?: ProjectContent) => {
-
    proj.navigateTo({ content, revision, path: null }, source);
+
    project.navigateTo({ content, revision, path: null });
  };
  const fetchCommits = async (parentCommit: string): Promise<GroupedCommitsHistory> => {
-
    const commitsQuery = await getCommits(urn, parentCommit, seed.api);
+
    const commitsQuery = await project.getCommits(parentCommit);
    return groupCommitHistory(commitsQuery);
  };
</script>
modified src/base/projects/Project.svelte
@@ -14,22 +14,20 @@

  export let peer: string | null = null;
  export let config: Config;
-
  export let source: proj.Source;
+
  export let project: proj.Project;
  export let content: proj.ProjectContent;
  export let revision: string;

-
  const project = source.project;
-

-
  let parentName = source.profile ? formatProfile(source.profile.nameOrAddress, config) : null;
+
  let parentName = project.profile ? formatProfile(project.profile.nameOrAddress, config) : null;
  let pageTitle = parentName ? `${parentName}/${project.name}` : project.name;

-
  function rootPath(source: proj.Source): string {
-
    return proj.pathTo({
+
  function rootPath(): string {
+
    return project.pathTo({
      content: proj.ProjectContent.Tree,
      peer: null,
      path: "/",
      revision: null,
-
    }, source);
+
    });
  }

  const baseName = parentName
@@ -107,30 +105,30 @@

<header>
  <div class="title bold">
-
    {#if source.profile}
-
      <a class="org-avatar" title={source.profile.nameOrAddress} href="/{source.profile.nameOrAddress}">
-
        <Avatar source={source.profile.avatar || source.profile.address} address={source.profile.address}/>
+
    {#if project.profile}
+
      <a class="org-avatar" title={project.profile.nameOrAddress} href="/{project.profile.nameOrAddress}">
+
        <Avatar source={project.profile.avatar || project.profile.address} address={project.profile.address}/>
      </a>
      <span class="divider">/</span>
    {/if}
-
    <Link to={rootPath(source)}>{source.project.name}</Link>
+
    <Link to={rootPath()}>{project.name}</Link>
    {#if peer}
      <span class="divider" title={peer}>/ {formatSeedId(peer)}</span>
    {/if}
  </div>
-
  <div class="urn">{source.urn}</div>
-
  <div class="description">{source.project.description}</div>
+
  <div class="urn">{project.urn}</div>
+
  <div class="description">{project.description}</div>
</header>

-
{#await proj.getRoot(source.project, revision, source.branches, source.seed.api) then { tree, commit }}
-
  <Header {tree} {commit} {browserStore} {source} peerSelector={! source.profile} />
+
{#await project.getRoot(revision) then { tree, commit }}
+
  <Header {tree} {commit} {browserStore} {project} peerSelector={! project.profile} />

  {#if content == proj.ProjectContent.Tree}
-
    <Browser {source} {tree} {browserStore} />
+
    <Browser {project} {tree} {browserStore} />
  {:else if content == proj.ProjectContent.History}
-
    <History {source} {commit} />
+
    <History {project} {commit} />
  {:else if content == proj.ProjectContent.Commit}
-
    <Commit {source} {commit} />
+
    <Commit {project} {commit} />
  {/if}
{:catch err}
  <div class="container center-content">
modified src/base/projects/ProjectRoute.svelte
@@ -10,15 +10,15 @@
  export let revision: string | null = null;
  export let peer: string | null;
  export let content: proj.ProjectContent = proj.ProjectContent.Tree;
-
  export let source: proj.Source;
+
  export let project: proj.Project;
  export let config: Config;

  const browse: proj.BrowseTo = { content, peer, path: "/" };
-
  const head = source.branches[source.project.defaultBranch];
+
  const head = project.branches[project.defaultBranch];

  // route is passed when the URL has more params after e.g. /tree or /history
  $: if (route) {
-
    const { path, revision } = proj.parseRoute(route, source.branches);
+
    const { path, revision } = proj.parseRoute(route, project.branches);

    if (path) browse.path = path;
    if (revision) browse.revision = revision;
@@ -36,6 +36,6 @@
  peer={browser.peer}
  revision={browser.revision || head}
  content={browser.content}
-
  {source}
+
  {project}
  {config}
/>
modified src/base/projects/View.svelte
@@ -1,11 +1,8 @@
<script lang="ts">
  import type { Config } from '@app/config';
  import { Route, Router } from "svelte-routing";
-
  import * as proj from '@app/project';
+
  import { Project, ProjectContent } from '@app/project';
  import Loading from '@app/Loading.svelte';
-
  import { Profile, ProfileType } from '@app/profile';
-
  import { isRadicleId } from '@app/utils';
-
  import { Seed } from '@app/base/seeds/Seed';
  import NotFound from '@app/NotFound.svelte';

  import ProjectRoute from "./ProjectRoute.svelte";
@@ -15,38 +12,6 @@
  export let profileName: string | null = null; // Address or name of parent profile.
  export let peer: string | null = null;
  export let config: Config;
-

-
  const getProject = async (peer: string | null): Promise<proj.Source> => {
-
    const profile = profileName ? await Profile.get(profileName, ProfileType.Project, config) : null;
-
    const seed = profile ? profile.seed : seedHost ? await Seed.lookup(seedHost, config) : null;
-

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

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

-
    // Older versions of http-api don't include the URN.
-
    if (! project.urn) project.urn = urn;
-

-
    const peers: proj.PeerId[] = project.delegates
-
      ? await proj.getRemotes(urn, seed.api)
-
      : [];
-

-
    let remote: proj.Remote = {
-
      heads: { [project.defaultBranch]: project.head }
-
    };
-

-
    if (peer) {
-
      remote = await proj.getRemote(urn, peer, seed.api);
-
    }
-
    return { urn, seed, project, peers, branches: remote.heads, profile, anchors };
-
  };
</script>

<style>
@@ -72,35 +37,35 @@
</style>

<main>
-
  {#await getProject(peer)}
+
  {#await Project.get(id, peer, profileName, seedHost, config)}
    <header>
      <Loading center />
    </header>
-
  {:then source}
+
  {:then project}
    <Router>
      <!-- The default action is to render Browser with the default branch head -->
      <Route path="/">
-
        <ProjectRoute content={proj.ProjectContent.Tree} {peer} {source} {config} />
+
        <ProjectRoute content={ProjectContent.Tree} {peer} {project} {config} />
      </Route>
      <Route path="/tree">
-
        <ProjectRoute content={proj.ProjectContent.Tree} {peer} {source} {config} />
+
        <ProjectRoute content={ProjectContent.Tree} {peer} {project} {config} />
      </Route>
      <Route path="/tree/*" let:params>
-
        <ProjectRoute route={params["*"]} content={proj.ProjectContent.Tree} {peer} {source} {config} />
+
        <ProjectRoute route={params["*"]} content={ProjectContent.Tree} {peer} {project} {config} />
      </Route>

      <Route path="/history">
-
        <ProjectRoute content={proj.ProjectContent.History} {peer} {source} {config} />
+
        <ProjectRoute content={ProjectContent.History} {peer} {project} {config} />
      </Route>
      <Route path="/history/*" let:params>
-
        <ProjectRoute route={params["*"]} content={proj.ProjectContent.History} {peer} {source} {config} />
+
        <ProjectRoute route={params["*"]} content={ProjectContent.History} {peer} {project} {config} />
      </Route>

      <Route path="/commits/:commit" let:params>
-
        <ProjectRoute revision={params.commit} content={proj.ProjectContent.Commit} {peer} {source} {config} />
+
        <ProjectRoute revision={params.commit} content={ProjectContent.Commit} {peer} {project} {config} />
      </Route>
      <Route path="/commits/*" let:params>
-
        <ProjectRoute route={params["*"]} content={proj.ProjectContent.Commit} {peer} {source} {config} />
+
        <ProjectRoute route={params["*"]} content={ProjectContent.Commit} {peer} {project} {config} />
      </Route>
    </Router>
  {:catch}
modified src/base/seeds/Seed.ts
@@ -1,6 +1,6 @@
import * as api from '@app/api';
import type { Config } from '@app/config';
-
import * as proj from '@app/project';
+
import * as proj from "@app/project";
import { isDomain } from '@app/utils';
import { assert } from '@app/error';

@@ -65,15 +65,15 @@ export class Seed {
  }

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

  async getProjects(id?: string): Promise<proj.ProjectInfo[]> {
    const result = id
-
      ? await proj.getDelegateProjects(id, this.api)
-
      : await proj.getProjects(this.api);
+
      ? await proj.Project.getDelegateProjects(id, this.api)
+
      : await proj.Project.getProjects(this.api);

-
    return result.map((project: any) => ({ ...project, id: project.urn }));
+
    return result.map((project: proj.ProjectInfo) => ({ ...project, id: project.urn }));
  }

  static async getPeer({ host, port }: api.Host): Promise<{ id: string }> {
modified src/project.ts
@@ -2,9 +2,10 @@ import { navigate } from 'svelte-routing';
import { get, writable } from 'svelte/store';
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';
+
import { isOid, isRadicleId } from '@app/utils';
+
import { Profile, ProfileType } from '@app/profile';
+
import { Seed } from '@app/base/seeds/Seed';
+
import type { Config } from '@app/config';

export type Urn = string;
export type PeerId = string;
@@ -28,17 +29,6 @@ export interface PendingAnchor {
  };
}

-
// Params to render correctly source code related views
-
export interface Source {
-
  urn: string;
-
  project: ProjectInfo;
-
  peers: PeerId[];
-
  branches: Branches;
-
  anchors: string[];
-
  seed: Seed;
-
  profile?: Profile | null;
-
}
-

// Enumerates the space below the Header component in the projects View component
export enum ProjectContent {
  Tree,
@@ -129,107 +119,6 @@ export function browse(browse: BrowseTo): void {
  browserStore.set({ ...browser, ...browse });
}

-
export function pathTo(browse: BrowseTo, source: Source): string {
-
  const browser = get(browserStore);
-
  const options: PathOptions = {
-
    urn: source.urn,
-
    ...browser,
-
    ...browse
-
  };
-

-
  if (source.profile) {
-
    options.profile = source.profile?.nameOrAddress;
-
  } else {
-
    options.seed = source.seed.host;
-
  }
-

-
  return path(options);
-
}
-

-
export function navigateTo(browse: BrowseTo, source: Source): void {
-
  navigate(pathTo(browse, source));
-
}
-

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

-
  return {
-
    ...info,
-
    ...info.meta // Nb. This is only needed while we are upgrading to the new http-api.
-
  };
-
}
-

-
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, host: api.Host): Promise<Commit> {
-
  return api.get(`projects/${urn}/commits/${commit}`, {}, host);
-
}
-

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

-
export async function getDelegateProjects(delegate: string, host: api.Host): Promise<ProjectInfo[]> {
-
  return api.get(`delegates/${delegate}/projects`, {}, host);
-
}
-

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

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

-
export async function getRoot(
-
  project: ProjectInfo,
-
  revision: string | null,
-
  heads: Branches,
-
  host: api.Host
-
): Promise<{ tree: Tree; commit: string }> {
-
  const urn = project.urn;
-

-
  const head = heads[project.defaultBranch];
-
  const commit = revision ? getOid(revision, heads) : head;
-

-
  if (! commit) {
-
    throw new Error(`Revision ${revision} not found`);
-
  }
-
  const tree = await getTree(urn, commit, "/", host);
-

-
  return { tree, commit };
-
}
-

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

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

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

export function path(opts: PathOptions): string {
  const { urn, profile, seed, peer, content, revision, path } = opts;
  const result = [];
@@ -304,3 +193,156 @@ export function parseRoute(input: string, branches: Branches): { path?: string;
  }
  return parsed;
}
+

+
export class Project implements ProjectInfo {
+
  urn: string;
+
  head: string;
+
  name: string;
+
  description: string;
+
  defaultBranch: string;
+
  maintainers: Urn[];
+
  delegates: PeerId[];
+
  seed: Seed;
+
  peers: string[];
+
  branches: Branches;
+
  profile: Profile | null;
+
  anchors: string[];
+

+
  constructor(urn: string, info: ProjectInfo, seed: Seed, peers: string[], branches: Branches, profile: Profile | null, anchors: string[]) {
+
    this.urn = urn;
+
    this.head = info.head;
+
    this.name = info.name;
+
    this.description = info.description;
+
    this.defaultBranch = info.defaultBranch;
+
    this.maintainers = info.maintainers;
+
    this.delegates = info.delegates;
+
    this.seed = seed;
+
    this.peers = peers;
+
    this.branches = branches;
+
    this.profile = profile;
+
    this.anchors = anchors;
+
  }
+

+
  async getRoot(
+
    revision: string | null,
+
  ): Promise<{ tree: Tree; commit: string }> {
+
    const head = this.branches[this.defaultBranch];
+
    const commit = revision ? getOid(revision, this.branches) : head;
+

+
    if (! commit) {
+
      throw new Error(`Revision ${revision} not found`);
+
    }
+
    const tree = await this.getTree(commit, "/");
+

+
    return { tree, commit };
+
  }
+

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

+
    return {
+
      ...info,
+
      ...info.meta // Nb. This is only needed while we are upgrading to the new http-api.
+
    };
+
  }
+

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

+
  static async getDelegateProjects(delegate: string, host: api.Host): Promise<ProjectInfo[]> {
+
    return api.get(`delegates/${delegate}/projects`, {}, host);
+
  }
+

+
  static async getRemote(urn: string, peer: string, host: api.Host): Promise<Remote> {
+
    return api.get(`projects/${urn}/remotes/${peer}`, {}, host);
+
  }
+

+
  static async getRemotes(urn: string, host: api.Host): Promise<PeerId[]> {
+
    return api.get(`projects/${urn}/remotes`, {}, host);
+
  }
+

+
  async getCommits(commit: string): Promise<CommitsHistory> {
+
    return api.get(`projects/${this.urn}/commits?from=${commit}`, {}, this.seed.api);
+
  }
+

+
  async getCommit(commit: string): Promise<Commit> {
+
    return api.get(`projects/${this.urn}/commits/${commit}`, {}, this.seed.api);
+
  }
+

+
  async getTree(
+
    commit: string,
+
    path: string,
+
  ): Promise<Tree> {
+
    if (path === "/") path = "";
+
    return api.get(`projects/${this.urn}/tree/${commit}/${path}`, {}, this.seed.api);
+
  }
+

+
  async getBlob(
+
    commit: string,
+
    path: string,
+
    options: { highlight: boolean },
+
  ): Promise<Blob> {
+
    return api.get(`projects/${this.urn}/blob/${commit}/${path}`, options, this.seed.api);
+
  }
+

+
  async getReadme(
+
    commit: string,
+
  ): Promise<Blob> {
+
    return api.get(`projects/${this.urn}/readme/${commit}`, {}, this.seed.api);
+
  }
+

+
  navigateTo(browse: BrowseTo): void {
+
    navigate(this.pathTo(browse));
+
  }
+

+
  pathTo(browse: BrowseTo): string {
+
    const browser = get(browserStore);
+
    const options: PathOptions = {
+
      urn: this.urn,
+
      ...browser,
+
      ...browse
+
    };
+

+
    if (this.profile) {
+
      options.profile = this.profile?.nameOrAddress;
+
    } else {
+
      options.seed = this.seed.host;
+
    }
+

+
    return path(options);
+
  }
+

+
  static async get(id: string, peer: string | null, profileName: string | null, seedHost: string | null, config: Config): Promise<Project> {
+
    const profile = profileName ? await Profile.get(profileName, ProfileType.Project, config) : null;
+
    const seed = profile ? profile.seed : seedHost ? await Seed.lookup(seedHost, config) : null;
+

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

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

+
    // Older versions of http-api don't include the URN.
+
    if (! info.urn) info.urn = urn;
+

+
    const peers: PeerId[] = info.delegates
+
      ? await Project.getRemotes(urn, seed.api)
+
      : [];
+

+
    let remote: Remote = {
+
      heads: { [info.defaultBranch]: info.head }
+
    };
+

+
    if (peer) {
+
      remote = await Project.getRemote(urn, peer, seed.api);
+
    }
+

+
    return new Project(urn, info, seed, peers, remote.heads, profile, anchors);
+
  }
+
}