Scripting with VSCode Tasks and Typescript
Tags: typescript, node, vscode, tutorial
Published: 2022-11-16 18:01:55

Cover photo by <a href="https://unsplash.com/@markuswinkler?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Markus Winkler</a> on <a href="https://unsplash.com/s/photos/script?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>

This article will attempt to demonstrate the minimum setup required to run Typescript scripts through Node using VSCode Tasks. We're going to create from scratch a small project that allows someone to:
- Write some Typescript
- Run that Typescript without converting it to Javascript first
- Run the entire process using VSCode tasks to capture user arguments without using the command line.

The article assumes a passing familiarity with [Visual Studio Code](https://code.visualstudio.com/), [Typescript](https://www.typescriptlang.org/), and [Node.js](https://nodejs.org/en/), but I've tried to include links wherever someone might want additional context. 

**Important Note**: This article is meant to be more of a how-to than a why-to, but I felt like it was worth talking about the _reason_ behind scripting in the general, and Typescript in particular. If you'd rather jump straight to the implementation though, you can [head straight there](#implementing-our-scripts).

## Why script in Typescript? Why script at all?

![Medical professional asking why](https://media.giphy.com/media/1M9fmo1WAFVK0/giphy.gif)

Recently, I've been taking a hard look at my work patterns, and making note of opportunities for improvement. What I've discovered are a large amount of repetitive tasks that are made _unnecessarily inconsistent and haphazard_ because I do them from scratch whenever I need them, with little preparation.

I don't think all tasks require automation, and there's a real measure of "value-gained-per-time-lost" that should always be considered, but at least for me personally, it feels like a space where I can make meaningful changes to great benefit.

[Scripting](https://en.wikipedia.org/wiki/Scripting_language) can be a great way to reduce cognitive overhead and save time by automating away actions that are:
- Repetitive, meaning the same actions take place with sufficient frequency and duration that there's a significant chunk of your time repeating the same actions.
- Tediously Mechanical, meaning actions that require specificity and precision, but not analysis (long sequences of commands, file creation and naming, migration of text, etc)

Often we associate scripts with the built-in environments that interface with our operating systems, like [bash](https://www.gnu.org/software/bash/manual/html_node/What-is-Bash_003f.html) or [Powershell](https://learn.microsoft.com/en-us/powershell/scripting/overview?view=powershell-7.3), but we can use any language for scripting provided it has some mechanism (like a [runtime environment](https://en.wikipedia.org/wiki/Runtime_system) for executing instructions on a server/OS.

Frequently in my experience, scripts have been described and treated as short, throwaway code, or tedious config work, devoid of the care and rigour we apply to application or operations code.

**But if something is important enough to commit to code, it's probably important enough to try to write correctly.** The paradigms of readability, testability, and extensibility are just as relevant for short, targeted actions as long-standing operations. Maybe even more so?

### Enter Typescript

Anyone who knows me knows I have a deep fondness for [Type Driven Development](https://blog.ploeh.dk/2015/08/10/type-driven-development/) and a particular fascination with [Typescript's Type system](https://www.learningtypescript.com/articles/extreme-explorations-of-typescripts-type-system#:~:text=TypeScript%27s%20type%20system%20is%20Turing,variables%2C%20functions%2C%20and%20recursion.). Many folks do not see the value in static type systems and I don't see the value in refuting those opinions; I can only attempt to express my own satisfaction through the work I do. I try as much as possible to write code with the support of a strong type system whenever I am able to, and for me, the benefits are real and worthwhile. So this is for anyone who might be interested in the same.

Also, just as a personal note, _I love writing scripts._ I love writing short, powerful, durable piece of code, that do a thing, well. I like the completeness of small actions, done well. Scripts make me _happy_.

Manually debugging scripts though, does not make me happy. It does not spark joy. There is a wailing and gnashing of teeth. So I want every possible safeguard to ensure my scripts work as well as they can the first time around. I'm not under the delusion that I can prevent all errors before I run the code. I'd just like to prevent _some_ of them.

##Implementing Our Scripts

![Man excitedly saying "It's go time!"](https://media.giphy.com/media/f6z5TkrTIBZILYOd1t/giphy.gif)

We're going to split this into two major parts; firstly, how to run typescript code in node without prior compilation to js, and then how to configure that process using a Visual Studio Code Tasks features.

First step is ensuring we have both Node and Visual Studio Code on your machine. I'm going to create a new project folder and open it up in VSCode. We won't need VSCode till we implement tasks though, so working in a File Explorer or Terminal is also fine.

In our project folder, we're going to create our first file, `createTemplate.ts` that contains the code we want to execute.

```typescript
import { writeFile } from 'fs'

const [_, __, cwdInput, dateInput] = process.argv

const rawDay = new Date(dateInput)

const day = new Date(rawDay.getTime() + rawDay.getTimezoneOffset()*60000);

const path = `${cwd}/${day.toLocaleDateString('en-CA')}.md`

const template = 
`
${day.toLocaleDateString('en-CA')} - ${day.toLocaleDateString(undefined, { weekday: 'long' })}

# Gratitude
-
-
-

# Frogs
-
-
-

# Wins
-
-
-
`

console.log(path, template)
// writeFile(path, template, console.log)
```

This script creates a markdown file with some prewritten headers that I tend to use when I write my daily journals. It then sticks that file in the current working directory, with some context of the current date. Currently, we're just opting to log it console first though, while we're getting setup.

As it stands, it currently works fine, but we have two problems.

First, we're getting some rudimentary, and dubious type hints right now. We're being told that the command line arguments `cwdInput` and `dateInput` are strings, but there's actually no guarantee that they're there at all. `process.env` pulls info from the environment the script is run in, and thus is an I/O operation, which is inherently impure. Typescript has the capacity to warn us about this, but isn't currently configured to surface that, so let's give it that capacity.

We can add a `tsconfig.json` file that informs VSCode's typescript language server of how we want it to compile our code, and what guarantees we'd like. 

```json
{
    "compilerOptions": {
        "target": "ES2021",
        "strict": true,
        "noUnusedLocals": true,
        "esModuleInterop": true,
        "module": "CommonJS",
        "moduleResolution": "NodeNext",
        "lib": [
            "ES2021"
        ],
        "noUncheckedIndexedAccess": true,
    }
}
```

We've got a few things in there, but most important is the `strict: true` and `noUncheckedIndexedAccess: true`, configuration. This makes the compiler much less permissive about unsafe operations and our intellisence now correctly sees that our process values are `string | undefined`.

If you've been following along directly, your project folder should now look like,


![Folder with a single typescript file, a tsconfig file and the example configuration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43p7dq47si8wkuqi9pry.png)

### But how to run it

The second problem we've got with this script right now is of course, we've got nothing to run it! Because Node can't directly run Typescript! 

**Note:** To be clear, there is currently no popular runtime engine for Typescript. Meaning, there is no application that accepts Typescript natively, turns it to an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree), and executes in an environment, without _first, compiling it to javascript_. That's what Typescript _is_ really:
- An expanded syntax and type system that layers over Javascript syntax, providing an powerful set of additional information, context, and guarantees at compile time
- A compiler than converts that to javascript to be consumed by some runtime.
- A language itself for expressing complex shapes and a powerful engine for type manipulation

Typically, we would compile our Typescript to Javascript, and run those javascript files with Node. But in this case, we don't need to ever use those javascript files. And this code isn't going to the browser either. So we have the option of finding solutions that don't need those intermediate javascript files.

### Ts-Node

The one we're going to use is [ts-node](https://typestrong.org/ts-node/). Ts-node has a simple premise; you can pass it typescript files, and it will convert them to JS, and pass them to node for you. It can function also function as a REPL.

To get it, we first add a `package.json` to our directory. A lot of folks think you need a filled out package.json for npm to function effectively. But really you just need the file.

We can then install the necessary packages using the following:

```shell
npm i -D typescript ts-node
```

That will install ts-node and the Typescript compiler locally, as dev-dependencies. There's an argument for having these as just regular dependencies here, but we're not planning to ship this code anywhere else, so I'm not sure what the best practise is (if anyone reading has opinions, please let me know!)

So we should now see something like this in out `package.json`.

```json
{
  "devDependencies": {
    "ts-node": "^10.9.1",
    "typescript": "^4.8.4"
  }
}
```

Check in time for those following along! Now your project folder should look like this


![Image showing project folder with the addition of a package.json, and node modules](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sje6klwvcmt506u3nv76.png)

We've added a little addition to our `tsconfig` to tell `ts-node` to **not** run type-checking on our code, because the language intellisense in VSCode is already doing that for us. We've also added some controls to ensure Typescript doesn't attempt to output any javascript files.


```json
{
    "compilerOptions": {
        "target": "ES2021",
        "strict": true,
        "noUnusedLocals": true,
        "noEmit": true,
        "esModuleInterop": true,
        "module": "CommonJS",
        "moduleResolution": "NodeNext",
        "lib": [
            "ES2021"
        ],
        "noUncheckedIndexedAccess": true,
        "allowJs": false
    },
    "ts-node": {
        "transpileOnly": true,
    }
}
```

And now we can simply tell ts-node to run our typescript using [npx](https://www.npmjs.com/package/npx)

```
npx ts-node createTemplate.ts /path/to/root/ 2022-11-15
```

And we get the result we want!


![Showing the console logs of our script, where we can see the path and the markdown](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82d42qko7b0bux6tbhn3.png)

Yay, that worked! And honestly, we could leave it there if you don't mind typing commands in the terminal. We don't need vscode for any of this really, we could let ts-node do our type-checking.

But we don't *want* to do our own command line instructions now do we? We want to AUTOMATE. And particularly, I want VSCode to do the work for me!

## Part 2 - Tasks

![Manager saying to office worker, "just try to complete the tasks as satisfactorily as you can"](https://media.giphy.com/media/CCKYPqiFptOLGfjGms/giphy.gif)

To start with, I'm going to pull a line of text straight from the [VSCode documentation](https://code.visualstudio.com/docs/editor/tasks): 

"Tasks in VS Code can be configured to run scripts and start processes so that many of these existing tools can be used from within VS Code without having to enter a command line or write new code."

Great, that looks like exactly what we want.

"Workspace or folder specific tasks are configured from the tasks.json file in the .vscode folder for a workspace."

Say less, fam.

Jokes aside, this is exactly what we're going to do next, making a `.vscode` folder in our project and adding a `tasks.json` file.

![Image showing updated project folders with a .vscode folder containing a tasks.json file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ux3hj3cwwy0u977z4ljk.png)

The [tasks API](https://code.visualstudio.com/docs/editor/tasks#_examples-of-tasks-in-action) is pretty robust, coming with a suite of built-in variables, integrations for common operations like build-time and run-time actions, and pre-existing hooks and plug-ins, but for our needs we just need a small custom task ourselves.

Here is the json that we will add to out tasks.json file to tell VSCode how to execute our little ts-node script.

```json
{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "shell",
            "label": "CreateDay",
            "command": "npx",
            "args": [
                "ts-node",
                "./createBujoTemplate.ts",
                "${cwd}",
                "${input:date}"
            ]
        }
    ],
    "inputs": [
        {
            "type": "promptString",
            "id": "date",
            "description": "Date for your new template. Preferred format yyyy-mm-dd",
            "default": ""
        }
    ]
}
```

I'm going to try my best to step through the various parts of this json.
- First, we declare the task version we're working with. At the time of writing, 2.0.0 is the default. 
- Then we make a `tasks` field; a list of the all the tasks we would like VSCode to use. We currently have the one.
- In that task, we specify the task type (shell or process), the tasks _unique_ label (it will yell if you give two tasks the same label), and a way to specify the main command, and any arguments it needs. 
- Note "${cwd}" as our third argument. CWD (which stands for current working directory) is a built in variable VSCode makes available for use in writing tasks.
- Below that, we define another variable `"${input:date}"`, which makes use of VSCode's ability to define custom input variables that need to be provided by the user.
- After our task list, we define our list of inputs, where we specify an input we call date.
- The type `promptString` is a VSCode definition that just means the user needs to provide some string of text. There's options for providing a list or dropdown or even other commands. We're just using a string.
- Finally, we add a description and a default value for our input.

## Putting it all together

And with that all in place, we should be be hit `Cmd-Shift-P` (or `Cntl-Shift-P` on Windows Systems) to bring up the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) and find the `Run Task` command.

That will open up a dropdown showing available tasks where we find our newly added custom task `CreateTemplate`.

Selecting _that_ takes us to a prompt asking if we need any specific debugging environment while running the script; we can just click `continue without scanning output` for now.

Finally, we get a prompt asking for that input we defined earlier!

With all that in place, we can watch as the terminal details the running of our code.

And if we check the file system...


![Showing our full vscode setup, and the file that was just created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t7f0dmwywzjxw42t9trv.png)

We did it!

## To Sum Up

So just to recap, we originally had a very manual process, that required us to create and populate a file with the same information every day. And now we've reduced all those to a few keystrokes! I know this doesn't feel like a lot, in terms of the complexity of the code we automated away, but we've barely scratched the surface of what tasks are capable of here. We could've set this task to run whenever we opened our project, or connected it to a cron job or a Github action.

Not everything should be automated, but it's worth considering what could be automated, and if it's your goal to get this kind of behaviour, hopefully this article let you realise it's not as far away as you might think. Give it a try.

Build your own tools sometimes, not because you have to, but because you want to.

![Image of Jeanine from the tv show Abbott Elementary saying "Perseverance, Ingenuity, and Effort"](https://media.giphy.com/media/v4Nmigh6iwJwK5yfrT/giphy.gif)

Thank you for your time.