├── .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 |
3 |
4 |
5 | # npm-on-IPFS
6 |
7 | [](https://protocol.ai)
8 | [](http://ipfs.io/)
9 | [](http://webchat.freenode.net/?channels=%23ipfs)
10 | [](https://travis-ci.com/ipfs-shipyard/npm-on-ipfs)
11 | [](https://codecov.io/gh/ipfs-shipyard/npm-on-ipfs)
12 | [](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 |
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 |
--------------------------------------------------------------------------------