Commit 23453bd
Changed files (6)
src/core.ts
@@ -21,10 +21,12 @@ import {
import { Readable, Writable } from 'node:stream'
import { inspect, promisify } from 'node:util'
import { spawn } from 'node:child_process'
+
import { chalk, which } from './goods.js'
-import { getCtx, runInCtx, setRootCtx } from './context.js'
+import { runInCtx, getCtx, setRootCtx } from './context.js'
import { printStd, printCmd } from './print.js'
import { quote, substitute } from './guards.js'
+
import psTreeModule from 'ps-tree'
const psTree = promisify(psTreeModule)
test/cli.test.js
@@ -12,55 +12,55 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import test from 'ava'
+import { test } from 'uvu'
+import * as assert from 'uvu/assert'
import '../build/globals.js'
$.verbose = false
-test('supports `-v` flag / prints version', async (t) => {
- t.regex((await $`node build/cli.js -v`).toString(), /\d+.\d+.\d+/)
+test('supports `-v` flag / prints version', async () => {
+ assert.match((await $`node build/cli.js -v`).toString(), /\d+.\d+.\d+/)
})
-test('prints help', async (t) => {
+test('prints help', async () => {
let help
try {
await $`node build/cli.js`
} catch (err) {
help = err.toString().trim()
}
- t.true(help.includes('print current zx version'))
+ assert.ok(help.includes('print current zx version'))
})
-test('supports `--experimental` flag', async (t) => {
+test('supports `--experimental` flag', async () => {
await $`echo 'echo("test")' | node build/cli.js --experimental`
- t.pass()
})
-test('supports `--quiet` flag / Quiet mode is working', async (t) => {
+test('supports `--quiet` flag / Quiet mode is working', async () => {
let p = await $`node build/cli.js --quiet docs/markdown.md`
- t.true(!p.stdout.includes('whoami'))
+ assert.ok(!p.stdout.includes('whoami'))
})
-test('supports `--shell` flag ', async (t) => {
+test('supports `--shell` flag ', async () => {
let shell = $.shell
let p =
await $`node build/cli.js --shell=${shell} <<< '$\`echo \${$.shell}\`'`
- t.true(p.stdout.includes(shell))
+ assert.ok(p.stdout.includes(shell))
})
-test('supports `--prefix` flag ', async (t) => {
+test('supports `--prefix` flag ', async () => {
let prefix = 'set -e;'
let p =
await $`node build/cli.js --prefix=${prefix} <<< '$\`echo \${$.prefix}\`'`
- t.true(p.stdout.includes(prefix))
+ assert.ok(p.stdout.includes(prefix))
})
-test('scripts from https', async (t) => {
+test('scripts from https', async () => {
let script = path.resolve('test/fixtures/echo.http')
let server = quiet($`while true; do cat ${script} | nc -l 8080; done`)
let p = await quiet($`node build/cli.js http://127.0.0.1:8080/echo.mjs`)
- t.true(p.stdout.includes('test'))
+ assert.ok(p.stdout.includes('test'))
server.kill()
let err
@@ -69,34 +69,32 @@ test('scripts from https', async (t) => {
} catch (e) {
err = e
}
- t.true(err.stderr.includes('ECONNREFUSED'))
+ assert.ok(err.stderr.includes('ECONNREFUSED'))
})
-test('scripts with no extension', async (t) => {
+test('scripts with no extension', async () => {
await $`node build/cli.js test/fixtures/no-extension`
- t.true(
+ assert.ok(
/Test file to verify no-extension didn't overwrite similarly name .mjs file./.test(
(await fs.readFile('test/fixtures/no-extension.mjs')).toString()
)
)
})
-test('require() is working from stdin', async (t) => {
+test('require() is working from stdin', async () => {
await $`node build/cli.js <<< 'require("./package.json").name'`
- t.pass()
})
-test('require() is working in ESM', async (t) => {
+test('require() is working in ESM', async () => {
await $`node build/cli.js test/fixtures/require.mjs`
- t.pass()
})
-test('__filename & __dirname are defined', async (t) => {
+test('__filename & __dirname are defined', async () => {
await $`node build/cli.js test/fixtures/filename-dirname.mjs`
- t.pass()
})
-test('markdown scripts are working', async (t) => {
+test('markdown scripts are working', async () => {
await $`node build/cli.js docs/markdown.md`
- t.pass()
})
+
+test.run()
test/experimental.test.js
@@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import test from 'ava'
+import { test } from 'uvu'
+import * as assert from 'uvu/assert'
import '../build/globals.js'
-$.verbose = false
-
import {
echo,
retry,
@@ -24,7 +23,11 @@ import {
withTimeout,
} from '../build/experimental.js'
-test('retry works', async (t) => {
+import chalk from 'chalk'
+
+$.verbose = false
+
+test('retry works', async () => {
let exitCode = 0
let now = Date.now()
try {
@@ -32,11 +35,11 @@ test('retry works', async (t) => {
} catch (p) {
exitCode = p.exitCode
}
- t.is(exitCode, 123)
- t.true(Date.now() >= now + 50 * (5 - 1))
+ assert.is(exitCode, 123)
+ assert.ok(Date.now() >= now + 50 * (5 - 1))
})
-test('withTimeout works', async (t) => {
+test('withTimeout works', async () => {
let exitCode = 0
let signal
try {
@@ -45,14 +48,14 @@ test('withTimeout works', async (t) => {
exitCode = p.exitCode
signal = p.signal
}
- t.is(exitCode, null)
- t.is(signal, 'SIGKILL')
+ assert.is(exitCode, null)
+ assert.is(signal, 'SIGKILL')
let p = await withTimeout(0)`echo 'test'`
- t.is(p.stdout.trim(), 'test')
+ assert.is(p.stdout.trim(), 'test')
})
-test('echo works', async (t) => {
+test('echo works', async () => {
echo(chalk.cyan('foo'), chalk.green('bar'), chalk.bold('baz'))
echo`${chalk.cyan('foo')} ${chalk.green('bar')} ${chalk.bold('baz')}`
echo(
@@ -60,12 +63,12 @@ test('echo works', async (t) => {
await $`echo ${chalk.green('bar')}`,
await $`echo ${chalk.bold('baz')}`
)
- t.pass()
})
-test('spinner works', async (t) => {
+test('spinner works', async () => {
let s = startSpinner('waiting')
await sleep(1000)
s()
- t.pass()
})
+
+test.run()
test/index.test.js
@@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import test from 'ava'
+import { test } from 'uvu'
+import * as assert from 'uvu/assert'
import { inspect } from 'node:util'
import chalk from 'chalk'
import { Writable } from 'node:stream'
@@ -20,48 +21,46 @@ import { Socket } from 'node:net'
import '../build/globals.js'
import { ProcessPromise } from '../build/index.js'
-$.verbose = false
-
-test('only stdout is used during command substitution', async (t) => {
+test('only stdout is used during command substitution', async () => {
let hello = await $`echo Error >&2; echo Hello`
let len = +(await $`echo ${hello} | wc -c`)
- t.is(len, 6)
+ assert.is(len, 6)
})
-test('env vars works', async (t) => {
+test('env vars works', async () => {
process.env.ZX_TEST_FOO = 'foo'
let foo = await $`echo $ZX_TEST_FOO`
- t.is(foo.stdout, 'foo\n')
+ assert.is(foo.stdout, 'foo\n')
})
-test('env vars is safe to pass', async (t) => {
+test('env vars is safe to pass', async () => {
process.env.ZX_TEST_BAR = 'hi; exit 1'
await $`echo $ZX_TEST_BAR`
- t.pass()
})
-test('arguments are quoted', async (t) => {
+test('arguments are quoted', async () => {
let bar = 'bar"";baz!$#^$\'&*~*%)({}||\\/'
- t.is((await $`echo ${bar}`).stdout.trim(), bar)
+ assert.is((await $`echo ${bar}`).stdout.trim(), bar)
})
-test('undefined and empty string correctly quoted', async (t) => {
- t.verbose = true
- t.is((await $`echo -n ${undefined}`).toString(), 'undefined')
- t.is((await $`echo -n ${''}`).toString(), '')
+test('undefined and empty string correctly quoted', async () => {
+ $.verbose = true
+ assert.is((await $`echo -n ${undefined}`).toString(), 'undefined')
+ assert.is((await $`echo -n ${''}`).toString(), '')
})
-test('can create a dir with a space in the name', async (t) => {
+test('can create a dir with a space in the name', async () => {
let name = 'foo bar'
try {
await $`mkdir /tmp/${name}`
+ } catch {
+ assert.unreachable()
} finally {
await fs.rmdir('/tmp/' + name)
}
- t.pass()
})
-test('pipefail is on', async (t) => {
+test('pipefail is on', async () => {
let p
try {
p = await $`cat /dev/not_found | sort`
@@ -69,21 +68,21 @@ test('pipefail is on', async (t) => {
console.log('Caught an exception -> ok')
p = e
}
- t.not(p.exitCode, 0)
+ assert.is.not(p.exitCode, 0)
})
-test('toString() is called on arguments', async (t) => {
+test('toString() is called on arguments', async () => {
let foo = 0
let p = await $`echo ${foo}`
- t.is(p.stdout, '0\n')
+ assert.is(p.stdout, '0\n')
})
-test('can use array as an argument', async (t) => {
+test('can use array as an argument', async () => {
let args = ['-n', 'foo']
- t.is((await $`echo ${args}`).toString(), 'foo')
+ assert.is((await $`echo ${args}`).toString(), 'foo')
})
-test.serial('quiet mode is working', async (t) => {
+test('quiet mode is working', async () => {
let stdout = ''
let log = console.log
console.log = (...args) => {
@@ -91,28 +90,28 @@ test.serial('quiet mode is working', async (t) => {
}
await quiet($`echo 'test'`)
console.log = log
- t.is(stdout, '')
+ assert.is(stdout, '')
})
-test('pipes are working', async (t) => {
+test('pipes are working', async () => {
let { stdout } = await $`echo "hello"`
.pipe($`awk '{print $1" world"}'`)
.pipe($`tr '[a-z]' '[A-Z]'`)
- t.is(stdout, 'HELLO WORLD\n')
+ assert.is(stdout, 'HELLO WORLD\n')
try {
await $`echo foo`.pipe(fs.createWriteStream('/tmp/output.txt'))
- t.is((await fs.readFile('/tmp/output.txt')).toString(), 'foo\n')
+ assert.is((await fs.readFile('/tmp/output.txt')).toString(), 'foo\n')
let r = $`cat`
fs.createReadStream('/tmp/output.txt').pipe(r.stdin)
- t.is((await r).stdout, 'foo\n')
+ assert.is((await r).stdout, 'foo\n')
} finally {
await fs.rm('/tmp/output.txt')
}
})
-test('question', async (t) => {
+test('question', async () => {
let p = question('foo or bar? ', { choices: ['foo', 'bar'] })
setImmediate(() => {
@@ -121,10 +120,10 @@ test('question', async (t) => {
process.stdin.emit('data', '\n')
})
- t.is(await p, 'foo')
+ assert.is(await p, 'foo')
})
-test('ProcessPromise', async (t) => {
+test('ProcessPromise', async () => {
let contents = ''
let stream = new Writable({
write: function (chunk, encoding, next) {
@@ -134,9 +133,9 @@ test('ProcessPromise', async (t) => {
})
let p = $`echo 'test'`.pipe(stream)
await p
- t.true(p._piped)
- t.is(contents, 'test\n')
- t.true(p.stderr instanceof Socket)
+ assert.ok(p._piped)
+ assert.is(contents, 'test\n')
+ assert.ok(p.stderr instanceof Socket)
let err
try {
@@ -144,38 +143,38 @@ test('ProcessPromise', async (t) => {
} catch (p) {
err = p
}
- t.is(err.message, 'The pipe() method does not take strings. Forgot $?')
+ assert.is(err.message, 'The pipe() method does not take strings. Forgot $?')
})
-test('ProcessPromise: inherits native Promise', async (t) => {
+test('ProcessPromise: inherits native Promise', async () => {
const p1 = $`echo 1`
const p2 = p1.then((v) => v)
const p3 = p2.then((v) => v)
const p4 = p3.catch((v) => v)
const p5 = p1.finally((v) => v)
- t.true(p1 instanceof Promise)
- t.true(p1 instanceof ProcessPromise)
- t.true(p2 instanceof ProcessPromise)
- t.true(p3 instanceof ProcessPromise)
- t.true(p4 instanceof ProcessPromise)
- t.true(p5 instanceof ProcessPromise)
- t.true(p1 !== p2)
- t.true(p2 !== p3)
- t.true(p3 !== p4)
- t.true(p5 !== p1)
+ assert.ok(p1 instanceof Promise)
+ assert.ok(p1 instanceof ProcessPromise)
+ assert.ok(p2 instanceof ProcessPromise)
+ assert.ok(p3 instanceof ProcessPromise)
+ assert.ok(p4 instanceof ProcessPromise)
+ assert.ok(p5 instanceof ProcessPromise)
+ assert.ok(p1 !== p2)
+ assert.ok(p2 !== p3)
+ assert.ok(p3 !== p4)
+ assert.ok(p5 !== p1)
})
-test('ProcessOutput thrown as error', async (t) => {
+test('ProcessOutput thrown as error', async () => {
let err
try {
await $`wtf`
} catch (p) {
err = p
}
- t.true(err.exitCode > 0)
- t.true(err.stderr.includes('/bin/bash: wtf: command not found\n'))
- t.true(err[inspect.custom]().includes('Command not found'))
+ assert.ok(err.exitCode > 0)
+ assert.ok(err.stderr.includes('/bin/bash: wtf: command not found\n'))
+ assert.ok(err[inspect.custom]().includes('Command not found'))
})
test('pipe() throws if already resolved', async (t) => {
@@ -185,7 +184,7 @@ test('pipe() throws if already resolved', async (t) => {
try {
out = await p.pipe($`less`)
} catch (err) {
- t.is(
+ assert.is(
err.message,
`The pipe() method shouldn't be called after promise is already resolved!`
)
@@ -195,38 +194,38 @@ test('pipe() throws if already resolved', async (t) => {
}
})
-test('await $`cmd`.exitCode does not throw', async (t) => {
- t.not(await $`grep qwerty README.md`.exitCode, 0)
- t.is(await $`[[ -f README.md ]]`.exitCode, 0)
+test('await $`cmd`.exitCode does not throw', async () => {
+ assert.is.not(await $`grep qwerty README.md`.exitCode, 0)
+ assert.is(await $`[[ -f README.md ]]`.exitCode, 0)
})
-test('nothrow() do not throw', async (t) => {
+test('nothrow() do not throw', async () => {
let { exitCode } = await nothrow($`exit 42`)
- t.is(exitCode, 42)
+ assert.is(exitCode, 42)
})
-test('globby available', async (t) => {
- t.is(globby, glob)
- t.is(typeof globby, 'function')
- t.is(typeof globby.globbySync, 'function')
- t.is(typeof globby.globbyStream, 'function')
- t.is(typeof globby.generateGlobTasks, 'function')
- t.is(typeof globby.isDynamicPattern, 'function')
- t.is(typeof globby.isGitIgnored, 'function')
- t.is(typeof globby.isGitIgnoredSync, 'function')
+test('globby available', async () => {
+ assert.is(globby, glob)
+ assert.is(typeof globby, 'function')
+ assert.is(typeof globby.globbySync, 'function')
+ assert.is(typeof globby.globbyStream, 'function')
+ assert.is(typeof globby.generateGlobTasks, 'function')
+ assert.is(typeof globby.isDynamicPattern, 'function')
+ assert.is(typeof globby.isGitIgnored, 'function')
+ assert.is(typeof globby.isGitIgnoredSync, 'function')
console.log(chalk.greenBright('globby available'))
- t.deepEqual(await globby('*.md'), ['README.md'])
+ assert.equal(await globby('*.md'), ['README.md'])
})
-test('fetch', async (t) => {
- t.regex(
+test('fetch', async () => {
+ assert.match(
await fetch('https://medv.io').then((res) => res.text()),
/Anton Medvedev/
)
})
-test('executes a script from $PATH', async (t) => {
+test('executes a script from $PATH', async () => {
const isWindows = process.platform === 'win32'
const oldPath = process.env.PATH
@@ -249,33 +248,30 @@ test('executes a script from $PATH', async (t) => {
process.env.PATH = oldPath
fs.rmSync('/tmp/script-from-path')
}
- t.pass()
})
-test('cd() works with relative paths', async (t) => {
+test('cd() works with relative paths', async () => {
await $`node build/cli.js test/fixtures/cd-relative-paths.mjs`
- t.pass()
})
-test('cd() does not affect parallel contexts', async (t) => {
+test('cd() does not affect parallel contexts', async () => {
await $`node build/cli.js test/fixtures/cd-parallel-contexts.mjs`
- t.pass()
})
-test('kill() method works', async (t) => {
+test('kill() method works', async () => {
await $`node build/cli.js test/fixtures/kill.mjs`
- t.pass()
})
-test('a signal is passed with kill() method', async (t) => {
+test('a signal is passed with kill() method', async () => {
await $`node build/cli.js test/fixtures/kill-signal.mjs`
- t.pass()
})
-test('YAML works', async (t) => {
- t.deepEqual(YAML.parse(YAML.stringify({ foo: 'bar' })), { foo: 'bar' })
+test('YAML works', async () => {
+ assert.equal(YAML.parse(YAML.stringify({ foo: 'bar' })), { foo: 'bar' })
})
-test('which available', async (t) => {
- t.is(which.sync('npm'), await which('npm'))
+test('which available', async () => {
+ assert.is(which.sync('npm'), await which('npm'))
})
+
+test.run()
package.json
@@ -6,11 +6,26 @@
"main": "build/index.js",
"types": "build/index.d.ts",
"exports": {
- ".": "./build/index.js",
- "./globals": "./build/globals.js",
- "./experimental": "./build/experimental.js",
- "./cli": "./build/cli.js",
- "./core": "./build/core.js",
+ ".": {
+ "import": "./build/index.js",
+ "types": "./build/index.d.ts"
+ },
+ "./globals": {
+ "import": "./build/globals.js",
+ "types": "./build/globals.d.ts"
+ },
+ "./experimental": {
+ "import": "./build/experimental.js",
+ "types": "./build/experimental.d.ts"
+ },
+ "./cli": {
+ "import": "./zx.js",
+ "types": "./zx.d.ts"
+ },
+ "./core": {
+ "import": "./build/core.js",
+ "types": "./build/core.d.ts"
+ },
"./package.json": "./package.json"
},
"bin": {
@@ -22,8 +37,12 @@
"scripts": {
"fmt": "prettier --write .",
"build": "tsc",
- "test": "tsc && ava",
- "coverage": "c8 --reporter=html npm test"
+ "test": "tsc && npm run test:unit",
+ "coverage": "c8 --reporter=html npm test",
+ "test:unit": "uvu test -i fixtures",
+ "test:cov": "c8 --reporter=html npm run test:unit",
+ "test:zx": "npm run test zx",
+ "test:index": "npm run test index"
},
"dependencies": {
"@types/fs-extra": "^9.0.13",
@@ -31,7 +50,6 @@
"@types/node": "^17.0",
"@types/ps-tree": "^1.1.2",
"@types/which": "^2.0.1",
- "ava": "^4.2.0",
"chalk": "^5.0.1",
"fs-extra": "^10.1.0",
"globby": "^13.1.1",
@@ -44,15 +62,15 @@
"devDependencies": {
"c8": "^7.11.3",
"prettier": "^2.6.2",
- "typescript": "^4.8.0-dev.20220529"
+ "typescript": "^4.8.0-dev.20220529",
+ "uvu": "^0.5.3"
},
"publishConfig": {
"registry": "https://wombat-dressing-room.appspot.com"
},
"files": [
- "src",
- "*.mjs",
- "*.d.ts"
+ "build",
+ "zx.js"
],
"prettier": {
"semi": false,
tsconfig.json
@@ -2,6 +2,7 @@
"compilerOptions": {
"target": "ES2021",
"lib": ["ES2021"],
+ "moduleResolution": "nodenext",
"module": "nodenext",
"strict": true,
"noImplicitReturns": true,