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

Image of Docusign

πŸ› οΈ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

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

Image of Docusign

πŸ› οΈ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

πŸ‘‹ Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay