-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix: CLI should accept content from stdin (#474) #785
Conversation
Adds support for unix-like piping in js-ipfs, like in go-ipfs cli. e.g. `echo "hello" | IPFS_PATH=~/.ipfs-go ipfs add` is handled as expected
Failing in the module used to parse the piped arguments. Looking into it. What do you guys think about the testing approach? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for bringing this fix, it will still require some improvement, but you are in the right track :)
src/cli/bin.js
Outdated
@@ -2,6 +2,7 @@ | |||
|
|||
'use strict' | |||
|
|||
require('pipe-args').load() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool module!
It is doing a bit more than what we want, though. If enabled, every command will take stdin->args, which opens space for other errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but it's not only add that supports stdin. You can find a list of the commands that supports stdin and which argument it applies to here: https://github.com/ipfs/go-ipfs/search?utf8=%E2%9C%93&q=EnableStdin
We should probably have something similar for js-ipfs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@diasdavid being /dev/stdin a symbolic link to /proc/self/fd/0 (and /proc/self a symlink to the running process), we want to grab whatever it is there and pass it as argument, right? The problem I see is echo "file.sx file.json" | ipfs add
will not work since the input 'filesx file.json' will be passed as one string. Or I am missing something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are commands like ipfs swarm peers
that should not take input from stdin
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@victorbjelkholm yep, the committed tests are just as proof of concept. Once the feature is polished, I will add for all the tests for commands that must support stdin redirection. The link is super helpful, thanks!!
@diasdavid added the necessary changes for the pipe-args to copy the stdin to process.argv in selected commands. It could also be done explicitly in the cli.js without using the pipe-args There was a fix in pipe-args module related to the Let me know what you think so I can go ahead and add more tests or change what's needed :) |
src/cli/bin.js
Outdated
const enableStdin = [ | ||
'files', 'path', 'object data', 'ref', 'domain-name', 'key', 'ipfs-path', | ||
'name', 'address', 'data', 'peer', 'recursive', 'default-config', 'peer ID' | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is domain name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be renamed to 'dns'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add
, get
and cat
should also be here temporary as we have them as aliases.
Lines 26 to 35 in ebaf9a0
// 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) | |
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- needs rebase from master
- needs a test for
jsipfs block put
Next coming up on this PR:
|
@gpestana no need for "domain-name -> dns" since js-ipfs does not support dnslink resolution yet. |
@gpestana mind rebasing master into your branch? @victorbjelkholm could you provide the final review and recommendation to get this merged? Thank you both :) |
@diasdavid Will do, thanks for reminding me :) @victorbjelkholm, let's get this done! In meanwhile, if you come up with anything besides rebase, jsipfs block put test and improve test coverage let me know. |
src/cli/bin.js
Outdated
|
||
pipe.load({ commands: enableStdin }) | ||
|
||
const yargs = require('yargs') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just put this at the top so all the dependencies can be seen easily
test/cli/files.js
Outdated
@@ -127,6 +127,23 @@ describe('files', () => runOnAndOff((thing) => { | |||
}) | |||
}) | |||
|
|||
it('add with piped argument', () => { | |||
// echo 'src/init-files/init-docs/readme' | jsipfs files add | |||
return ipfs('files add', { piped: 'src/init-files/init-docs/readme' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unable to actually test this due to conflicts, if those could be resolved, would be 👍
Also, judging by this test, the output of echo 'src/init-files/init-docs/readme' | jsipfs files add
should place src/init-files/init-docs/readme
as the content, without any filename, but the assertion seems to say that it just added a file called readme
which is incorrect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conflicts are resolved now! And good point, gonna take a look at that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, just got a chance to try it again and this should work:
head -c 10 /dev/urandom | base64 | node src/cli/bin.js files add
but instead is giving me an error:
/Users/user/projects/ipfs/js-monorepo/packages/js-ipfs/node_modules/yargs/yargs.js:1079
else throw err
^
Error: ENOENT: no such file or directory, stat 'Ksw1ReYL8xR2VA=='
at Error (native)
at Object.fs.statSync (fs.js:987:18)
at checkPath (/Users/user/projects/ipfs/js-monorepo/packages/js-ipfs/src/cli/commands/files/add.js:32:10)
at Object.handler (/Users/user/projects/ipfs/js-monorepo/packages/js-ipfs/src/cli/commands/files/add.js:72:20)
Because it's trying to find a file with that name, rather than actually using the data from stdin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understood it correctly, it seems that it might be an issue with the CLI itself. The piped arguments work exactly the same way as if the arguments were passed directly:
node src/cli/bin.js files add $(head -c 10 /dev/urandom | base64)
/Users/home/dev/js-ipfs/node_modules/yargs/yargs.js:1079
else throw err
^
Error: ENOENT: no such file or directory, stat 're+g0ZVkYUUsKg=='
at Object.fs.statSync (fs.js:967:11)
at checkPath (/Users/home/dev/js-ipfs/src/cli/commands/files/add.js:32:10)
at Object.handler (/Users/home/dev/js-ipfs/src/cli/commands/files/add.js:72:20)
at Object.self.runCommand (/Users/home/dev/js-ipfs/node_modules/yargs/lib/command.js:233:22)
at Object.Yargs.self._parseArgs (/Users/home/dev/js-ipfs/node_modules/yargs/yargs.js:990:30)
at Object.self.runCommand (/Users/home/dev/js-ipfs/node_modules/yargs/lib/command.js:204:45)
at Object.Yargs.self._parseArgs (/Users/home/dev/js-ipfs/node_modules/yargs/yargs.js:990:30)
at Object.Yargs.self.parse (/Users/home/dev/js-ipfs/node_modules/yargs/yargs.js:532:23)
at utils.getIPFS (/Users/home/dev/js-ipfs/src/cli/bin.js:67:6)
at IPFS.node.once (/Users/home/dev/js-ipfs/src/cli/utils.js:64:5)
Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, using stdin and passing a argument should not work the same way. Passing a argument should use that argument as a path to a file. Using stdin should use whatever comes from stdin as the content to add, without any filename.
Compare it to how go-ipfs works, and maybe it'll be a bit easier to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it!
a0220ce
to
ed3f719
Compare
@diasdavid yep, I think it's possible. Let's aim at getting this merged by end of the week. |
src/cli/bin.js
Outdated
const enableStdin = [ | ||
'files', 'path', 'object data', 'ref', 'domain-name', 'key', 'ipfs-path', | ||
'name', 'address', 'data', 'peer', 'recursive', 'default-config', 'peer ID' | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add
, get
and cat
should also be here temporary as we have them as aliases.
Lines 26 to 35 in ebaf9a0
// 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) | |
}) |
… it as a file path
@victorbjelkholm, the new commit should fix what you pointed out in the last comment: in the Not sure if this would be possible to fork the flow in the pull stream itself, but I'm not that experienced with it so I didn't figure out other way. Any other suggestion? There is still a problem though. When running the tests, I get the following error:
This happens only with the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @gpestana, just went through your code and a couple of things are not making sense to me.
Essentially, this feature should be in place by doing something:
if (hasStdin) {
ipfs.files.add({path:'', content: process.stdin}, (err, files)
} else {
// do the normal stuff
}
} | ||
|
||
addDataPipeline([dataStream], addStream) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something doesn't look right here:
createAddStream
is going to glob a dir and return the stream which has all the files resulting from that glob.
If you want to add one stream, you should not need to call that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. So the right solution would be to do something like this?
if (hasStdin) {
ipfs.files.add({path:'', content: process.stdin}, (err, files)
} else {
// as it is now
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, see: #785 (review)
if(hasPipedArgs) { | ||
const data = argv.file | ||
const dataStream = new stream.Readable() | ||
dataStream.push(Buffer.from(data, 'utf8')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might not be utf8
, that option out, it doesn't really matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, it can use the default. I will remove that option parameter.
// if there are piped arguments, input is data to publish instead of a file | ||
// path | ||
if(hasPipedArgs) { | ||
const data = argv.file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I do cat someFile.txt | ipfs add
does pipe-args buffer the whole file first? Well, then it is missing the point of pipes, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the whole file is going to be buffered before the command is triggered. I assumed that since this is for a CLI interface, it would make sense to do so. So it's better to buffer the file using streams and make it async?
PR now needs rebase from master |
@hacdias would you like to get this PR merged? |
const yargs = require('yargs') | ||
const updateNotifier = require('update-notifier') | ||
const readPkgUp = require('read-pkg-up') | ||
const utils = require('./utils') | ||
|
||
const enableStdin = [ | ||
'data', 'path', 'object data', 'ref', 'key', 'ipfs-path', 'add', 'get', 'cat', | ||
'name', 'address', 'files', 'peer', 'recursive', 'default-config', 'peer ID' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to be missing a few commands. config
and pubsub
are two I remember from the top of my head, but I guess there is more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a Github search that shows which go-ipfs
commands accepts stdin: https://github.com/ipfs/go-ipfs/search?utf8=%E2%9C%93&q=EnableStdin&type=
@hacdias can you land this one? |
@diasdavid old and most likely not the best approach. closing this PR |
Adds support for unix-like piping in js-ipfs.
e.g.
echo "hello" | IPFS_PATH=~/.ipfs-go ipfs add
is handled as expected