UNPKG

@storm-software/cloudflare-tools

Version:

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

210 lines (207 loc) 7.02 kB
import { getInternalDependencies, r2UploadFile } from "./chunk-DW5K6TQT.mjs"; import { createCliOptions, getPackageInfo } from "./chunk-PH3DHY4Q.mjs"; import { getConfig } from "./chunk-I5P7M77J.mjs"; import { findWorkspaceRoot, writeDebug, writeInfo, writeSuccess, writeWarning } from "./chunk-LVQQJNPK.mjs"; // src/executors/r2-upload-publish/executor.ts import { S3 } from "@aws-sdk/client-s3"; import { createProjectGraphAsync, joinPathFragments, readCachedProjectGraph } from "@nx/devkit"; import { glob } from "glob"; import { execSync } from "node:child_process"; 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."); } 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 sourceRoot = context.projectsConfigurations.projects[context.projectName]?.sourceRoot ?? workspaceRoot; const projectName = context.projectsConfigurations.projects[context.projectName]?.name ?? context.projectName; const projectDetails = getPackageInfo( context.projectsConfigurations.projects[context.projectName] ); if (!projectDetails?.content) { throw new Error( `Could not find the project details for ${context.projectName}` ); } const args = createCliOptions({ ...options }); if (isDryRun) { args.push("--dry-run"); } const cloudflareAccountId = process.env.STORM_BOT_CLOUDFLARE_ACCOUNT; if (!options?.registry && !cloudflareAccountId) { throw new Error( "The Storm Registry URL is not set in the Storm config. Please set either the `extensions.cyclone.registry` or `config.extensions.cyclone.accountId` property in the Storm config." ); } if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) { throw new Error( "The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables are not set. Please set these environment variables to upload to the Cyclone Registry." ); } const endpoint = 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." ); } writeInfo( `Publishing ${context.projectName} to the Storm Registry at ${endpoint}` ); const s3Client = new S3({ region: "auto", endpoint, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY } }); const version = projectDetails.content?.version; writeInfo(`Generated component version: ${version}`); const files = await glob(joinPathFragments(sourceRoot, "**/*"), { ignore: "**/{*.stories.tsx,*.stories.ts,*.spec.tsx,*.spec.ts}" }); const projectPath = `registry/${context.projectName}`; 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(); writeInfo(`Clearing out existing items in ${projectPath}`); if (!isDryRun) { const response = await s3Client.listObjects({ Bucket: options.bucketId, Prefix: projectPath }); if (response?.Contents && response.Contents.length > 0) { writeDebug( `Deleting the following existing items from the component registry: ${response.Contents.map((item) => item.Key).join(", ")}` ); await Promise.all( response.Contents.map( (item) => s3Client.deleteObjects({ Bucket: options.bucketId, Delete: { Objects: [ { Key: item.Key } ], Quiet: false } }) ) ); } else { writeDebug( `No existing items to delete in the component registry path ${projectPath}` ); } } else { writeWarning("[Dry run]: skipping upload to the Cyclone Registry."); } 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; } const metaJson = JSON.stringify(meta); writeInfo(`Generating meta.json file: ${metaJson}`); await r2UploadFile( s3Client, options.bucketId, projectPath, "meta.json", version, metaJson, "application/json", isDryRun ); await Promise.all( files.map((file) => { const fileName = file.replaceAll("\\", "/").replace(sourceRoot.replaceAll("\\", "/"), ""); return readFile(file, { encoding: "utf8" }).then( (fileContent) => r2UploadFile( s3Client, options.bucketId, projectPath, fileName, version, fileContent, "text/plain", isDryRun ) ); }) ); writeSuccess( `Successfully uploaded the ${projectName} component to the Cyclone Registry`, config ); return { success: true }; } catch (error) { console.error("Failed to publish to Cloudflare Workers Registry"); console.error(error); console.log(""); return { success: false }; } } export { runExecutor };