diff --git a/docs/roadmap.md b/docs/roadmap.md index c1d3313ae0..9a2284cca9 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -10,8 +10,8 @@ - [x] compiled packages (`npm i node-sass`) - [x] bundled dependencies (`npm i fsevents@1.0.6`) - [ ] git-hosted packages (`npm i rstacruz/scourjs`) - - [ ] optional dependencies (`npm i escodegen@1.8.0` wants `source-map@~0.2.0`) - - [ ] file packages (`npm i file:../path`) + - [x] optional dependencies (`npm i escodegen@1.8.0` wants `source-map@~0.2.0`) + - [x] file packages (`npm i file:../path`) - [x] windows support - [x] bin executables - [ ] `--global` installs diff --git a/lib/fetch.js b/lib/fetch.js index c7272baf3e..6942a89d38 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -3,14 +3,18 @@ var got = require('./got') var crypto = require('crypto') var gunzip = require('gunzip-maybe') var tar = require('tar-fs') +var fs = require('fs') /* * Fetches a tarball `tarball` and extracts it into `dir` */ -module.exports = function fetch (dir, tarball, shasum, log) { - return got.getStream(tarball) - .then(stream => fetchStream(dir, tarball, shasum, log, stream)) +module.exports = function fetch (dir, dist, log) { + if (!dist.local) { + return got.getStream(dist.tarball) + .then(stream => fetchStream(dir, dist.tarball, dist.shasum, log, stream)) + } + return unpackStream(fs.createReadStream(dist.tarball), dir) } function fetchStream (dir, tarball, shasum, log, stream) { @@ -19,13 +23,12 @@ function fetchStream (dir, tarball, shasum, log, stream) { var size var downloaded = 0 - stream - .on('response', start) - .on('data', _ => { actualShasum.update(_) }) - .on('error', reject) - .pipe(gunzip()).on('error', reject) - .pipe(tar.extract(dir, { strip: 1 })).on('error', reject) - .on('finish', finish) + unpackStream( + stream + .on('response', start) + .on('data', _ => { actualShasum.update(_) }) + .on('error', reject), dir + ).then(finish) function start (res) { if (res.statusCode !== 200) { @@ -53,3 +56,12 @@ function fetchStream (dir, tarball, shasum, log, stream) { } }) } + +function unpackStream (stream, dir) { + return new Promise((resolve, reject) => { + stream + .pipe(gunzip()).on('error', reject) + .pipe(tar.extract(dir, { strip: 1 })).on('error', reject) + .on('finish', resolve) + }) +} diff --git a/lib/install.js b/lib/install.js index 77673b0396..a4df812db5 100644 --- a/lib/install.js +++ b/lib/install.js @@ -95,7 +95,7 @@ module.exports = function install (ctx, pkgSpec, modules, options) { .then(_ => _ ? saveCachedResolution() .then(data => log('package.json', data)) - : resolve(pkg.spec, log) + : resolve(Object.assign({}, pkg.spec, {root: ctx.root}), log) .then(saveResolution) .then(_ => log('resolved', pkg)) .then(_ => buildToStoreCached(ctx, paths, pkg, log)) @@ -186,7 +186,8 @@ function fetchToStore (ctx, paths, pkg, log) { .then(_ => mkdirp(ctx.store)) .then(_ => mkdirp(join(paths.tmp, '_'))) .then(_ => fs.writeFile(join(paths.tmp, '.pnpm_inprogress'), '', 'utf-8')) - .then(_ => fetch(join(paths.tmp, '_'), pkg.dist.tarball, pkg.dist.shasum, log)) + .then(_ => fetch(join(paths.tmp, '_'), pkg.dist, log)) + .then(_ => pkg.dist.local ? fs.unlink(pkg.dist.tarball) : Promise.resolve()) // TODO: this is the point it becomes partially useable. // ie, it can now be symlinked into .store/foo@1.0.0. diff --git a/lib/resolve.js b/lib/resolve.js index 5044ede009..d4d0d6b8e4 100644 --- a/lib/resolve.js +++ b/lib/resolve.js @@ -1,6 +1,7 @@ var resolveNpm = require('./resolve/npm') var resolveTarball = require('./resolve/tarball') var resolveGithub = require('./resolve/github') +var resolveLocal = require('./resolve/local') /** * Resolves a package in the NPM registry. Done as part of `install()`. @@ -23,6 +24,8 @@ module.exports = function resolve (pkg, log) { return resolveTarball(pkg, log) } else if (pkg.type === 'hosted' && pkg.hosted.type === 'github') { return resolveGithub(pkg, log) + } else if (pkg.type === 'local') { + return resolveLocal(pkg, log) } else { throw new Error('' + pkg.rawSpec + ': ' + pkg.type + ' packages not supported') } diff --git a/lib/resolve/github.js b/lib/resolve/github.js index 5c21267a67..c39396e130 100644 --- a/lib/resolve/github.js +++ b/lib/resolve/github.js @@ -11,7 +11,7 @@ module.exports = function resolveGithub (pkg) { return resolveRef(spec).then(function (ref) { spec.ref = ref return resolvePackageName(spec).then(function (name) { - var fullname = name + ['@github', spec.owner, spec.repo, spec.ref].join('!') + var fullname = name.replace('/', '!') + ['@github', spec.owner, spec.repo, spec.ref].join('!') return { name: name, fullname: fullname, diff --git a/lib/resolve/local.js b/lib/resolve/local.js new file mode 100644 index 0000000000..bc42051980 --- /dev/null +++ b/lib/resolve/local.js @@ -0,0 +1,42 @@ +var resolve = require('path').resolve +var spawn = require('child_process').spawn + +/** + * Resolves a package hosted on the local filesystem + */ + +module.exports = function resolveLocal (pkg) { + var dependencyPath = resolve(pkg.root, pkg.spec) + + return new Promise((resolve, reject) => { + var proc = spawn('sh', ['-c', 'npm pack'], { + cwd: dependencyPath + }) + + proc.on('error', reject) + + proc.on('close', code => { + if (code > 0) return reject(new Error('Exit code ' + code)) + return resolve() + }) + }) + .then(_ => { + var localPkg = require(resolve(dependencyPath, 'package.json')) + var tgzFilename = localPkg.name + '-' + localPkg.version + '.tgz' + return { + name: localPkg.name, + fullname: localPkg.name.replace('/', '!') + [ + '@file', + escapePkgPath(dependencyPath) + ].join('!'), + dist: { + local: true, + tarball: resolve(dependencyPath, tgzFilename) + } + } + }) +} + +function escapePkgPath (pkgPath) { + return pkgPath.replace(/[/\\]/g, '!').replace(/^!/, '') +} diff --git a/test/index.js b/test/index.js index b83d2a3cfd..f4bbe998fc 100644 --- a/test/index.js +++ b/test/index.js @@ -184,6 +184,20 @@ test('tarballs (is-array-1.0.1.tgz)', function (t) { }, t.end) }) +test('local file', function (t) { + prepare() + var localPkgPath = join(__dirname, 'local-pkg') + install(['file:' + localPkgPath], { quiet: true }) + .then(function () { + var localPkg = require( + join(process.cwd(), 'node_modules', 'local-pkg')) + + t.ok(localPkg, 'localPkg() is available') + + t.end() + }, t.end) +}) + test('shrinkwrap compatibility', function (t) { prepare() fs.writeFileSync('package.json', diff --git a/test/local-pkg/index.js b/test/local-pkg/index.js new file mode 100644 index 0000000000..37d7332f73 --- /dev/null +++ b/test/local-pkg/index.js @@ -0,0 +1 @@ +module.exports = function () {} diff --git a/test/local-pkg/package.json b/test/local-pkg/package.json new file mode 100644 index 0000000000..e7362abbe8 --- /dev/null +++ b/test/local-pkg/package.json @@ -0,0 +1,4 @@ +{ + "name": "local-pkg", + "version": "1.0.0" +}