-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
211 additions
and
1 deletion.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ node_modules | |
coverage | ||
/__play.js | ||
/index.js | ||
/tmp |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import os from 'os'; | ||
import crypto from 'crypto'; | ||
import createResolve from '@pnpm/git-resolver'; | ||
import git from 'graceful-git'; | ||
import got from 'got'; | ||
import gunzip from 'gunzip-maybe'; | ||
import tar from 'tar-fs'; | ||
|
||
function resolveRepo(supplier) { | ||
return createResolve({})({pref: supplier}) | ||
.then( | ||
result => result.resolution, | ||
err => { | ||
console.error(err); | ||
} | ||
); | ||
} | ||
|
||
function ensureTmpFolder() { | ||
const folder = path.join(os.tmpdir(), 'makes'); | ||
try { | ||
fs.mkdirSync(folder); | ||
} catch (e) { | ||
if (e.code !== 'EEXIST') { | ||
throw e; | ||
} | ||
} | ||
return folder; | ||
} | ||
|
||
function generateHash(bufOrStr) { | ||
return crypto.createHash('md5').update(bufOrStr).digest('hex'); | ||
} | ||
|
||
function isDir(dir) { | ||
try { | ||
const stats = fs.statSync(dir); | ||
return stats.isDirectory(); | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
|
||
async function useCached(folder, hash, generate) { | ||
const target = path.join(folder, hash); | ||
if (!isDir(target)) { | ||
await generate(target); | ||
} | ||
|
||
const files = fs.readdirSync(target); | ||
if (files.length === 1) { | ||
return path.join(target, files[0]); | ||
} | ||
return target; | ||
} | ||
|
||
export default async function(supplier, { | ||
_resolve = resolveRepo, | ||
_tmpFolder | ||
} = {}) { | ||
// local folder | ||
if (supplier.startsWith('.')) { | ||
if (isDir(supplier)) return supplier; | ||
throw new Error(`Local folder "${supplier}" does not exist.`); | ||
} | ||
|
||
// for "something" or "github:something" | ||
// turn to "something/new" or "github:something/new" | ||
if (supplier.match(/^([a-zA-Z0-9-_]+:)?[a-zA-Z0-9-_]+$/)) { | ||
supplier += '/new'; | ||
} | ||
|
||
const resolution = await _resolve(supplier); | ||
|
||
if (!resolution) { | ||
throw new Error('Cannot find git repo ' + supplier); | ||
} | ||
|
||
if (!_tmpFolder) { | ||
_tmpFolder = ensureTmpFolder(); | ||
} | ||
|
||
if (resolution.tarball) { | ||
return await useCached( | ||
_tmpFolder, | ||
generateHash(resolution.tarball), | ||
target => { | ||
return new Promise((resolve, reject) => { | ||
got.stream(resolution.tarball) | ||
.pipe(gunzip()) | ||
.pipe(tar.extract(target)) | ||
.once('error', reject) | ||
.once('finish', resolve); | ||
}); | ||
} | ||
); | ||
} else { | ||
// git | ||
const {type, repo, commit} = resolution; | ||
if (type !== 'git') { | ||
throw new Error('Unknown repo type: ' + type); | ||
} | ||
|
||
return await useCached( | ||
_tmpFolder, | ||
generateHash(repo + commit), | ||
async target => { | ||
await git(['clone', repo, target]); | ||
await git(['checkout', commit], {cwd: target}); | ||
} | ||
); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import test from 'ava'; | ||
import skeletonDir from '../src/skeleton-dir'; | ||
import mockfs from 'mock-fs'; | ||
import path from 'path'; | ||
import fs from 'fs'; | ||
import rmdir from 'rimraf'; | ||
|
||
test.afterEach(() => { | ||
mockfs.restore(); | ||
}); | ||
|
||
test.serial('skeletonDir simply returns local dir', async t => { | ||
mockfs({ | ||
'../my/dir/.keep': '', | ||
'./my/dir/.keep': '', | ||
}); | ||
t.is(await skeletonDir('../my/dir'), '../my/dir'); | ||
t.is(await skeletonDir('./my/dir'), './my/dir'); | ||
}); | ||
|
||
test.serial('skeletonDir complains about missing local dir', async t => { | ||
mockfs(); | ||
await t.throwsAsync(async() => skeletonDir('./my/dir')); | ||
}); | ||
|
||
test.serial('skeletonDir complains about unresolved repo', async t => { | ||
function resolveRepo() {} | ||
|
||
await t.throwsAsync(async() => skeletonDir('something', { | ||
_resolve: resolveRepo | ||
})); | ||
}); | ||
|
||
test.serial('skeletonDir returns tmp folder untar git repo, and caches it', async t => { | ||
mockfs({ | ||
'tmp/.keep': '' | ||
}); | ||
|
||
const repo = 'huochunpeng/debug-npm#v1.0.0'; | ||
|
||
const dir = await skeletonDir(repo, {_tmpFolder: 'tmp'}); | ||
t.truthy(fs.readdirSync(dir).includes('README.md')); | ||
t.truthy(fs.readFileSync(path.join(dir, 'README.md'), 'utf8').includes('debug repo for npm')); | ||
|
||
const dir2 = await skeletonDir(repo, {_tmpFolder: 'tmp'}); | ||
t.is(dir2, dir); | ||
t.truthy(fs.readdirSync(dir2).includes('README.md')); | ||
t.truthy(fs.readFileSync(path.join(dir2, 'README.md'), 'utf8').includes('debug repo for npm')); | ||
}); | ||
|
||
test.serial('skeletonDir complains about unknown repo type', async t => { | ||
function resolveRepo() { | ||
return { | ||
type: 'subversion', | ||
repo: 'lorem' | ||
}; | ||
} | ||
|
||
await t.throwsAsync(async() => skeletonDir('something', { | ||
_resolve: resolveRepo | ||
})); | ||
}); | ||
|
||
test.serial('skeletonDir returns tmp folder checked out git repo, and caches it', async t => { | ||
const repo = 'huochunpeng/debug-npm#v1.0.0'; | ||
|
||
function resolveRepo(repo) { | ||
if (repo === 'huochunpeng/debug-npm#v1.0.0') { | ||
return { | ||
type: 'git', | ||
repo: 'git://github.com/huochunpeng/debug-npm.git', | ||
commit: '361ff261e60f60a91da09bb259230303ccef8087' | ||
}; | ||
} | ||
} | ||
|
||
const dir = await skeletonDir(repo, {_resolve: resolveRepo, _tmpFolder: 'tmp'}); | ||
t.truthy(fs.readdirSync(dir).includes('README.md')); | ||
t.truthy(fs.readFileSync(path.join(dir, 'README.md'), 'utf8').includes('debug repo for npm')); | ||
|
||
// use cache | ||
const dir2 = await skeletonDir(repo, {_resolve: resolveRepo, _tmpFolder: 'tmp'}); | ||
t.is(dir2, dir); | ||
t.truthy(fs.readdirSync(dir2).includes('README.md')); | ||
t.truthy(fs.readFileSync(path.join(dir2, 'README.md'), 'utf8').includes('debug repo for npm')); | ||
|
||
rmdir.sync('tmp'); | ||
}); |