diff --git a/README.md b/README.md
index ddf3f0938..4d76b70ae 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@
-# tea/cli 0.30.0
+# tea/cli 0.31.0
`tea` puts the whole open source ecosystem at your fingertips:
diff --git a/src/hooks/useRun.ts b/src/hooks/useRun.ts
index fa11bbf41..890ed1883 100644
--- a/src/hooks/useRun.ts
+++ b/src/hooks/useRun.ts
@@ -1,7 +1,7 @@
import Path from "path"
import { isArray } from "is_what"
-export interface RunOptions extends Omit {
+export interface RunOptions extends Omit {
cmd: (string | Path)[] | Path
cwd?: (string | Path)
clearEnv?: boolean //NOTE might not be cross platform!
@@ -21,15 +21,15 @@ export default async function useRun({ spin, ...opts }: RunOptions) {
const cwd = opts.cwd?.toString()
console.verbose({ cwd, ...opts, cmd })
- const stdio = { stdout: 'inherit', stderr: 'inherit', stdin: 'inherit' } as Pick
+ const stdio = { stdout: 'inherit', stderr: 'inherit', stdin: 'inherit' } as Pick
if (spin) {
stdio.stderr = stdio.stdout = 'piped'
}
- let proc: Deno.Process | undefined
+ let proc: Deno.ChildProcess | undefined
try {
- proc = _internals.nativeRun({ ...opts, cmd, cwd, ...stdio })
- const exit = await proc.status()
+ proc = _internals.nativeRun(cmd.shift()!, { ...opts, args: cmd, cwd, ...stdio }).spawn()
+ const exit = await proc.status
console.verbose({ exit })
if (!exit.success) throw new RunError(exit.code, cmd)
} catch (err) {
@@ -37,17 +37,17 @@ export default async function useRun({ spin, ...opts }: RunOptions) {
//FIXME this doesn’t result in the output being correctly interlaced
// ie. stderr and stdout may (probably) have been output interleaved rather than sequentially
const decode = (() => { const e = new TextDecoder(); return e.decode.bind(e) })()
- console.error(decode(await proc.output()))
- console.error(decode(await proc.stderrOutput()))
+ console.error(decode((await proc.output()).stdout))
+ console.error(decode((await proc.output()).stderr))
}
+
err.cmd = cmd // help us out since deno-devs don’t want to
throw err
- } finally {
- proc?.close()
}
}
-const nativeRun = (runOptions: Deno.RunOptions) => Deno.run(runOptions)
+
+const nativeRun = (cmd: string, opts: Deno.CommandOptions) => new Deno.Command(cmd, opts)
// _internals are used for testing
export const _internals = {
diff --git a/src/vendor/Path.ts b/src/vendor/Path.ts
index 79a30642a..6d7dcde23 100644
--- a/src/vendor/Path.ts
+++ b/src/vendor/Path.ts
@@ -317,12 +317,12 @@ export default class Path {
let opts = "-s"
if (force) opts += "fn"
- const status = await Deno.run({
- cmd: ["/bin/ln", opts, src, dst],
+ const status = await new Deno.Command("/bin/ln", {
+ args: [opts, src, dst],
cwd: this.string
- }).status()
+ }).spawn().status
- if (status.code != 0) throw new Error(`failed: cd ${this} && ln -sf ${src} ${dst}`)
+ if (!status.success) throw new Error(`failed: cd ${this} && ln -sf ${src} ${dst}`)
return to
}
diff --git a/tests/functional/exec.test.ts b/tests/functional/exec.test.ts
index 896772eaf..920e06258 100644
--- a/tests/functional/exec.test.ts
+++ b/tests/functional/exec.test.ts
@@ -8,54 +8,55 @@ Deno.test("exec", { sanitizeResources: false, sanitizeOps: false }, async () =>
const useRunSpy = spy(useRunInternals, "nativeRun")
try {
- await run(["node", "--version"])
+ await run(["node", "--version"])
} finally {
useRunSpy.restore()
}
- assertEquals(useRunSpy.calls[0].args[0].cmd, ["node", "--version"], "should have run node --version")
+ const foo = [useRunSpy.calls[0].args[0], ...useRunSpy.calls[0].args[1].args!]
+ assertEquals(foo, ["node", "--version"], "should have run node --version")
})
-Deno.test("forward env to exec", { sanitizeResources: false, sanitizeOps: false }, async () => {
+Deno.test("forward env to exec", { sanitizeResources: false, sanitizeOps: false }, async () => {
const {run, TEA_PREFIX, useRunInternals } = await createTestHarness()
const useRunSpy = spy(useRunInternals, "nativeRun")
try {
- await run(["sh", "-c", "echo $TEA_PREFIX"])
+ await run(["sh", "-c", "echo $TEA_PREFIX"])
} finally {
useRunSpy.restore()
}
- assertEquals(useRunSpy.calls[0].args[0].env?.["TEA_PREFIX"], TEA_PREFIX.string)
+ assertEquals(useRunSpy.calls[0].args[1].env?.["TEA_PREFIX"], TEA_PREFIX.string)
})
Deno.test("exec run errors", { sanitizeResources: false, sanitizeOps: false }, async test => {
const tests = [
{
name: "exit error",
- procStatus: (): Promise => Promise.resolve({success: false, code: 123}),
+ procStatus: (): Promise => Promise.resolve({success: false, code: 123, signal: null}),
expectedErr: "exiting with code: 123",
- },
+ },
{
name: "normal error",
- procStatus: (): Promise => Promise.reject(new Error("test error")),
+ procStatus: (): Promise => Promise.reject(new Error("test error")),
expectedErr: "exiting with code: 1",
- },
+ },
{
name: "tea error",
- procStatus: (): Promise => Promise.reject(new TeaError("confused: interpreter", {})),
+ procStatus: (): Promise => Promise.reject(new TeaError("confused: interpreter", {})),
expectedErr: "exiting with code: 1",
- },
+ },
{
name: "not found",
- procStatus: (): Promise => Promise.reject(new Deno.errors.NotFound()),
+ procStatus: (): Promise => Promise.reject(new Deno.errors.NotFound()),
expectedErr: "exiting with code: 127",
- },
+ },
{
name: "permission denied",
- procStatus: (): Promise => Promise.reject(new Deno.errors.PermissionDenied()),
+ procStatus: (): Promise => Promise.reject(new Deno.errors.PermissionDenied()),
expectedErr: "exiting with code: 127",
- },
+ },
]
for (const { name, procStatus, expectedErr } of tests) {
@@ -66,7 +67,7 @@ Deno.test("exec run errors", { sanitizeResources: false, sanitizeOps: false }, a
const useRunStub = stub(useRunInternals, "nativeRun", returnsNext([mockProc]))
await assertRejects(async () => {
try {
- await run(["node", "--version"])
+ await run(["node", "--version"])
} finally {
useRunStub.restore()
}
@@ -76,7 +77,7 @@ Deno.test("exec run errors", { sanitizeResources: false, sanitizeOps: false }, a
})
Deno.test("exec forkbomb protector", { sanitizeResources: false, sanitizeOps: false }, async () => {
- const {run } = await createTestHarness()
+ const { run } = await createTestHarness()
await assertRejects(
() => run(["sh", "-c", "echo $TEA_PREFIX"], { env: {TEA_FORK_BOMB_PROTECTOR: "21" }}),
"FORK BOMB KILL SWITCH ACTIVATED")
diff --git a/tests/functional/repl.test.ts b/tests/functional/repl.test.ts
index 2648769b7..5d31a022d 100644
--- a/tests/functional/repl.test.ts
+++ b/tests/functional/repl.test.ts
@@ -3,9 +3,9 @@ import { stub, returnsNext } from "deno/testing/mock.ts"
import { ExitError } from "types"
import { createTestHarness, newMockProcess } from "./testUtils.ts"
-Deno.test("should enter repl - sh", { sanitizeResources: false, sanitizeOps: false }, async test => {
+Deno.test("should enter repl - sh", { sanitizeResources: false, sanitizeOps: false }, async test => {
const tests = [
- {
+ {
shell: "/bin/sh",
expectedCmd: ["/bin/sh", "-i"],
expectedEnv: {"PS1": "\\[\\033[38;5;86m\\]tea\\[\\033[0m\\] %~ "},
@@ -41,14 +41,15 @@ Deno.test("should enter repl - sh", { sanitizeResources: false, sanitizeOps: fal
const useRunStub = stub(useRunInternals, "nativeRun", returnsNext([newMockProcess()]))
try {
- await run(["sh"], { env: { SHELL: shell } })
+ await run(["sh"], { env: { SHELL: shell } })
} finally {
useRunStub.restore()
}
- assertEquals(useRunStub.calls[0].args[0].cmd, expectedCmd)
+ const foo = [useRunStub.calls[0].args[0], ...useRunStub.calls[0].args[1].args!]
+ assertEquals(foo, expectedCmd)
- const { env } = useRunStub.calls[0].args[0]
+ const { env } = useRunStub.calls[0].args[1]
assertEquals(env?.["TEA_PREFIX"], TEA_PREFIX.string)
Object.entries(expectedEnv).forEach(([key, value]) => {
assertEquals(env?.[key], value)
@@ -58,18 +59,17 @@ Deno.test("should enter repl - sh", { sanitizeResources: false, sanitizeOps: fal
})
-Deno.test("repl errors", { sanitizeResources: false, sanitizeOps: false }, async test => {
+Deno.test("repl errors", { sanitizeResources: false, sanitizeOps: false }, async test => {
await test.step("run error", async () => {
const {run, useRunInternals } = await createTestHarness()
- const mockProc = newMockProcess()
- mockProc.status = () => Promise.resolve({success: false, code: 123})
+ const mockProc = newMockProcess(() => Promise.resolve({success: false, code: 123, signal: null}))
const useRunStub = stub(useRunInternals, "nativeRun", returnsNext([mockProc]))
await assertRejects(async () => {
try {
- await run(["sh"])
+ await run(["sh"])
} finally {
useRunStub.restore()
}
@@ -79,14 +79,13 @@ Deno.test("repl errors", { sanitizeResources: false, sanitizeOps: false }, async
await test.step("other error", async () => {
const {run, useRunInternals } = await createTestHarness()
- const mockProc = newMockProcess()
- mockProc.status = () => Promise.reject(new Error("test error"))
+ const mockProc = newMockProcess(() => Promise.reject(new Error("test error")))
const useRunStub = stub(useRunInternals, "nativeRun", returnsNext([mockProc]))
await assertRejects(async () => {
try {
- await run(["sh"])
+ await run(["sh"])
} finally {
useRunStub.restore()
}
diff --git a/tests/functional/script.test.ts b/tests/functional/script.test.ts
index 5f3c92da9..e9b6ca34c 100644
--- a/tests/functional/script.test.ts
+++ b/tests/functional/script.test.ts
@@ -11,12 +11,12 @@ Deno.test("run a python script", { sanitizeResources: false, sanitizeOps: false
const useRunSpy = spy(useRunInternals, "nativeRun")
try {
- await run([scriptFile])
+ await run([scriptFile])
} finally {
useRunSpy.restore()
}
- const [python, script] = useRunSpy.calls[0].args[0].cmd
+ const [python, script] = [useRunSpy.calls[0].args[0], ...useRunSpy.calls[0].args[1].args!]
assert(python.toString().startsWith("python3."))
assertEquals(script, scriptFile)
diff --git a/tests/functional/testUtils.ts b/tests/functional/testUtils.ts
index b36220d88..3c56a90e9 100644
--- a/tests/functional/testUtils.ts
+++ b/tests/functional/testUtils.ts
@@ -28,7 +28,7 @@ export const createTestHarness = async (config?: TestConfig) => {
const [syncArgs, flags] = parseArgs(["--sync", "--silent"], teaDir.string)
init(flags)
updateConfig({ teaPrefix: new Path(TEA_PREFIX.string), env: { NO_COLOR: "1" } })
- await run(syncArgs)
+ await run(syncArgs)
}
const runTea = async (args: string[], configOverrides: Partial = {}) => {
@@ -46,7 +46,7 @@ export const createTestHarness = async (config?: TestConfig) => {
init(flags)
updateConfig({ execPath: teaDir, teaPrefix: new Path(TEA_PREFIX.string), ...configOverrides })
- await run(appArgs)
+ await run(appArgs)
} finally {
usePrintSpy.restore()
Deno.chdir(cwd)
@@ -75,15 +75,27 @@ function updateConfig(updated: Partial) {
useConfigInternals.setConfig({...config, ...updated, env: {...config.env, ...updated.env}})
}
-// the Deno.Process object cannot be created externally with `new` so we'll just return a
+// we need Deno.ChildProcress.status to be mutable
+type Mutable = {
+ -readonly [Key in keyof Type]: Type[Key];
+}
+
+// the Deno.Process object cannot be created externally with `new` so we'll just return a
// ProcessLike object
-export function newMockProcess(statusFunction?: () => Promise): Deno.Process {
- const status = statusFunction ?? (() => Promise.resolve({success: true, code: 0}))
+export function newMockProcess(status?: () => Promise): Deno.Command {
return {
- status,
- output: () => Promise.resolve(""),
- stderrOutput: () => Promise.resolve(""),
- close: () => {
- },
- } as unknown as Deno.Process
+ output: function(): Promise { throw new Error("UNIMPLEMENTED") },
+ outputSync(): Deno.CommandOutput { throw new Error("UNIMPLEMENTED") },
+ spawn: () => ({
+ pid: 10,
+ stdin: new WritableStream(),
+ stdout: new ReadableStream(),
+ stderr: new ReadableStream(),
+ status: status ? status() : Promise.resolve({ success: true, code: 0, signal: null }),
+ output: () => Promise.resolve({ stdout: new Uint8Array(), stderr: new Uint8Array(), success: false, code: 1, signal: null }),
+ kill: _ => {},
+ ref: () => {},
+ unref: () => {}
+ })
+ }
}