DEV Community

Chris Cook
Chris Cook

Posted on

46 4 4 4 4

TypeScript CLI: Automate Build and Deploy Scripts

I would like to follow up on my previous post about TypeScript CLIs. Here's how I want to proceed: I plan to implement the build command to build a Vite app and the deploy command to deploy the app to Amazon S3 and AWS CloudFront.

We will use Listr2 as a task runner to define the steps required to build and deploy the app. We will use execa to run CLI commands for Vite and AWS. Since we're running TypeScript code, we could use the programmatic APIs instead of CLI commands, but let's keep it simple!

#!/usr/bin/env -S pnpm tsx
import chalk from 'chalk';
import { Command } from 'commander';
import { Listr } from 'listr2';
import { $ } from 'execa';

interface Ctx {
  command: 'build' | 'deploy';
}

const tasks = new Listr<Ctx>(
  [
    /**
     * Build tasks
     */
    {
      enabled: (ctx) => ctx.command === 'build' || ctx.command === 'deploy',
      title: 'Build',
      task: (ctx, task): Listr =>
        task.newListr<Ctx>([
          /**
           * Runs `vite build`.
           */
          {
            title: `Run ${chalk.magenta('vite build')}`,
            task: async (ctx, task): Promise<void> => {
              const cmd = $({ all: true })`vite build`;
              cmd.all.pipe(task.stdout());

              await cmd;

              task.output = `Build completed: ${chalk.dim('./dist')}`;
            },
            rendererOptions: { persistentOutput: true },
          },
        ]),
    },
    /**
     * Deploy tasks
     */
    {
      enabled: (ctx) => ctx.command === 'deploy',
      title: 'Deploy',
      task: (ctx, task): Listr =>
        task.newListr<Ctx>([
          /**
           * Runs `aws s3 sync`.
           */
          {
            title: `Run ${chalk.magenta('aws s3 sync')}`,
            task: async (ctx, task): Promise<void> => {
              const build = './dist';
              const bucket = 's3://my-bucket';

              const cmd = $({ all: true })`aws s3 sync ${build} ${bucket} --delete`;
              cmd.all.pipe(task.stdout());

              await cmd;

              task.output = `S3 sync completed: ${chalk.dim(bucket)}`;
            },
            rendererOptions: { persistentOutput: true },
          },
          /**
           * Runs `aws cloudfront create-invalidation`.
           */
          {
            title: `Run ${chalk.magenta('aws cloudfront create-invalidation')}`,
            task: async (ctx, task): Promise<void> => {
              const distributionId = 'E1234567890ABC';

              const cmd = $({ all: true })`aws cloudfront create-invalidation --distribution-id ${distributionId} --paths /* --no-cli-pager`;
              cmd.all.pipe(task.stdout());

              await cmd;

              task.output = `CloudFront invalidation completed: ${chalk.dim(distributionId)}`;
            },
            rendererOptions: { persistentOutput: true },
          },
        ]),
    },
  ],
  {
    rendererOptions: {
      collapseSubtasks: false,
    },
  },
);

const program = new Command()
  .name('monorepo')
  .description('CLI for Monorepo')
  .version('1.0.0');

program
  .command('build')
  .description('Build the monorepo')
  .action(async () => {
    await tasks.run({ command: 'build' });
  });

program
  .command('deploy')
  .description('Deploy the monorepo')
  .action(async () => {
    await tasks.run({ command: 'deploy' });
  });

await program.parseAsync(process.argv);
Enter fullscreen mode Exit fullscreen mode

The tasks are split into build tasks and deploy tasks. Since deploying requires a build step, we use the enabled property to conditionally enable the tasks based on the CLI command build or deploy. Each task executes the corresponding CLI command and pipes its output to the console.

Save this script as cli.ts and run it with pnpm tsx cli:

asciicast

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (6)

Collapse
 
mcubico profile image
Mauricio Montoya Medrano

great, thanks for sharing. Could you share how to do the same using ftp?

Collapse
 
zirkelc profile image
Chris Cook

Do you mean uploading the build via FTP to a server?

Collapse
 
mcubico profile image
Mauricio Montoya Medrano

Hi Chis, yes I mean that.

Thank you for your time

Collapse
 
bbauer82 profile image
bbauer82

thank you very much for your efforts

Collapse
 
prawee profile image
Prawee Wongsa

thank you so much

Collapse
 
firstflask profile image
Sarama'd

Was helpfull thank you.

Cloudinary image

Video API: manage, encode, and optimize for any device, channel or network condition. Deliver branded video experiences in minutes and get deep engagement insights.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay