Running stuff on Deno
Tags: deno, javascript, node, typescript
Published: 2021-05-17 12:45:31

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](https://youtu.be/BEQ7D97XzsA).

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.

```js
const message = "hello world".split("").reverse().join("");
```

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

```js
Deno.writeTextFile(`${Deno.cwd()}/scratch`, message); 
```

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

```js
Deno.readTextFile(`${Deno.cwd()}/scratch`).then(message => console.log(message));
```

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.

```js
await Deno.readTextFile(`${Deno.cwd()}/scratch`);
```

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.

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

await getUser();
```

The REPL is full of [shortcuts](https://deno.land/manual@v1.10.1/tools/repl). Here's a few that I enjoy!

`ctrl+R`, up/down arrows to search for something.
```
> "he
const message = "hello world".split("").reverse().join("");
```

`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 ]
```

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

```
[ 42, 24 12 ]
> SyntaxError: Unexpected number
_error 
```

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

```js
Deno.consoleSize(Deno.stdout.rid);
```

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

```
echo "2 + 2" | deno --
```

You can also add the `--unstable` flag.

```
echo "Deno.consoleSize(Deno.stdout.rid);" | deno --unstable --
```

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 --
```

## Running with permissions 

All the code for this demo is available on [Github](https://github.com/sebastienfilion/i-y/tree/main/deno/03).

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.

```js
// movies.js
export function getMovieByTitle (APIKey, title) {
  
  return fetch(`http://www.omdbapi.com/?apikey=65ea1e8b&t=${encodeURIComponent(title)}`)
    .then(response => response.json());
}
```

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

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

getMovieByTitle(Deno.env.get("OMDB_API_KEY"), "Tenet")
  .then(movie => console.log(movie));
```

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

```shell
OMDB_API_KEY=████████ deno run --allow-net="www.omdbapi.com" --allow-env="OMDB_API_KEY" scratch.js
```

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. 

```js
// 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"));
```

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

```shell
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"
```

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.

```shell
OMDB_API_KEY=████████  deno run --prompt cli.js "Tron"
```

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

```js
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 }));
```

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"`...

```js
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" };
```

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.

```js
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}"`);
}
```

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`.

```shell
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"
```

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`.

```shell
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
```

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.

```shell
const process = Deno.run({ cmd: [ "ls", "." ] });
await process.status();
```

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.

```shell
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
```

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!