diff --git a/package.json b/package.json index 266b43a..7d536d2 100644 --- a/package.json +++ b/package.json @@ -30,14 +30,14 @@ }, "dependencies": { "@electron/get": "^2.0.0", + "@octokit/rest": "^19.0.4", "debug": "^4.3.3", "env-paths": "^2.2.1", "extract-zip": "^2.0.1", "fs-extra": "^10.0.0", "getos": "^3.2.1", "node-fetch": "^2.6.1", - "semver": "^7.3.5", - "simple-git": "^2.48.0" + "semver": "^7.3.5" }, "devDependencies": { "@continuous-auth/semantic-release-npm": "^3.0.0", diff --git a/src/fiddle.ts b/src/fiddle.ts index 17b8864..8b9f497 100644 --- a/src/fiddle.ts +++ b/src/fiddle.ts @@ -1,10 +1,11 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import debug from 'debug'; -import simpleGit from 'simple-git'; +import fetch from 'node-fetch'; import { createHash } from 'crypto'; import { DefaultPaths } from './paths'; +import { getOctokit } from './utils/octokit'; function hashString(str: string): string { const md5sum = createHash('md5'); @@ -32,10 +33,43 @@ export class Fiddle { export type FiddleSource = Fiddle | string | Iterable<[string, string]>; export class FiddleFactory { + private readonly VALID_FILES: Array = [ + 'main.js', + 'renderer.js', + 'index.html', + 'preload.js', + 'styles.css', + ]; + // Thanks to https://serverfault.com/a/917253 + private readonly GITHUB_URL_REGEX = new RegExp( + '^(https|git)(://|@)([^/:]+)[/:]([^/:]+)/(.+).git$', + ); + constructor(private readonly fiddles: string = DefaultPaths.fiddles) {} - public async fromGist(gistId: string): Promise { - return this.fromRepo(`https://gist.github.com/${gistId}.git`); + public async fromGist(gistId: string) { + // stores in format [filename, content] + const gistContents: [string, string][] = []; + const octokit = getOctokit(process.env.FIDDLE_CORE_GITHUB_TOKEN); + const gist = await octokit.gists.get({ gist_id: gistId }); + + if (gist.data.files === undefined) { + return; + } + + for (const [, data] of Object.entries(gist.data.files)) { + const fileName = data?.filename; + const content = data?.content; + + if (fileName === undefined || content === undefined) { + continue; + } + if (this.VALID_FILES.includes(fileName)) { + gistContents.push([fileName, content]); + } + } + + return this.fromEntries(gistContents); } public async fromFolder(source: string): Promise { @@ -55,23 +89,43 @@ export class FiddleFactory { return new Fiddle(path.join(folder, 'main.js'), source); } - public async fromRepo(url: string, checkout = 'master'): Promise { + public async fromRepo(url: string) { const d = debug('fiddle-core:FiddleFactory:fromRepo'); - const folder = path.join(this.fiddles, hashString(url)); - d({ url, checkout, folder }); - - // get the repo - if (!fs.existsSync(folder)) { - d(`cloning "${url}" into "${folder}"`); - const git = simpleGit(); - await git.clone(url, folder, { '--depth': 1 }); + const match = this.GITHUB_URL_REGEX.exec(url); + if (match === null) { + throw new Error(`Invalid github URL`); + } + // This has to be done because octokit expects an owner and repo + // params to be passed instead of just HTTPs/SSH git link. + const owner = match[4]; + const repo = match[5]; + const repoContents: [string, string][] = []; + + d({ url, owner, repo }); + const octokit = getOctokit(process.env.FIDDLE_CORE_GITHUB_TOKEN); + const folder = await octokit.repos.getContent({ + owner, + repo, + path: '.', // Look for in the base directory only + }); + + if (!Array.isArray(folder.data)) { + return; } - const git = simpleGit(folder); - await git.checkout(checkout); - await git.pull('origin', checkout); + for (const file of folder.data) { + if (!this.VALID_FILES.includes(file.name)) { + continue; + } + + if (file.download_url) { + const resp = await fetch(file.download_url); + const content = await resp.text(); + repoContents.push([file.name, content]); + } + } - return new Fiddle(path.join(folder, 'main.js'), url); + return this.fromEntries(repoContents); } public async fromEntries(src: Iterable<[string, string]>): Promise { diff --git a/src/index.ts b/src/index.ts index a9337c5..332e653 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ import { compareVersions, } from './versions'; import { runFromCommandLine } from './command-line'; +import { setGithubToken, removeGithubToken } from './utils/env'; export { BaseVersions, @@ -41,6 +42,8 @@ export { TestResult, Versions, compareVersions, + setGithubToken, + removeGithubToken, runFromCommandLine, }; diff --git a/src/utils/env.ts b/src/utils/env.ts new file mode 100644 index 0000000..a18e015 --- /dev/null +++ b/src/utils/env.ts @@ -0,0 +1,7 @@ +export function setGithubToken(githubToken: string) { + process.env.FIDDLE_CORE_GITHUB_TOKEN = githubToken; +} + +export function removeGithubToken() { + delete process.env.FIDDLE_CORE_GITHUB_TOKEN; +} diff --git a/src/utils/octokit.ts b/src/utils/octokit.ts new file mode 100644 index 0000000..2e9ef5c --- /dev/null +++ b/src/utils/octokit.ts @@ -0,0 +1,20 @@ +import { Octokit } from '@octokit/rest'; + +let _octo: Octokit; +/** + * Returns a loaded Octokit. If state is passed and authentication + * is available, we'll token-authenticate. + * @returns {Octokit} + */ +export function getOctokit(token?: string): Octokit { + // It's possible to load Gists without being authenticated, + // but we get better rate limits when authenticated. + _octo = + _octo || token + ? new Octokit({ + auth: token, + }) + : new Octokit(); + + return _octo; +} diff --git a/tests/fiddle.test.ts b/tests/fiddle.test.ts index a352ad7..60e3e19 100644 --- a/tests/fiddle.test.ts +++ b/tests/fiddle.test.ts @@ -93,7 +93,15 @@ describe('FiddleFactory', () => { expect(fiddle).toBe(fiddleIn); }); - it.todo('reads fiddles from git repositories'); + it('reads fiddles from git repositories', async () => { + const repo = 'https://github.com/electron/electron-quick-start.git'; + const fiddle = await fiddleFactory.create(repo); + expect(fiddle).toBeTruthy(); + expect(fs.existsSync(fiddle!.mainPath)).toBe(true); + expect(path.basename(fiddle!.mainPath)).toBe('main.js'); + expect(path.dirname(path.dirname(fiddle!.mainPath))).toBe(fiddleDir); + }); + it.todo('refreshes the cache if given a previously-cached git repository'); it('returns undefined for unknown input', async () => { diff --git a/yarn.lock b/yarn.lock index 5bc1edc..b0cf081 100644 --- a/yarn.lock +++ b/yarn.lock @@ -789,18 +789,6 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" -"@kwsites/file-exists@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" - integrity sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw== - dependencies: - debug "^4.1.1" - -"@kwsites/promise-deferred@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919" - integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw== - "@microsoft/api-extractor-model@7.15.3": version "7.15.3" resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.15.3.tgz#cf76deeeb2733d974da678f530c2dbaceb18a065" @@ -1119,7 +1107,7 @@ node-fetch "^2.6.7" universal-user-agent "^6.0.0" -"@octokit/rest@^19.0.0": +"@octokit/rest@^19.0.0", "@octokit/rest@^19.0.4": version "19.0.4" resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.4.tgz#fd8bed1cefffa486e9ae46a9dc608ce81bcfcbdd" integrity sha512-LwG668+6lE8zlSYOfwPj4FxWdv/qFXYBpv79TWIQEpBLKA9D/IMcWsF/U9RGpA3YqMVDiTxpgVpEW3zTFfPFTA== @@ -6101,15 +6089,6 @@ signale@^1.2.1: figures "^2.0.0" pkg-conf "^2.1.0" -simple-git@^2.48.0: - version "2.48.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.48.0.tgz#87c262dba8f84d7b96bb3a713e9e34701c1f6e3b" - integrity sha512-z4qtrRuaAFJS4PUd0g+xy7aN4y+RvEt/QTJpR184lhJguBA1S/LsVlvE/CM95RsYMOFJG3NGGDjqFCzKU19S/A== - dependencies: - "@kwsites/file-exists" "^1.1.1" - "@kwsites/promise-deferred" "^1.1.1" - debug "^4.3.2" - sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"