UNPKG

@storm-software/cloudflare-tools

Version:

A Nx plugin package that contains various executors, generators, and utilities that assist in managing Cloudflare services.

232 lines (229 loc) 8.21 kB
import { createHttpHandler } from "./chunk-SWYYMID7.mjs"; import { getEncoding, getInternalDependencies, uploadFile } from "./chunk-4F6THJ6M.mjs"; import { createCliOptions, getPackageInfo } from "./chunk-F5RW4AZL.mjs"; import { findWorkspaceRoot, getConfig } from "./chunk-JOBVIJOH.mjs"; import { correctPaths, joinPaths, writeDebug, writeSuccess, writeTrace, writeWarning } from "./chunk-OBUVLRI3.mjs"; // src/executors/r2-upload-publish/executor.ts import { DeleteObjectsCommand, ListObjectsCommand, S3Client } from "@aws-sdk/client-s3"; import { createProjectGraphAsync, readCachedProjectGraph } from "@nx/devkit"; import { glob } from "glob"; import mime from "mime-types"; import { execSync } from "node:child_process"; import { statSync } from "node:fs"; import { readFile } from "node:fs/promises"; async function runExecutor(options, context) { const isDryRun = process.env.NX_DRY_RUN === "true" || options.dryRun || false; if (!context.projectName) { throw new Error("The executor requires a projectName."); } if (!options.path) { throw new Error("The executor requires the `path` option to upload."); } console.info( `\u{1F680} Running Storm Cloudflare Publish executor on the ${context.projectName} worker` ); if (!context.projectName || !context.projectsConfigurations?.projects || !context.projectsConfigurations.projects[context.projectName] || !context.projectsConfigurations.projects[context.projectName]?.root) { throw new Error("The executor requires projectsConfigurations."); } try { const workspaceRoot = findWorkspaceRoot(); const config = await getConfig(workspaceRoot); const projectName = context.projectsConfigurations.projects[context.projectName]?.name ?? context.projectName; const projectDetails = getPackageInfo( context.projectsConfigurations.projects[context.projectName] ); const bucketId = options.bucketId; const bucketPath = options.bucketPath || "/"; if (!bucketId) { throw new Error("The executor requires a bucketId."); } const args = createCliOptions({ ...options }); if (isDryRun) { args.push("--dry-run"); } const cloudflareAccountId = process.env.CLOUDFLARE_ACCOUNT_ID || process.env.STORM_BOT_CLOUDFLARE_ACCOUNT; if (!options?.registry && !cloudflareAccountId) { throw new Error( "The registry option and `CLOUDFLARE_ACCOUNT_ID` (or `STORM_BOT_CLOUDFLARE_ACCOUNT`) environment variable are not set. Please set one of these values to upload to the Cloudflare R2 bucket." ); } if (!process.env.STORM_BOT_ACCESS_KEY_ID && !process.env.ACCESS_KEY_ID && !process.env.CLOUDFLARE_ACCESS_KEY_ID && !process.env.AWS_ACCESS_KEY_ID || !process.env.STORM_BOT_SECRET_ACCESS_KEY && !process.env.CLOUDFLARE_SECRET_ACCESS_KEY && !process.env.SECRET_ACCESS_KEY && !process.env.AWS_SECRET_ACCESS_KEY) { throw new Error( "The `ACCESS_KEY_ID` (or `STORM_BOT_ACCESS_KEY_ID`) and `SECRET_ACCESS_KEY` (or `STORM_BOT_SECRET_ACCESS_KEY`) environment variables are not set. Please set these environment variables to upload to the Cloudflare R2 bucket." ); } const registry = options?.registry ? options.registry : `https://${cloudflareAccountId}.r2.cloudflarestorage.com`; let projectGraph; try { projectGraph = readCachedProjectGraph(); } catch { await createProjectGraphAsync(); projectGraph = readCachedProjectGraph(); } if (!projectGraph) { throw new Error( "The executor failed because the project graph is not available. Please run the build command again." ); } writeDebug( `Publishing ${context.projectName} to the ${bucketId} R2 Bucket (at ${registry})` ); const client = new S3Client({ region: "auto", endpoint: registry, credentials: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion accessKeyId: process.env.STORM_BOT_ACCESS_KEY_ID || process.env.CLOUDFLARE_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY_ID || process.env.ACCESS_KEY_ID, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion secretAccessKey: process.env.STORM_BOT_SECRET_ACCESS_KEY || process.env.CLOUDFLARE_SECRET_ACCESS_KEY || process.env.AWS_SECRET_ACCESS_KEY || process.env.SECRET_ACCESS_KEY }, requestHandler: createHttpHandler() }); const version = projectDetails?.content?.version; if (version) { writeDebug(`Starting upload version ${version}`); } const basePath = options.path; const files = await glob(joinPaths(basePath, "**/*"), { ignore: "**/{*.stories.tsx,*.stories.ts,*.spec.tsx,*.spec.ts}" }); const internalDependencies = await getInternalDependencies( context.projectName, projectGraph ); const dependencies = internalDependencies.filter( (projectNode) => !projectNode.data.tags || projectNode.data.tags.every((tag) => tag.toLowerCase() !== "component") ).reduce((ret, dep) => { if (!ret[dep.name]) { ret[dep.name] = "latest"; } return ret; }, projectDetails?.content.dependencies ?? {}); const release = options.tag ?? execSync("npm config get tag").toString().trim(); if (options.clean === true) { writeDebug(`Clearing out existing items in ${bucketPath}`); if (!isDryRun) { const response = await client.send( new ListObjectsCommand({ Bucket: bucketId, Prefix: !bucketPath || bucketPath === "/" ? void 0 : bucketPath }) ); if (response?.Contents && response.Contents.length > 0) { writeTrace( `Deleting the following existing items from the R2 bucket path ${bucketPath}: ${response.Contents.map((item) => item.Key).join(", ")}` ); await client.send( new DeleteObjectsCommand({ Bucket: bucketId, Delete: { Objects: response.Contents.map((item) => ({ Key: item.Key })), Quiet: false } }) ); } else { writeDebug( `No existing items to delete in the R2 bucket path ${bucketPath}` ); } } else { writeWarning("[Dry run]: Skipping R2 bucket clean."); } } if (options.writeMetaJson === true) { const meta = { name: context.projectName, version, release, description: projectDetails?.content?.description, tags: projectDetails?.content?.keywords, dependencies, devDependencies: null, internalDependencies: internalDependencies.filter( (projectNode) => projectNode.data.tags && projectNode.data.tags.some( (tag) => tag.toLowerCase() === "component" ) ).map((dep) => dep.name) }; if (projectDetails?.type === "package.json") { meta.devDependencies = projectDetails?.content?.devDependencies; } await uploadFile( client, bucketId, bucketPath, "meta.json", version, JSON.stringify(meta), "application/json", isDryRun ); } await Promise.all( files.map(async (file) => { if (statSync(file, { throwIfNoEntry: false })?.isFile()) { const name = correctPaths(file).replace(correctPaths(basePath), ""); const type = mime.lookup(name) || "application/octet-stream"; await uploadFile( client, bucketId, bucketPath, name, version, await readFile(file, getEncoding(type)), type, isDryRun ); } }) ); writeSuccess( `Successfully uploaded the ${projectName} project to the Cloudflare R2 bucket.`, config ); return { success: true }; } catch (error) { console.error("Failed to publish to Cloudflare R2 bucket"); console.error(error); console.log(""); return { success: false }; } } export { runExecutor };