Commit fbf3351
Changed files (4)
index.mjs
@@ -18,6 +18,7 @@ import {promisify} from 'util'
import {createInterface} from 'readline'
import {default as nodeFetch} from 'node-fetch'
import chalk from 'chalk'
+import {default as escape} from 'shq'
export {chalk}
@@ -37,8 +38,7 @@ function substitute(arg) {
export function $(pieces, ...args) {
let __from = (new Error().stack.split('at ')[2]).trim()
let cmd = pieces[0], i = 0
- for (; i < args.length; i++) cmd += substitute(args[i]) + pieces[i + 1]
- for (++i; i < pieces.length; i++) cmd += pieces[i]
+ while (i < args.length) cmd += escape(substitute(args[i])) + pieces[++i]
if ($.verbose) console.log('$', colorize(cmd))
@@ -49,7 +49,8 @@ export function $(pieces, ...args) {
if (typeof $.shell !== 'undefined') options.shell = $.shell
if (typeof $.cwd !== 'undefined') options.cwd = $.cwd
- let child = exec(cmd, options), stdout = '', stderr = '', combined = ''
+ let child = exec('set -euo pipefail;' + cmd, options)
+ let stdout = '', stderr = '', combined = ''
child.stdout.on('data', data => {
if ($.verbose) process.stdout.write(data)
stdout += data
package.json
@@ -13,6 +13,7 @@
"dependencies": {
"chalk": "^4.1.1",
"node-fetch": "^2.6.1",
+ "shq": "^1.0.2",
"uuid": "^8.3.2"
},
"publishConfig": {
README.md
@@ -14,14 +14,16 @@ await Promise.all([
$`sleep 3; echo 3`,
])
-await $`ssh medv.io uptime`
+let name = 'foo bar'
+await $`mkdir /tmp/${name}`
```
Bash is great, but when it comes to writing scripts,
people usually choose a more convenient programming language.
JavaScript is a perfect choice, but standard Node.js library
requires additional hassle before using. `zx` package provides
-useful wrappers around `child_process` and gives sensible defaults.
+useful wrappers around `child_process`, escapes arguments and
+gives sensible defaults like `set -o pipefail`.
## Install
test.mjs
@@ -12,11 +12,59 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-let foo = await $`echo Error >&2; echo Hello`
-await $`echo ${foo} | wc`
-
-await Promise.all([
- $`sleep 1; echo 1`,
- $`sleep 2; echo 2`,
- $`sleep 3; echo 3`,
-])
+function assert(cond, msg) {
+ if (cond) return
+ console.error('Assertion failed')
+ if (msg) console.error(msg)
+ process.exit(1)
+
+}
+
+{
+ let hello = await $`echo Error >&2; echo Hello`
+ let len = parseInt(await $`echo ${hello} | wc -c`)
+ assert(len === 6)
+}
+
+{
+ process.env.FOO = 'foo'
+ let foo = await $`echo $FOO`
+ assert(foo.stdout === 'foo\n')
+}
+
+{
+ let greeting = `"quota'" & pwd`
+ let {stdout} = await $`echo ${greeting}`
+ assert(stdout === greeting + '\n')
+}
+
+{
+ let foo = 'hi; ls'
+ let len = parseInt(await $`echo ${foo} | wc -l`)
+ assert(len === 1)
+}
+
+{
+ let bar = 'bar"";baz!$#^$\'&*~*%)({}||\\/'
+ assert((await $`echo ${bar}`).stdout.trim() === bar)
+}
+
+{
+ let name = 'foo bar'
+ try {
+ await $`mkdir /tmp/${name}`
+ } finally {
+ await fs.rmdir('/tmp/' + name)
+ }
+}
+
+{
+ let p
+ try {
+ p = await $`cat /dev/not_found | sort`
+ } catch (e) {
+ console.log('Caught an exception -> ok')
+ p = e
+ }
+ assert(p.exitCode === 1)
+}