1 min read

Auto Delete Old Cloudflare Pages Deployments

Cloudflare Pages automatically deploy your project whenever there is an update to your repository, which is convenient. However, this means that when commits get more, there will be a lot of old and probably unuseful Pages deployments in your history. Therefore, we can create a script that periodically deletes deployments older than some time by calling the Cloudflare API. Better yet, we can use a Cloudflare Worker to accomplish this whole process.

package.json

{
  "private": true,
  "type": "module",
  "scripts": {
    "build": "cross-env ENVIRONMENT=production esbuild workers/pages-cleanup/src/index.ts --bundle --format=esm --minify --outfile=workers/pages-cleanup/dist/index.js --legal-comments=none",
    "dev": "npm run watch && npm run start",
    "publish": "wrangler publish --config workers/pages-cleanup/wrangler.toml",
    "start": "miniflare workers/pages-cleanup/dist/index.js --wrangler-config workers/pages-cleanup/wrangler.toml --watch",
    "watch": "cross-env ENVIRONMENT=development esbuild workers/pages-cleanup/src/index.ts --bundle --format=esm --minify --outfile=workers/pages-cleanup/dist/index.js --sourcemap --watch --legal-comments=none"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^4.20230801.0",
    "wrangler": "^3.4.0"
  }
}

types.d.ts

/// <reference types="@cloudflare/workers-types" />

src/index.ts

interface ScheduleEnv {
  readonly ACCOUNT_ID: string;
  readonly PROJECT_NAME: string;
  readonly API_TOKEN: string;
  readonly EXPIRATION_DAYS: string;
}

function debugLog(message: string): void {
  console.debug(`[${new Date().toISOString()}] ${message}`);
}

const scheduled: ExportedHandlerScheduledHandler<ScheduleEnv> = async (
  event,
  env
) => {
  debugLog(
    `Account "${env.ACCOUNT_ID}", project "${
      env.PROJECT_NAME
    }". Deleting all deployments ${
      env.EXPIRATION_DAYS
    } days older than ${new Date(event.scheduledTime).toISOString()}...`
  );

  const endpoint = `https://api.cloudflare.com/client/v4/accounts/${env.ACCOUNT_ID}/pages/projects/${env.PROJECT_NAME}/deployments`;
  const init = {
    headers: {
      "Content-Type": "application/json;charset=UTF-8",
      Authorization: `Bearer ${env.API_TOKEN}`,
    },
  };
  const deployments: { id: string; created_on: string }[] = [];
  let count = 0;
  let total: number | null = null;
  let page = 1;
  do {
    const response: any = await (
      await fetch(`${endpoint}?page=${page}`, init)
    ).json();
    if (!total) {
      total = response.result_info.total_count as number;
      debugLog(`Found ${total} deployments in project.`);
    }
    count += response.result_info.count;
    debugLog(`Retrieved ${count} deployments, ${total - count} remaining.`);
    deployments.push(...response.result);
    page++;
  } while (count < total);
  debugLog(`All ${deployments.length} deployments retrieved.`);

  let deleted = 0;
  await Promise.all(
    deployments.map(async (deployment) => {
      if (
        (event.scheduledTime - new Date(deployment.created_on).getTime()) /
          86400000 >
        parseInt(env.EXPIRATION_DAYS)
      ) {
        await fetch(`${endpoint}/${deployment.id}`, {
          method: "DELETE",
          ...init,
        });
        debugLog(
          `Deleted deployment ${deployment.id} (created on ${deployment.created_on}).`
        );
        deleted++;
      }
    })
  );
  debugLog(`Done. Deleted ${deleted} deployments.`);
};

export default { scheduled };