DEV Community

Chris Cook
Chris Cook

Posted on

50 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

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.

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More