Skip to content

Commit

Permalink
feat: skeleton-dir
Browse files Browse the repository at this point in the history
  • Loading branch information
3cp committed Apr 17, 2019
1 parent 5a77cff commit 726e63b
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ node_modules
coverage
/__play.js
/index.js
/tmp
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,21 @@
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.4.3",
"@babel/register": "^7.4.0",
"@pnpm/git-resolver": "^2.0.3",
"ansi-colors": "^3.2.4",
"ava": "^1.4.1",
"eslint": "^5.16.0",
"hosted-git-info": "^2.7.1",
"got": "^9.6.0",
"graceful-git": "^2.0.0",
"gunzip-maybe": "^1.4.1",
"isutf8": "^2.0.3",
"lodash": "^4.17.11",
"mock-fs": "^4.8.0",
"nock": "^10.0.6",
"nyc": "^14.0.0",
"rimraf": "^2.6.3",
"sisteransi": "^1.0.0",
"tar-fs": "^2.0.0",
"through2": "^3.0.1",
"vinyl": "^2.2.0",
"vinyl-fs": "^3.0.3",
Expand Down
115 changes: 115 additions & 0 deletions src/skeleton-dir.js
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});
}
);
}
}
88 changes: 88 additions & 0 deletions test/skeleton-dir.spec.js
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');
});

0 comments on commit 726e63b

Please sign in to comment.