Running stuff on Deno

sebastienfilion

Sebastien Filion

Posted on May 17, 2021

Running stuff on Deno

Oh. Hey there!

I'm happy you came back for this third post. The subject today is: "Running stuff on Deno".

This post is a transcript of a Youtube video I made.

I brushed over this on the previous post because I wanted to cover it in detail.
One of the thing that I truly love about Deno is that it is chuck full of tools -- out-of-the-box; with all
of that, I can be productive in seconds without any setup.

The REPL

The first tool that I think we should explore is the REPL. It is a terminal-based interactive runtime.
It is especially useful when you need to test bits of code without having to create a file or bootstrap
a project.

To bring up the REPL, all you need to do is to execute the deno command and, you're ready to go.
The first thing you'll see, is the current version and instruction on how to exit.
On an empty line just hit ctrl+d or type close().
Here we can type-in any valid JavaScript expression.

const message = "hello world".split("").reverse().join("");
Enter fullscreen mode Exit fullscreen mode

Using the tab key, you get autocompletion. Deno.writeTe [tab] and poof:

Deno.writeTextFile(`${Deno.cwd()}/scratch`, message); 
Enter fullscreen mode Exit fullscreen mode

Here we can just read the file back to confirm that it was written properly.

Deno.readTextFile(`${Deno.cwd()}/scratch`).then(message => console.log(message));
Enter fullscreen mode Exit fullscreen mode

Since all the IO method returns a Promise, this is a perfect segway into "top-level await" -- For this
example, I will assume that you are familiar with the async/await. Deno allows the use of await within
the global scope although it is usually reserved for specifically flagged functions. We can leverage this
feature when using the REPL.

await Deno.readTextFile(`${Deno.cwd()}/scratch`);
Enter fullscreen mode Exit fullscreen mode

In the REPL usual import syntax isn't available, we can leverage top-level await and the import
function available in the runtime to import modules.

const { getUser } = await import("https://raw.githubusercontent.com/sebastienfilion/i-y/main/02/users.js");

await getUser();
Enter fullscreen mode Exit fullscreen mode

The REPL is full of shortcuts. Here's a few that I enjoy!

ctrl+R, up/down arrows to search for something.

> "he
const message = "hello world".split("").reverse().join("");
Enter fullscreen mode Exit fullscreen mode

CTRL+U, CTRL+Y to cut or paste a line, useful when you need to remove a line quickly.

There's also a special character _ that always refers to the last evaluated value.

const multiply = (x) => x * 2;

[ 42, 24, 12 ].map(multiply);

_.map(multiply);

> [ 168, 96, 48 ]
Enter fullscreen mode Exit fullscreen mode

In the same vein, _error refers to the last error that was thrown.

[ 42, 24 12 ]
> SyntaxError: Unexpected number
_error 
Enter fullscreen mode Exit fullscreen mode

It's important to note that the REPL might be executed with the --unstable flag, if you need access unstable APIs.

Deno.consoleSize(Deno.stdout.rid);
Enter fullscreen mode Exit fullscreen mode

Finally, you can pipe files or expression into the REPL using the --.

echo "2 + 2" | deno --
Enter fullscreen mode Exit fullscreen mode

You can also add the --unstable flag.

echo "Deno.consoleSize(Deno.stdout.rid);" | deno --unstable --
Enter fullscreen mode Exit fullscreen mode

Be careful though cause running code like this doesn't execute in a sandbox. So you may be giving open access to your computer to some stranger... This is a perfect segue into permissions...

echo "await Deno.readTextFile(\"./scratch\")" | deno --
Enter fullscreen mode Exit fullscreen mode

Running with permissions

All the code for this demo is available on Github.

So that's for the REPL. In my experience, it is one of the most complete and friendlier REPL out there.
Now let's talk about the run subcommand in detail. As I mentioned earlier, I brushed over it during the
previous videos because I wanted to cover it in detail. I also want to explore the permission API as
it is one of Deno main selling-point.

Take this code as an example. It uses the fetch function to access a given movie's data over HTTP.

// movies.js
export function getMovieByTitle (APIKey, title) {

  return fetch(`http://www.omdbapi.com/?apikey=65ea1e8b&t=${encodeURIComponent(title)}`)
    .then(response => response.json());
}
Enter fullscreen mode Exit fullscreen mode

To run this code, we'll import it into a file and pass the OMDB_API_KEY environment variable.

// scratch.js
import { getMovieByTitle } from "./movies.js";

getMovieByTitle(Deno.env.get("OMDB_API_KEY"), "Tenet")
  .then(movie => console.log(movie));
Enter fullscreen mode Exit fullscreen mode

So, now we use the --allow-net and --allow-env flags to grant the right permissions when running the file.

OMDB_API_KEY=████████ deno run --allow-net="www.omdbapi.com" --allow-env="OMDB_API_KEY" scratch.js
Enter fullscreen mode Exit fullscreen mode

Ok, so now let's say that we want to write to a file the title and the description of our favourite movies; we can create a CLI that will take the title of the movie, fetch it and write it to the File System.

// cli.js
const [ title ] = Deno.args;

getMovieByTitle(Deno.env.get("OMDB_API_KEY"), title)
  .then(
    movie => Deno.writeTextFile(
      `${Deno.cwd()}/movies`,
      `${movie.Title}: ${movie.Plot}\r\n`,
      { append: true }
    )
  )
  .then(() => console.log("...done"));
Enter fullscreen mode Exit fullscreen mode

To run this file, we'll need to grand "write" permission with --allow-write.

OMDB_API_KEY=████████ deno run --allow-net="www.omdbapi.com" --allow-env="OMDB_API_KEY" --allow-read=$(pwd) --allow-write=$(pwd) cli.js "The Imitation Game"
Enter fullscreen mode Exit fullscreen mode

Another way to grant permission is with --prompt. This option will prompt the user for every permission not granted already when the runtime reaches the code.

OMDB_API_KEY=████████  deno run --prompt cli.js "Tron"
Enter fullscreen mode Exit fullscreen mode

From this, I just want to take a quick detour to explore the runtime's Permission API.

console.log(await Deno.permissions.query({ name: "write", path: import.meta.url }));

await Deno.permissions.request({ name: "write", path: import.meta.url })

console.log(await Deno.permissions.query({ name: "write", path: import.meta.url }));

await Deno.permissions.revoke({ name: "write", path: import.meta.url })

console.log(await Deno.permissions.query({ name: "write", path: import.meta.url }));
Enter fullscreen mode Exit fullscreen mode

The object part is called a "permission descriptor" -- they all have a "name" property but, the other property might be
different.
For example... to read and write it's "path"...

const readDescriptor = { name: "read", path: import.meta.url };
const writeDescriptor = { name: "write", path: import.meta.url };

const environmentDescriptor = { name: "env", variable: "OMDB_API_KEY" };
const netDescriptor = { name: "net", command: "www.omdbapi.com" };

const runDescriptor = { name: "run", command: "git" };
Enter fullscreen mode Exit fullscreen mode

Okay, we're back on track now. Now that we can add movies to our file, I think it would be useful for our tool to read them back to us. I wrote a small utility to display the file while creating an opportunity to use an unstable API.

import { getMovieByTitle } from "./movies.js";
import { prepareForViewport } from "https://raw.githubusercontent.com/sebastienfilion/i-y/main/deno/03/utilities.js";

function displayMovies (data) {
  const { columns, rows } = Deno.consoleSize(Deno.stdout.rid);

  return Deno.write(
    Deno.stdout.rid,
    prepareForViewport(data, { columns, rows, title: "My movie collection" })
  );
}

if (import.meta.main) {
  const [ action, title ] = Deno.args;

  if (action === "fetch") getMovieByTitle(Deno.env.get("OMDB_API_KEY"), title)
    .then(
      movie => Deno.writeTextFile(
        `${Deno.cwd()}/movies`,
        `${movie.Title}: ${movie.Plot}\r\n`,
        { append: true }
      )
    )
    .then(() => console.log("...done"));
  else if (action === "display") Deno.readFile(`${Deno.cwd()}/movies`)
    .then(displayMovies);
  else console.error(`There are no action "${action}"`);
}
Enter fullscreen mode Exit fullscreen mode

So this time, because we use Deno.consoleSize which is marked as unstable, we need to add the --unstable flag. Also, because we are reading from our movie file, we need to grand read permission with --allow-read.

OMDB_API_KEY=████████ deno run --allow-net="www.omdbapi.com" --allow-env="OMDB_API_KEY" --allow-read=$(pwd) --allow-write=$(pwd) cli.js fetch "WarGames"
Enter fullscreen mode Exit fullscreen mode

If you were to download the code and run it with --watch, you'd be able to play with the options of prepareForViewport.

You can change the title, or the ratio for a number between 0 and 1, the default is 0.8.

OMDB_API_KEY=65ea1e8b deno run --allow-env=OMDB_API_KEY --allow-net="www.omdbapi.com" --allow-read=$(pwd) --allow-write=$(pwd) --unstable --watch cli.js display
Enter fullscreen mode Exit fullscreen mode

Before closing on this chapter, I want to talk about one more permission flag, --allow-run. This flag allows the code
to run a command, for example ls, git, etc...
The command will not be executed in the same sandbox as Deno.
Meaning that a malicious developer could do the following... which would output all the structure of your current
working directory.

const process = Deno.run({ cmd: [ "ls", "." ] });
await process.status();
Enter fullscreen mode Exit fullscreen mode

Giving permission to a process to run any command could be a huge security risk.
Always use --allow-run along with the commands that you know will be used. For example --allow-run=git... to allow a process to use Git on the current working directory.
I will do a full video on the Deno.run API later down the line.

Um, I've avoided using it up until now; there's also a --allow--all flag or -A... To grand all the
permissions...
It's safe to use while you're developing -- but don't be lazy use the appropriate flag when running code you find on the
Internet.

When you'll get bored of always typing the run command with all of it's permissions, you may want to consider simply
creating an executable.

echo "OMDB_API_KEY=65ea1e8b deno run --allow-env=OMDB_API_KEY --allow-net="www.omdbapi.com" --allow-read=$(pwd) --allow-write=$(pwd) --unstable --watch cli.js display" | ilm
chmod +x ilm
./ilm
Enter fullscreen mode Exit fullscreen mode

That was a long one...
In this post we saw how to run stuff with Deno and more importantly how to run stuff safely using the Permission flags.
On the next post, we will resume our Deno-journey and explore all the tools that can help us write better code...
Like the linter, the formatter, the test runner and the documentation generator!

💖 💪 🙅 🚩
sebastienfilion
Sebastien Filion

Posted on May 17, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Running stuff on Deno
deno Running stuff on Deno

May 17, 2021

Deno and modules
deno Deno and modules

May 3, 2021

What is Deno?
deno What is Deno?

June 12, 2020