UNPKG

@seasketch/geoprocessing

Version:

Geoprocessing and reporting framework for SeaSketch 2.0

149 lines • 7.73 kB
import fs from "fs-extra"; import path from "node:path"; import * as esbuild from "esbuild"; import { generateManifest } from "./generateManifest.js"; import { getHandlerFilenameFromSrcPath } from "../util/handler.js"; import { generateHandler } from "./generateHandler.js"; /** * Builds project geoprocessing and preprocessing functions * @param projectPath - project path containing package.json, geoprocessing.json, and geoprocessing or preprocessing functions * @param destBuildPath - path to write bundled functions to */ export async function buildProjectFunctions(projectPath, destBuildPath) { if (!fs.existsSync(destBuildPath)) { fs.mkdirSync(destBuildPath); } const geoprocessing = JSON.parse(fs .readFileSync(path.join(projectPath, "project", "geoprocessing.json")) .toString()); const packageGp = JSON.parse(fs.readFileSync("./package.json").toString()); const packageProject = JSON.parse(fs.readFileSync(path.join(projectPath, "package.json")).toString()); if (!geoprocessing.preprocessingFunctions && !geoprocessing.geoprocessingFunctions) { throw new Error("No functions found in geoprocessing.json"); } // For each project function generate root lambda function and bundle into single JS file with all dependencies const functionPaths = [ ...geoprocessing.geoprocessingFunctions, ...geoprocessing.preprocessingFunctions, ]; if (process.env.NODE_ENV !== "test") console.log("Bundling functions found in geoprocessing.json...\n"); await Promise.all(functionPaths.map(async (functionPath) => { // generate function (lambda handler) that wraps the preprocessing or geoprocessing function // the handler is the entry point for the lmabda and receives the event (payload) from the API gateway // and passes it on const handlerPath = generateHandler(path.join(projectPath, functionPath), destBuildPath); const handlerDestPath = `${path.basename(functionPath)}`.replace(".ts", "Handler.mjs"); // Build a local npm package for the function const pkgName = handlerDestPath.replace(".mjs", ""); const pkgPath = path.join(destBuildPath, pkgName).replace("Handler", ""); const bundledPath = path.join(pkgPath, handlerDestPath); fs.ensureDirSync(pkgPath); // Build esm bundle with all dependencies const buildResult = await esbuild.build({ entryPoints: [handlerPath], bundle: true, outfile: bundledPath, platform: "node", format: "esm", logLevel: process.env.NODE_ENV === "test" ? "error" : "info", sourcemap: false, external: ["aws-cdk-lib", "aws-sdk"], banner: { // workaround require bug https://github.com/evanw/esbuild/pull/2067#issuecomment-1324171716 js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);", }, }); if (buildResult.errors.length > 0 || buildResult.warnings.length > 0) { console.log(JSON.stringify(buildResult, null, 2)); } // package.json with type: module, needed in the build directory containing handlers in order to enable ESM runtime in lambda // WRITE A FUNCTION THAT GENERATES PACKAGE.JSON that points to handler fs.writeJSONSync(path.join(pkgPath, "package.json"), { name: handlerDestPath, type: "module", description: "This package will be treated as an ES module.", version: "1.0", main: handlerDestPath, }, { spaces: 2 }); })); // package.json with type: module, needed in the build directory containing handlers in order to enable ESM runtime in lambda fs.writeJSONSync(path.join(destBuildPath, "package.json"), { name: "gp-project-build", type: "module", description: "This package will be treated as an ES module.", version: "1.0", main: "index.js", }, { spaces: 2 }); // OTHER_FUNCTIONS if (process.env.NODE_ENV !== "test") console.log("\nBundling support functions...\n"); const otherFunctions = [ "src/aws/serviceHandlers.ts", "src/sockets/sendmessage.ts", "src/sockets/connect.ts", "src/sockets/disconnect.ts", ]; await Promise.all(otherFunctions.map(async (functionPath) => { const bundledName = path.basename(functionPath).replace(".ts", ".mjs"); const functionName = bundledName.replace(".mjs", ""); const bundledPath = path.join(destBuildPath, functionName, bundledName); const pkgPath = path.join(destBuildPath, functionName); const minify = process.env.NOMINIFY ? false : true; const buildResult = await esbuild.build({ entryPoints: [functionPath], bundle: true, outfile: bundledPath, platform: "node", format: "esm", logLevel: process.env.NODE_ENV === "test" ? "error" : "info", minify: minify, treeShaking: true, metafile: true, sourcemap: false, external: ["aws-cdk-lib"], // keep aws-sdk until migrate to v3 built into lambda banner: { // workaround require bug https://github.com/evanw/esbuild/pull/2067#issuecomment-1324171716 js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);", }, }); if (process.env.NODE_ENV !== "test" && (buildResult.errors.length > 0 || buildResult.warnings.length > 0)) { console.log(JSON.stringify(buildResult, null, 2)); } if (buildResult.metafile && process.env.ANALYZE) { if (process.env.NODE_ENV !== "test") console.log("Generating metafile esbuild-metafile-lambda.json"); // use https://bundle-buddy.com/esbuild to analyze await fs.writeFile(`${projectPath}/esbuild-metafile-lambda.json`, JSON.stringify(buildResult.metafile)); } // package.json with type: module (to enable ESM) and entry point to lambda hander fs.writeJSONSync(path.join(pkgPath, "package.json"), { name: bundledName, type: "module", description: "This package will be treated as an ES module.", version: "1.0", main: bundledName, }, { spaces: 2 }); })); // MANIFEST /** * Given full path to source geoprocessing function, requires and returns its pre-generated handler module */ async function getHandlerModule(srcFuncPath) { const name = getHandlerFilenameFromSrcPath(srcFuncPath); const p = path.join(destBuildPath, name); return await import(p); } const preprocessingBundles = geoprocessing.preprocessingFunctions && (await Promise.all(geoprocessing.preprocessingFunctions.map(getHandlerModule))); const geoprocessingBundles = geoprocessing.geoprocessingFunctions && (await Promise.all(geoprocessing.geoprocessingFunctions.map(getHandlerModule))); const manifest = generateManifest(geoprocessing, packageProject, preprocessingBundles, geoprocessingBundles, packageGp.version); const manifestPath = path.join(destBuildPath, "manifest.json"); if (process.env.NODE_ENV !== "test") console.log(`\nCreating service manifest ${manifestPath}\n`); fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, " ")); fs.copyFileSync(manifestPath, path.join(destBuildPath, "serviceHandlers", "manifest.json")); } //# sourceMappingURL=buildProjectFunctions.js.map