Created
June 9, 2020 17:03
-
-
Save jsejcksn/0f306acb8d529aa399c96864ab0e2cd2 to your computer and use it in GitHub Desktop.
Clone all gists using Deno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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/[email protected]/path/mod.ts'; | |
import {parse} from 'https://deno.land/[email protected]/flags/mod.ts'; | |
import {writeJson} from 'https://deno.land/[email protected]/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