< Summary - Envilder IaC (CDK)

Information
Class: src/iac/bin/main.ts
Assembly: Default
File(s): src/iac/bin/main.ts
Tag: 151_24479375065
Line coverage
78%
Covered lines: 61
Uncovered lines: 17
Coverable lines: 78
Total lines: 270
Line coverage: 78.2%
Branch coverage
62%
Covered branches: 36
Total branches: 58
Branch coverage: 62%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

File(s)

src/iac/bin/main.ts

#LineLine coverage
 1#!/usr/bin/env node
 2import path from "node:path";
 3import type { Environment, Stack } from "aws-cdk-lib";
 4/**
 5 * CDK Infrastructure Deployment Entry Point
 6 */
 7import { App } from "aws-cdk-lib";
 8import { AppEnvironment } from "../lib/core/types";
 9import { StaticWebsiteStack } from "../lib/stacks/staticWebsiteStack";
 10
 11// ============================================================================
 12// Types
 13// ============================================================================
 14
 15interface StaticWebsiteConfig {
 16  name: string;
 17  projectPath: string;
 18  subdomain?: string;
 19}
 20
 21interface DeploymentConfig {
 22  repoName: string;
 23  branch: string;
 24  environment: AppEnvironment;
 25  domain: {
 26    name: string;
 27    certificateId: string;
 28    hostedZoneId: string;
 29  };
 30  stacks: {
 31    frontend: {
 32      staticWebsites: readonly StaticWebsiteConfig[];
 33    };
 34  };
 35  rootPath?: string;
 36}
 37
 38// ============================================================================
 39// Configuration
 40// ============================================================================
 41
 142const config: DeploymentConfig = {
 43  repoName: "envilder",
 44  branch: "main",
 45  environment: AppEnvironment.Production,
 46  domain: {
 47    name: "envilder.com",
 48    certificateId: "e04983fe-1561-4ebe-9166-83f77789964a",
 49    hostedZoneId: "Z0718467FEEOZ35UNCTO",
 50  },
 51  stacks: {
 52    frontend: {
 53      staticWebsites: [
 54        {
 55          name: "Website",
 56          projectPath: "envilder/src/website/dist",
 57        },
 58      ],
 59    },
 60  },
 61};
 62
 63// ============================================================================
 64// Utils
 65// ============================================================================
 66
 67function getRootPath(rootPath?: string): string {
 268  return rootPath ?? path.join(process.cwd(), "../../../");
 69}
 70
 71function resolveFullPath(rootPath: string, relativePath: string): string {
 072  return path.join(rootPath, relativePath);
 73}
 74
 75function logInfo(message: string): void {
 176  process.stderr.write(`${message}\x1b[E\n`);
 77}
 78
 79function logError(error: Error): void {
 180  process.stderr.write(`\x1b[31m❌ Error: ${error.message}\x1b[0m\x1b[E\n`);
 181  if (error.stack) {
 182    process.stderr.write(`${error.stack}\x1b[E\n`);
 83  }
 84}
 85
 86function logTable(
 87  entries: ReadonlyArray<{ label: string; value: string }>,
 88): void {
 189  const MAX_VALUE_WIDTH = 40;
 190  const truncate = (s: string) =>
 691    s.length > MAX_VALUE_WIDTH ? `…${s.slice(-(MAX_VALUE_WIDTH - 1))}` : s;
 92
 693  const rows = entries.map(({ label, value }) => ({
 94    label,
 95    value: truncate(value),
 96  }));
 97
 698  const maxLabel = Math.max(...rows.map((e) => e.label.length));
 699  const maxValue = Math.max(...rows.map((e) => e.value.length));
 1100  const header = " 📁 Deployment Info ";
 1101  const innerWidth = maxLabel + maxValue + 4;
 1102  const padding = Math.max(0, innerWidth - header.length);
 103
 1104  const nl = "\x1b[E\n";
 105
 1106  process.stderr.write(nl);
 1107  process.stderr.write(`╭─${header}${"─".repeat(padding)}╮${nl}`);
 1108  for (const { label, value } of rows) {
 6109    process.stderr.write(
 110      `│ ${label.padEnd(maxLabel)} │ ${value.padEnd(maxValue)} │${nl}`,
 111    );
 112  }
 1113  process.stderr.write(
 114    `╰${"─".repeat(maxLabel + 2)}┴${"─".repeat(maxValue + 2)}╯${nl}`,
 115  );
 1116  process.stderr.write(nl);
 117}
 118
 119export function validateConfig(config: DeploymentConfig): void {
 4120  const errors: string[] = [];
 121
 4122  if (!config.repoName || config.repoName.trim() === "") {
 2123    errors.push("repoName is required and cannot be empty");
 124  }
 125
 4126  if (!config.branch || config.branch.trim() === "") {
 1127    errors.push("branch is required and cannot be empty");
 128  }
 129
 4130  if (config.environment === undefined || config.environment === null) {
 0131    errors.push("environment is required and cannot be empty");
 132  }
 133
 4134  if (!config.domain) {
 0135    errors.push("domain configuration is required");
 136  } else {
 4137    if (!config.domain.name || config.domain.name.trim() === "") {
 0138      errors.push("domain.name is required and cannot be empty");
 139    }
 4140    if (
 141      !config.domain.certificateId ||
 142      config.domain.certificateId.trim() === ""
 143    ) {
 0144      errors.push("domain.certificateId is required and cannot be empty");
 145    }
 4146    if (
 147      !config.domain.hostedZoneId ||
 148      config.domain.hostedZoneId.trim() === ""
 149    ) {
 0150      errors.push("domain.hostedZoneId is required and cannot be empty");
 151    }
 152  }
 153
 4154  if (!config.stacks) {
 0155    errors.push("stacks configuration is required");
 156  } else {
 4157    if (!config.stacks.frontend) {
 0158      errors.push("stacks.frontend is required");
 159    } else {
 4160      const { staticWebsites } = config.stacks.frontend;
 4161      if (staticWebsites) {
 4162        for (const [index, website] of staticWebsites.entries()) {
 0163          if (!website.name || website.name.trim() === "") {
 0164            errors.push(`frontend.staticWebsites[${index}].name is required`);
 165          }
 0166          if (!website.projectPath || website.projectPath.trim() === "") {
 0167            errors.push(
 168              `frontend.staticWebsites[${index}].projectPath is required`,
 169            );
 170          }
 171        }
 172      }
 173    }
 174  }
 175
 4176  if (errors.length > 0) {
 2177    throw new Error(
 178      `Configuration validation failed with ${errors.length} error(s):\n${errors.join("\n")}`,
 179    );
 180  }
 181}
 182
 183// ============================================================================
 184// Deployment
 185// ============================================================================
 186
 187export function deploy(configOverride?: DeploymentConfig): Stack[] {
 2188  const effectiveConfig = configOverride ?? config;
 2189  const rootPath = getRootPath(effectiveConfig.rootPath);
 190
 2191  try {
 2192    validateConfig(effectiveConfig);
 193
 194    // Log deployment info
 2195    const entries: Array<{ label: string; value: string }> = [
 196      { label: "Repository", value: effectiveConfig.repoName },
 197      { label: "Branch", value: effectiveConfig.branch },
 198      { label: "Environment", value: String(effectiveConfig.environment) },
 199    ];
 200
 2201    if (process.env.CDK_DEFAULT_REGION) {
 1202      entries.push({ label: "Region", value: process.env.CDK_DEFAULT_REGION });
 203    }
 1204    if (process.env.CDK_DEFAULT_ACCOUNT) {
 1205      entries.push({
 206        label: "Account",
 207        value: `***${process.env.CDK_DEFAULT_ACCOUNT.slice(-4)}`,
 208      });
 209    }
 210
 1211    entries.push({ label: "Root Path", value: rootPath });
 212
 1213    for (const ws of effectiveConfig.stacks.frontend.staticWebsites) {
 0214      entries.push({
 215        label: ws.name,
 216        value: resolveFullPath(rootPath, ws.projectPath),
 217      });
 218    }
 219
 1220    logTable(entries);
 221
 1222    logInfo("🎯 Requested stacks:");
 223
 1224    const app = new App();
 1225    const envFromCli: Environment = {
 226      account: process.env.CDK_DEFAULT_ACCOUNT,
 227      region: process.env.CDK_DEFAULT_REGION,
 228    };
 229
 1230    const stacks: Stack[] = [];
 231
 1232    for (const websiteConfig of effectiveConfig.stacks.frontend
 233      .staticWebsites) {
 0234      const distFolderPath = resolveFullPath(
 235        rootPath,
 236        websiteConfig.projectPath,
 237      );
 238
 0239      const stack = new StaticWebsiteStack(app, {
 240        env: envFromCli,
 241        name: websiteConfig.name,
 242        domains: [
 243          {
 244            subdomain: websiteConfig.subdomain,
 245            domainName: effectiveConfig.domain.name,
 246            certificateId: effectiveConfig.domain.certificateId,
 247            hostedZoneId: effectiveConfig.domain.hostedZoneId,
 248          },
 249        ],
 250        distFolderPath,
 251        envName: effectiveConfig.environment,
 252        githubRepo: effectiveConfig.repoName,
 253        stackName: `${effectiveConfig.repoName}-${websiteConfig.name}`,
 254      });
 255
 0256      stacks.push(stack);
 257    }
 258
 1259    return stacks;
 260  } catch (error) {
 1261    if (error instanceof Error) {
 1262      logError(error);
 263    }
 1264    throw error;
 265  }
 266}
 267
 1268if (!process.env.VITEST) {
 0269  deploy();
 270}