├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ └── open_an_issue.md
├── actions
│ ├── check-for-kubo-release
│ │ ├── Dockerfile
│ │ ├── action.yml
│ │ └── entrypoint.sh
│ └── publish
│ │ ├── Dockerfile
│ │ ├── action.yml
│ │ └── entrypoint.sh
├── dependabot.yml
└── workflows
│ ├── generated-pr.yml
│ ├── main.yml
│ ├── stale.yml
│ └── test.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── bin
└── ipfs
├── package.json
├── src
├── download.js
├── go-platform.js
├── index.js
├── post-install.js
└── types.d.ts
├── test
├── download.js
├── fixtures
│ ├── clean.js
│ └── example-project
│ │ └── package.json
├── install.js
└── path.js
└── tsconfig.json
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Getting Help on IPFS
4 | url: https://ipfs.io/help
5 | about: All information about how and where to get help on IPFS.
6 | - name: IPFS Official Forum
7 | url: https://discuss.ipfs.io
8 | about: Please post general questions, support requests, and discussions here.
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/open_an_issue.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Open an issue
3 | about: Only for actionable issues relevant to this repository.
4 | title: ''
5 | labels: need/triage
6 | assignees: ''
7 |
8 | ---
9 |
20 |
--------------------------------------------------------------------------------
/.github/actions/check-for-kubo-release/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14
2 |
3 | LABEL "com.github.actions.name"="Update and publish"
4 | LABEL "com.github.actions.description"="Publish new version when a new kubo version is relased"
5 | LABEL "com.github.actions.icon"="rss"
6 | LABEL "com.github.actions.color"="green"
7 |
8 | COPY entrypoint.sh /entrypoint.sh
9 | ENTRYPOINT ["/entrypoint.sh"]
10 |
--------------------------------------------------------------------------------
/.github/actions/check-for-kubo-release/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Check for kubo release'
2 | runs:
3 | using: 'docker'
4 | image: 'Dockerfile'
5 | outputs:
6 | publish:
7 | description: 'Whether to publish a new version'
8 |
--------------------------------------------------------------------------------
/.github/actions/check-for-kubo-release/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eu
3 |
4 | echo '💫 Checking https://dist.ipfs.tech/kubo/versions for new releases...'
5 |
6 | # The version in packge.json e.g. "0.4.20"
7 | CURRENT=`node -e 'console.log(require("./package.json").version)'`
8 | # The latest version on dist.ipfs.tech e.g. "0.4.21"
9 | LATEST=`curl --silent https://dist.ipfs.tech/kubo/versions | tail -n 1 | cut -c 2-`
10 |
11 | # Verify $LATEST is valid semver!
12 | if ! npx semver $LATEST; then
13 | echo "⚠️ Ignoring version $LATEST - Invalid SemVer string"
14 | exit 1
15 | fi
16 |
17 | if [[ "$CURRENT" != "$LATEST" ]]; then
18 | echo "🎉 New release exists $LATEST"
19 |
20 | echo "publish=true" >> $GITHUB_OUTPUT
21 | else
22 | echo "💤 $CURRENT is the latest release. Going back to sleep"
23 |
24 | echo "publish=false" >> $GITHUB_OUTPUT
25 | fi
26 |
--------------------------------------------------------------------------------
/.github/actions/publish/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14
2 |
3 | LABEL "com.github.actions.name"="Version and publish"
4 | LABEL "com.github.actions.description"="npm version and publish with new kubo version"
5 | LABEL "com.github.actions.icon"="box"
6 | LABEL "com.github.actions.color"="green"
7 |
8 | COPY entrypoint.sh /entrypoint.sh
9 | ENTRYPOINT ["/entrypoint.sh"]
10 |
--------------------------------------------------------------------------------
/.github/actions/publish/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Publish release'
2 | runs:
3 | using: 'docker'
4 | image: 'Dockerfile'
5 |
--------------------------------------------------------------------------------
/.github/actions/publish/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eu
3 |
4 | # Set up npm home, so `npm publish` works.
5 | # https://github.com/actions/npm/blob/59b64a598378f31e49cb76f27d6f3312b582f680/entrypoint.sh
6 | if [ -n "$NPM_AUTH_TOKEN" ]; then
7 | # Respect NPM_CONFIG_USERCONFIG if it is provided, default to $HOME/.npmrc
8 | NPM_CONFIG_USERCONFIG="${NPM_CONFIG_USERCONFIG-"$HOME/.npmrc"}"
9 | NPM_REGISTRY_URL="${NPM_REGISTRY_URL-registry.npmjs.org}"
10 | NPM_STRICT_SSL="${NPM_STRICT_SSL-true}"
11 | NPM_REGISTRY_SCHEME="https"
12 | if ! $NPM_STRICT_SSL
13 | then
14 | NPM_REGISTRY_SCHEME="http"
15 | fi
16 |
17 | # Allow registry.npmjs.org to be overridden with an environment variable
18 | printf "//%s/:_authToken=%s\\nregistry=%s\\nstrict-ssl=%s" "$NPM_REGISTRY_URL" "$NPM_AUTH_TOKEN" "${NPM_REGISTRY_SCHEME}://$NPM_REGISTRY_URL" "${NPM_STRICT_SSL}" > "$NPM_CONFIG_USERCONFIG"
19 |
20 | chmod 0600 "$NPM_CONFIG_USERCONFIG"
21 | fi
22 |
23 | # The version in packge.json e.g. "0.4.20"
24 | CURRENT=`node -e 'console.log(require("./package.json").version)'`
25 | # The latest version on dist.ipfs.tech e.g. "0.4.21"
26 | LATEST=`curl --silent https://dist.ipfs.tech/kubo/versions | tail -n 1 | cut -c 2-`
27 |
28 | # Verify $LATEST is valid semver!
29 | if ! npx semver $LATEST; then
30 | echo "⚠️ Ignoring version $LATEST - Invalid SemVer string"
31 | exit 1
32 | fi
33 |
34 | if [[ "$CURRENT" != "$LATEST" ]]; then
35 |
36 | # If the version contains a dash it's a pre-release, e.g "0.4.21-rc3"
37 | # Publish pre-releases under the @next tag and releases @latest tag.
38 | if [[ $LATEST =~ "-" ]]; then
39 | NPM_DIST_TAG='next'
40 | echo "🧪 Found new kubo pre-release $LATEST@$NPM_DIST_TAG"
41 | else
42 | NPM_DIST_TAG='latest'
43 | echo "🎉 Found new kubo release $LATEST@$NPM_DIST_TAG"
44 | fi
45 |
46 | git config --global --add safe.directory /github/workspace
47 |
48 | # The workspace starts as a detached commit for scheduled builds...
49 | git rev-parse --abbrev-ref HEAD
50 | git checkout master
51 |
52 | # post-install rewrites bin/ipfs so undo that change
53 | git checkout -- bin/ipfs
54 |
55 | # Set sensible commit info
56 | git config --global user.name "${GITHUB_ACTOR}"
57 | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com"
58 |
59 |
60 | npm install
61 | npm version $LATEST
62 | npm publish --access public --tag $NPM_DIST_TAG
63 | echo "📦 Published $LATEST to npm as kubo@$NPM_DIST_TAG"
64 |
65 | git push -u origin master
66 | git push --tags
67 | echo "👍 Pushed changes back to master"
68 |
69 | else
70 | echo "💤 $CURRENT is the latest release. Going back to sleep"
71 | # neutral github action exit... not good, not bad.
72 | # https://developer.github.com/actions/creating-github-actions/accessing-the-runtime-environment/#exit-codes-and-statuses
73 | exit 78
74 | fi
75 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 |
--------------------------------------------------------------------------------
/.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/main.yml:
--------------------------------------------------------------------------------
1 | name: Release to npm
2 | on:
3 | workflow_dispatch:
4 | schedule:
5 | - cron: '0 * * * *'
6 |
7 | jobs:
8 | publish:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v4
13 | - name: Check for new kubo version
14 | id: check
15 | uses: ./.github/actions/check-for-kubo-release
16 | - name: Set up node
17 | if: steps.check.outputs.publish == 'true'
18 | uses: actions/setup-node@v4
19 | with:
20 | node-version: 20.x
21 | - name: Install
22 | if: steps.check.outputs.publish == 'true'
23 | run: npm install
24 | - name: Test
25 | if: steps.check.outputs.publish == 'true'
26 | run: npm test
27 | - name: Publish
28 | if: steps.check.outputs.publish == 'true'
29 | uses: ./.github/actions/publish
30 | env:
31 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
32 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Close Stale Issues
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 | workflow_dispatch:
7 |
8 | permissions:
9 | issues: write
10 | pull-requests: write
11 |
12 | jobs:
13 | stale:
14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1
15 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on:
4 | push:
5 | branches: [ main, master ]
6 | pull_request:
7 | branches: [ main, master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | matrix:
14 | os: [windows-latest, macos-latest, ubuntu-latest]
15 | node-version: [20.x]
16 | steps:
17 | - uses: actions/checkout@v4
18 | - uses: actions/setup-node@v4
19 | with:
20 | node-version: ${{ matrix.node-version }}
21 | - run: npm install
22 | - uses: gozala/typescript-error-reporter-action@v1.0.9
23 | - run: npm run build --if-present
24 | - run: npm test
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Dependency directory
6 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
7 | node_modules
8 | dist
9 |
10 | # while testing npm5
11 | package-lock.json
12 |
13 | # Where the binaries go
14 | go-ipfs
15 | kubo
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 'stable'
4 | sudo: false
5 | cache:
6 | directories:
7 | - node_modules
8 | script:
9 | - npm test
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2018, Protocol Labs, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Kubo: IPFS Implementation in GO
6 |
7 |
8 |
9 |
10 | Install Kubo (previously known as "go-ipfs") from NPM
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ## Table of Contents
22 |
23 | - [Install](#install)
24 | - [Usage](#usage)
25 | - [Development](#development)
26 | - [Publish a new version](#publish-a-new-version)
27 | - [Contribute](#contribute)
28 | - [License](#license)
29 |
30 | ## Install
31 |
32 | Install the latest [Kubo](https://github.com/ipfs/kubo/) (go-ipfs) binary:
33 |
34 | ```sh
35 | # Install globally
36 | > npm install -g kubo
37 | > ipfs version
38 | ipfs version v0.23.0
39 |
40 | # Install locally
41 | > npm install kubo
42 | > ./node_modules/.bin/ipfs
43 | ipfs version v0.23.0
44 | ```
45 |
46 | ## Usage
47 |
48 | This module downloads Kubo (go-ipfs) binaries from https://dist.ipfs.tech into your project.
49 |
50 | It will download the kubo version that matches the npm version of this module. So depending on `kubo@0.23.0` will install `kubo v0.23.0` for your current system architecture, in to your project at `node_modules/kubo/kubo/ipfs` and additional symlink to it at `node_modules/kubo/bin/ipfs`.
51 |
52 | On Windows, `ipfs.exe` file is used, and if the symlink can't be created under a regular user, a copy of `ipfs.exe` is created instead.
53 |
54 | After downloading you can find out the path of the installed binary by calling the `path` function exported by this module:
55 |
56 | ```javascript
57 | import { path } from 'kubo'
58 |
59 | console.info('kubo is installed at', path())
60 | ```
61 |
62 | An error will be thrown if the path to the binary cannot be resolved.
63 |
64 | ### Caching
65 |
66 | Downloaded archives are placed in OS-specific cache directory which can be customized by setting `NPM_KUBO_CACHE` in env.
67 |
68 | ### Overriding with `KUBO_BINARY` env
69 |
70 | If the `KUBO_BINARY` env variable is set at runtime this will override the path of the binary used.
71 |
72 | This must point to the file, not the directory containing the file.
73 |
74 | ## Development
75 |
76 | **Warning**: the file `bin/ipfs` is a placeholder, when downloading stuff, it gets replaced. so if you run `node install.js` it will then be dirty in the git repo. **Do not commit this file**, as then you would be commiting a big binary and publishing it to npm. A pre-commit hook exists and should protect against this, but better safe than sorry.
77 |
78 | ### Publish a new version
79 |
80 | You should be able to just run `./publish.sh` for example:
81 |
82 | ```sh
83 | > ./publish.sh
84 | usage ./publish.sh
85 | publish a version of kubo to npm
86 |
87 | > ./publish.sh 0.3.11
88 | ```
89 |
90 | This will:
91 |
92 | - check the version is indeed a tag in https://github.com/ipfs/kubo
93 | - check the size of `bin/ipfs` is right (must be the checked in file)
94 | - update the version numbers in `package.json` and `README.md`
95 | - `git commit` the changes
96 | - push to https://github.com/ipfs/npm-kubo
97 | - publish to `kubo@$version` to https://npmjs.com/package/kubo
98 |
99 | Open an issue in the repo if you run into trouble.
100 |
101 | ### Publish a new version of this module with exact same kubo version
102 |
103 | If some problem happens, and you need to publish a new version of this module targetting _the same_ kubo version, then please follow this convention:
104 |
105 | 1. **Clean up bad stuff:** unpublish all modules with this exact same ``
106 | 2. **Add a "hacky" version suffix:** use version: `-hacky`
107 | 3. **Publish version:** publish the module. Since it's the only one with the kubo version, then it should be installed.
108 |
109 | > Why do this?
110 |
111 | Well, if you previously published npm module `kubo@0.4.0` and there was a problem, we now must publish a different version, but we want to keep the version number the same. so the strategy is to publish as `kubo@0.4.0-hacky1`, and unpublish `kubo@0.4.0`.
112 |
113 | > Why `-hacky`?
114 |
115 | Because it is unlikely to be a legitimate kubo version, and we want to support kubo versions like `floodsub-1` etc.
116 |
117 | > Do i have to say `-hacky` or can i just use `-`?
118 |
119 | `-` won't work, as [link-ipfs.js](./link-ipfs.js) expects `-hacky`. If you want to
120 | change the convention, go for it, and update this readme accordingly.
121 |
122 | ## Contribute
123 |
124 | Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/npm-kubo/issues)!
125 |
126 | This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
127 |
128 | [](https://github.com/ipfs/community/blob/master/contributing.md)
129 |
130 | ## License
131 |
132 | [MIT](LICENSE)
133 |
--------------------------------------------------------------------------------
/bin/ipfs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | console.log('IPFS is not installed.')
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kubo",
3 | "version": "0.35.0",
4 | "description": "Install the latest Kubo (go-ipfs) binary",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "postinstall": "node src/post-install.js",
8 | "restore-bin": "git reset -- bin/ipfs && git checkout -- bin/ipfs",
9 | "test": "tape test/*.js | tap-spec",
10 | "lint": "tsc --noEmit && standard",
11 | "prepublishOnly": "tsc"
12 | },
13 | "pre-commit": "restore-bin",
14 | "bin": {
15 | "ipfs": "bin/ipfs"
16 | },
17 | "files": [
18 | "bin",
19 | "dist",
20 | "src",
21 | "test",
22 | "LICENSE",
23 | "package.json",
24 | "README.md",
25 | "tsconfig.json"
26 | ],
27 | "repository": {
28 | "type": "git",
29 | "url": "git+https://github.com/ipfs/npm-kubo.git"
30 | },
31 | "keywords": [
32 | "ipfs",
33 | "install"
34 | ],
35 | "author": "Protocol Labs, Inc.",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/ipfs/npm-kubo/issues"
39 | },
40 | "homepage": "https://github.com/ipfs/npm-kubo",
41 | "types": "./src/types.d.ts",
42 | "devDependencies": {
43 | "@types/got": "^9.6.12",
44 | "@types/gunzip-maybe": "^1.4.0",
45 | "@types/tar-fs": "^2.0.1",
46 | "@types/unzip-stream": "^0.3.1",
47 | "execa": "^4.0.1",
48 | "fs-extra": "^9.0.0",
49 | "pre-commit": "^1.2.2",
50 | "standard": "^13.1.0",
51 | "tap-spec": "^5.0.0",
52 | "tape": "^4.13.2",
53 | "tape-promise": "^4.0.0",
54 | "typescript": "^4.3.5"
55 | },
56 | "dependencies": {
57 | "cachedir": "^2.3.0",
58 | "got": "^11.7.0",
59 | "gunzip-maybe": "^1.4.2",
60 | "hasha": "^5.2.2",
61 | "pkg-conf": "^3.1.0",
62 | "tar-fs": "^2.1.0",
63 | "unzip-stream": "^0.3.0"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/download.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /*
4 | Download kubo distribution package for desired version, platform and architecture,
5 | and unpack it to a desired output directory.
6 |
7 | API:
8 | download(, , , )
9 |
10 | Defaults:
11 | kubo version: value in package.json/kubo/version
12 | kubo platform: the platform this program is run from
13 | kubo architecture: the architecture of the hardware this program is run from
14 | kubo install path: './kubo'
15 | */
16 | const goenv = require('./go-platform')
17 | const gunzip = require('gunzip-maybe')
18 | const got = require('got').default
19 | const path = require('path')
20 | const tarFS = require('tar-fs')
21 | const unzip = require('unzip-stream')
22 | const pkgConf = require('pkg-conf')
23 | // @ts-ignore no types
24 | const cachedir = require('cachedir')
25 | const pkg = require('../package.json')
26 | const fs = require('fs')
27 | const hasha = require('hasha')
28 | const cproc = require('child_process')
29 | const isWin = process.platform === 'win32'
30 |
31 | /**
32 | * avoid expensive fetch if file is already in cache
33 | * @param {string} url
34 | */
35 | async function cachingFetchAndVerify (url) {
36 | const cacheDir = process.env.NPM_KUBO_CACHE || process.env.NPM_GO_IPFS_CACHE || cachedir('npm-kubo')
37 | const filename = url.split('/').pop()
38 |
39 | if (!filename) {
40 | throw new Error('Invalid URL')
41 | }
42 |
43 | const cachedFilePath = path.join(cacheDir, filename)
44 | const cachedHashPath = `${cachedFilePath}.sha512`
45 |
46 | if (!fs.existsSync(cacheDir)) {
47 | fs.mkdirSync(cacheDir, { recursive: true })
48 | }
49 | if (!fs.existsSync(cachedFilePath)) {
50 | console.info(`Downloading ${url} to ${cacheDir}`)
51 | // download file
52 | fs.writeFileSync(cachedFilePath, await got(url).buffer())
53 | console.info(`Downloaded ${url}`)
54 |
55 | // ..and checksum
56 | console.info(`Downloading ${filename}.sha512`)
57 | fs.writeFileSync(cachedHashPath, await got(`${url}.sha512`).buffer())
58 | console.info(`Downloaded ${filename}.sha512`)
59 | } else {
60 | console.info(`Found ${cachedFilePath}`)
61 | }
62 |
63 | console.info(`Verifying ${filename}.sha512`)
64 |
65 | const digest = Buffer.alloc(128)
66 | const fd = fs.openSync(cachedHashPath, 'r')
67 | fs.readSync(fd, digest, 0, digest.length, 0)
68 | fs.closeSync(fd)
69 | const expectedSha = digest.toString('utf8')
70 | const calculatedSha = await hasha.fromFile(cachedFilePath, { encoding: 'hex', algorithm: 'sha512' })
71 | if (calculatedSha !== expectedSha) {
72 | console.log(`Expected SHA512: ${expectedSha}`)
73 | console.log(`Calculated SHA512: ${calculatedSha}`)
74 | throw new Error(`SHA512 of ${cachedFilePath}' (${calculatedSha}) does not match expected value from ${cachedFilePath}.sha512 (${expectedSha})`)
75 | }
76 | console.log(`OK (${expectedSha})`)
77 |
78 | return fs.createReadStream(cachedFilePath)
79 | }
80 |
81 | /**
82 | * @param {string} url
83 | * @param {string} installPath
84 | * @param {import('stream').Readable} stream
85 | */
86 | function unpack (url, installPath, stream) {
87 | return new Promise((resolve, reject) => {
88 | if (url.endsWith('.zip')) {
89 | return stream.pipe(
90 | unzip
91 | .Extract({ path: installPath })
92 | .on('close', resolve)
93 | .on('error', reject)
94 | )
95 | }
96 |
97 | return stream
98 | .pipe(gunzip())
99 | .pipe(
100 | tarFS
101 | .extract(installPath)
102 | .on('finish', resolve)
103 | .on('error', reject)
104 | )
105 | })
106 | }
107 |
108 | /**
109 | * @param {string} [version]
110 | * @param {string} [platform]
111 | * @param {string} [arch]
112 | * @param {string} [installPath]
113 | */
114 | function cleanArguments (version, platform, arch, installPath) {
115 | const conf = pkgConf.sync('kubo', {
116 | cwd: process.env.INIT_CWD || process.cwd(),
117 | defaults: {
118 | version: 'v' + pkg.version.replace(/-[0-9]+/, ''),
119 | distUrl: 'https://dist.ipfs.tech'
120 | }
121 | })
122 |
123 | return {
124 | version: process.env.TARGET_VERSION || version || conf.version,
125 | platform: process.env.TARGET_OS || platform || goenv.GOOS,
126 | arch: process.env.TARGET_ARCH || arch || goenv.GOARCH,
127 | distUrl: process.env.KUBO_DIST_URL || process.env.GO_IPFS_DIST_URL || conf.distUrl,
128 | installPath: installPath ? path.resolve(installPath) : process.cwd()
129 | }
130 | }
131 |
132 | /**
133 | * @param {string} version
134 | * @param {string} distUrl
135 | */
136 | async function ensureVersion (version, distUrl) {
137 | console.info(`${distUrl}/kubo/versions`)
138 | const versions = (await got(`${distUrl}/kubo/versions`).text()).trim().split('\n')
139 |
140 | if (versions.indexOf(version) === -1) {
141 | throw new Error(`Version '${version}' not available`)
142 | }
143 | }
144 |
145 | /**
146 | * @param {string} version
147 | * @param {string} platform
148 | * @param {string} arch
149 | * @param {string} distUrl
150 | */
151 | async function getDownloadURL (version, platform, arch, distUrl) {
152 | await ensureVersion(version, distUrl)
153 |
154 | const data = await got(`${distUrl}/kubo/${version}/dist.json`).json()
155 |
156 | if (!data.platforms[platform]) {
157 | throw new Error(`No binary available for platform '${platform}'`)
158 | }
159 |
160 | if (!data.platforms[platform].archs[arch]) {
161 | throw new Error(`No binary available for arch '${arch}'`)
162 | }
163 |
164 | const link = data.platforms[platform].archs[arch].link
165 | return `${distUrl}/kubo/${version}${link}`
166 | }
167 |
168 | /**
169 | * @param {object} options
170 | * @param {string} options.version
171 | * @param {string} options.platform
172 | * @param {string} options.arch
173 | * @param {string} options.installPath
174 | * @param {string} options.distUrl
175 | */
176 | async function download ({ version, platform, arch, installPath, distUrl }) {
177 | const url = await getDownloadURL(version, platform, arch, distUrl)
178 | const data = await cachingFetchAndVerify(url)
179 |
180 | await unpack(url, installPath, data)
181 | console.info(`Unpacked ${installPath}`)
182 |
183 | return path.join(installPath, 'kubo', `ipfs${platform === 'windows' ? '.exe' : ''}`)
184 | }
185 |
186 | /**
187 | * @param {object} options
188 | * @param {string} options.depBin
189 | * @param {string} options.version
190 | */
191 | async function link ({ depBin, version }) {
192 | let localBin = path.resolve(path.join(__dirname, '..', 'bin', 'ipfs'))
193 |
194 | if (isWin) {
195 | if (fs.existsSync(localBin)) {
196 | fs.unlinkSync(localBin)
197 | }
198 | localBin += '.exe'
199 | }
200 |
201 | if (!fs.existsSync(depBin)) {
202 | throw new Error('ipfs binary not found. maybe kubo did not install correctly?')
203 | }
204 |
205 | if (fs.existsSync(localBin)) {
206 | fs.unlinkSync(localBin)
207 | }
208 |
209 | console.info('Linking', depBin, 'to', localBin)
210 | try {
211 | fs.symlinkSync(depBin, localBin)
212 | } catch (err) {
213 | // Try to recover when creating symlink on modern Windows fails (https://github.com/ipfs/npm-kubo/issues/68)
214 | if (isWin && typeof err === 'object' && err !== null && 'code' in err && err.code === 'EPERM') {
215 | console.info('Symlink creation failed due to insufficient privileges. Attempting to copy file instead...')
216 | try {
217 | fs.copyFileSync(depBin, localBin)
218 | console.info('Copying', depBin, 'to', localBin)
219 | } catch (copyErr) {
220 | console.error('File copy also failed:', copyErr)
221 | throw copyErr
222 | }
223 | } else {
224 | throw err
225 | }
226 | }
227 |
228 | if (isWin) {
229 | // On Windows, update the shortcut file to use the .exe
230 | const cmdFile = path.join(__dirname, '..', '..', 'ipfs.cmd')
231 |
232 | fs.writeFileSync(cmdFile, `@ECHO OFF
233 | "%~dp0\\node_modules\\kubo\\bin\\ipfs.exe" %*`)
234 | }
235 |
236 | // test ipfs installed correctly.
237 | var result = cproc.spawnSync(localBin, ['version'])
238 | if (result.error) {
239 | throw new Error('ipfs binary failed: ' + result.error)
240 | }
241 |
242 | var outstr = result.stdout.toString()
243 | var m = /ipfs version ([^\n]+)\n/.exec(outstr)
244 |
245 | if (!m) {
246 | throw new Error('Could not determine IPFS version')
247 | }
248 |
249 | var actualVersion = `v${m[1]}`
250 |
251 | if (actualVersion !== version) {
252 | throw new Error(`version mismatch: expected ${version} got ${actualVersion}`)
253 | }
254 |
255 | return localBin
256 | }
257 |
258 | /**
259 | * @param {object} options
260 | * @param {string} options.version
261 | * @param {string} options.platform
262 | * @param {string} options.arch
263 | * @param {string} options.installPath
264 | * @param {string} options.distUrl
265 | */
266 | module.exports.download = download
267 |
268 | /**
269 | * @param {string} [version]
270 | * @param {string} [platform]
271 | * @param {string} [arch]
272 | * @param {string} [installPath]
273 | */
274 | module.exports.downloadAndUpdateBin = async (version, platform, arch, installPath) => {
275 | const args = cleanArguments(version, platform, arch, installPath)
276 |
277 | return link({
278 | ...args,
279 | depBin: await download(args)
280 | })
281 | }
282 |
--------------------------------------------------------------------------------
/src/go-platform.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | function getGoOs () {
4 | switch (process.platform) {
5 | case 'sunos':
6 | return 'solaris'
7 | case 'win32':
8 | return 'windows'
9 | }
10 |
11 | return process.platform
12 | }
13 |
14 | function getGoArch () {
15 | switch (process.arch) {
16 | case 'ia32':
17 | return '386'
18 | case 'x64':
19 | return 'amd64'
20 | case 'arm':
21 | return 'arm'
22 | case 'arm64':
23 | return 'arm64'
24 | }
25 |
26 | return process.arch
27 | }
28 |
29 | module.exports = {
30 | GOOS: getGoOs(),
31 | GOARCH: getGoArch()
32 | }
33 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs')
4 | const path = require('path')
5 | const { download } = require('./download')
6 |
7 | module.exports.download = download
8 |
9 | module.exports.path = function () {
10 | if (process.env.KUBO_BINARY) {
11 | return process.env.KUBO_BINARY
12 | }
13 |
14 | const paths = [
15 | path.resolve(path.join(__dirname, '..', 'kubo', 'ipfs')),
16 | path.resolve(path.join(__dirname, '..', 'kubo', 'ipfs.exe'))
17 | ]
18 |
19 | for (const bin of paths) {
20 | if (fs.existsSync(bin)) {
21 | return bin
22 | }
23 | }
24 |
25 | throw new Error('kubo binary not found, it may not be installed or an error may have occurred during installation')
26 | }
27 |
--------------------------------------------------------------------------------
/src/post-install.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { downloadAndUpdateBin } = require('./download')
4 |
5 | downloadAndUpdateBin()
6 | .catch(err => {
7 | console.error(err)
8 | process.exit(1)
9 | })
10 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | export function path(): string
--------------------------------------------------------------------------------
/test/download.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const test = require('tape-promise').default(require('tape'))
4 | const fs = require('fs-extra')
5 | const os = require('os')
6 | const path = require('path')
7 | const { download, downloadAndUpdateBin } = require('../src/download')
8 | const { path: detectLocation } = require('../')
9 | const clean = require('./fixtures/clean')
10 |
11 | test('Ensure ipfs gets downloaded (current version and platform)', async (t) => {
12 | await clean()
13 |
14 | const installPath = await downloadAndUpdateBin()
15 | const stats = await fs.stat(installPath)
16 |
17 | t.ok(stats, 'kubo was downloaded')
18 | t.ok(installPath, detectLocation(), 'kubo binary was detected')
19 |
20 | t.end()
21 | })
22 |
23 | test('Returns an error when version unsupported', async (t) => {
24 | await clean()
25 |
26 | await t.rejects(downloadAndUpdateBin('bogusversion', 'linux'), /Error: Version 'bogusversion' not available/)
27 |
28 | t.end()
29 | })
30 |
31 | test('Returns an error when KUBO_DIST_URL is 404', async (t) => {
32 | await clean()
33 |
34 | process.env.KUBO_DIST_URL = 'https://dist.ipfs.tech/notfound'
35 |
36 | await t.rejects(downloadAndUpdateBin(), /404/)
37 |
38 | delete process.env.KUBO_DIST_URL
39 |
40 | t.end()
41 | })
42 |
43 | test('Returns an error when legacy GO_IPFS_DIST_URL is 404', async (t) => {
44 | await clean()
45 |
46 | process.env.GO_IPFS_DIST_URL = 'https://dist.ipfs.tech/notfound'
47 |
48 | await t.rejects(downloadAndUpdateBin(), /404/)
49 |
50 | delete process.env.GO_IPFS_DIST_URL
51 |
52 | t.end()
53 | })
54 |
55 | test('Path returns undefined when no binary has been downloaded', async (t) => {
56 | await clean()
57 |
58 | t.throws(detectLocation, /not found/, 'Path throws if binary is not installed')
59 |
60 | t.end()
61 | })
62 |
63 | test('Ensure calling download function manually with static values works', async (t) => {
64 | await clean()
65 |
66 | const { version } = require('../package.json')
67 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'temp-dir-'))
68 |
69 | console.log(tempDir)
70 | const kuboPath = await download({
71 | version: `v${version}`,
72 | platform: 'darwin',
73 | arch: 'arm64',
74 | distUrl: 'https://dist.ipfs.tech',
75 | installPath: tempDir
76 | })
77 | console.log(kuboPath)
78 | const stats = await fs.stat(kuboPath)
79 |
80 | t.ok(stats, 'kubo was downloaded to installPath')
81 |
82 | t.end()
83 | })
84 |
--------------------------------------------------------------------------------
/test/fixtures/clean.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs-extra')
4 | const path = require('path')
5 | const execa = require('execa')
6 |
7 | module.exports = async function clean () {
8 | await fs.remove(path.resolve(__dirname, '../../kubo'))
9 | await execa('git', ['checkout', '--', path.resolve(__dirname, '../../bin/ipfs')])
10 | }
11 |
--------------------------------------------------------------------------------
/test/fixtures/example-project/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "author": "",
7 | "license": "ISC",
8 | "dependencies": {
9 | "kubo": "file://../../../"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/install.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs-extra')
2 | const path = require('path')
3 | const test = require('tape')
4 | const execa = require('execa')
5 | const cachedir = require('cachedir')
6 |
7 | /*
8 | Test that correct kubo is downloaded during npm install.
9 | */
10 |
11 | const expectedVersion = require('../package.json').version
12 |
13 | async function clean () {
14 | await fs.remove(path.join(__dirname, 'fixtures', 'example-project', 'node_modules'))
15 | await fs.remove(path.join(__dirname, 'fixtures', 'example-project', 'package-lock.json'))
16 | await fs.remove(cachedir('npm-kubo'))
17 | }
18 |
19 | test.onFinish(clean)
20 |
21 | test('Ensure kubo defined in package.json is fetched on dependency install', async (t) => {
22 | await clean()
23 |
24 | const exampleProjectRoot = path.join(__dirname, 'fixtures', 'example-project')
25 |
26 | // from `example-project`, install the module
27 | execa.sync('npm', ['install'], {
28 | cwd: exampleProjectRoot
29 | })
30 |
31 | // confirm package.json is correct
32 | const fetchedVersion = require(path.join(exampleProjectRoot, 'node_modules', 'kubo', 'package.json')).version
33 | t.ok(expectedVersion === fetchedVersion, `package.json versions match '${expectedVersion}'`)
34 |
35 | // confirm binary is correct
36 | const binary = path.join(exampleProjectRoot, 'node_modules', 'kubo', 'bin', 'ipfs')
37 | const versionRes = execa.sync(binary, ['--version'], {
38 | cwd: exampleProjectRoot
39 | })
40 |
41 | t.ok(versionRes.stdout === `ipfs version ${expectedVersion}`, `ipfs --version output match '${expectedVersion}'`)
42 |
43 | t.end()
44 | })
45 |
--------------------------------------------------------------------------------
/test/path.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var fs = require('fs')
3 | var path = require('path')
4 | var cp = require('child_process')
5 | var ipfs = path.join(__dirname, '..', 'bin', 'ipfs')
6 |
7 | if (process.platform === 'win32') {
8 | ipfs += '.exe'
9 | }
10 |
11 | test('ensure ipfs bin path exists', function (t) {
12 | t.plan(4)
13 | fs.stat(ipfs, function (err, stats) {
14 | t.error(err, 'ipfs bin should stat witout error')
15 | cp.exec([ipfs, 'version'].join(' '), function (err, stdout, stderr) {
16 | t.error(err, 'ipfs runs without error')
17 | t.true(stdout.indexOf('ipfs version') >= 0, 'ipfs version retreived')
18 | t.false(stderr, 'no stderr output')
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | // project options
5 | "allowJs": true,
6 | "checkJs": true,
7 | "target": "ES2019",
8 | "lib": [
9 | "ES2019",
10 | "ES2020.Promise",
11 | "ES2020.String",
12 | "ES2020.BigInt",
13 | "DOM",
14 | "DOM.Iterable"
15 | ],
16 | "noEmitOnError": true,
17 | "incremental": true,
18 | "composite": true,
19 | "isolatedModules": true,
20 | "removeComments": false,
21 | // module resolution
22 | "esModuleInterop": true,
23 | "moduleResolution": "node",
24 | // linter checks
25 | "noImplicitReturns": false,
26 | "noFallthroughCasesInSwitch": true,
27 | "noUnusedLocals": true,
28 | "noUnusedParameters": false,
29 | // advanced
30 | "importsNotUsedAsValues": "error",
31 | "forceConsistentCasingInFileNames": true,
32 | "skipLibCheck": true,
33 | "stripInternal": true,
34 | "resolveJsonModule": true,
35 | "outDir": "dist"
36 | },
37 | "include": [
38 | "src",
39 | "package.json"
40 | ],
41 | "exclude": [
42 | "node_modules"
43 | ]
44 | }
--------------------------------------------------------------------------------