├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | bundle.js 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | bundle.js 5 | test 6 | test.js 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This software is released under the MIT license: 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # npmdl 2 | ![](http://img.shields.io/badge/stability-experimental-orange.svg?style=flat) 3 | ![](http://img.shields.io/npm/v/npmdl.svg?style=flat) 4 | ![](http://img.shields.io/npm/dm/npmdl.svg?style=flat) 5 | ![](http://img.shields.io/npm/l/npmdl.svg?style=flat) 6 | 7 | Conveniently download files from npm packages, caching 8 | the results on the file system. 9 | 10 | You could use this to build your own [npm-cdn](http://github.com/zeke/npm-cdn), 11 | or a simple [requirebin](http://requirebin.com)-type editor. Note however 12 | that because scripts aren't run that some packages might not work this way. 13 | 14 | ## Usage 15 | 16 | [![NPM](https://nodei.co/npm/npmdl.png)](https://nodei.co/npm/npmdl/) 17 | 18 | ### `dl = npmdl([directory])` 19 | 20 | Creates a new downloader, using `directory` to store downloaded 21 | packages in. `directory` defaults to `~/.npmdl`. 22 | 23 | ### `dl(package, version, filename, done)` 24 | 25 | Downloads `package@version`, and calls `done(err, content)` 26 | with the contents of `filename` when complete. If already 27 | downloaded, the file will be read out directly so we can 28 | save bandwidth and go a little easier on the npm registry :) 29 | 30 | ``` javascript 31 | var npmdl = require('npmdl') 32 | 33 | npmdl(__dirname)('browserify', '9.0.0', 'bin/advanced.txt', function(err, content) { 34 | if (err) throw err 35 | 36 | // logs browserify@9.0.0's advanced help to the console 37 | console.log(content) 38 | }) 39 | ``` 40 | 41 | ## License 42 | 43 | MIT. See [LICENSE.md](http://github.com/hughsk/npmdl/blob/master/LICENSE.md) for details. 44 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const userhome = require('userhome') 2 | const request = require('request') 3 | const tar = require('tar-fs') 4 | const mkdirp = require('mkdirp') 5 | const zlib = require('zlib') 6 | const path = require('path') 7 | const url = require('url') 8 | const fs = require('fs') 9 | 10 | module.exports = NpmDownloader 11 | 12 | function NpmDownloader(root) { 13 | mkdirp.sync(root = root || userhome('.npmdl')) 14 | 15 | return getFile 16 | 17 | function getFile(name, vers, file, done) { 18 | name = sanitize(name).replace(path.sep, '') 19 | vers = sanitize(vers).replace(path.sep, '') 20 | file = sanitize(file) 21 | 22 | // For safety :D 23 | if (!name) return done(new Error('Missing package name')) 24 | if (!vers) return done(new Error('Missing package version ('+name+')')) 25 | if (!file) return done(new Error('Missing package file ('+name+'@'+vers+')')) 26 | 27 | var pkgDir = path.join(root, name, vers) 28 | var pkgFile = path.join(root, name, vers, 'package', file) 29 | 30 | fs.exists(pkgDir, function(exists) { 31 | if (exists) { 32 | return fs.readFile(pkgFile, 'utf8', done) 33 | } 34 | 35 | downloadPkg(name, vers, pkgDir, function(err) { 36 | if (err) return done(err) 37 | 38 | return fs.readFile(pkgFile, 'utf8', done) 39 | }) 40 | }) 41 | } 42 | 43 | function downloadPkg(name, version, dest, done) { 44 | var uri = [ 45 | name, '-', 46 | name +'-'+version+'.tgz' 47 | ].join('/') 48 | 49 | uri = url.resolve('http://registry.npmjs.com', uri) 50 | 51 | request.get(uri) 52 | .pipe(zlib.createGunzip()) 53 | .pipe(tar.extract(dest)) 54 | .on('error', done) 55 | .on('finish', done) 56 | } 57 | 58 | function sanitize(chunk) { 59 | chunk = chunk || '' 60 | chunk = chunk.replace(/%2e/ig, '.') 61 | chunk = chunk.replace(/%2f|%5c/ig, '/') 62 | chunk = chunk.replace(/[\/\\]/g, '/') 63 | chunk = chunk.replace(/[\/\\]\.\.[\/\\]/g, '') 64 | chunk = chunk.replace(/^\/+/g, '') 65 | 66 | return chunk 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npmdl", 3 | "version": "1.0.2", 4 | "description": "Conveniently download files from npm packages", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "node test | tap-spec" 9 | }, 10 | "author": { 11 | "name": "Hugh Kennedy", 12 | "email": "hughskennedy@gmail.com", 13 | "url": "http://hughsk.io/" 14 | }, 15 | "dependencies": { 16 | "mkdirp": "^0.5.0", 17 | "request": "^2.53.0", 18 | "tar-fs": "^1.5.0", 19 | "userhome": "^1.0.0" 20 | }, 21 | "devDependencies": { 22 | "rimraf": "^2.3.2", 23 | "tap-spec": "^2.2.1", 24 | "tape": "^3.5.0" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git://github.com/hughsk/npmdl.git" 29 | }, 30 | "keywords": [ 31 | "npm", 32 | "download", 33 | "dl", 34 | "files", 35 | "remote", 36 | "unpack", 37 | "untar" 38 | ], 39 | "homepage": "https://github.com/hughsk/npmdl", 40 | "bugs": { 41 | "url": "https://github.com/hughsk/npmdl/issues" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const rimraf = require('rimraf') 2 | const test = require('tape') 3 | const path = require('path') 4 | const npmdl = require('./') 5 | const fs = require('fs') 6 | 7 | const testDir = path.join(__dirname, '.test') 8 | 9 | test('setup', function(t) { 10 | if (fs.existsSync(testDir)) 11 | rimraf.sync(testDir) 12 | 13 | t.end() 14 | }) 15 | 16 | test('npmdl', function(t) { 17 | t.plan(12) 18 | 19 | ;['9.0.0', '8.0.0'].forEach(function(version) { 20 | npmdl(testDir)('browserify', version, 'package.json', function(err, content) { 21 | if (err) return t.fail(err.message || err) 22 | 23 | t.pass('version: ' + version) 24 | t.ok(fs.existsSync(path.join(testDir, 'browserify')), 'package directory created') 25 | t.ok(fs.existsSync(path.join(testDir, 'browserify', version)), 'version directory created') 26 | t.ok(fs.existsSync(path.join(testDir, 'browserify', version, 'package')), 'files directory created') 27 | t.ok(fs.existsSync(path.join(testDir, 'browserify', version, 'package', 'package.json')), 'package.json created') 28 | t.equal(fs.readFileSync(path.join(testDir, 'browserify', version, 'package', 'package.json'), 'utf8'), content, 'content argument matches') 29 | }) 30 | }) 31 | }) 32 | 33 | test('npmdl: handles missing name safely', function(t) { 34 | npmdl(testDir)(null, null, null, function(err, content) { 35 | t.ok(err, 'error reported') 36 | t.end() 37 | }) 38 | }) 39 | 40 | test('npmdl: handles missing version safely', function(t) { 41 | npmdl(testDir)('glsl-raytrace', null, null, function(err, content) { 42 | t.ok(err, 'error reported') 43 | t.end() 44 | }) 45 | }) 46 | 47 | test('npmdl: handles missing file safely', function(t) { 48 | npmdl(testDir)('glsl-raytrace', '1.0.0', null, function(err, content) { 49 | t.ok(err, 'error reported') 50 | t.end() 51 | }) 52 | }) 53 | 54 | test('teardown', function(t) { 55 | rimraf.sync(testDir) 56 | t.end() 57 | }) 58 | --------------------------------------------------------------------------------