Skip to content

Instantly share code, notes, and snippets.

@jsejcksn
Created June 9, 2020 17:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsejcksn/0f306acb8d529aa399c96864ab0e2cd2 to your computer and use it in GitHub Desktop.
Save jsejcksn/0f306acb8d529aa399c96864ab0e2cd2 to your computer and use it in GitHub Desktop.
Clone all gists using Deno
// Deno v1.0.5
// deno run --allow-net --allow-run --allow-write --unstable _clone-all-gists.deno.ts --user your_username --token your_github_access_token [--directory parent_directory_to_clone_into]
// https://developer.github.com/v3/gists/
import * as path from 'https://deno.land/std@0.56.0/path/mod.ts';
import {parse} from 'https://deno.land/std@0.56.0/flags/mod.ts';
import {writeJson} from 'https://deno.land/std@0.56.0/fs/mod.ts';
type GistMetadata = {
created_at: string;
description: string;
files: {
[key: string]: {
filename: string;
size: number;
};
};
git_pull_url: string;
html_url: string;
id: string;
public: boolean;
truncated: boolean;
updated_at: string;
};
const parseLinkHeader = (headerString: string) => {
const throwUnrecognizedError = () => {
throw new Error('Unrecognized format');
};
const formatPart = (linkPart: string) => {
let [link, rel] = linkPart.split(';').map(str => str.trim());
if (!(link.startsWith('<') && link.endsWith('>'))) throwUnrecognizedError();
if (!(rel.startsWith('rel="') && rel.endsWith('"'))) throwUnrecognizedError();
link = link.slice(1, -1);
rel = rel.slice(5, -1);
const url = new URL(link);
return [
rel,
{...Object.fromEntries(url.searchParams.entries()), _url: url.href},
];
};
const entries = headerString.split(',').map(part => formatPart(part));
return Object.fromEntries(entries);
};
const fetchGists = async (username: string, token: string) => {
const gists: GistMetadata[] = [];
const options = {
headers: new Headers({
Accept: 'application/vnd.github.v3+json',
Authorization: `Basic ${btoa(`${username}:${token}`)}`,
}),
method: 'GET',
};
let url = 'https://api.github.com/gists';
while (url) {
const response = await fetch(url, options);
if (!response.ok) throw Object.assign(new Error('Fetch response not OK'), {response});
const data = await response.json() as GistMetadata[];
gists.push(...data);
const links = parseLinkHeader(Object.fromEntries(response.headers).link);
url = links.next?._url;
}
return gists;
};
const cloneGist = async (gist: GistMetadata, parentDirectory: string) => {
const gistDir = path.join(parentDirectory, gist.id);
const p = Deno.run({
cmd: ['git', 'clone', gist.git_pull_url, gistDir],
});
const {success} = await p.status();
if (!success) throw new Error(`Failed to clone ${gist.html_url}`);
const gistMetaPath = path.join(gistDir, '.gist-meta.json');
await writeJson(gistMetaPath, gist, {spaces: 2});
};
const main = async () => {
const args = parse(Deno.args);
const username = args.user ?? args.u;
const token = args.token ?? args.t;
const directory = args.directory ?? args.d;
if (!(username && token)) throw new Error('username (--user/-u) and token (--token/-t) required');
const scriptDir = path.dirname(path.fromFileUrl(import.meta.url));
const parentDir = directory ?? scriptDir;
const gists = await fetchGists(username, token);
for (const [index, gist] of gists.entries()) {
console.log(`\nCloning Gist ${index + 1}/${gists.length}`);
await cloneGist(gist, parentDir);
}
console.log(`\nGists cloned into ${parentDir}`);
};
if (import.meta.main) main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment