├── .github └── workflows │ ├── generated-pr.yml │ └── stale.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs └── index.md ├── img ├── ip-npm-small.png ├── ip-npm.png ├── npm-on-ipfs.jpg └── npm-on-ipfs.svg ├── package.json ├── src ├── cli │ └── bin.js ├── core │ ├── commands │ │ ├── proxy.js │ │ ├── rewrite-lock-file.js │ │ ├── start-ipfs.js │ │ ├── start-server.js │ │ └── update.js │ ├── config.js │ ├── handlers │ │ ├── manifest.js │ │ ├── root.js │ │ └── tarball.js │ └── index.js └── index.js └── test ├── fixtures └── package.json ├── install.spec.js └── node.js /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/t-run* 2 | # Logs 3 | logs 4 | *.log 5 | dist 6 | 7 | registry 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 31 | node_modules 32 | package-lock.json 33 | 34 | .env 35 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | dist 3 | img 4 | ci 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: npm 3 | stages: 4 | - check 5 | - test 6 | - cov 7 | 8 | node_js: 9 | - '10' 10 | 11 | os: 12 | - linux 13 | 14 | script: npx nyc -s npm run test -- --bail 15 | after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov 16 | 17 | jobs: 18 | include: 19 | - stage: check 20 | script: 21 | - npm run lint 22 | 23 | - stage: test 24 | name: node 25 | script: npm run test 26 | 27 | notifications: 28 | email: false 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [0.16.3](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.16.2...v0.16.3) (2019-06-06) 3 | 4 | 5 | ### Bug Fixes 6 | 7 | * do not stop external IPFS node ([#99](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/99)) ([40c12a6](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/40c12a6)) 8 | * dont spawn node for local ops ([#110](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/110)) ([36f4e8f](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/36f4e8f)) 9 | * only spawn node once ([#111](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/111)) ([8741a66](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/8741a66)) 10 | * remove redundant ipfs configing code ([55978ab](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/55978ab)) 11 | * tests by adding go-ipfs to PATH ([#100](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/100)) ([7489c94](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/7489c94)) 12 | 13 | 14 | ### Features 15 | 16 | * support config files ([#102](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/102)) ([877840b](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/877840b)) 17 | 18 | 19 | 20 | 21 | ## [0.16.2](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.16.1...v0.16.2) (2019-01-18) 22 | 23 | 24 | 25 | 26 | ## [0.16.1](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.16.0...v0.16.1) (2018-12-10) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * catch errors when connecting to the registry ([a412f98](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/a412f98)) 32 | 33 | 34 | 35 | 36 | # [0.16.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.15.0...v0.16.0) (2018-12-07) 37 | 38 | 39 | ### Features 40 | 41 | * inherit stdin so we can interact with package manager ([267b73d](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/267b73d)) 42 | 43 | 44 | 45 | 46 | # [0.15.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.14.0...v0.15.0) (2018-12-07) 47 | 48 | 49 | 50 | 51 | # [0.14.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.13.0...v0.14.0) (2018-12-07) 52 | 53 | 54 | 55 | 56 | # [0.13.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.12.0...v0.13.0) (2018-12-07) 57 | 58 | 59 | ### Features 60 | 61 | * default to yarn for yarn and npm for npm ([011baad](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/011baad)) 62 | 63 | 64 | 65 | 66 | # [0.12.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.11.0...v0.12.0) (2018-12-06) 67 | 68 | 69 | ### Features 70 | 71 | * do not delete old registry index for speed ([96ff811](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/96ff811)) 72 | 73 | 74 | 75 | 76 | # [0.11.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.10.0...v0.11.0) (2018-10-26) 77 | 78 | 79 | ### Features 80 | 81 | * add logo ([1cf85b9](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/1cf85b9)) 82 | * allow using non-disposable ipfs nodes ([101e630](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/101e630)) 83 | 84 | 85 | ### Performance Improvements 86 | 87 | * fall back to npm if ipfs is too slow ([f96a751](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/f96a751)) 88 | 89 | 90 | 91 | 92 | # [0.10.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.9.0...v0.10.0) (2018-10-04) 93 | 94 | 95 | ### Features 96 | 97 | * add s3 storage option ([6a522c8](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/6a522c8)) 98 | 99 | 100 | 101 | 102 | # [0.9.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.8.0...v0.9.0) (2018-10-04) 103 | 104 | 105 | ### Bug Fixes 106 | 107 | * friendlier error pages ([747320d](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/747320d)) 108 | 109 | 110 | ### Features 111 | 112 | * use ipfs to fetch files if available ([5e135fe](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/5e135fe)) 113 | 114 | 115 | 116 | 117 | # [0.8.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.7.4...v0.8.0) (2018-09-21) 118 | 119 | 120 | ### Features 121 | 122 | * adds retry when npm 404s, fixes [#61](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/61) ([d884991](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/d884991)) 123 | 124 | 125 | 126 | 127 | ## [0.7.4](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.7.3...v0.7.4) (2018-09-20) 128 | 129 | 130 | 131 | 132 | ## [0.7.3](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.7.2...v0.7.3) (2018-09-20) 133 | 134 | 135 | ### Bug Fixes 136 | 137 | * increase max body size ([d478378](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/d478378)) 138 | 139 | 140 | 141 | 142 | ## [0.7.2](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.7.1...v0.7.2) (2018-09-20) 143 | 144 | 145 | ### Bug Fixes 146 | 147 | * fixes [#59](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/59) and [#60](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/60) with better error detection and messages ([93ad289](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/93ad289)) 148 | 149 | 150 | 151 | 152 | ## [0.7.1](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.7.0...v0.7.1) (2018-09-20) 153 | 154 | 155 | 156 | 157 | # [0.7.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.6.4...v0.7.0) (2018-09-20) 158 | 159 | 160 | ### Features 161 | 162 | * forward non-get requests to the registry, fixes [#58](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/58) ([b493e4c](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/b493e4c)) 163 | 164 | 165 | 166 | 167 | ## [0.6.4](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.5.1...v0.6.4) (2018-09-20) 168 | 169 | 170 | ### Bug Fixes 171 | 172 | * do not use path.join because windows ([8b51156](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/8b51156)) 173 | * handle scoped modules, fixes [#57](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/57) ([bd08ddd](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/bd08ddd)) 174 | * linting errors ([ca7fd83](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/ca7fd83)) 175 | * update arg name ([b707cc0](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/b707cc0)) 176 | * use same blobstore instance as it will create an ipfs node ([afe5c58](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/afe5c58)) 177 | 178 | 179 | ### Features 180 | 181 | * allow configuring external host address separately from internal ([3405f0a](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/3405f0a)) 182 | * mirror npm instead of pulling/publishing registry tree ([c734d44](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/c734d44)) 183 | 184 | 185 | ### Performance Improvements 186 | 187 | * increase concurrency when running local IPFS node ([126747b](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/126747b)) 188 | * throttle requests to daemon and use build in ipfs node ([4118ed5](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/4118ed5)) 189 | 190 | 191 | 192 | 193 | ## [0.5.1](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.5.0...v0.5.1) (2016-03-23) 194 | 195 | 196 | 197 | 198 | # [0.5.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.4.0...v0.5.0) (2016-02-01) 199 | 200 | 201 | 202 | 203 | # [0.4.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.3.3...v0.4.0) (2016-02-01) 204 | 205 | 206 | 207 | 208 | ## [0.3.3](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.3.1...v0.3.3) (2016-01-02) 209 | 210 | 211 | 212 | 213 | ## [0.3.1](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.3.0...v0.3.1) (2015-12-18) 214 | 215 | 216 | 217 | 218 | # [0.3.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.12...v0.3.0) (2015-12-18) 219 | 220 | 221 | 222 | 223 | ## [0.2.12](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.11...v0.2.12) (2015-12-17) 224 | 225 | 226 | 227 | 228 | ## [0.2.11](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.10...v0.2.11) (2015-12-17) 229 | 230 | 231 | 232 | 233 | ## [0.2.10](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.9...v0.2.10) (2015-11-30) 234 | 235 | 236 | 237 | 238 | ## [0.2.9](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.8...v0.2.9) (2015-11-26) 239 | 240 | 241 | 242 | 243 | ## [0.2.8](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.7...v0.2.8) (2015-11-25) 244 | 245 | 246 | 247 | 248 | ## [0.2.7](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.6...v0.2.7) (2015-11-25) 249 | 250 | 251 | 252 | 253 | ## [0.2.6](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.5...v0.2.6) (2015-11-25) 254 | 255 | 256 | 257 | 258 | ## [0.2.5](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.4...v0.2.5) (2015-11-24) 259 | 260 | 261 | 262 | 263 | ## [0.2.4](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.3...v0.2.4) (2015-11-24) 264 | 265 | 266 | 267 | 268 | ## [0.2.3](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.2...v0.2.3) (2015-11-23) 269 | 270 | 271 | 272 | 273 | ## [0.2.2](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.1...v0.2.2) (2015-11-23) 274 | 275 | 276 | 277 | 278 | ## [0.2.1](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.2.0...v0.2.1) (2015-11-23) 279 | 280 | 281 | 282 | 283 | # [0.2.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.1.6...v0.2.0) (2015-11-23) 284 | 285 | 286 | 287 | 288 | ## [0.1.6](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.1.5...v0.1.6) (2015-11-23) 289 | 290 | 291 | ### Features 292 | 293 | * Add host and port options. ([5c3eeea](https://github.com/ipfs-shipyard/npm-on-ipfs/commit/5c3eeea)), closes [#3](https://github.com/ipfs-shipyard/npm-on-ipfs/issues/3) 294 | 295 | 296 | 297 | 298 | ## [0.1.5](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.1.4...v0.1.5) (2015-11-18) 299 | 300 | 301 | 302 | 303 | ## [0.1.4](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.1.3...v0.1.4) (2015-11-16) 304 | 305 | 306 | 307 | 308 | ## [0.1.3](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.1.2...v0.1.3) (2015-11-16) 309 | 310 | 311 | 312 | 313 | ## [0.1.2](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.1.1...v0.1.2) (2015-11-16) 314 | 315 | 316 | 317 | 318 | ## [0.1.1](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.1.0...v0.1.1) (2015-11-16) 319 | 320 | 321 | 322 | 323 | # [0.1.0](https://github.com/ipfs-shipyard/npm-on-ipfs/compare/v0.0.1...v0.1.0) (2015-11-15) 324 | 325 | 326 | 327 | 328 | ## 0.0.1 (2015-11-14) 329 | 330 | 331 | 332 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 David Dias 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 |

2 | npm distributed on top of lots of connected IPFS nodes worldwide 3 |

4 | 5 | # npm-on-IPFS 6 | 7 | [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) 8 | [![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) 9 | [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) 10 | [![Build Status](https://flat.badgen.net/travis/ipfs-shipyard/npm-on-ipfs)](https://travis-ci.com/ipfs-shipyard/npm-on-ipfs) 11 | [![Code Coverage](https://codecov.io/gh/ipfs-shipyard/npm-on-ipfs/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs-shipyard/npm-on-ipfs) 12 | [![Dependency Status](https://david-dm.org/ipfs-shipyard/npm-on-ipfs.svg?style=flat-square)](https://david-dm.org/ipfs-shipyard/npm-on-ipfs) 13 | 14 | **TLDR: npm-on-ipfs enables you to install your favourite modules from the distributed web using IPFS, as well as to have a cache always ready and shared on your local network — great for enterprise and community coding settings, or even just enabling more speedy work when you and your friends are working at a low-bandwidth coffee shop.** 15 | 16 | ## Quick background 17 | 18 | As the largest software registry in the world, [npm](https://www.npmjs.com) is also the de facto package manager for the JavaScript ecosystem, with more than [900k](https://replicate.npmjs.com/_all_docs) packages and more than 7 billion downloads a week. It's incredibly fast and reliable — however, we couldn't stop ourselves from wondering what would happen if we put the world's largest registry on the distributed web. 19 | 20 | The result is npm-on-ipfs: a module that wraps your package manager of choice (npm or yarn) in configuration to use [IPFS](https://ipfs.io/), not HTTP, to retrieve your dependencies from the central npm registry. It's still a work in progress, but we think you'll find it useful and awesome for the following reasons: 21 | 22 | - Having dependencies on the distributed web makes development **more available** because multiple nodes supplying tarballs means no panic if a single source goes dark 23 | - It can also be **faster and cheaper** — if dependencies are already being hosted on your local network, this means lower bandwidth cost and higher speed 24 | - If enough dependencies are hosted on your local network (think enterprise or community development settings), that network can operate **offline-first**: Take your team on a remote mountain retreat and hack away! 25 | 26 | ## Install & use 27 | 28 | ```console 29 | $ npm i ipfs-npm -g 30 | ``` 31 | 32 | ### Get started! 33 | 34 | `ipfs-npm` wraps your favorite package manager (npm or yarn) with configuration that uses IPFS, rather than HTTP, to retrieve your dependencies from the central npm registry. Since it's intended to replace npm/yarn, all the commands you're used to will work in the same way. 35 | 36 | For example: In the directory with your `package.json` file, run ... 37 | 38 | ```console 39 | $ ipfs-npm install 40 | 👿 Spawning an in-process IPFS node 41 | Swarm listening on /ip4/127.0.0.1/tcp/57029/ipfs/QmVDtTRCoYyYu5JFdtrtBMS4ekPn8f9NndymoHdWuuJ7N2 42 | 🗂️ Loading registry index from https://registry.js.ipfs.io 43 | ☎️ Dialling registry mirror /ip4/35.178.192.119/tcp/10015/ipfs/QmWBaYSnmgZi6F6D69JuZGhyL8rm6pt8GX5r7Atc6Gd7vR,/dns4/registry.js.ipfs.io/tcp/10015/ipfs/QmWBaYSnmgZi6F6D69JuZGhyL8rm6pt8GX5r7Atc6Gd7vR 44 | 🗑️ Replacing old registry index if it exists 45 | 📠 Copying registry index /ipfs/QmQmVsNFw3stJky7agrETeB9kZqkcvLSLRnFFMrhiR8zG1 to /npm-registry 46 | 👩‍🚀 Starting local proxy 47 | 🚀 Server running on port 57314 48 | 🎁 Installing dependencies with /Users/alex/.nvm/versions/node/v10.8.0/bin/npm 49 | ... 50 | ``` 51 | 52 | You can use any command you'd use with npm/yarn with ipfs-npm in exactly the same way: 53 | 54 | ```console 55 | $ ipfs-npm install 56 | $ ipfs-npm version minor 57 | $ ipfs-npm publish 58 | $ ipfs-npm --package-manager=yarn 59 | // etc 60 | ``` 61 | 62 | ## CLI guide 63 | 64 | ```console 65 | $ ipfs-npm --help 66 | ipfs-npm 67 | 68 | Installs your js dependencies using IPFS 69 | 70 | Options: 71 | --help Show help [boolean] 72 | --version Show version number [boolean] 73 | --package-manager Which package manager to use - eg. npm or yarn 74 | [default: "npm"] 75 | --ipfs-registry Where to download any packages that haven't made 76 | it into the registry index yet from 77 | [default: "https://registry.js.ipfs.io"] 78 | --registry-upload-size-limit How large a file upload to allow when proxying 79 | for the registry [default: "1024MB"] 80 | --registry-update-interval Only request the manifest for a given module 81 | every so many ms [default: 60000] 82 | --registry-connect-timeout How long to wait while dialling the mirror 83 | before timing out [default: 5000] 84 | --registry-read-timeout How long to wait for individual packages before 85 | timing out [default: 5000] 86 | --ipfs-mfs-prefix Which mfs prefix to use 87 | [default: "/npm-registry"] 88 | --ipfs-node "proc" to start an in-process IPFS node, 89 | "disposable" to start an in-process disposable 90 | node, "go" or "js" to spawn an IPFS node as a 91 | separate process or a multiaddr that resolves to 92 | a running node [default: "proc"] 93 | --ipfs-repo If --ipfs-node is set to "proc", this is the 94 | path that contains the IPFS repo to use 95 | [default: "/Users/alex/.jsipfs"] 96 | --ipfs-flush Whether to flush the MFS cache [default: true] 97 | --clone-pin Whether to pin cloned modules [default: false] 98 | --request-max-sockets How many concurrent http requests to make while 99 | cloning the repo [default: 10] 100 | --request-retries How many times to retry when downloading 101 | manifests and tarballs from the registry 102 | [default: 5] 103 | --request-retry-delay How long in ms to wait between retries 104 | [default: 1000] 105 | --request-timeout How long in ms we should wait when requesting 106 | files [default: 30000] 107 | --npm-registry A fallback to use if the IPFS npm registry is 108 | unavailable 109 | [default: "https://registry.npmjs.com"] 110 | ``` 111 | 112 | ## Configuration files 113 | 114 | ipfs-npm uses [`rc`](https://github.com/dominictarr/rc) to parse configuration files. Please see the [`rc` repository](https://github.com/dominictarr/rc#standards) for the order of precedence used when searching for configuration files. The app is `ipfs-npm`. 115 | 116 | For instance, if you want to always use a remote daemon, you could create a `~/.ipfs-npmrc` file like this: 117 | 118 | ```json 119 | { 120 | "ipfsNode": "/ip4/127.0.0.1/tcp/5001" 121 | } 122 | ``` 123 | 124 | ## To learn more 125 | 126 | [Protocol Labs](https://protocol.ai), the organization behind IPFS, is actively working on improving the landscape for package managers and the distributed web in 2019 and beyond. To that end, we've created an [IPFS Package Managers Special Interest Group](https://github.com/ipfs/package-managers), and your feedback and contributions are very welcome! 127 | 128 | If you're actively (or just casually) using npm-on-ipfs and have feedback about your user experience, we'd love to hear from you, too. Please open an issue in the [Special Interest Group](https://github.com/ipfs/package-managers) and we'll get right back to you. 129 | 130 | More resources you may find useful: 131 | - [The original npm-on-ipfs demo video](https://vimeo.com/147968322) 132 | - [A more detailed introduction to npm-on-ipfs from David Dias' blog](http://daviddias.me/blog/stellar-module-management/) 133 | - [Node.js Interactive talk on Stellar Module Management, aka npm-on-ipfs](https://www.youtube.com/watch?v=-S-Tc7Gl8FM) 134 | 135 | ## Lead maintainer 136 | 137 | [Alex Potsides](https://github.com/achingbrain) 138 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Advanced npm-on-ipfs features 2 | 3 | ## Updating your registry index 4 | 5 | By default we keep a local copy of the npm registry index in your [`mfs`](https://github.com/ipfs/js-ipfs-mfs) at `/npm-registry` and update it when you install new packages. This means for the first request for a given module it will be fetched from npm over http. There is a continously updated copy of the npm registry index with CIDs for all modules being generated by the [`ipfs-npm-registry-mirror`](https://github.com/ipfs-shipyard/ipfs-npm-registry-mirror) - if you'd like to clone this registry index, use the `update-registry-index` subcommand: 6 | 7 | ```console 8 | $ ipfs-npm update-registry-index 9 | ``` 10 | -------------------------------------------------------------------------------- /img/ip-npm-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/npm-on-ipfs/14956f58e085c6c7c58e42645d5ab466d9635c7f/img/ip-npm-small.png -------------------------------------------------------------------------------- /img/ip-npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/npm-on-ipfs/14956f58e085c6c7c58e42645d5ab466d9635c7f/img/ip-npm.png -------------------------------------------------------------------------------- /img/npm-on-ipfs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/npm-on-ipfs/14956f58e085c6c7c58e42645d5ab466d9635c7f/img/npm-on-ipfs.jpg -------------------------------------------------------------------------------- /img/npm-on-ipfs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | npm-on-ipfs 4 | Original idea by @olizilla. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipfs-npm", 3 | "version": "0.16.3", 4 | "description": "Install your npm dependencies using IPFS and the distributed web! :D", 5 | "leadMaintainer": "Alex Potsides ", 6 | "main": "src/cli/bin.js", 7 | "bin": { 8 | "ipfs-npm": "src/cli/bin.js", 9 | "npm-ipfs": "src/cli/bin.js", 10 | "ipfs-yarn": "src/cli/bin.js", 11 | "yarn-ipfs": "src/cli/bin.js" 12 | }, 13 | "scripts": { 14 | "test": "aegir test -t node", 15 | "test:node": "aegir test -t node", 16 | "coverage": "aegir coverage", 17 | "lint": "aegir lint", 18 | "start": "node .", 19 | "release": "aegir release -t node", 20 | "release-minor": "aegir release --type minor -t node", 21 | "release-major": "aegir release --type major -t node" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/ipfs-shipyard/npm-on-ipfs.git" 26 | }, 27 | "keywords": [ 28 | "IPFS", 29 | "npm", 30 | "cache", 31 | "offline" 32 | ], 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/ipfs-shipyard/npm-on-ipfs/issues" 36 | }, 37 | "homepage": "https://github.com/ipfs-shipyard/npm-on-ipfs#readme", 38 | "dependencies": { 39 | "@yarnpkg/lockfile": "^1.1.0", 40 | "debug": "^4.1.1", 41 | "dnscache": "^1.0.2", 42 | "express": "^4.16.4", 43 | "express-http-proxy": "^1.5.1", 44 | "ipfs": "^0.36.3", 45 | "ipfs-http-client": "^32.0.1", 46 | "ipfs-registry-mirror-common": "^1.0.13", 47 | "ipfsd-ctl": "~0.42.2", 48 | "once": "^1.4.0", 49 | "rc": "^1.2.8", 50 | "request": "^2.88.0", 51 | "request-promise": "^4.2.4", 52 | "which-promise": "^1.0.0", 53 | "yargs": "^13.2.4" 54 | }, 55 | "devDependencies": { 56 | "aegir": "^19.0.3", 57 | "go-ipfs-dep": "~0.4.20", 58 | "mock-require": "^3.0.3", 59 | "npm-run-all": "^4.1.5", 60 | "output-buffer": "^1.2.0", 61 | "sinon": "^7.3.2", 62 | "yarn": "^1.16.0" 63 | }, 64 | "contributors": [ 65 | "Alan Shaw ", 66 | "Andrew Nesbitt ", 67 | "Danny Arnold ", 68 | "David Dias ", 69 | "Diogo Silva ", 70 | "Henrique Dias ", 71 | "Jessica Schilling ", 72 | "Lars-Magnus Skog ", 73 | "Mateusz Naściszewski ", 74 | "achingbrain ", 75 | "dignifiedquire ", 76 | "gonders " 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /src/cli/bin.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | require('dnscache')({ enable: true }) 6 | 7 | const pkg = require('../../package') 8 | const path = require('path') 9 | const os = require('os') 10 | const rc = require('rc') 11 | 12 | process.title = pkg.name 13 | 14 | const yargs = require('yargs') 15 | .config(rc(pkg.name, null, {})) 16 | 17 | yargs.command('$0', 'Installs your js dependencies using IPFS', (yargs) => { // eslint-disable-line no-unused-expressions 18 | yargs 19 | .option('package-manager', { 20 | describe: 'Which package manager to use - eg. npm or yarn', 21 | default: process.argv[1].indexOf('yarn') === -1 ? 'npm' : 'yarn' 22 | }) 23 | 24 | .option('ipfs-registry', { 25 | describe: 'Where to download any packages that haven\'t made it into the registry index yet from', 26 | default: 'https://registry.js.ipfs.io' 27 | }) 28 | .option('registry-upload-size-limit', { 29 | describe: 'How large a file upload to allow when proxying for the registry', 30 | default: '1024MB' 31 | }) 32 | .option('registry-update-interval', { 33 | describe: 'Only request the manifest for a given module every so many ms', 34 | default: 60000 35 | }) 36 | .option('registry-connect-timeout', { 37 | describe: 'How long to wait while dialling the mirror before timing out', 38 | default: 5000 39 | }) 40 | .option('registry-read-timeout', { 41 | describe: 'How long to wait for individual packages before timing out', 42 | default: 5000 43 | }) 44 | 45 | .option('ipfs-mfs-prefix', { 46 | describe: 'Which mfs prefix to use', 47 | default: '/npm-registry' 48 | }) 49 | .option('ipfs-node', { 50 | describe: '"proc" to start an in-process IPFS node, "disposable" to start an in-process disposable node, "go" or "js" to spawn an IPFS node as a separate process or a multiaddr that resolves to a running node', 51 | default: 'proc' 52 | }) 53 | .option('ipfs-repo', { 54 | describe: 'If --ipfs-node is set to "proc", this is the path that contains the IPFS repo to use', 55 | default: path.join(os.homedir(), '.jsipfs') 56 | }) 57 | .option('ipfs-flush', { 58 | describe: 'Whether to flush the MFS cache', 59 | default: true 60 | }) 61 | 62 | .option('clone-pin', { 63 | describe: 'Whether to pin cloned modules', 64 | default: false 65 | }) 66 | 67 | .option('request-max-sockets', { 68 | describe: 'How many concurrent http requests to make while cloning the repo', 69 | default: 10 70 | }) 71 | .option('request-retries', { 72 | describe: 'How many times to retry when downloading manifests and tarballs from the registry', 73 | default: 5 74 | }) 75 | .option('request-retry-delay', { 76 | describe: 'How long in ms to wait between retries', 77 | default: 1000 78 | }) 79 | .option('request-timeout', { 80 | describe: 'How long in ms we should wait when requesting files', 81 | default: 30000 82 | }) 83 | 84 | .option('npm-registry', { 85 | describe: 'A fallback to use if the IPFS npm registry is unavailable', 86 | default: 'https://registry.npmjs.com' 87 | }) 88 | }, require('../core')) 89 | .argv 90 | -------------------------------------------------------------------------------- /src/core/commands/proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const config = require('../config') 4 | const startServer = require('./start-server') 5 | const rewriteLockfile = require('./rewrite-lock-file') 6 | const { spawn } = require('child_process') 7 | const which = require('which-promise') 8 | 9 | const cleanUpOps = [] 10 | 11 | const cleanUp = () => { 12 | Promise.all( 13 | cleanUpOps.map(op => op()) 14 | ) 15 | .then(() => { 16 | process.exit(0) 17 | }) 18 | } 19 | 20 | process.on('SIGTERM', cleanUp) 21 | process.on('SIGINT', cleanUp) 22 | 23 | module.exports = async (options) => { 24 | options = config(options) 25 | 26 | console.info('👩‍🚀 Starting local proxy') // eslint-disable-line no-console 27 | 28 | const server = await startServer(options) 29 | 30 | cleanUpOps.push(() => { 31 | return new Promise((resolve) => { 32 | server.close(() => { 33 | console.info('✋ Server stopped') // eslint-disable-line no-console 34 | resolve() 35 | }) 36 | }) 37 | }) 38 | 39 | const packageManager = await which(options.packageManager) 40 | 41 | console.info(`🎁 Installing dependencies with ${packageManager}`) // eslint-disable-line no-console 42 | 43 | const proc = spawn(packageManager, [ 44 | `--registry=http://localhost:${options.http.port}` 45 | ].concat(process.argv.slice(2)), { 46 | stdio: 'inherit' 47 | }) 48 | 49 | proc.on('close', async (code) => { 50 | console.log(`🎁 ${packageManager} exited with code ${code}`) // eslint-disable-line no-console 51 | 52 | await rewriteLockfile(options) 53 | await cleanUp() 54 | 55 | process.exit(code) 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /src/core/commands/rewrite-lock-file.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { 4 | readFileSync, 5 | writeFileSync, 6 | existsSync 7 | } = require('fs') 8 | const path = require('path') 9 | const URL = require('url').URL 10 | const yarnLockfile = require('@yarnpkg/lockfile') 11 | 12 | const replaceRegistryPath = (dependencies, registry) => { 13 | Object.keys(dependencies) 14 | .map(name => dependencies[name]) 15 | .forEach(dependency => { 16 | if (dependency.resolved) { 17 | const url = new URL(dependency.resolved) 18 | 19 | url.protocol = registry.protocol 20 | url.host = registry.host 21 | url.port = registry.protocol === 'https:' ? 443 : 80 22 | 23 | dependency.resolved = url.toString() 24 | } 25 | 26 | replaceRegistryPath(dependency.dependencies || {}, registry) 27 | }) 28 | } 29 | 30 | module.exports = (options) => { 31 | if (options.packageManager === 'npm') { 32 | const lockfilePath = path.join(process.cwd(), 'package-lock.json') 33 | 34 | if (!existsSync(lockfilePath)) { 35 | console.info(`🤷 No package-lock.json found`) // eslint-disable-line no-console 36 | return 37 | } 38 | 39 | console.info(`🔏 Updating package-lock.json`) // eslint-disable-line no-console 40 | 41 | const lockfile = JSON.parse(readFileSync(lockfilePath, 'utf8')) 42 | 43 | replaceRegistryPath(lockfile.dependencies || {}, new URL(options.registry)) 44 | 45 | writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2)) 46 | } else if (options.packageManager === 'yarn') { 47 | const lockfilePath = path.join(process.cwd(), 'yarn.lock') 48 | 49 | if (!existsSync(lockfilePath)) { 50 | console.info(`🤷 No yarn.lock found`) // eslint-disable-line no-console 51 | return 52 | } 53 | 54 | console.info(`🔏 Updating yarn.lock`) // eslint-disable-line no-console 55 | 56 | const lockfile = yarnLockfile.parse(readFileSync(lockfilePath, 'utf8')) 57 | 58 | replaceRegistryPath(lockfile.object, new URL(options.registry)) 59 | 60 | writeFileSync(lockfilePath, yarnLockfile.stringify(lockfile, null, 2)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/core/commands/start-ipfs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const IpfsApi = require('ipfs-http-client') 4 | const ipfsdCtrl = require('ipfsd-ctl') 5 | const which = require('which-promise') 6 | const promisify = require('util').promisify 7 | const request = require('ipfs-registry-mirror-common/utils/retry-request') 8 | const timeout = require('ipfs-registry-mirror-common/utils/timeout-promise') 9 | 10 | const cleanUpOps = [] 11 | 12 | const cleanUp = () => { 13 | Promise.all( 14 | cleanUpOps.map(op => op()) 15 | ) 16 | .then(() => { 17 | process.exit(0) 18 | }) 19 | } 20 | 21 | process.on('SIGTERM', cleanUp) 22 | process.on('SIGINT', cleanUp) 23 | 24 | const spawn = (createArgs, spawnArgs = { init: true }) => { 25 | return new Promise((resolve, reject) => { 26 | ipfsdCtrl 27 | .create(createArgs) 28 | .spawn(spawnArgs, (error, node) => { 29 | if (error) { 30 | return reject(error) 31 | } 32 | 33 | resolve(node) 34 | }) 35 | }) 36 | } 37 | 38 | const startIpfs = async (config) => { 39 | if (config.ipfs.node === 'proc') { 40 | console.info(`😈 Spawning an in-process IPFS node using repo at ${config.ipfs.repo}`) // eslint-disable-line no-console 41 | 42 | const node = await spawn({ 43 | type: 'proc', 44 | exec: require('ipfs') 45 | }, { 46 | disposable: false, 47 | repoPath: config.ipfs.repo 48 | }) 49 | 50 | const initalise = promisify(node.init.bind(node)) 51 | const start = promisify(node.start.bind(node)) 52 | 53 | if (!node.initialized) { 54 | await initalise() 55 | } 56 | 57 | await start() 58 | 59 | return node 60 | } else if (config.ipfs.node === 'disposable') { 61 | console.info('😈 Spawning an in-process disposable IPFS node') // eslint-disable-line no-console 62 | 63 | return spawn({ 64 | type: 'proc', 65 | exec: require('ipfs') 66 | }) 67 | } else if (config.ipfs.node === 'js') { 68 | console.info('😈 Spawning a js-IPFS node') // eslint-disable-line no-console 69 | 70 | return spawn({ 71 | type: 'js', 72 | exec: await which('jsipfs') 73 | }) 74 | } else if (config.ipfs.node === 'go') { 75 | console.info('😈 Spawning a go-IPFS node') // eslint-disable-line no-console 76 | 77 | return spawn({ 78 | type: 'go', 79 | exec: await which('ipfs') 80 | }) 81 | } 82 | 83 | console.info(`😈 Connecting to a remote IPFS node at ${config.ipfs.node}`) // eslint-disable-line no-console 84 | 85 | return { 86 | api: new IpfsApi(config.ipfs.node), 87 | stop: (cb) => cb() 88 | } 89 | } 90 | 91 | const createIpfs = options => { 92 | return async () => { 93 | const ipfs = await startIpfs(options) 94 | 95 | cleanUpOps.push(() => { 96 | return new Promise((resolve) => { 97 | if (options.ipfs.node !== 'proc') { 98 | return resolve() 99 | } 100 | 101 | ipfs.stop(() => { 102 | console.info('😈 IPFS node stopped') // eslint-disable-line no-console 103 | 104 | resolve() 105 | }) 106 | }) 107 | }) 108 | 109 | console.info('🗂️ Loading registry index from', options.registry) // eslint-disable-line no-console 110 | 111 | try { 112 | const mirror = await request(Object.assign({}, options.request, { 113 | uri: options.registry, 114 | json: true 115 | })) 116 | 117 | console.info('☎️ Dialling registry mirror', mirror.ipfs.addresses.join(',')) // eslint-disable-line no-console 118 | 119 | await timeout( 120 | Promise.race( 121 | mirror.ipfs.addresses.map(addr => { 122 | return ipfs.api.swarm.connect(mirror.ipfs.addresses[0]) 123 | }) 124 | ), 125 | options.registryConnectTimeout 126 | ) 127 | 128 | console.info('📱️ Connected to registry') // eslint-disable-line no-console 129 | } catch (error) { 130 | console.info('📴 Not connected to registry') // eslint-disable-line no-console 131 | } 132 | 133 | return ipfs 134 | } 135 | } 136 | 137 | module.exports = createIpfs 138 | -------------------------------------------------------------------------------- /src/core/commands/start-server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const express = require('express') 4 | const proxy = require('express-http-proxy') 5 | const once = require('once') 6 | const requestLog = require('ipfs-registry-mirror-common/handlers/request-log') 7 | const errorLog = require('ipfs-registry-mirror-common/handlers/error-log') 8 | const favicon = require('ipfs-registry-mirror-common/handlers/favicon') 9 | const root = require('../handlers/root') 10 | const tarball = require('../handlers/tarball') 11 | const manifest = require('../handlers/manifest') 12 | const startIpfs = require('./start-ipfs') 13 | 14 | const startServer = (config) => { 15 | const app = express() 16 | 17 | app.use(requestLog) 18 | 19 | app.get('/favicon.ico', favicon(config, app)) 20 | app.get('/favicon.png', favicon(config, app)) 21 | 22 | app.get('/', root(config, app)) 23 | 24 | // intercept requests for tarballs and manifests 25 | app.get('/*.tgz', tarball(config, app)) 26 | app.get('/*', manifest(config, app)) 27 | 28 | // everything else should just proxy for the registry 29 | const registry = proxy(config.registry, { 30 | limit: config.registryUploadSizeLimit 31 | }) 32 | app.put('/*', registry) 33 | app.post('/*', registry) 34 | app.patch('/*', registry) 35 | app.delete('/*', registry) 36 | 37 | app.use(errorLog) 38 | 39 | app.locals.ipfs = startIpfs(config) 40 | 41 | return new Promise((resolve, reject) => { 42 | const callback = once((error) => { 43 | if (error) { 44 | reject(error) 45 | } 46 | 47 | if (!config.http.port) { 48 | config.http.port = server.address().port 49 | } 50 | 51 | console.info(`🚀 Server running on port ${config.http.port}`) // eslint-disable-line no-console 52 | 53 | resolve(server) 54 | }) 55 | 56 | let server = app.listen(config.http.port, callback) 57 | server.once('error', callback) 58 | }) 59 | } 60 | 61 | module.exports = startServer 62 | -------------------------------------------------------------------------------- /src/core/commands/update.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const request = require('ipfs-registry-mirror-common/utils/retry-request') 4 | const config = require('../config') 5 | const startIpfs = require('./start-ipfs') 6 | 7 | module.exports = async (options) => { 8 | options = config(options) 9 | 10 | const ipfs = await startIpfs(options)() 11 | 12 | const mirror = await request(Object.assign({}, options.request, { 13 | uri: options.registry, 14 | json: true 15 | })) 16 | 17 | const tempPath = `${options.ipfs.prefix}-${Date.now()}` 18 | 19 | console.info('📠 Copying registry index', mirror.root, 'to', tempPath) // eslint-disable-line no-console 20 | 21 | try { 22 | await ipfs.api.files.rm(tempPath, { 23 | recursive: true 24 | }) 25 | } catch (e) { 26 | // ignore 27 | } 28 | 29 | await ipfs.api.files.cp(mirror.root, tempPath) 30 | 31 | console.info('💌 Copied registry index', mirror.root, 'to', tempPath) // eslint-disable-line no-console 32 | console.info('🗑️ Replacing old registry index if it exists') // eslint-disable-line no-console 33 | 34 | try { 35 | await ipfs.api.files.rm(options.ipfs.prefix, { 36 | recursive: true 37 | }) 38 | } catch (e) { 39 | // ignore 40 | } 41 | 42 | await ipfs.api.files.mv(tempPath, options.ipfs.prefix) 43 | } 44 | -------------------------------------------------------------------------------- /src/core/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const toBoolean = require('ipfs-registry-mirror-common/utils/to-boolean') 4 | const option = require('ipfs-registry-mirror-common/utils/option') 5 | 6 | module.exports = (overrides = {}) => { 7 | return { 8 | packageManager: option(process.env.PACKAGE_MANAGER, overrides.packageManager), 9 | registry: option(process.env.IPFS_REGISTRY, overrides.ipfsRegistry), 10 | registryUpdateInterval: option(Number(process.env.REGISTRY_UPDATE_INTERVAL), overrides.registryUpdateInterval), 11 | registryUploadSizeLimit: option(process.env.MIRROR_UPLOAD_SIZE_LIMIT, overrides.registryUploadSizeLimit), 12 | registryConnectTimeout: option(Number(process.env.REGISTRY_CONNECT_TIMEOUT), overrides.registryConnectTimeout), 13 | registryReadTimeout: option(Number(process.env.REGISTRY_READ_TIMEOUT), overrides.registryReadTimeout), 14 | 15 | ipfs: { 16 | host: option(process.env.IPFS_HOST, overrides.ipfsHost), 17 | port: option(Number(process.env.IPFS_PORT), overrides.ipfsPort), 18 | node: option(process.env.IPFS_NODE, overrides.ipfsNode), 19 | prefix: option(process.env.IPFS_MFS_PREFIX, overrides.ipfsMfsPrefix), 20 | flush: option(process.env.IPFS_FLUSH, overrides.ipfsFlush), 21 | repo: option(process.env.IPFS_REPO, overrides.ipfsRepo) 22 | }, 23 | 24 | npm: { 25 | registry: option(process.env.NPM_REGISTRY, overrides.npmRegistry) 26 | }, 27 | 28 | clone: { 29 | pin: option(Number(process.env.CLONE_PIN), overrides.clonePin) 30 | }, 31 | 32 | http: { 33 | host: 'localhost' 34 | }, 35 | 36 | request: { 37 | pool: { 38 | maxSockets: option(Number(process.env.REQUEST_MAX_SOCKETS), overrides.requestMaxSockets) 39 | }, 40 | retries: option(process.env.REQUEST_RETRIES, overrides.requestRetries), 41 | retryDelay: option(process.env.REQUEST_RETRY_DELAY, overrides.requestRetryDelay), 42 | timeout: option(process.env.REQUEST_TIMEOUT, overrides.requestTimeout) 43 | } 44 | } 45 | } 46 | 47 | module.exports.option = option 48 | module.exports.toBoolean = toBoolean 49 | -------------------------------------------------------------------------------- /src/core/handlers/manifest.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const log = require('debug')('ipfs:ipfs-npm:handlers:manifest') 4 | const loadManifest = require('ipfs-registry-mirror-common/utils/load-manifest') 5 | const sanitiseName = require('ipfs-registry-mirror-common/utils/sanitise-name') 6 | const lol = require('ipfs-registry-mirror-common/utils/error-message') 7 | const getExternalUrl = require('ipfs-registry-mirror-common/utils/get-external-url') 8 | 9 | const replaceTarballUrls = (pkg, config) => { 10 | const prefix = getExternalUrl(config) 11 | const packageName = pkg.name 12 | const moduleName = packageName.startsWith('@') ? packageName.split('/').pop() : packageName 13 | 14 | // change tarball URLs to point to us 15 | Object.keys(pkg.versions || {}) 16 | .forEach(versionNumber => { 17 | const version = pkg.versions[versionNumber] 18 | 19 | version.dist.tarball = `${prefix}/${packageName}/-/${moduleName}-${versionNumber}.tgz` 20 | }) 21 | 22 | return pkg 23 | } 24 | 25 | module.exports = (config, app) => { 26 | return async (request, response, next) => { 27 | log(`Requested ${request.path}`) 28 | 29 | let moduleName = sanitiseName(request.path) 30 | 31 | log(`Loading manifest for ${moduleName}`) 32 | 33 | const ipfs = await request.app.locals.ipfs() 34 | 35 | try { 36 | const manifest = await loadManifest(config, ipfs.api, moduleName) 37 | 38 | // because we start the server on a random high port, the previously stored 39 | // manifests may have port numbers from the last time we ran, so overwrite 40 | // them before returning them to the client 41 | replaceTarballUrls(manifest, config) 42 | 43 | response.statusCode = 200 44 | response.setHeader('Content-type', 'application/json; charset=utf-8') 45 | response.send(JSON.stringify(manifest, null, request.query.format === undefined ? 0 : 2)) 46 | } catch (error) { 47 | console.error(`💥 Could not load manifest for ${moduleName}`, error) // eslint-disable-line no-console 48 | 49 | if (error.message.includes('Not found')) { 50 | response.statusCode = 404 51 | response.send(lol(`💥 Could not load ${moduleName}, has it been published?`)) 52 | 53 | return 54 | } 55 | 56 | // a 500 will cause the npm client to retry 57 | response.statusCode = 500 58 | response.send(lol(`💥 ${error.message}`)) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/core/handlers/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const pkg = require('../../../package.json') 4 | 5 | module.exports = () => { 6 | return (request, response, next) => { 7 | const info = { 8 | name: pkg.name, 9 | version: pkg.version 10 | } 11 | 12 | response.statusCode = 200 13 | response.setHeader('Content-type', 'application/json; charset=utf-8') 14 | response.send(JSON.stringify(info, null, request.query.format === undefined ? 0 : 2)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/core/handlers/tarball.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const log = require('debug')('ipfs:ipfs-npm:handlers:tarball') 4 | const path = require('path') 5 | const loadTarball = require('ipfs-registry-mirror-common/utils/load-tarball') 6 | const lol = require('ipfs-registry-mirror-common/utils/error-message') 7 | 8 | module.exports = (config, app) => { 9 | return async (request, response, next) => { 10 | log(`Requested ${request.path}`) 11 | 12 | let file = request.path 13 | 14 | log(`Loading ${file}`) 15 | 16 | const ipfs = await request.app.locals.ipfs() 17 | 18 | try { 19 | const readStream = await loadTarball(config, ipfs.api, file) 20 | 21 | readStream.on('error', (error) => { 22 | log(`Error loading ${file} - ${error}`) 23 | 24 | if (error.code === 'ECONNREFUSED') { 25 | response.statusCode = 504 26 | } else if (error.code === 'ECONNRESET') { 27 | // will trigger a retry from the npm client 28 | response.statusCode = 500 29 | } else { 30 | response.statusCode = 404 31 | } 32 | 33 | next(error) 34 | }) 35 | .once('data', () => { 36 | log(`Loaded ${file}`) 37 | 38 | response.statusCode = 200 39 | response.setHeader('Content-Disposition', `attachment; filename="${path.basename(request.url)}"`) 40 | }) 41 | .pipe(response) 42 | } catch (error) { 43 | console.error(`💥 Could not load tarball for ${file}`, error) // eslint-disable-line no-console 44 | 45 | if (error.message.includes('Not found')) { 46 | response.statusCode = 404 47 | response.send(lol(`💥 Could not load ${file}, has it been published?`)) 48 | 49 | return 50 | } 51 | 52 | // a 500 will cause the npm client to retry 53 | response.statusCode = 500 54 | response.send(lol(`💥 ${error.message}`)) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/core/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const update = require('./commands/update') 4 | const proxy = require('./commands/proxy') 5 | 6 | module.exports = (options) => { 7 | if (process.argv.slice(2)[0] === 'update-registry-index') { 8 | return update(options) 9 | } 10 | 11 | return proxy(options) 12 | } 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/npm-on-ipfs/14956f58e085c6c7c58e42645d5ab466d9635c7f/src/index.js -------------------------------------------------------------------------------- /test/fixtures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a-project", 3 | "version": "1.0.0", 4 | "description": "A really nice project", 5 | "dependencies": { 6 | "bignumber": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/install.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const promisify = require('util').promisify 5 | const fs = { 6 | mkdir: promisify(require('fs').mkdir), 7 | copyFile: promisify(require('fs').copyFile) 8 | } 9 | const path = require('path') 10 | const os = require('os') 11 | const hat = require('hat') 12 | const { 13 | spawn 14 | } = require('child_process') 15 | const OutputBuffer = require('output-buffer') 16 | 17 | describe('install', function () { 18 | this.timeout(30 * 60000) 19 | 20 | let projectDirectory 21 | 22 | const runInstall = (args, callback) => { 23 | const installer = spawn( 24 | 'nyc', [ 25 | '--reporter=lcov', 26 | path.resolve(__dirname, '../src/cli/bin.js') 27 | ].concat(args), { 28 | cwd: projectDirectory, 29 | env: { 30 | ...process.env, 31 | PATH: `${path.dirname(require.resolve('go-ipfs-dep/go-ipfs/ipfs'))}:${process.env.PATH}` 32 | } 33 | }) 34 | 35 | const buffer = new OutputBuffer((line) => { 36 | console.info(line) // eslint-disable-line no-console 37 | }) 38 | 39 | installer.stdout.on('data', (data) => { 40 | buffer.append(data.toString()) 41 | }) 42 | 43 | installer.stderr.on('data', (data) => { 44 | buffer.append(data.toString()) 45 | }) 46 | 47 | installer.on('close', (code) => { 48 | buffer.flush() 49 | 50 | if (code === 0) { 51 | return callback() 52 | } 53 | 54 | callback(new Error(`Unexpected exit code ${code}`)) 55 | }) 56 | } 57 | 58 | beforeEach(async () => { 59 | projectDirectory = path.join(os.tmpdir(), hat()) 60 | 61 | await fs.mkdir(projectDirectory) 62 | await fs.copyFile(path.resolve(__dirname, './fixtures/package.json'), path.join(projectDirectory, 'package.json')) 63 | }) 64 | 65 | it('should install a project with npm', (done) => { 66 | runInstall([ 67 | '--ipfs-node=disposable', 68 | '--package-manager=npm', 69 | 'install' 70 | ], done) 71 | }) 72 | 73 | it('should install a project with yarn', (done) => { 74 | runInstall([ 75 | '--ipfs-node=disposable', 76 | '--package-manager=yarn', 77 | 'install' 78 | ], done) 79 | }) 80 | 81 | it('should install a project using go-ipfs', (done) => { 82 | runInstall([ 83 | '--ipfs-node=go', 84 | 'install' 85 | ], done) 86 | }) 87 | 88 | it('should install a project using js-ipfs', (done) => { 89 | runInstall([ 90 | '--ipfs-node=js', 91 | 'install' 92 | ], done) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /test/node.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./install.spec') 4 | --------------------------------------------------------------------------------