diff --git a/README.md b/README.md index a373615839..77a34f908d 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ Creates and returns an instance of an IPFS node. Use the `options` argument to s - `hop` (object) - `enabled` (boolean): Make this node a relay (other nodes can connect *through* it). (Default: `false`) - `active` (boolean): Make this an *active* relay node. Active relay nodes will attempt to dial a destination peer even if that peer is not yet connected to the relay. (Default: `false`) - + - `config` (object) Modify the default IPFS node config. Find the Node.js defaults at [`src/core/runtime/config-nodejs.js`](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/config-nodejs.js) and the browser defaults at [`src/core/runtime/config-browser.js`](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/config-browser.js). This object will be *merged* with the default config; it will not replace it. - `libp2p` (object) add custom modules to the libp2p stack of your node diff --git a/examples/circuit-relaying/package.json b/examples/circuit-relaying/package.json index 3e92c83f8a..6a3e5cf58d 100644 --- a/examples/circuit-relaying/package.json +++ b/examples/circuit-relaying/package.json @@ -18,7 +18,7 @@ "ipfs-pubsub-room": "~0.3.0" }, "devDependencies": { - "aegir": "^13.0.5", + "aegir": "^14.0.0", "http-server": "~0.10.0", "ipfs-css": "~0.2.0", "parcel-bundler": "^1.6.2", diff --git a/package.json b/package.json index dba09a090d..481259e260 100644 --- a/package.json +++ b/package.json @@ -61,20 +61,21 @@ }, "homepage": "https://github.com/ipfs/js-ipfs#readme", "devDependencies": { - "aegir": "^13.1.0", + "aegir": "^14.0.0", "buffer-loader": "~0.0.1", "chai": "^4.1.2", "delay": "^2.0.0", "detect-node": "^2.0.3", + "detect-webworker": "^1.0.0", "dir-compare": "^1.4.0", "dirty-chai": "^2.0.1", "eslint-plugin-react": "^7.7.0", "execa": "~0.10.0", "expose-loader": "~0.7.5", "form-data": "^2.3.2", - "hat": "0.0.3", - "interface-ipfs-core": "~0.66.2", - "ipfsd-ctl": "~0.37.3", + "hat": "~0.0.3", + "interface-ipfs-core": "ipfs/interface-ipfs-core#enable-js-mfs-tests", + "ipfsd-ctl": "~0.37.4", "lodash": "^4.17.10", "mocha": "^5.1.1", "ncp": "^2.0.0", @@ -109,7 +110,8 @@ "ipfs-bitswap": "~0.20.0", "ipfs-block": "~0.7.1", "ipfs-block-service": "~0.14.0", - "ipfs-http-response": "^0.1.2", + "ipfs-http-response": "~0.1.2", + "ipfs-mfs": "~0.0.4", "ipfs-multipart": "~0.1.0", "ipfs-repo": "~0.22.1", "ipfs-unixfs": "~0.1.15", @@ -172,7 +174,8 @@ "through2": "^2.0.3", "update-notifier": "^2.5.0", "yargs": "^11.0.0", - "yargs-parser": "^10.0.0" + "yargs-parser": "^10.0.0", + "yargs-promise": "^1.1.0" }, "optionalDependencies": { "prom-client": "^11.0.0", diff --git a/src/cli/bin.js b/src/cli/bin.js index 2ccf2f66a0..fff221e947 100755 --- a/src/cli/bin.js +++ b/src/cli/bin.js @@ -2,13 +2,14 @@ 'use strict' +const YargsPromise = require('yargs-promise') const yargs = require('yargs') const updateNotifier = require('update-notifier') const readPkgUp = require('read-pkg-up') -const fs = require('fs') -const path = require('path') const utils = require('./utils') const print = utils.print +const mfs = require('ipfs-mfs/cli') +const debug = require('debug')('ipfs:cli') const pkg = readPkgUp.sync({cwd: __dirname}).pkg updateNotifier({ @@ -18,10 +19,6 @@ updateNotifier({ const args = process.argv.slice(2) -// Determine if the first argument is a sub-system command -const commandNames = fs.readdirSync(path.join(__dirname, 'commands')) -const isCommand = commandNames.includes(`${args[0]}.js`) - const cli = yargs .option('silent', { desc: 'Write no output', @@ -34,14 +31,6 @@ const cli = yargs type: 'string', default: '' }) - .commandDir('commands', { - // Only include the commands for the sub-system we're using, or include all - // if no sub-system command has been passed. - include (path, filename) { - if (!isCommand) return true - return `${args[0]}.js` === filename - } - }) .epilog(utils.ipfsPathHelp) .demandCommand(1) .fail((msg, err, yargs) => { @@ -56,20 +45,6 @@ const cli = yargs yargs.showHelp() }) -// If not a sub-system command then load the top level aliases -if (!isCommand) { - // NOTE: This creates an alias of - // `jsipfs files {add, get, cat}` to `jsipfs {add, get, cat}`. - // This will stay until https://github.com/ipfs/specs/issues/98 is resolved. - const addCmd = require('./commands/files/add') - const catCmd = require('./commands/files/cat') - const getCmd = require('./commands/files/get') - const aliases = [addCmd, catCmd, getCmd] - aliases.forEach((alias) => { - cli.command(alias.command, alias.describe, alias.builder, alias.handler) - }) -} - // Need to skip to avoid locking as these commands // don't require a daemon if (args[0] === 'daemon' || args[0] === 'init') { @@ -77,6 +52,8 @@ if (args[0] === 'daemon' || args[0] === 'init') { .help() .strict() .completion() + .command(require('./commands/daemon')) + .command(require('./commands/init')) .parse(args) } else { // here we have to make a separate yargs instance with @@ -86,19 +63,54 @@ if (args[0] === 'daemon' || args[0] === 'init') { if (err) { throw err } + utils.getIPFS(argv, (err, ipfs, cleanup) => { - if (err) { throw err } + if (err) { + throw err + } + + // add mfs commands + mfs(cli) + + // NOTE: This creates an alias of + // `jsipfs files {add, get, cat}` to `jsipfs {add, get, cat}`. + // This will stay until https://github.com/ipfs/specs/issues/98 is resolved. + const addCmd = require('./commands/files/add') + const catCmd = require('./commands/files/cat') + const getCmd = require('./commands/files/get') + const aliases = [addCmd, catCmd, getCmd] + aliases.forEach((alias) => { + cli.command(alias) + }) cli + .commandDir('commands') .help() .strict() .completion() - .parse(args, { ipfs: ipfs }, (err, argv, output) => { - if (output) { print(output) } - cleanup(() => { - if (err) { throw err } - }) + const parser = new YargsPromise(cli, { ipfs }) + parser.parse(args) + .then(({ data, argv }) => { + if (data) { + print(data) + } + + return cleanup() + }) + .catch((arg) => { + debug(arg) + + // the argument can have a different shape depending on where the error came from + if (arg.message) { + print(arg.message) + } else if (arg.error && arg.error.message) { + print(arg.error.message) + } else { + print('Unknown error, please re-run the command with DEBUG=ipfs:cli to see debug output') + } + + process.exit(1) }) }) }) diff --git a/src/cli/commands/dag/get.js b/src/cli/commands/dag/get.js index 6088d2eb34..d2795f7829 100644 --- a/src/cli/commands/dag/get.js +++ b/src/cli/commands/dag/get.js @@ -41,7 +41,7 @@ module.exports = { // * reads as 'agree in' if (node._json) { delete node._json.multihash - node._json.data = '0x' + node._json.data.toString('hex') + node._json.data = node._json.data.toString('base64') print(JSON.stringify(node._json)) return } diff --git a/src/cli/commands/files.js b/src/cli/commands/files.js deleted file mode 100644 index 1e24b3c226..0000000000 --- a/src/cli/commands/files.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict' - -const print = require('../utils').print -const lsCmd = require('./ls') - -module.exports = { - command: 'files ', - - description: 'Operations over files (add, cat, get, ls, etc)', - - builder (yargs) { - return yargs - .commandDir('files') - .command(lsCmd) - }, - - handler (argv) { - print('Type `jsipfs files --help` for more instructions') - } -} diff --git a/src/cli/commands/files/get.js b/src/cli/commands/files/get.js index c177fb8719..d9e0a39a67 100644 --- a/src/cli/commands/files/get.js +++ b/src/cli/commands/files/get.js @@ -30,6 +30,7 @@ function fileHandler (dir) { callback(err) } else { const fullFilePath = path.join(dir, file.path) + if (file.content) { file.content .pipe(fs.createWriteStream(fullFilePath)) diff --git a/src/cli/commands/object/data.js b/src/cli/commands/object/data.js index f47cad4035..13d0bfa7c7 100644 --- a/src/cli/commands/object/data.js +++ b/src/cli/commands/object/data.js @@ -17,7 +17,7 @@ module.exports = { throw err } - print(data.toString(), false) + print(data, false) }) } } diff --git a/src/cli/utils.js b/src/cli/utils.js index f993e0caef..02f830f6c6 100644 --- a/src/cli/utils.js +++ b/src/cli/utils.js @@ -9,6 +9,7 @@ const log = debug('cli') log.error = debug('cli:error') const Progress = require('progress') const byteman = require('byteman') +const promisify = require('promisify-es6') exports = module.exports @@ -40,7 +41,7 @@ function getAPICtl (apiAddr) { exports.getIPFS = (argv, callback) => { if (argv.api || isDaemonOn()) { - return callback(null, getAPICtl(argv.api), (cb) => cb()) + return callback(null, getAPICtl(argv.api), promisify((cb) => cb())) } // Required inline to reduce startup time @@ -55,13 +56,13 @@ exports.getIPFS = (argv, callback) => { } }) - const cleanup = (cb) => { + const cleanup = promisify((cb) => { if (node && node._repo && !node._repo.closed) { - node._repo.close(() => cb()) + node._repo.close((err) => cb(err)) } else { cb() } - } + }) node.on('error', (err) => { throw err diff --git a/src/core/components/index.js b/src/core/components/index.js index ce95b27a53..a21eb40ebc 100644 --- a/src/core/components/index.js +++ b/src/core/components/index.js @@ -25,3 +25,4 @@ exports.dht = require('./dht') exports.dns = require('./dns') exports.key = require('./key') exports.stats = require('./stats') +exports.mfs = require('ipfs-mfs/core') diff --git a/src/core/config.js b/src/core/config.js index 493a30e5eb..82635162c4 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -16,7 +16,8 @@ const schema = Joi.object().keys({ EXPERIMENTAL: Joi.object().keys({ pubsub: Joi.boolean(), sharding: Joi.boolean(), - dht: Joi.boolean() + dht: Joi.boolean(), + mfs: Joi.boolean() }).allow(null), config: Joi.object().keys({ Addresses: Joi.object().keys({ diff --git a/src/core/index.js b/src/core/index.js index 0b19160429..ade8c98547 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -136,6 +136,15 @@ class IPFS extends EventEmitter { isIPFS: isIPFS } + // ipfs.files + const mfs = components.mfs(this) + + Object.keys(mfs).forEach(key => { + if (mfs.hasOwnProperty(key)) { + this.files[key] = mfs[key] + } + }) + boot(this) } } diff --git a/src/http/api/routes/files.js b/src/http/api/routes/files.js index 2400f6012b..fc8222f180 100644 --- a/src/http/api/routes/files.js +++ b/src/http/api/routes/files.js @@ -1,6 +1,7 @@ 'use strict' const resources = require('./../resources') +const mfs = require('ipfs-mfs/http') module.exports = (server) => { const api = server.select('API') @@ -54,4 +55,6 @@ module.exports = (server) => { handler: resources.files.immutableLs.handler } }) + + mfs(api) } diff --git a/src/http/index.js b/src/http/index.js index 93a5ec172e..d15f1375b0 100644 --- a/src/http/index.js +++ b/src/http/index.js @@ -100,7 +100,11 @@ function HttpApi (repo, config, cliArgs) { routes: { cors: true } - } + }, + debug: process.env.DEBUG ? { + request: ['*'], + log: ['*'] + } : undefined }) this.server.app.ipfs = this.node diff --git a/test/browser.js b/test/browser.js new file mode 100644 index 0000000000..4b41564257 --- /dev/null +++ b/test/browser.js @@ -0,0 +1,8 @@ +'use strict' + +const isWebWorker = require('detect-webworker') + +if (isWebWorker) { + // https://github.com/Joris-van-der-Wel/karma-mocha-webworker/issues/4 + global.MFS_DISABLE_CONCURRENCY = true +} diff --git a/test/cli/commands.js b/test/cli/commands.js index 1e194eb143..98d16c055d 100644 --- a/test/cli/commands.js +++ b/test/cli/commands.js @@ -4,7 +4,7 @@ const expect = require('chai').expect const runOnAndOff = require('../utils/on-and-off') -const commandCount = 74 +const commandCount = 73 describe('commands', () => runOnAndOff((thing) => { let ipfs diff --git a/test/cli/file.js b/test/cli/file.js index e20a16101f..e4a5e0c9ce 100644 --- a/test/cli/file.js +++ b/test/cli/file.js @@ -12,7 +12,7 @@ describe('file ls', () => runOnAndOff((thing) => { before(function () { this.timeout(50 * 1000) ipfs = thing.ipfs - return ipfs('files add -r test/fixtures/test-data/recursive-get-dir') + return ipfs('add -r test/fixtures/test-data/recursive-get-dir') }) it('prints a filename', () => { diff --git a/test/cli/files.js b/test/cli/files.js index 60175ddfab..6dd19614e3 100644 --- a/test/cli/files.js +++ b/test/cli/files.js @@ -124,7 +124,7 @@ describe('files', () => runOnAndOff((thing) => { it('add with progress', function () { this.timeout(30 * 1000) - return ipfs('files add -p src/init-files/init-docs/readme') + return ipfs('add -p src/init-files/init-docs/readme') .then((out) => { expect(out) .to.eql('added QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB readme\n') @@ -134,7 +134,7 @@ describe('files', () => runOnAndOff((thing) => { it('add', function () { this.timeout(30 * 1000) - return ipfs('files add src/init-files/init-docs/readme') + return ipfs('add src/init-files/init-docs/readme') .then((out) => { expect(out) .to.eql('added QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB readme\n') @@ -154,7 +154,7 @@ describe('files', () => runOnAndOff((thing) => { it('add recursively test', function () { this.timeout(60 * 1000) - return ipfs('files add -r test/fixtures/test-data/recursive-get-dir') + return ipfs('add -r test/fixtures/test-data/recursive-get-dir') .then((out) => { expect(out).to.eql(recursiveGetDirResults.join('\n') + '\n') }) @@ -163,7 +163,7 @@ describe('files', () => runOnAndOff((thing) => { it('add directory with trailing slash test', function () { this.timeout(30 * 1000) - return ipfs('files add -r test/fixtures/test-data/recursive-get-dir/') + return ipfs('add -r test/fixtures/test-data/recursive-get-dir/') .then((out) => { expect(out).to.eql(recursiveGetDirResults.join('\n') + '\n') }) @@ -177,7 +177,7 @@ describe('files', () => runOnAndOff((thing) => { 'added QmXJGoo27bg7ExNAtr9vRcivxDwcfHtkxatGno9HrUdR16 odd-name-[v0]' ] - return ipfs('files add -r test/fixtures/odd-name-[v0]') + return ipfs('add -r test/fixtures/odd-name-[v0]') .then((out) => { expect(out).to.eql(expected.join('\n') + '\n') }) @@ -258,7 +258,7 @@ describe('files', () => runOnAndOff((thing) => { it('add --quiet', function () { this.timeout(30 * 1000) - return ipfs('files add -q src/init-files/init-docs/readme') + return ipfs('add -q src/init-files/init-docs/readme') .then((out) => { expect(out) .to.eql('added QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB\n') @@ -268,7 +268,7 @@ describe('files', () => runOnAndOff((thing) => { it('add --quieter', function () { this.timeout(30 * 1000) - return ipfs('files add -Q -w test/fixtures/test-data/hello test/test-data/node.json') + return ipfs('add -Q -w test/fixtures/test-data/hello test/test-data/node.json') .then((out) => { expect(out) .to.eql('QmYRMUVULBfj7WrdPESnwnyZmtayN6Sdrwh1nKcQ9QgQeZ\n') @@ -278,7 +278,7 @@ describe('files', () => runOnAndOff((thing) => { it('add --silent', function () { this.timeout(30 * 1000) - return ipfs('files add --silent src/init-files/init-docs/readme') + return ipfs('add --silent src/init-files/init-docs/readme') .then((out) => { expect(out) .to.eql('') @@ -286,7 +286,7 @@ describe('files', () => runOnAndOff((thing) => { }) it('add --only-hash outputs correct hash', function () { - return ipfs('files add --only-hash src/init-files/init-docs/readme') + return ipfs('add --only-hash src/init-files/init-docs/readme') .then(out => expect(out) .to.eql('added QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB readme\n') @@ -330,7 +330,7 @@ describe('files', () => runOnAndOff((thing) => { it('cat', function () { this.timeout(30 * 1000) - return ipfs('files cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB') + return ipfs('cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB') .then((out) => { expect(out).to.eql(readme) }) @@ -348,7 +348,7 @@ describe('files', () => runOnAndOff((thing) => { it('cat part of a file using `count`', function () { this.timeout(30 * 1000) - return ipfs('files cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB --offset 21 --count 5') + return ipfs('cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB --offset 21 --count 5') .then((out) => { expect(out).to.eql(readme.substring(21, 26)) }) @@ -357,7 +357,7 @@ describe('files', () => runOnAndOff((thing) => { it('cat part of a file using `length`', function () { this.timeout(30 * 1000) - return ipfs('files cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB --offset 21 --length 5') + return ipfs('cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB --offset 21 --length 5') .then((out) => { expect(out).to.eql(readme.substring(21, 26)) }) @@ -374,7 +374,7 @@ describe('files', () => runOnAndOff((thing) => { it('get', function () { this.timeout(20 * 1000) - return ipfs('files get QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB') + return ipfs('get QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB') .then((out) => { expect(out) .to.eql('Saving file(s) QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB\n') @@ -409,7 +409,7 @@ describe('files', () => runOnAndOff((thing) => { const outDir = path.join(process.cwd(), 'Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z') rimraf(outDir) - return ipfs('files get Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z') + return ipfs('get Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z') .then((out) => { expect(out).to.eql( 'Saving file(s) Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z\n' diff --git a/test/cli/init.js b/test/cli/init.js index 86e0dbacda..b1a826c1db 100644 --- a/test/cli/init.js +++ b/test/cli/init.js @@ -42,7 +42,7 @@ describe('init', function () { // Test that the following was written when init-ing the repo // jsipfs files cat /ipfs/QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr/readme - let command = out.substring(out.indexOf('files cat'), out.length - 2 /* omit the newline char */) + let command = out.substring(out.indexOf('cat'), out.length - 2 /* omit the newline char */) return ipfs(command) }).then((out) => expect(out).to.equal(readme)) }) diff --git a/test/cli/ls.js b/test/cli/ls.js index bd4ed58bfd..4eccf82c07 100644 --- a/test/cli/ls.js +++ b/test/cli/ls.js @@ -9,7 +9,7 @@ describe('ls', () => runOnAndOff((thing) => { before(() => { ipfs = thing.ipfs - return ipfs('files add -r test/fixtures/test-data/recursive-get-dir') + return ipfs('add -r test/fixtures/test-data/recursive-get-dir') }) it('prints added files', function () { diff --git a/test/core/interface/files-mfs.js b/test/core/interface/files-mfs.js new file mode 100644 index 0000000000..fe24868fa6 --- /dev/null +++ b/test/core/interface/files-mfs.js @@ -0,0 +1,37 @@ +/* eslint-env mocha */ +'use strict' + +const test = require('interface-ipfs-core') +const parallel = require('async/parallel') + +const IPFS = require('../../../src') + +process.env.IPFS_MFS = true + +const DaemonFactory = require('ipfsd-ctl') +const df = DaemonFactory.create({ type: 'proc', exec: IPFS }) + +const nodes = [] +const common = { + setup: function (callback) { + callback(null, { + spawnNode: (cb) => { + df.spawn({ + initOptions: { bits: 1024 } + }, (err, _ipfsd) => { + if (err) { + return cb(err) + } + + nodes.push(_ipfsd) + cb(null, _ipfsd.api) + }) + } + }) + }, + teardown: function (callback) { + parallel(nodes.map((node) => (cb) => node.stop(cb)), callback) + } +} + +test.filesMFS(common) diff --git a/test/core/interface/interface.spec.js b/test/core/interface/interface.spec.js index 356036adeb..c8fc24de13 100644 --- a/test/core/interface/interface.spec.js +++ b/test/core/interface/interface.spec.js @@ -9,6 +9,7 @@ describe('interface-ipfs-core tests', () => { require('./bootstrap') require('./config') require('./files') + require('./files-mfs') require('./generic') require('./object') require('./dag') diff --git a/test/http-api/files.js b/test/http-api/files.js index 7e85697299..2f352b75bd 100644 --- a/test/http-api/files.js +++ b/test/http-api/files.js @@ -46,4 +46,342 @@ describe('.files', () => { }) }) }) + + describe('.ls', function () { + it('lists empty directory', () => { + return ipfs.files.ls() + .then(files => { + expect(files).to.be.empty() + }) + }) + + it('lists files', () => { + const fileName = `single-file-${Math.random()}.txt` + + return ipfs.files.write(`/${fileName}`, Buffer.from('Hello world'), { + create: true + }) + .then(() => ipfs.files.ls()) + .then(files => { + expect(files.length).to.equal(1) + expect(files[0].name).to.equal(fileName) + }) + }) + + it('lists files in directories', () => { + const dirName = `dir-${Math.random()}` + const fileName = `file-in-dir-${Math.random()}.txt` + + return ipfs.files.write(`/${dirName}/${fileName}`, Buffer.from('Hello world'), { + create: true, + parents: true + }) + .then(() => ipfs.files.ls(`/${dirName}`)) + .then(files => { + expect(files.length).to.equal(1) + expect(files[0].name).to.equal(fileName) + }) + }) + }) + + describe('.cp', function () { + it('copies a file', () => { + const source = `source-file-${Math.random()}.txt` + const destination = `destination-file-${Math.random()}.txt` + + return ipfs.files.write(`/${source}`, Buffer.from('Hello world'), { + create: true + }) + .then(() => ipfs.files.cp(`/${source}`, `/${destination}`)) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const sourceFile = files + .filter(file => file.name === source) + .pop() + + expect(sourceFile.type).to.equal('file') + + const destFile = files + .filter(file => file.name === destination) + .pop() + + expect(destFile.type).to.equal('file') + }) + }) + + it('copies a directory', () => { + const source = `source-dir-${Math.random()}` + const destination = `destination-dir-${Math.random()}` + + return ipfs.files.mkdir(`/${source}`) + .then(() => ipfs.files.cp(`/${source}`, `/${destination}`)) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const sourceDir = files + .filter(file => file.name === source) + .pop() + + expect(sourceDir.type).to.equal('directory') + + const destDir = files + .filter(file => file.name === destination) + .pop() + + expect(destDir.type).to.equal('directory') + }) + }) + + it('copies a file with array args', () => { + const source = `source-file-${Math.random()}.txt` + const destination = `destination-file-${Math.random()}.txt` + + return ipfs.files.write(`/${source}`, Buffer.from('Hello world'), { + create: true + }) + .then(() => ipfs.files.cp([`/${source}`, `/${destination}`])) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const sourceFile = files + .filter(file => file.name === source) + .pop() + + expect(sourceFile.type).to.equal('file') + + const destFile = files + .filter(file => file.name === destination) + .pop() + + expect(destFile.type).to.equal('file') + }) + }) + + it('copies a directory with array args', () => { + const source = `source-dir-${Math.random()}` + const destination = `destination-dir-${Math.random()}` + + return ipfs.files.mkdir(`/${source}`) + .then(() => ipfs.files.cp([`/${source}`, `/${destination}`])) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const sourceDir = files + .filter(file => file.name === source) + .pop() + + expect(sourceDir.type).to.equal('directory') + + const destDir = files + .filter(file => file.name === destination) + .pop() + + expect(destDir.type).to.equal('directory') + }) + }) + }) + + describe('.mkdir', function () { + it('makes a directory', () => { + const directory = `directory-${Math.random()}` + + return ipfs.files.mkdir(`/${directory}`) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const dir = files + .filter(file => file.name === directory) + .pop() + + expect(dir.type).to.equal('directory') + }) + }) + }) + + describe('.mv', function () { + it('moves a file', () => { + const source = `source-file-${Math.random()}.txt` + const destination = `destination-file-${Math.random()}.txt` + + return ipfs.files.write(`/${source}`, Buffer.from('Hello world'), { + create: true + }) + .then(() => ipfs.files.mv(`/${source}`, `/${destination}`)) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const sourceFile = files + .filter(file => file.name === source) + .pop() + + expect(sourceFile).to.not.exist() + + const destFile = files + .filter(file => file.name === destination) + .pop() + + expect(destFile.type).to.equal('file') + }) + }) + + it('moves a directory', () => { + const source = `source-dir-${Math.random()}` + const destination = `destination-dir-${Math.random()}` + + return ipfs.files.mkdir(`/${source}`) + .then(() => ipfs.files.mv(`/${source}`, `/${destination}`)) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const sourceDir = files + .filter(file => file.name === source) + .pop() + + expect(sourceDir).to.not.exist() + + const destDir = files + .filter(file => file.name === destination) + .pop() + + expect(destDir.type).to.equal('directory') + }) + }) + + it('moves a file with array args', () => { + const source = `source-file-${Math.random()}.txt` + const destination = `destination-file-${Math.random()}.txt` + + return ipfs.files.write(`/${source}`, Buffer.from('Hello world'), { + create: true + }) + .then(() => ipfs.files.mv([`/${source}`, `/${destination}`])) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const sourceFile = files + .filter(file => file.name === source) + .pop() + + expect(sourceFile).to.not.exist() + + const destFile = files + .filter(file => file.name === destination) + .pop() + + expect(destFile.type).to.equal('file') + }) + }) + + it('moves a directory with array args', () => { + const source = `source-dir-${Math.random()}` + const destination = `destination-dir-${Math.random()}` + + return ipfs.files.mkdir(`/${source}`) + .then(() => ipfs.files.mv([`/${source}`, `/${destination}`])) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const sourceDir = files + .filter(file => file.name === source) + .pop() + + expect(sourceDir).to.not.exist() + + const destDir = files + .filter(file => file.name === destination) + .pop() + + expect(destDir.type).to.equal('directory') + }) + }) + }) + + describe('.read', function () { + it('reads a file', () => { + const fileName = `single-file-${Math.random()}.txt` + const content = Buffer.from('Hello world') + + return ipfs.files.write(`/${fileName}`, content, { + create: true + }) + .then(() => ipfs.files.read(`/${fileName}`)) + .then(buffer => { + expect(buffer).to.deep.equal(content) + }) + }) + }) + + describe('.rm', function () { + it('removes a file', () => { + const fileName = `single-file-${Math.random()}.txt` + + return ipfs.files.write(`/${fileName}`, Buffer.from('Hello world'), { + create: true + }) + .then(() => ipfs.files.rm(`/${fileName}`)) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const file = files + .filter(file => file.name === fileName) + .pop() + + expect(file).to.not.exist() + }) + }) + + it('removes a directory', () => { + const dirName = `dir-${Math.random()}` + const fileName = `file-in-dir-${Math.random()}.txt` + + return ipfs.files.write(`/${dirName}/${fileName}`, Buffer.from('Hello world'), { + create: true, + parents: true + }) + .then(() => ipfs.files.rm(`/${dirName}`, { + recursive: true + })) + .then(() => ipfs.files.ls(`/`)) + .then(files => { + const dir = files + .filter(file => file.name === dirName) + .pop() + + expect(dir).to.not.exist() + }) + }) + }) + + describe('.stat', function () { + it('stats a file', () => { + const fileName = `single-file-${Math.random()}.txt` + + return ipfs.files.write(`/${fileName}`, Buffer.from('Hello world'), { + create: true + }) + .then(() => ipfs.files.stat(`/${fileName}`)) + .then(stats => { + expect(stats).to.deep.equal({ + blocks: 1, + cumulativeSize: 69, + hash: 'Qmetpc7cZmN25Wcc6R27cGCAvCDqCS5GjHG4v7xABEfpmJ', + local: undefined, + size: 11, + sizeLocal: undefined, + type: 'file', + withLocality: false + }) + }) + }) + + it('stats a directory', () => { + const dirName = `dir-${Math.random()}` + + return ipfs.files.mkdir(`/${dirName}`) + .then(() => ipfs.files.stat(`/${dirName}`)) + .then(stats => { + expect(stats).to.deep.equal({ + blocks: 0, + cumulativeSize: 4, + hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', + local: undefined, + size: 0, + sizeLocal: undefined, + type: 'directory', + withLocality: false + }) + }) + }) + }) }) diff --git a/test/http-api/index.js b/test/http-api/index.js index 44d48f7f1f..43bcaad005 100644 --- a/test/http-api/index.js +++ b/test/http-api/index.js @@ -9,3 +9,4 @@ require('./dns') require('./id') require('./object') require('./version') +require('./files') diff --git a/test/utils/ipfs-exec.js b/test/utils/ipfs-exec.js index 2437f136a9..59876b787f 100644 --- a/test/utils/ipfs-exec.js +++ b/test/utils/ipfs-exec.js @@ -13,7 +13,7 @@ const _ = require('lodash') // The top level export is a function that can be passed a `repoPath` // and optional `opts` to customize the execution of the commands. // This function returns the actual executer, which consists of -// `ipfs('files get ')` and `ipfs.fail('files get ')` +// `ipfs('get ')` and `ipfs.fail('files get ')` // The first one executes and asserts that the command ran successfully // and returns a promise which is resolved to `stdout` of the command. // The `.fail` variation asserts that the command exited with `Code > 0`