Skip to content

Instantly share code, notes, and snippets.

@abstractalgo
Last active March 29, 2023 15:21
Show Gist options
  • Save abstractalgo/3f1646964f7cbfe48397cd7d67b07cea to your computer and use it in GitHub Desktop.
Save abstractalgo/3f1646964f7cbfe48397cd7d67b07cea to your computer and use it in GitHub Desktop.

Revisions

  1. abstractalgo revised this gist Mar 29, 2023. 1 changed file with 42 additions and 34 deletions.
    76 changes: 42 additions & 34 deletions github-web-container.ts
    Original file line number Diff line number Diff line change
    @@ -1,72 +1,80 @@
    import { DirectoryNode, FileSystemTree } from '@webcontainer/api';
    import { DirectoryNode, FileSystemTree } from "@webcontainer/api";

    export const getGithubFiles = async ({
    export const getGithubFilesTree = async ({
    owner,
    repo,
    path,
    branch = 'master',
    branch = "master",
    }: {
    owner: string;
    repo: string;
    path: string;
    branch?: string;
    }): Promise<FileSystemTree> => {
    // 1. get file tree from Github

    const latestCommitRes = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/commits/${branch}`
    );
    const latestCommit = (await latestCommitRes.json()) as { sha: string };

    const treeRes =
    await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${latestCommit.sha}?re
    cursive=1`);
    const ghTreeRes = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/git/trees/${latestCommit.sha}?recursive=1`
    );
    const fsNodes = (
    (await treeRes.json()) as {
    (await ghTreeRes.json()) as {
    tree: {
    path: string;
    type: 'blob' | 'tree';
    type: "blob" | "tree";
    size: number;
    url: string;
    }[];
    }
    ).tree;

    const filteredTree = fsNodes.filter(
    (i) => i.path.startsWith(path) && i.type === 'blob'
    );
    // 2. get file contents from Github for files that match the path

    // const contentFetches = filteredTree.map(i=>fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${i.path}`))
    const contentFetches = filteredTree.map((i) =>
    fetch(
    `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${i.path}`
    )
    );
    const fileContents: Record<string, string> = await (async () => {
    const filteredTree = fsNodes.filter(
    (i) => i.path.startsWith(path) && i.type === "blob"
    );

    const contentRes = await Promise.all(contentFetches);
    const content = await Promise.all(contentRes.map((res) => res.text()));
    const contentFetches = filteredTree.map((i) =>
    fetch(
    `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${i.path}`
    )
    );

    const fileContents: Record<string, string> = {};
    for (let idx = 0; idx < filteredTree.length; idx++) {
    fileContents[filteredTree[idx].path] = content[idx];
    }
    const contentRes = await Promise.all(contentFetches);
    const content = await Promise.all(contentRes.map((res) => res.text()));

    // -----------------------
    const fileContents: Record<string, string> = {};
    for (let idx = 0; idx < filteredTree.length; idx++) {
    fileContents[filteredTree[idx].path] = content[idx];
    }

    let tree: FileSystemTree = {};
    return fileContents;
    })();

    // 3. construct a full file system tree

    let fsTree: FileSystemTree = {};

    fsNodes.forEach((item) => {
    const path = item.path;
    const pathParts = path.split('/');
    let currentLevel = tree;
    const pathParts = path.split("/");
    let currentLevel = fsTree;

    pathParts.forEach((part) => {
    const isPartAFilename = fsNodes.find(
    (i) => i.path === item.path && i.type === 'blob'
    (i) => i.path === item.path && i.type === "blob"
    );

    if (!currentLevel[part]) {
    currentLevel[part] = isPartAFilename
    ? {
    file: {
    contents: fileContents[path] ?? '~',
    contents: fileContents[path] ?? "~",
    },
    }
    : {
    @@ -77,11 +85,11 @@ cursive=1`);
    });
    });

    for (const pathPart of path.split('/')) {
    tree = (tree[pathPart] as DirectoryNode).directory;
    }
    // 4. return the subtree for the path

    console.log(tree);
    for (const pathPart of path.split("/")) {
    fsTree = (fsTree[pathPart] as DirectoryNode).directory;
    }

    return tree;
    return fsTree;
    };
  2. abstractalgo created this gist Mar 29, 2023.
    87 changes: 87 additions & 0 deletions github-web-container.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,87 @@
    import { DirectoryNode, FileSystemTree } from '@webcontainer/api';

    export const getGithubFiles = async ({
    owner,
    repo,
    path,
    branch = 'master',
    }: {
    owner: string;
    repo: string;
    path: string;
    branch?: string;
    }): Promise<FileSystemTree> => {
    const latestCommitRes = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/commits/${branch}`
    );
    const latestCommit = (await latestCommitRes.json()) as { sha: string };

    const treeRes =
    await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${latestCommit.sha}?re
    cursive=1`);
    const fsNodes = (
    (await treeRes.json()) as {
    tree: {
    path: string;
    type: 'blob' | 'tree';
    size: number;
    url: string;
    }[];
    }
    ).tree;

    const filteredTree = fsNodes.filter(
    (i) => i.path.startsWith(path) && i.type === 'blob'
    );

    // const contentFetches = filteredTree.map(i=>fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${i.path}`))
    const contentFetches = filteredTree.map((i) =>
    fetch(
    `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${i.path}`
    )
    );

    const contentRes = await Promise.all(contentFetches);
    const content = await Promise.all(contentRes.map((res) => res.text()));

    const fileContents: Record<string, string> = {};
    for (let idx = 0; idx < filteredTree.length; idx++) {
    fileContents[filteredTree[idx].path] = content[idx];
    }

    // -----------------------

    let tree: FileSystemTree = {};

    fsNodes.forEach((item) => {
    const path = item.path;
    const pathParts = path.split('/');
    let currentLevel = tree;

    pathParts.forEach((part) => {
    const isPartAFilename = fsNodes.find(
    (i) => i.path === item.path && i.type === 'blob'
    );
    if (!currentLevel[part]) {
    currentLevel[part] = isPartAFilename
    ? {
    file: {
    contents: fileContents[path] ?? '~',
    },
    }
    : {
    directory: {},
    };
    }
    currentLevel = (currentLevel[part] as DirectoryNode).directory;
    });
    });

    for (const pathPart of path.split('/')) {
    tree = (tree[pathPart] as DirectoryNode).directory;
    }

    console.log(tree);

    return tree;
    };