🔥Pulstack:在不到 1 分钟的时间内将您的静态站点部署到 S3 或 GitHub ⚡ Pulstack – 使用 Pulumi 即时部署静态站点

2025-06-08

🔥Pulstack:在不到 1 分钟的时间内将您的静态站点部署到 S3 或 GitHub

⚡ Pulstack – 使用 Pulumi 即时部署静态站点

这是Pulumi 部署和文档挑战赛的提交内容:利用 Pulumi 和 GitHub 发挥创意

我建造了什么

我构建了pulstack ( pulumi+stack ),这是一个 CLI 工具,可让您将静态网站部署到以下位置:

  • ☁️AWS S3 + CloudFront
  • 🌐 GitHub 页面

全部由Pulumi Automation API、GitHub 和 AWS提供支持。🙌

只需一个命令,不到一分钟,你就能从一个静态文件夹切换到一个实时网站🕛。无需编写任何代码,也无需在 GitHub 上点击。100% 自动化 🙂

工作原理

这是一个两步流程,取决于您的目标:

对于 GitHub 页面:

node index.js init --github
# prompts for GitHub token and repo info

node index.js deploy --target github-pages --dir ./public
# creates repo, pushes gh-pages branch, enables github Pages
Enter fullscreen mode Exit fullscreen mode

对于 AWS (S3 + CloudFront):

node index.js init
# prompts for project name, stack name, and AWS region

node index.js deploy --target aws --dir ./public
# provisions an S3 bucket, uploads files, sets up CloudFront distro
Enter fullscreen mode Exit fullscreen mode

它使用 Pulumi 的自动化 API 来管理堆栈、配置、资源和输出,所有这些都在代码中——无需 YAML。

流动

现场演示链接

项目回购

GitHub 徽标 Kiran1689 / pulstack

Pulstack – 使用 Pulumi 进行即时静态站点部署的 CLI 工具

⚡ Pulstack – 使用 Pulumi 即时部署静态站点

pulstack是一款开发者友好的工具,可让您零配置将静态网站部署到 AWS(S3 + CloudFront)或 GitHub Pages。它使用Pulumi底层将基础设施视为代码,因此您的部署完全自动化且受版本控制。

Pulumi徽章 AWS 徽章 GitHub Pages 徽章

🧑‍💻 这是给谁的?

如果您符合以下条件,Pulstack 是完美的选择:

  • 拥有一个静态网站(HTML/CSS/JS 或 React/Vite/Next.js 构建)
  • 想要通过 1 个命令部署到 AWS(S3+CloudFront)或 GitHub Pages
  • 不想编写 YAML、CloudFormation 或 Terraform
  • 类似于带有引导和简单提示的简单 CLI 工作流程

✨ 特点

  • 🚀 使用 CloudFront CDN 将静态站点部署到 AWS S3

  • 🌍 自动创建 Repo 并发布到 GitHub Pages

  • 🔒 使用最佳实践确保 AWS 部署安全(无需公共存储桶!)

  • 💡 简洁的 CLI 提示可指导您完成设置

  • 🧨 完成后,只需一个命令即可销毁整个堆栈

📦先决条件

你…




我的旅程

我开始把它当作一个有趣的周末挑战,探索 Pulumi 自动化 API。最初,我只是想自动化 AWS S3 站点的部署。但后来我想——为什么要止步于此呢?我们也来试试 GitHub Pages 吧。

为什么选择 Pulstack?🤔

当我运行时pulumi new aws-javascript,它生成了默认的 Pulumi YAML 文件和一个基本的 index.js,我必须在其中手动定义 S3 存储桶、策略、CloudFront 分发等。

因此,我决定构建 Pulstack,这是一个零配置 CLI,您只需回答几个提示,它就可以处理堆栈创建、配置、部署,甚至 GitHub repo 设置(如果您要使用 Pages 路线)。

事情是这样的:👇

🛠️ 玩乐前的准备

在深入研究 Pulstack 代码之前,我必须确保所有合适的工具都已到位。如果你之前使用过PulumiAWS,你就会知道这需要一些准备工作——但一旦完成,自动化的可能性就值得你付出这些。

🔧 安装 Pulumi

首先——安装 Pulumi。
下载链接:https://www.pulumi.com/docs/iac/download-install/

这使我可以本地访问 Pulumi 的命令,并且我使用以下命令登录:

pulumi login
Enter fullscreen mode Exit fullscreen mode

🌐 AWS 设置

由于我要部署到 AWS S3 和 CloudFront,因此我需要具有正确权限的 AWS CLI 和凭证。

我创建了一个专用的 IAM 用户,该用户拥有访问 S3、CloudFront 和 IAM 读取权限。这样,Pulumi 就可以在堆栈部署期间干净地配置所有内容。请AdministratorAccess为该 IAM 用户提供相关信息。

我安装了AWS CLI并运行:

aws configure
Enter fullscreen mode Exit fullscreen mode

然后配置AccessKey ID、Secret和Default origin。

🔑 GitHub 令牌(用于页面部署)

当我添加对 GitHub Pages 部署的支持时,我意识到我需要与 GitHub API 进行交互——因此个人访问令牌至关重要。

我从我的 GitHub 帐户设置中生成了一个包含repo作用域的delete_repo配置文件,并将其安全保存。Pulstack 稍后在初始化基于 GitHub 的技术栈时会提示输入此配置文件。

至此,我已经准备好了一切:安装了 Pulumi CLI,配置了 AWS CLI,手头有 GitHub 令牌。是时候开始构建了。

👷‍♂️ 构建 Pulstack

📁index.js

我首先用 设置 CLI commander.js。它简单、轻量,而且完全满足我的需求。

#!/usr/bin/env node
const { Command } = require("commander");
const { deploy } = require("./deploy");
const { destroy } = require("./destroy");
const { initProject } = require("./init");
const { deployGithub  } = require("./deployGithub");
const program = new Command();

program
  .name("pulstack")
  .description("Deploy static site to AWS S3  or GitHub using Pulumi instantly")
  .version("0.1.0");

  program
  .command("deploy")
  .description("Deploy static site to AWS or GitHub Pages")
  .requiredOption("-d, --dir <path>", "Path to static site files")
  .option("-e, --env <name>", "Environment/stack name", "dev")
  .option("-t, --target <provider>", "Target platform: aws | github-pages", "aws")
  .action(async (opts) => {
    const target = opts.target;

    if (target === "github-pages") {
      await deployGithub(opts.dir);
    } else if (target === "aws") {
      await deploy(opts.dir, opts.env);
    } else {
      console.error(`❌ Unsupported target: ${target}`);
      process.exit(1);
    }
  });

program
.command("init")
.description("Initialize project and config")
.option("--github", "Initialize for GitHub Pages")
.action(async (opts) => {
await initProject({ github: opts.github });
});

program
.command("destroy")
.description("Destroy project")
.action(async () => {
await destroy();
});

program.parse();
Enter fullscreen mode Exit fullscreen mode

CLI 有三个主要命令:

  • init– 设置 Pulumi 项目配置

  • deploy– 处理到 AWS 或 GitHub Pages 的部署

  • destroy– 销毁你创建的堆栈

根据通过 传递的目标平台--target,它会路由到AWS(deploy.js)或GitHub Pages(deployGithub.js)。我还将静态文件夹路径设为必填选项,以便用户不会忘记。

初始化文件

在部署任何内容之前,我们需要收集一些信息——这就是 init.js 的处理方式。它会根据您要部署到 AWS 还是 GitHub Pages 来设置项目。

const fs = require("fs");
const path = require("path");
const prompts = require("prompts");
const { LocalWorkspace } = require("@pulumi/pulumi/automation");
const { execSync } = require("child_process");

function checkCLI(command, name) {
  try {
    execSync(command, { stdio: "ignore" });
    console.log(`✅ ${name} CLI is installed`);
    return true;
  } catch {
    console.error(`❌ ${name} CLI is not installed. Please install it first.`);
    return false;
  }
}

function checkPulumiLogin() {
  try {
    const user = execSync("pulumi whoami", { stdio: "pipe" }).toString().trim();
    console.log(`🔐 Logged in as ${user}`);
    return true;
  } catch {
    console.error("⚠️  Pulumi CLI is not logged in. Run `pulumi login` and try again.");
    return false;
  }
}

function checkAwsConfigured() {
  try {
    const identity = execSync("aws sts get-caller-identity", { stdio: "pipe" }).toString();
    const parsed = JSON.parse(identity);
    console.log(`🧾 AWS Configured for Account: ${parsed.Account}, ARN: ${parsed.Arn}`);
    return true;
  } catch {
    console.error("❌ AWS CLI is not configured. Run `aws configure` with your IAM credentials first.");
    return false;
  }
}

async function initProject(options = {}) {
  const useGitHub = options.github || false;

  console.log("🔍 Checking environment...");
  const PulumiCheck = checkCLI("pulumi version", "Pulumi");
  if (!PulumiCheck) process.exit(1);

  if (useGitHub) {
    const { repoName, description, deployDir, stackName, githubToken } = await prompts([
      {
        type: "text",
        name: "repoName",
        message: "GitHub repo name:",
        initial: path.basename(process.cwd()),
      },
      {
        type: "text",
        name: "description",
        message: "Repo description:",
      },
      {
        type: "text",
        name: "deployDir",
        message: "Directory to deploy (e.g., ./build):",
        initial: "./build",
      },
      {
        type: "text",
        name: "stackName",
        message: "Stack name:",
        initial: "github-pages",
      },
      {
        type: "password",
        name: "githubToken",
        message: "Enter your github token",
      },
    ]);

    const githubConfig = {
      projectName: repoName,
      description,
      deployDir,
      stackName,
      githubToken,
      target: "github",
    };

    fs.writeFileSync("config.json", JSON.stringify(githubConfig, null, 2));
    console.log("✅ GitHub Pages project initialized and saved to config.json");
    return;
  }

  // For AWS S3 setup
  const hasAws = checkCLI("aws --version", "AWS");
  const isPulumiLoggedIn = checkPulumiLogin();
  const isAwsConfigured = checkAwsConfigured();

  if (!hasAws || !isPulumiLoggedIn || !isAwsConfigured) {
    process.exit(1);
  }

  const response = await prompts([
    {
      type: "text",
      name: "projectName",
      message: "Project name:",
      initial: "Pulumi",
    },
    {
      type: "text",
      name: "stackName",
      message: "Stack name:",
      initial: "dev",
    },
    {
      type: "text",
      name: "projectDescription",
      message: "Project Description:",
      initial: "This is a cool project",
    },
    {
      type: "text",
      name: "region",
      message: "AWS region:",
      initial: "us-east-1",
    },
    {
      type: "confirm",
      name: "generateSite",
      message: "Create a sample index.html?",
      initial: true,
    },
  ]);

  const config = {
    projectName: response.projectName,
    stackName: response.stackName,
    projectDescription: response.projectDescription,
    region: response.region,
    target: "aws",
  };

  fs.writeFileSync("config.json", JSON.stringify(config, null, 2));
  console.log("📦 Saved all config → config.json");

  // Create sample static site
  const publicDir = path.join(process.cwd(), "public");
  if (response.generateSite && !fs.existsSync(publicDir)) {
    fs.mkdirSync(publicDir);
    fs.writeFileSync(
      path.join(publicDir, "index.html"),
      `<html><body><h1>Pulumi is awesome broo!🔥</h1></body></html>`
    );
    console.log("🌐 Created sample static site in ./public/");
  }

  // Initialize Pulumi stack for AWS only
  const stack = await LocalWorkspace.createOrSelectStack({
    stackName: response.stackName,
    projectName: response.projectName,
    program: async () => {},
  });

  await stack.setConfig("aws:region", { value: response.region });
  console.log("✅ Pulumi stack initialized!");
}

module.exports = { initProject };
Enter fullscreen mode Exit fullscreen mode

运行后:

node index.js init
# or
node index.js init --github
Enter fullscreen mode Exit fullscreen mode

它执行以下操作:

  • ✅ 检查所需的 CLI(Pulumi、AWS CLI)

  • 🧠 验证 Pulumi 登录和 AWS 凭证(适用于 AWS 模式)

  • 🗣️ 提示您输入配置,例如项目名称、堆栈名称、区域和目标(如果您想在 GitHub 上部署,则需要 GitHub 访问令牌)

  • 📝 将所有内容保存到 config.json — 这样您就不必再次回答

  • 🌐(可选)在 public/ 文件夹中创建示例 index.html,以便您可以立即测试部署

确保 IAM 用户拥有必要的权限,并且 GitHub 令牌也拥有repo必要的delete权限。访问我的 GitHub 仓库,查看所有必需的权限。

普卢米

普卢米

📁 pulumiProgram.js – 基础设施即代码

在这里,我将所有 AWS 基础设施定义为代码。

// pulumiProgram.js
"use strict";

const aws = require("@pulumi/aws");
const pulumi = require("@pulumi/pulumi");
//const mime = require("mime");
const fs = require("fs");
const path = require("path");

function createPulumiProgram(staticDir) {
  return async () => {
    // Create a bucket and expose a website index document

    const config = JSON.parse(fs.readFileSync("config.json", "utf-8"));
    const bucketName = config.projectName;

    let siteBucket = new aws.s3.BucketV2(bucketName, {});

    let siteBucketWebsiteConfig = new aws.s3.BucketWebsiteConfigurationV2("s3-website-bucket-config", {
        bucket: siteBucket.id,
        indexDocument: {
            suffix: "index.html",
        },
    });

    new aws.s3.BucketPublicAccessBlock("public-access-block", {
        bucket: siteBucket.id,
        blockPublicAcls: true,
        blockPublicPolicy: true,
        ignorePublicAcls: true,
        restrictPublicBuckets: true,

    });

    // Create CloudFront Origin Access Identity
    const oai = new aws.cloudfront.OriginAccessIdentity("pulumi-oai", {
        comment: `Access Identity for ${bucketName}`,
      });

    // Upload files from the staticDir
    const files = fs.readdirSync(staticDir);
    for (const file of files) {
      const filePath = path.join(staticDir, file);
      const contentType = getMimeType(file);

      new aws.s3.BucketObject(file, {
        bucket: siteBucket,
        source: new pulumi.asset.FileAsset(filePath),
        contentType,
      });
    }

    const addFolderContents = (staticDir, prefix) => {
        for (let item of fs.readdirSync(staticDir)) {
          let filePath = path.join(staticDir, item);
          let isDir = fs.lstatSync(filePath).isDirectory();

          // This handles adding subfolders and their content
          if (isDir) {
            const newPrefix = prefix ? path.join(prefix, item) : item;
            addFolderContents(filePath, newPrefix);
            continue;
          }

          let itemPath = prefix ? path.join(prefix, item) : item;
          itemPath = itemPath.replace(/\\/g,'/');             // convert Windows paths to something S3 will recognize

          let object = new aws.s3.BucketObject(itemPath, {
            bucket: siteBucket.id,
            source: new pulumi.asset.FileAsset(filePath),     // use FileAsset to point to a file
            contentType: getMimeType(filePath), // set the MIME type of the file
          });
        }
    }



    // Attach bucket policy for OAI
    new aws.s3.BucketPolicy("pulumi-bucket-policy", {
        bucket: siteBucket.bucket,
        policy: pulumi.all([siteBucket.bucket, oai.iamArn]).apply(([bucket, iamArn]) =>
          JSON.stringify({
            Version: "2012-10-17",
            Statement: [
              {
                Effect: "Allow",
                Principal: { AWS: iamArn },
                Action: "s3:GetObject",
                Resource: `arn:aws:s3:::${bucket}/*`,
              },
            ],
          })
        ),
      });

      // Upload static files
      const uploadFiles = (dir, prefix = "") => {
        for (const item of fs.readdirSync(dir)) {
          const filePath = path.join(dir, item);
          const stat = fs.statSync(filePath);

          if (stat.isDirectory()) {
            uploadFiles(filePath, path.join(prefix, item));
          } else {
            const relativePath = path.join(prefix, item).replace(/\\/g, "/");

            new aws.s3.BucketObject(relativePath, {
              bucket: siteBucket.id,
              source: new pulumi.asset.FileAsset(filePath),
              contentType: getMimeType(filePath),
            });
          }
        }
      };

      uploadFiles(staticDir);

      // CloudFront Distribution
      const distribution = new aws.cloudfront.Distribution("pulumi-cdn", {
        enabled: true,
        defaultRootObject: "index.html",
        origins: [
          {
            originId: siteBucket.arn,
            domainName: siteBucket.bucketRegionalDomainName,
            s3OriginConfig: {
              originAccessIdentity: oai.cloudfrontAccessIdentityPath,
            },
          },
        ],
        defaultCacheBehavior: {
          targetOriginId: siteBucket.arn,
          viewerProtocolPolicy: "redirect-to-https",
          allowedMethods: ["GET", "HEAD"],
          cachedMethods: ["GET", "HEAD"],
          forwardedValues: {
            queryString: false,
            cookies: { forward: "none" },
          },
          compress: true,
        },
        priceClass: "PriceClass_100",
        restrictions: {
          geoRestriction: {
            restrictionType: "none",
          },
        },
        viewerCertificate: {
          cloudfrontDefaultCertificate: true,
        },
      });

      return {
        bucketName: siteBucket.bucket,
        cloudfrontUrl: distribution.domainName.apply((domain) => `https://${domain}`),
      };
  };
}
// Simple mime type guesser
function getMimeType(file) {
    if (file.endsWith(".html")) return "text/html";
    if (file.endsWith(".css")) return "text/css";
    if (file.endsWith(".js")) return "application/javascript";
    if (file.endsWith(".json")) return "application/json";
    if (file.endsWith(".png")) return "image/png";
    if (file.endsWith(".jpg") || file.endsWith(".jpeg")) return "image/jpeg";
    return "text/plain";
}

module.exports = { createPulumiProgram };
Enter fullscreen mode Exit fullscreen mode
  • 🪣 S3 Bucket Creation:首先,我们创建一个 S3 存储桶来托管静态文件。

  • 🚫 Blocking Public Access(For Security):为了保持私密性,我们默认阻止所有公共访问。

  • 🕵️ CloudFront OAI (Origin Access Identity):我们没有将存储桶公开,而是使用 CloudFront OAI 来安全地访问存储桶。这意味着只有 CloudFront 可以从 S3 获取对象。

  • 📂 Upload Static Files:然后,它会递归地将提供的 --dir 中的所有内容上传到 S3 存储桶,保留文件夹结构并设置正确的 MIME 类型。我uploadFiles()为此编写了一个自定义函数。

🚀 deploy.js – 仅用一个命令即可部署到 AWS

当用户运行时,将执行该文件:

node index.js deploy --target aws --dir ./public
Enter fullscreen mode Exit fullscreen mode

注意:我在这里使用./public dir,但您可以传递任何目录。
例如,如果您已经构建了反应应用程序,则应该在这里传递./build dir

// deploy.js
const { LocalWorkspace } = require("@pulumi/pulumi/automation");
const path = require("path");
const fs = require("fs");
const { createPulumiProgram } = require("./pulumiProgram");

async function deploy(staticDir) {
  if (!fs.existsSync(staticDir)) {
    console.error(`Directory "${staticDir}" does not exist.`);
    process.exit(1);
  }

  const configPath = path.resolve("config.json");

    if (!fs.existsSync(configPath)) {
      console.error("❌ Missing config.json – have you run `init`?");
      process.exit(1);
    }

    const config = JSON.parse(fs.readFileSync(configPath, "utf8"));

    //const token = process.env.PULUMI_ACCESS_TOKEN || config.pulumiAccessToken;
    const stackName = config.stackName;
    const projectName = config.projectName;

  console.log("⏳ Initializing Pulumi stack...");

  const stack = await LocalWorkspace.createOrSelectStack({
    stackName,
    projectName,
    program: createPulumiProgram(staticDir),
  });

  console.log("✅ Stack initialized");

  await stack.setConfig("aws:region", { value: config.region || "us-east-1" });

  console.log("🚀 Deploying to AWS...");
  const upRes = await stack.up();

  console.log("\n✅ Deployment complete!");
  console.log(`📦 Bucket Name: ${upRes.outputs.bucketName.value}`);
  console.log(`🌐 Site URL: ${upRes.outputs.cloudfrontUrl.value}`);
}

module.exports = { deploy };
Enter fullscreen mode Exit fullscreen mode

TL;DR
✅ 读取静态站点和配置
🛠️ 通过 Pulumi Automation 提供基础设施
📡 将所有文件上传到 s3 bucket
🌐 返回实时站点 URL – 只需一个命令

普卢米

🚀 deployGithub.js – 一次性部署到 GitHub Pages

此功能可自动化 GitHub Pages 部署的整个生命周期:

node index.js deploy --target github-pages --dir ./public
Enter fullscreen mode Exit fullscreen mode

注意:我在这里使用./public dir,但您可以传递任何目录。
例如,如果您已经构建了反应应用程序,则应该在这里传递./build dir

const fs = require("fs");
const path = require("path");
const { LocalWorkspace } = require("@pulumi/pulumi/automation");
const simpleGit = require("simple-git");
require("dotenv").config();

async function deployGithub() {
  const configPath = path.resolve("config.json");

  if (!fs.existsSync(configPath)) {
    console.error("❌ Missing config.json – please run `init` first.");
    process.exit(1);
  }

  const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
  const { projectName, description, deployDir, stackName } = config;

  let enablePages = false;
  let fullName = "";
  let repoUrl = "";

  if (!fs.existsSync(deployDir)) {
    console.error(`❌ Deploy directory "${deployDir}" does not exist.`);
    process.exit(1);
  }

  const token = process.env.GITHUB_TOKEN || config.githubToken;
  if (!token) {
    console.error("❌ GitHub token not found. Please set GITHUB_TOKEN as an env variable.");
    process.exit(1);
  }

  const program = async () => {
    const github = require("@pulumi/github");

    const repo = new github.Repository(projectName, {
      name: projectName,
      description,
      visibility: "public",
      ...(enablePages && {
        pages: {
          source: {
            branch: "gh-pages",
            path: "/",
          },
        },
      }),
    });

    return {
      repoUrl: repo.htmlUrl,
      fullName: repo.fullName,
    };
  };

  const stack = await LocalWorkspace.createOrSelectStack({
    stackName,
    projectName,
    program,
  });

  await stack.setAllConfig({
    "github:token": { value: token, secret: true },
  });

  console.log("📦 Creating GitHub repo...");
  const result = await stack.up();
  fullName = result.outputs.fullName.value;
  repoUrl = result.outputs.repoUrl.value;
  console.log("✅ GitHub repo created:", repoUrl);

  // Step 2: Push static site to gh-pages
  console.log("📤 Pushing site content to `gh-pages` branch...");
  const git = simpleGit(deployDir);
  await git.init();
  await git.checkoutLocalBranch("gh-pages"); // ✅ Create gh-pages branch
  await git.add(".");
  await git.commit("Deploy to GitHub Pages from statik");

  const remotes = await git.getRemotes(true);
  if (remotes.find(r => r.name === "origin")) {
    await git.removeRemote("origin");
  }
  await git.addRemote("origin", `https://github.com/${fullName}`).catch(() => {});
  await git.push("origin", "gh-pages", ["--force"]);

  // Step 3: Enable GitHub Pages
  console.log("🌍 Enabling GitHub Pages...");
  enablePages = true;
  const updatedStack = await LocalWorkspace.createOrSelectStack({
    stackName,
    projectName,
    program,
  });

  await updatedStack.setAllConfig({
    "github:token": { value: token, secret: true },
  });

  await updatedStack.up(); // ✅ re-run with updated program


  const [owner, repoName] = fullName.split("/");
  const siteUrl = `https://${owner.toLowerCase()}.github.io/${repoName}/`;
  console.log(`🎉 GitHub Pages deployed at: ${siteUrl}`);
}

module.exports = { deployGithub };
Enter fullscreen mode Exit fullscreen mode

✅ 通过 Pulumi 创建仓库

✅ 将静态内容推送到 gh-pages(用于simple-git以编程方式管理 git 推送。)

✅ 通过 Pulumi 启用 GitHub Pages

✅ 输出实时站点 URL

普卢米

我按照以下两步流程启用 GitHub Pages:

  • 首先,创建 repowithout pages

  • 将静态内容推送到gh-pages分支

  • 然后重新运行Pulumi启用页面的程序

为什么?因为 GitHub Pages 要求分支必须先存在,Pulumi 才能激活它。

🔥 destroy.js – 销毁堆栈

该函数将破坏配置文件中存在的堆栈。

const fs = require("fs");
const path = require("path");
const { LocalWorkspace } = require("@pulumi/pulumi/automation");

async function destroy() {
  const configPath = path.resolve("config.json");

  if (!fs.existsSync(configPath)) {
    console.error("❌ Missing config.json – have you run ` init`?");
    process.exit(1);
  }

  const config = JSON.parse(fs.readFileSync(configPath, "utf8"));

  //const token = process.env.PULUMI_ACCESS_TOKEN || config.pulumiAccessToken;
  const stackName = config.stackName;
  const projectName = config.projectName;

  console.log(`🧨 Destroying stack "${stackName}" from project "${projectName}"...`);

  const stack = await LocalWorkspace.selectStack({
    stackName,
    projectName,
    program: async () => {}, // noop
  });

  await stack.destroy({ onOutput: console.log });

  console.log(`✅ Stack "${stackName}" destroyed successfully.`);
}

module.exports = { destroy };
Enter fullscreen mode Exit fullscreen mode

通过运行:

 node index.js destroy
Enter fullscreen mode Exit fullscreen mode

堆栈名称项目名称将从文件中获取config.json

普卢米

面临的挑战

  • 最大的挑战?GitHub Pages 需要 gh-pages 分支存在才能启用。这太烦人了。我最终不得不先创建仓库,推送网站内容,然后再次更新仓库才能启用 Pages。

  • deleting运行destroy命令时 Repo的 GitHub 访问令牌权限

  • 要让 CloudFront 兼容私有 S3 存储桶,需要设置一个 OAI 并正确配置 S3 存储桶策略,以允许通过该身份进行访问。因此,我查阅了 AWS 文档,并精心构建了一个基于 Pulumi 的 BucketPolicy,该策略专门向 OAI 授予 s3:GetObject 权限。正确配置后,它就能正常工作了。

我学到了什么

  • Pulumi 是一款功能强大的工具 - 能够用 JavaScript(支持多种语言)将基础设施定义为代码,并通过编程方式部署它,使自动化变得无缝。
  • 说实话,我以前从未将基础设施定义为代码。我一直直接使用 AWS GUI,但使用 Pulumi 之后,我学会了。
  • 也从未使用过 simple-git 并以编程方式进行提交和推送。

我一开始只是一个简单的自动化想法,但最终学到了很多东西,并拥有了一个方便的 CLI 工具🙌

目前它仅支持 AWS 云,但我还将添加 Azure 和 GCP,以便用户可以选择他们想要部署的云服务。

在 GitHub 上使用 Pulumi

在这个项目中,我使用 Pulumi 自动创建和管理云(AWS)和 GitHub 基础设施——只需一个命令就可以轻松地从本地代码转到实时站点。

🛠 我使用 Pulumi 做什么?

AWS 部署:
Pulumi 提供 S3 存储桶(带有静态网站托管),设置 CloudFront 分发,并使用 Origin Access Identity (OAI) 安全地连接它们。

GitHub 存储库管理:
使用 Pulumi GitHub 提供程序,我实现了以下自动化:

  • 从代码创建公共仓库

  • 将内容推送到 gh-pages 分支

  • 启用 GitHub Pages 进行即时静态托管

使用 Pulumi 内联程序进行堆栈。

Pulumi 副驾驶

每当堆栈更新失败时,我都会使用 Pulumi 仪表板中的“使用 Copilot 进行调试”功能。它分析了问题并提供了堆栈失败的原因 🙂

感谢有这个机会🫶

鏂囩珷鏉ユ簮锛�https://dev.to/dev_kiran/pulstack-deploy-your-static-site-to-s3-or-github-in-1-min-5cin
PREV
3 个轻量级 JavaScript 轮播库
NEXT
🤯你应该知道的强大AI工具v2🫵