Turn Your Resume Into an Interactive CLI in 10 minutes using TypeScript
Tags: node, tutorial, resume, typescript
Published: 2022-01-30 03:04:48

There are tons of creative portfolio sites out there, but it takes a lot of effort to build a website that stands out. Instead of building the perfect portfolio site, what if you built an interactive resume CLI? That also sounds difficult, but I'm going to show you how to do it in under 10 minutes using a [CLI generator](https://www.npmjs.com/package/@christianjuth/ts-cli-generator) I created.

We need TypeScript because this library generates the CLI by parsing the parameter types of our CLI functions. TypeScript provides enough context for the library to generate complex terminal interactions with minimal effort from you as the developer.

_Note: This library is an alpha release, so some bugs are expected. There are also currently some limitations so for now keep all your cli code in `index.ts`._

-----

## Setup

We first start by initializing a new CLI project using npx.

```bash
npx @christianjuth/ts-cli-generator
```

Enter whatever you like when prompted for the cli-name, but I recommend some combination of your name and resume. 

```bash
$ npx @christianjuth/ts-cli-generator
✔ Enter a name for your cli … christianjuth-resume
✔ Creating project files
⠧ Installing npm dependencies
```

Select `math-cli` as the starting template.

```
✔ Enter a name for your cli … christianjuth-resume
? Pick a template › - Use arrow-keys. Return to submit.
❯   math-cli
    rss-reader-cli
    stocks-cli
    with-auth
```

This gives us the following files:

```
christianjuth-resume
├── index.ts
├── package.json
├── README.md
└── tsconfig.json
```

If we open `index.ts` we should see something like this:

```typescript
import { call, CLI } from "@christianjuth/ts-cli-generator";

/**
 * Add two numbers
 */
function add(x: number, y: number) {
  console.log(x + y);
}

/**
 * Subtract two numbers
 */
function _subtract(x: number, y: number) {
  return x - y;
}

/**
 * Add then subtract as separate interactioins
 */
async function addSubtract(x: number, y: number) {
  console.log(x + y);
  // call allows you to call a cli function
  // from within another cli function
  console.log(await call(_subtract)());
}

/**
 * Get the length of a string
 */
function lengthOfString(str: string) {
  console.log(str.length);
}

export const cli: CLI = {
  add,
  addSubtract,
  lengthOfString,
  // underscore means function is available but hidden
  _subtract,
};
```

The above `index.ts` generates these commands when we run `npm start`:

```bash
christianjuth-resume CLI 0.0.0

Powered by @christianjuth/ts-cli-generator

Commands:
  add             	Add two numbers
  add-subtract    	Add then subtract as separate interactions
  length-of-string	Get the length of a string

Options:
  -i, --interactive	Run CLI in interactive mode
```

We have also get the following npm scripts:

```bash
npm start <cli-command> # more on this later
npm start # excluding <cli command> gives us usage
npm run dev <cli-command> # same as start but with nodemon
npm run build # build the cli
```

-----

## Customize your resume

We will use the npm `openurl` package so we can have our CLI programmatically open links in the browser. Start by installing `openurl` and it's types.

```bash
npm i openurl
npm i -D @types/openurl
```

Start by adding some social links to your resume. Replace the contents of `index.ts` with the following:

```typescript
import { CLI } from "@christianjuth/ts-cli-generator";
import { open, mailto } from "openurl";

/**
 * Follow me on Twitter 🐦
 */
function twitter() {
  open("<your Twitter url>");
}

/**
 * Connect with my on LinkedIn 👔
 */
function linkedin() {
  open("<your LinkedIn url>");
}

/**
 * See my projects on GitHub 🚀
 */
function github() {
  open("<your GitHub url>");
}

export const cli: CLI = {
  twitter,
  linkedin,
  github,
};
```

If we run our CLI using `npm start` we get the following commands:

```bash
$ npm start

> christianjuth-resume@0.0.0 start
> ts-cli-generator build && node .

✔ Detecting cli functions
✔ Writing cli files
✔ Emitting CLI build
christianjuth-resume CLI 0.0.0

Powered by @christianjuth/ts-cli-generator

Commands:
  twitter 	Follow me on Twitter 🐦
  linkedin	Connect with my on LinkedIn 👔
  github  	See my projects on GitHub 🚀
```

We can open the links by running:

```bash
# npm start <cli-command>
npm start twitter
npm start linkedin
npm start github
```

-----

### Add some projects to our resume

Add the following to `index.ts`

```typescript
// prev functions...

/**
 * Check out some of my projects ✨
 */
 function projects(str: "project1" | "project2") {
  switch (str) {
    case "project1":
      open("https://...");
      break;
    case "project2":
      open("https://...");
  }
}

export const cli: CLI = {
  twitter,
  linkedin,
  github,
  projects
};
```

If we run `npm start projects`, we now get a CLI interface to select what project we want to open.

```bash
$ npm start projects

> christianjuth-resume@0.0.0 start
> ts-cli-generator build && node . "projects"

✔ Detecting cli functions
✔ Writing cli files
✔ Emitting CLI build
? Select praram type for str › - Use arrow-keys. Return to submit.
❯   project1
    project2
```

Select a project and press enter to view the project in the browser.

-----

### Add a contact form

For simplicity, we will create a contact form that opens a mailto link, but you could easily make a POST request instead.

And add the following to `index.ts`:

```typescript
// prev functions...

/**
 * Contact me 📪
 */
function contact(info: {
  subject: string;
  name: string;
  email: string;
  phoneNumber: string;
}) {
  const { subject, name, email, phoneNumber } = info;
  const EMAIL = "your.email@example.com";

  const message = `
    Name: ${name}
    Email: ${email}
    ${phoneNumber ? `Number: ${phoneNumber}` : ""}
  `;

  mailto([EMAIL], {
    subject,
    body: message,
  });
}
```

Notice how our CLI is able to handle objects as parameters (it can even handle nested objects). Arrays aren't supported yet, but coming soon.

Try running `yarn start contact` and see what happens.

### The final product

_The final version of `index.ts` is posted at the bottom if you want to see it for reference._

Install the CLI locally by running:

```bash
npm run build
npm link

# substitute christianjuth-resume with your cli name
christianjuth-resume # print usage
christianjuth-resume <cli-command> # run command
```

## Next steps

You could publish your package to NPM to easily share with others, but for this tutorial we are going to use GitHub because it's simpler.

Before you can share your package with others, you must:
* Create a GitHub repo
* Build your project via `npm run build` before committing
* Commit and push your CLI project to GitHub
* We can now run the CLI directly off of GitHub using `npm exec`

You can check out my repo [here](https://github.com/christianjuth/resume-cli).

_Normally I would use `npx`, but I've had better luck with `npm exec` when working with GitHub repos._

```bash
# substitute github:christianjuth/resume-cli with your GitHub repo username/repo
npm exec github:christianjuth/resume-cli
npm exec github:christianjuth/resume-cli <cli command>
```

-----

## Thanks for reading!

Out final CLI has these commands:

```bash
christianjuth-resume CLI 0.0.0

Powered by @christianjuth/ts-cli-generator

Commands:
  twitter 	Follow me on Twitter 🐦
  linkedin	Connect with my on LinkedIn 👔
  github  	See my projects on GitHub 🚀
  projects	Check out some of my projects ✨
  contact 	Contact me 📪
```

And our final `index.ts` file looks like this:

```typescript
import { call, CLI } from "@christianjuth/ts-cli-generator";
import { open, mailto } from "openurl";

/**
 * Follow me on Twitter 🐦
 */
function twitter() {
  open("<your Twitter url>");
}

/**
 * Connect with my on LinkedIn 👔
 */
function linkedin() {
  open("<your LinkedIn url>");
}

/**
 * See my projects on GitHub 🚀
 */
function github() {
  open("<your GitHub url>");
}

/**
 * Check out some of my projects ✨
 */
function projects(str: "project1" | "project2") {
  switch (str) {
    case "project1":
      open("https://...");
      break;
    case "project2":
      open("https://...");
  }
}

/**
 * Contact me 📪
 */
function contact(info: {
  subject: string;
  name: string;
  email: string;
  phoneNumber: string;
}) {
  const { subject, name, email, phoneNumber } = info;
  const EMAIL = "your.email@example.com";

  const message = `
    Name: ${name}
    Email: ${email}
    ${phoneNumber ? `Number: ${phoneNumber}` : ""}
  `;

  mailto([EMAIL], {
    subject,
    body: message,
  });
}

export const cli: CLI = {
  twitter,
  linkedin,
  github,
  projects,
  contact,
};
```