Deno.run cmd arguments

Last updated:Β 
const p = Deno.run({
  cmd: ["echo", "I love πŸ‰"],
});

// await its completion
await p.status();

Which works just as expected, and echo'es (prints) the provided text to the terminal.
Though when I wanted my CLI to help me with more complex tasks I run into unexpected behaviour.

I'm using Fly.io to deploy my site using a custom Dockerfile and I wanted to include the current git hash into my website so I thought I'd pass it to the Dockerfile via a --build-arg πŸ€“

Though confusingly the following makes the flyctl CLI throw: Error unknown flag: --build-arg GIT_REVISION

const p = Deno.run({
  cmd: [
    "flyctl",
    "deploy",
    "--remote-only",
    "--no-cache",
    `--build-arg GIT_REVISION=${await getGitRevision()}`, // won't work
  ],
});

Which confused me as I actually expected I could just pass kinda any command even in a single string, though that's not the case as for example the following doesn't work either, or at least not as expected

const p = Deno.run({
  cmd: ["echo", "I love πŸ‰", "&&", "echo", "and 🍍"],
});

this prints

I love πŸ‰ && echo and 🍍

instead of

I love πŸ‰
and 🍍

Checking the docs

Spawns new subprocess. RunOptions must contain at a minimum the opt.cmd, an array of program arguments, the first of which is the binary.

"[..] the first of which is the binary.", so you can only spawn one command, which when thinking about it probably makes sense and things simpler and as we're already in-code it's no trouble running Deno.run as often as we like right πŸ₯³

How to properly pass argument values

So the solution to my initial problem is to put the value in it's own array item like in the following example

const p = Deno.run({
  cmd: [
    "flyctl",
    "deploy",
    "--remote-only",
    "--no-cache",
    "--build-arg",
    `GIT_REVISION=${await getGitRevision()}`,
  ],
});

Get && commands working in Deno

function run(cmd: string) {
  let cmds: string[];
  if (cmd.includes("&&")) {
    cmds = cmd.split("&&").map((c) => c.trim());
  } else {
    cmds = [cmd];
  }
  const processes: Deno.Process[] = [];
  for (const index in cmds) {
    const command = cmds[index];
    const p = Deno.run({
      cmd: command.split(" "),
      stdout: "piped",
    });
    processes.push(p);
  }
  return processes;
}

Which you can use like so

const [p1, p2] = run("echo 'command 1' && echo 'command 2'");
await p1.status();
await p2.status();

// and to get the commands outputs
const decoder = new TextDecoder();
console.log(decoder.decode(await p1.output()).replace(/\n$/, ""));
console.log(decoder.decode(await p2.output()).replace(/\n$/, ""));

You could chain more of course or even await Promise.all and because stdout is set to piped can read the commands outputs, or remove stdout from Deno.run to have it print to your terminal without the need for console.log.

πŸ¦•πŸ˜ƒ

Bonus: Get the current git revision hash

const getGitRevision = async () => {
  const p = await Deno.run({
    cmd: ["git", "rev-parse", "--short", "HEAD"],
    stdout: "piped",
  });
  const decoder = new TextDecoder();
  return decoder.decode(await p.output()).replace(/\n$/, "");
};

Read more about Deno.run in the manual and docs

Can Rau
Can Rau

Doing web-development since around 2000, building my digital garden with a mix of back-to-the-roots-use-the-platform and modern edge-rendered-client-side-magic tech πŸ“»πŸš€

Living and working in the tropical rainforest of PerΓΊ πŸ’