├── .gitignore ├── .github └── workflows │ ├── stale.yml │ └── generated-pr.yml ├── npm-shrinkwrap.json ├── bin └── main.js ├── LICENSE ├── README.md ├── lib ├── ipfs.js ├── npm.js ├── utils.js ├── registry.js └── inpm.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipfs-npm", 3 | "version": "0.2.0", 4 | "dependencies": { 5 | "ipfsd-ctl": { 6 | "version": "0.5.1", 7 | "from": "ipfsd-ctl@>=0.5.1 <0.6.0", 8 | "resolved": "https://registry.npmjs.org/ipfsd-ctl/-/ipfsd-ctl-0.5.1.tgz", 9 | "dependencies": { 10 | "ipfs-api": { 11 | "version": "2.4.0", 12 | "from": "ipfs-api@2.4.0", 13 | "resolved": "https://registry.npmjs.org/ipfs-api/-/ipfs-api-2.4.0.tgz" 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /bin/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var args = require('minimist')(process.argv, { 4 | boolean: [ 'dev', 'production', 'd', 'dupe', 'duplicate', 'preserve' ] 5 | }) 6 | var path = require('path') 7 | var W = require('watt').run 8 | require('colors') 9 | 10 | var INPM_PATH = process.env.INPM_PATH || path.join(process.env.HOME, '.inpm') 11 | process.env.INPM_PATH = INPM_PATH 12 | 13 | var inpm = require('../lib/inpm.js')(INPM_PATH) 14 | var opts = { 15 | dev: args.dev, 16 | production: args.production || process.env.NODE_ENV === 'production', 17 | duplicate: args.d || args.dupe || args.duplicate, 18 | preserve: args.preserve 19 | } 20 | 21 | W(function * (w) { 22 | try { 23 | var pkg = yield inpm.install(path.resolve('.'), opts, w) 24 | console.log('installed %s successfully'.green, (pkg.name + '@' + pkg.version).bold) 25 | process.exit(0) 26 | } catch (err) { 27 | console.error('%s %s', 'error:'.red.bold, err.message) 28 | console.error(err.stack.gray) 29 | process.exit(1) 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Protocol Labs, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ipfs-npm 2 | 3 | [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) 4 | [![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) 5 | [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) 6 | [![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 7 | 8 | > npm on IPFS 9 | 10 | ## Install 11 | 12 | ```js 13 | npm i -g ipfs-npm 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```js 19 | inpm 20 | ``` 21 | 22 | ### Options 23 | 24 | `boolean: [ 'dev', 'production', 'd', 'dupe', 'duplicate', 'preserve' ]` 25 | 26 | ## Contribute 27 | 28 | Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/ipfs-npm/issues)! 29 | 30 | This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). 31 | 32 | [![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) 33 | 34 | ## License 35 | 36 | [MIT](LICENSE) -------------------------------------------------------------------------------- /lib/ipfs.js: -------------------------------------------------------------------------------- 1 | var IpfsApi = require('ipfs-api') 2 | var ipfsd = require('ipfsd-ctl') 3 | var mkdirp = require('mkdirp') 4 | var path = require('path') 5 | var tar = require('tar-fs') 6 | var W = require('watt').wrap 7 | var utils = require('./utils.js') 8 | 9 | module.exports = function (inpmPath) { 10 | var ipfsPath = path.join(inpmPath, 'ipfs') 11 | 12 | var ipfsApi 13 | var getApi = W(function * (w) { 14 | if (ipfsApi) return ipfsApi 15 | try { 16 | ipfsApi = IpfsApi('localhost', 5001) 17 | yield ipfsApi.id(w) 18 | } catch (e) { 19 | yield mkdirp(inpmPath, w) 20 | var node = yield ipfsd.local(ipfsPath, w) 21 | if (!(yield utils.exists(ipfsPath, w))) { 22 | yield node.init(w) 23 | } 24 | ipfsApi = yield node.startDaemon(w) 25 | } 26 | return ipfsApi 27 | }) 28 | 29 | var get = W(function * (ipfsPath, dest, w) { 30 | var ipfs = yield getApi(w) 31 | var read = yield ipfs.send('get', [ ipfsPath ], null, false, w) 32 | var write = tar.extract(dest, { dmode: 0555, fmode: 0444, map: utils.tarStrip(1) }) 33 | read.pipe(write) 34 | yield write.on('finish', w) 35 | }) 36 | 37 | return { 38 | getApi, 39 | get: get 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipfs-npm", 3 | "version": "0.2.0", 4 | "description": "npm on IPFS", 5 | "main": "main.js", 6 | "bin": { 7 | "inpm": "bin/main.js" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "Matt Bell ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "colors": "^1.1.2", 16 | "concat-stream": "^1.5.0", 17 | "fs-extra": "^0.24.0", 18 | "ipfs-api": "^2.4.0", 19 | "ipfsd-ctl": "^0.5.1", 20 | "minimist": "^1.2.0", 21 | "mkdirp": "^0.5.1", 22 | "request": "^2.63.0", 23 | "rimraf": "^2.4.3", 24 | "semver": "^5.0.3", 25 | "subcomandante": "^1.0.3", 26 | "tar-fs": "^1.8.1", 27 | "watt": "^2.1.0" 28 | }, 29 | "ipfs": { 30 | "dependencies": { 31 | "concat-stream": "/ipfs/QmbhjvfvGhTT1ZVRnfRdh6EuJWfjGDscsfTgzjbSHSBFGp", 32 | "minimist": "/ipfs/QmbmjnDb9sAFnsgXPxgDAtyA7w38PuAAhCTWc8swdpggwC", 33 | "colors": "/ipfs/QmXGYMRouXoTNRXUtHJKGNKabq8s9FNUigU8kRPhtGQiWs", 34 | "ipfsd-ctl": "/ipfs/QmSR69763zgv4YxhceJd6jFE73KHBaTQJH2EUHzMuuYWZ6", 35 | "ipfs-api": "/ipfs/QmXDkWxQzjmQw7Lsk4ZVEwQTkrzNQ3JCB2gnutEfpHZzJr", 36 | "mkdirp": "/ipfs/QmTphhzzYm4d3Tn8f9UcfDEM2zZH6P3QB4jBxFjQENpbbw", 37 | "rimraf": "/ipfs/QmXhBNiCSagDMtHQr1qFWeGR5hk3ctVgg2Hi5nbsAD5f1i", 38 | "subcomandante": "/ipfs/QmctyeYfQ5NKu8wkC3f9UYKZCwE4C7sDaYdGAK56tgXnHK", 39 | "semver": "/ipfs/QmR5QwxSLH5Ro3NwfFqHn1K1RZtmjsFfHEvNKmCTcypgGz", 40 | "request": "/ipfs/QmczgjDpuChQGyEj5xUgWwjdYLguQ9WPGsVeXndh7Gj5vv", 41 | "watt": "/ipfs/Qmck6Wk9xodLv191qVVrmAZ1cUjpDVkezJxgb7ENJfPq36" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/npm.js: -------------------------------------------------------------------------------- 1 | var request = require('request') 2 | var semver = require('semver') 3 | var tar = require('tar-fs') 4 | var path = require('path') 5 | var utils = require('./utils.js') 6 | var W = require('watt').wrap 7 | var zlib = require('zlib') 8 | 9 | var REGISTRY_URI = 'https://registry.npmjs.org/' 10 | 11 | // fetches a package from npm and saves to the filesystem 12 | var fetch = W(function * (name, range, dest, w) { 13 | try { 14 | dest = path.resolve(dest) 15 | var version = range 16 | // skip checking registry for latest version if our version range is an exact version 17 | if (semver.clean(range) !== range) { 18 | version = yield latestVersion(name, range, w) 19 | } 20 | var tarballPath = REGISTRY_URI + name + '/-/' + name + '-' + version + '.tgz' 21 | var read = request(tarballPath, w.error) 22 | var gunzip = zlib.createGunzip() 23 | var write = tar.extract(dest, { dmode: 0555, fmode: 0444, map: utils.tarStrip(1) }) 24 | read.pipe(gunzip).pipe(write) 25 | yield write.once('finish', w.args) 26 | } catch (e) { 27 | if (e.syscall !== 'getaddrinfo' || e.code !== 'ENOTFOUND') throw e 28 | setTimeout(fetch, 50, name, range, dest, w._cb) 29 | } 30 | }) 31 | 32 | // gets the latest available version number that matches `range` 33 | var latestVersion = W(function * (name, range, w) { 34 | var pkgInfo = yield info(name, w) 35 | var versions = Object.keys(pkgInfo.versions) 36 | var latest = semver.maxSatisfying(versions, range) 37 | if (!latest) { 38 | throw new Error('No versions of "' + name + '" match version "' + range + '"') 39 | } 40 | return latest 41 | }) 42 | 43 | // gets info about a package from the registry 44 | var info = W(function * (name, w) { 45 | try { 46 | var res = yield request(REGISTRY_URI + name, w) 47 | return JSON.parse(res.body) 48 | } catch (e) { 49 | if (e.syscall !== 'getaddrinfo' || e.code !== 'ENOTFOUND') throw e 50 | setTimeout(info, 50, name, w._cb) 51 | } 52 | }) 53 | 54 | module.exports = { 55 | fetch, 56 | latestVersion, 57 | info 58 | } 59 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var subcom = require('subcomandante') 4 | var W = require('watt').wrap 5 | 6 | var readPackage = W(function * (modulePath, w) { 7 | var raw = yield fs.readFile(packagePath(modulePath), w) 8 | return JSON.parse(raw.toString()) 9 | }) 10 | 11 | var savePackage = W(function * (modulePath, data, w) { 12 | var raw = JSON.stringify(data, null, ' ') 13 | yield fs.writeFile(packagePath(modulePath), raw, w) 14 | }) 15 | 16 | var run = W(function * (cmd, args, opts, w) { 17 | if (typeof args === 'function') { 18 | w = args 19 | args = [] 20 | opts = {} 21 | } else if (typeof opts === 'function') { 22 | w = opts 23 | opts = {} 24 | } 25 | var c = subcom(cmd, args, opts, w) 26 | var res = yield c.once('exit', w.arg(0)) 27 | if (res !== 0) { 28 | throw yield c.once('error', w.arg(0)) 29 | } 30 | }) 31 | 32 | var resolveRequire = W(function * (from, name, w) { 33 | var modulePath = path.join(from, 'node_modules', name) 34 | if (yield exists(modulePath, w)) return modulePath 35 | var up = path.dirname(from) 36 | if (up === '.') { 37 | throw new Error('Could not resolve "' + name + '" from "' + from + '"') 38 | } 39 | return yield resolveRequire(up, name, w) 40 | }) 41 | 42 | function randomString () { 43 | return Math.random().toString(36).slice(3) 44 | } 45 | 46 | function exists (filename, cb) { 47 | fs.lstat(filename, function (err, stat) { 48 | if (err && err.code !== 'ENOENT') return cb(err) 49 | cb(null, !!stat) 50 | }) 51 | } 52 | 53 | function maybeJoin (basename) { 54 | return function (p) { 55 | if (path.basename(p) === basename) return p 56 | return path.join(p, basename) 57 | } 58 | } 59 | 60 | var packagePath = maybeJoin('package.json') 61 | var modulesPath = maybeJoin('node_modules') 62 | 63 | function getTempDir () { 64 | var dirname = 'inpm-' + process.pid + '-' + randomString() 65 | return path.join(process.env.TMPDIR || '/tmp', dirname) 66 | } 67 | 68 | function tarStrip (n) { 69 | return function (header) { 70 | var split = header.name.split(path.sep) 71 | header.name = split.slice(n).join(path.sep) 72 | return header 73 | } 74 | } 75 | 76 | module.exports = { 77 | readPackage, 78 | savePackage, 79 | run, 80 | randomString, 81 | exists, 82 | packagePath, 83 | modulesPath, 84 | getTempDir, 85 | resolveRequire, 86 | tarStrip 87 | } 88 | -------------------------------------------------------------------------------- /lib/registry.js: -------------------------------------------------------------------------------- 1 | var concat = require('concat-stream') 2 | var fs = require('fs') 3 | var mkdirp = require('mkdirp') 4 | var path = require('path') 5 | var semver = require('semver') 6 | var utils = require('./utils.js') 7 | var W = require('watt').wrap 8 | 9 | var ignoreFiles = new Set([ '.git', 'CVS', '.svn', '.hg' ]) 10 | 11 | module.exports = function (inpmPath) { 12 | var REGISTRY_PATH = path.join(inpmPath, 'node_modules') 13 | var ipfs = require('./ipfs.js')(inpmPath) 14 | 15 | function getPath (name, version) { 16 | if (typeof name === 'object') { 17 | version = name.version 18 | name = name.name 19 | } 20 | return path.join(REGISTRY_PATH, name, version) 21 | } 22 | 23 | var includedPaths = W(function * (dirPath, w) { 24 | var output = [] 25 | var dir = yield fs.readdir(dirPath, w) 26 | // TODO: .npmignore, .inpmignore 27 | for (var filename of dir) { 28 | if (ignoreFiles.has(filename)) continue 29 | output.push(path.join(dirPath, filename)) 30 | } 31 | return output 32 | }) 33 | 34 | // adds a module to IPFS and the local registry 35 | var add = W(function * (modulePath, w) { 36 | var pkg = yield utils.readPackage(modulePath, w) 37 | var regPath = getPath(pkg) 38 | if (yield has(pkg.name, pkg.version, w)) { 39 | throw new Error(pkg.name + '@' + pkg.version + ' is already in the registry') 40 | } 41 | yield mkdirp(path.dirname(regPath), w) 42 | var paths = yield includedPaths(modulePath, w) 43 | var ipfsApi = yield ipfs.getApi(w) 44 | var add = yield ipfsApi.send('add', null, { 45 | recursive: true, 46 | hidden: true, 47 | 'wrap-with-directory': true, 48 | followSymlinks: false 49 | }, paths, w) 50 | var ipfsPath = '/ipfs/' + add[add.length - 1].Hash 51 | yield fs.symlink(ipfsPath, regPath, w) 52 | return { ipfsPath, regPath, pkg } 53 | }) 54 | 55 | var latestVersion = W(function * (name, range, w) { 56 | var vs = yield versions(name, w) 57 | var latest = semver.maxSatisfying(vs, range) 58 | if (!latest) { 59 | throw new Error('No versions of "' + name + '" in the local registry satisfy "' + range + '"') 60 | } 61 | return latest 62 | }) 63 | 64 | var get = W(function * (name, range, w) { 65 | var latest = yield latestVersion(name, range, w) 66 | var regPath = getPath(name, latest) 67 | var ipfsPath = yield fs.readlink(regPath, w) 68 | var ipfsApi = yield ipfs.getApi(w) 69 | var pkgStream = yield ipfsApi.cat(path.join(ipfsPath, 'package.json'), w) 70 | var pkgRaw = yield pkgStream.pipe(concat(w.arg(0))) 71 | var pkg = JSON.parse(pkgRaw.toString()) 72 | return { ipfsPath, regPath, pkg } 73 | }) 74 | 75 | var has = W(function * (name, version, w) { 76 | return yield utils.exists(getPath(name, version), w) 77 | }) 78 | 79 | var versions = W(function * (name, w) { 80 | var modulePath = path.join(REGISTRY_PATH, name) 81 | if (!(yield utils.exists(modulePath, w))) { 82 | throw new Error('"' + name + '" is not in the local registry') 83 | } 84 | var versions = yield fs.readdir(modulePath, w) 85 | return versions.sort(semver.gt) 86 | }) 87 | 88 | return { 89 | path: getPath, 90 | includedPaths, 91 | add, 92 | latestVersion, 93 | get: get, 94 | has, 95 | versions 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/inpm.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs-extra') 4 | var mkdirp = require('mkdirp') 5 | var npm = require('./npm.js') 6 | var path = require('path') 7 | var rm = require('rimraf') 8 | var semver = require('semver') 9 | var utils = require('./utils.js') 10 | var W = require('watt').wrap 11 | require('colors') 12 | 13 | module.exports = function (inpmPath) { 14 | var registry = require('./registry.js')(inpmPath) 15 | var ipfs = require('./ipfs.js')(inpmPath) 16 | 17 | var dependencyKeys = [ 'dependencies', 'optionalDependencies', 'devDependencies' ] 18 | var installing = new Map() 19 | 20 | // installs an npm registry package, with IPFS dependencies added to the package.json 21 | var fetch = W(function * (name, version, opts, w) { 22 | if (typeof opts === 'function') { 23 | w = opts 24 | opts = {} 25 | } 26 | 27 | // check the npm registry for the latest version match 28 | var latest = yield npm.latestVersion(name, version, w) 29 | var id = name + '@' + latest 30 | 31 | // check if we already have that version in the local registry 32 | // TODO: if offline (no npm), use latest version match in local registry (if any) 33 | 34 | if (yield registry.has(name, latest, w)) { 35 | console.log('getting %s from registry'.green, id.bold) 36 | return yield registry.get(name, latest, w) 37 | } 38 | 39 | // check if this package is already being processed 40 | // if so, wait for the result rather than duplicating the work 41 | if (installing.has(id)) { 42 | console.log('getting %s from registry'.green, id.bold) 43 | return yield installing.get(id).push(w) 44 | } 45 | installing.set(id, []) 46 | 47 | var err = null 48 | try { 49 | // fetch package (without dependencies) from npm 50 | var tempPath = utils.getTempDir() 51 | yield mkdirp(tempPath, w) 52 | console.log('fetching %s'.magenta, id.bold) 53 | yield npm.fetch(name, latest, tempPath, w) 54 | 55 | // fetch the dependencies and install, then add this package to local registry 56 | yield install(tempPath, opts, w) 57 | var res = yield registry.add(tempPath, w) 58 | res.tempPath = tempPath 59 | 60 | // TODO: create a DAG node which links to the dependencies, and to the unixfs directory 61 | } catch (e) { 62 | err = e 63 | console.error(e.message.red) 64 | } 65 | 66 | var listeners = installing.get(id) 67 | installing.delete(id) 68 | listeners.forEach(cb => setImmediate(cb, err, res)) 69 | if (err) throw err 70 | 71 | console.log('%s %s: %s', '✓'.green.bold, id.bold, res.ipfsPath.gray) 72 | return res 73 | }) 74 | 75 | var fetchDependencies = W(function * (modulePath, opts, w) { 76 | if (typeof opts === 'function') { 77 | w = opts 78 | opts = {} 79 | } 80 | 81 | var pkg = opts.pkg || (yield utils.readPackage(modulePath, w)) 82 | // TODO: read npm-shrinkwrap.json 83 | 84 | // get paths of IPFSified dependencies 85 | var deps = {} 86 | for (let dep of dependencies(pkg, opts)) { 87 | w.parallel({ limit: 5 }, function * (w) { 88 | var depOpts = { 89 | depth: (opts.depth || 0) + 1, 90 | production: opts.production, 91 | dev: opts.dev 92 | } 93 | var depPkg = yield fetch(dep.name, dep.value, depOpts, w) 94 | deps[dep.key] = deps[dep.key] || {} 95 | deps[dep.key][dep.name] = depPkg 96 | }) 97 | } 98 | yield w.sync() // wait for all the parallel fetch tasks to finish 99 | return deps 100 | }) 101 | 102 | var install = W(function * (modulePath, opts, w) { 103 | if (typeof opts === 'function') { 104 | w = opts 105 | opts = {} 106 | } 107 | 108 | var pkg = opts.pkg = opts.pkg || (yield utils.readPackage(modulePath, w)) 109 | var deps = yield fetchDependencies(modulePath, opts, w) 110 | 111 | // TODO: run preinstall scripts. not sure how to handle this: 112 | // (if the script modifies files, it shouldn't affect the IPFS root of the package, 113 | // we should possible duplicate the package locally so the changes live outside of 114 | // the local registry) 115 | 116 | // install links in node_modules/ directory 117 | if (Object.keys(deps).length) { 118 | yield mkdirp(utils.modulesPath(modulePath), w) 119 | } 120 | for (let key in deps) { 121 | for (let depName in deps[key]) { 122 | var dep = deps[key][depName] 123 | var depPath = path.join(utils.modulesPath(modulePath), depName) 124 | 125 | // if module exists, delete if --force flag, skip if semver range is satisfied, 126 | // proceed with install otherwise 127 | if (yield utils.exists(depPath, w)) { 128 | if (opts.force) { 129 | yield rm(depPath, w) 130 | } else { 131 | var version = (yield utils.readPackage(depPath, w)).version 132 | if (semver.satisfies(version, pkg[key][depName])) { 133 | console.log('%s is already installed, skipping'.yellow, (depName + '@' + version).bold) 134 | continue 135 | } 136 | } 137 | } 138 | 139 | if (opts.duplicate) { 140 | if (dep.tempPath) { 141 | fs.copy(dep.tempPath, depPath, w) 142 | continue 143 | } 144 | console.log('duplicating %s'.cyan, (dep.pkg.name + '@' + dep.pkg.version).bold) 145 | yield duplicate(dep.ipfsPath, depPath, { pkg: dep.pkg }, w) 146 | } else { 147 | yield fs.symlink(dep.ipfsPath, depPath, w) 148 | } 149 | } 150 | } 151 | 152 | // TODO: run install, postinstall scripts. see preinstall comment above 153 | 154 | // save IPFS paths in package.json 155 | if (!opts.preserve) { 156 | pkg.ipfs = getIpfsPaths(deps) 157 | yield utils.savePackage(modulePath, pkg, w) 158 | } 159 | 160 | return pkg 161 | }) 162 | 163 | var duplicate = W(function * (ipfsPath, destPath, opts, w) { 164 | if (typeof opts === 'function') { 165 | w = opts 166 | opts = {} 167 | } 168 | 169 | yield ipfs.get(ipfsPath, destPath, w) 170 | var pkg = opts.pkg = opts.pkg || (yield utils.readPackage(destPath, w)) 171 | var nodeModulesPath = utils.modulesPath(destPath) 172 | yield rm(nodeModulesPath, w) 173 | 174 | for (let dep of dependencies(pkg.ipfs, opts)) { 175 | w.parallel({ limit: 5 }, function * (w) { 176 | var depPath = path.join(nodeModulesPath, dep.name) 177 | var depOpts = { 178 | depth: (opts.depth || 0) + 1, 179 | production: opts.production, 180 | dev: opts.dev 181 | } 182 | yield duplicate(dep.value, depPath, depOpts, w) 183 | }) 184 | } 185 | yield w.sync() 186 | }) 187 | 188 | var dependencies = function * (pkg, opts) { 189 | var dev = opts.dev || !opts.production && !opts.depth 190 | for (let key of dependencyKeys) { 191 | if (!pkg[key]) continue 192 | if (key === 'devDependencies' && !dev) continue 193 | for (let name in pkg[key]) { 194 | yield { name, value: pkg[key][name], key: key } 195 | } 196 | } 197 | } 198 | 199 | function getIpfsPaths (deps) { 200 | var output = {} 201 | for (var key in deps) { 202 | output[key] = {} 203 | for (var name in deps[key]) { 204 | output[key][name] = deps[key][name].ipfsPath 205 | } 206 | } 207 | return output 208 | } 209 | 210 | return { 211 | fetch, 212 | fetchDependencies, 213 | install, 214 | duplicate, 215 | dependencies 216 | } 217 | } 218 | --------------------------------------------------------------------------------