UNPKG

@seasketch/geoprocessing

Version:

Geoprocessing and reporting framework for SeaSketch 2.0

178 lines • 8.41 kB
import inquirer from "inquirer"; import ora from "ora"; import fs from "fs-extra"; import path from "node:path"; import chalk from "chalk"; import { projectSchema } from "../../src/types/index.js"; import camelcase from "camelcase"; import { getBaseFunctionPath, getProjectFunctionPath, getProjectConfigPath, getReportAssetFunctionPath, } from "../util/getPaths.js"; import { pathToFileURL } from "node:url"; async function createFunction() { const rawBasic = fs.readJSONSync(`${getProjectConfigPath("")}/basic.json`); const basic = projectSchema.parse(rawBasic); const answers = await inquirer.prompt([ { type: "list", name: "type", message: "Function type", choices: [ { value: "preprocessing", name: "Preprocessing - Validates and modifies user-drawn shapes", }, { value: "geoprocessing", name: "Geoprocessing - For sketch reports", }, ], }, { type: "input", name: "title", message: "Title for this function, in camelCase", default: (answers) => answers.type === "preprocessing" ? "clipToOceanEez" : "simpleFunction", validate: (value) => /^\w+$/.test(value) ? true : "Please use only alphabetical characters", transformer: (value) => camelcase(value), }, { type: "input", name: "description", message: "Describe what this function does", }, { when: (answers) => answers.type === "preprocessing" && basic.planningAreaType === "eez", type: "list", name: "clipToEez", message: `Should this clip sketches to the ${basic.planningAreaId} EEZ boundary?`, default: "yes", choices: [ { value: "yes", name: "Yes", }, { value: "no", name: "No", }, ], }, ]); answers.title = camelcase(answers.title); if (answers.type === "preprocessing" && answers.clipToEez === "yes") { answers.eez = basic.planningAreaId; } if (answers.type === "geoprocessing") { answers.executionMode = "async"; await makeGeoprocessingHandler(answers, true, ""); } else { await makePreprocessingHandler(answers, true, ""); } } if (import.meta.url === pathToFileURL(process.argv[1]).href) { // module was not imported but called directly await createFunction(); } export async function makeGeoprocessingHandler(options, interactive = true, basePath = "./") { const spinner = interactive ? ora("Creating new geoprocessing handler").start() : { start: () => false, stop: () => false, succeed: () => false }; spinner.start(`creating handler from templates`); // copy geoprocessing function template // rename metadata in function definition const projectFunctionPath = getProjectFunctionPath(basePath); // Copy from template-blank-project const functionTemplatePath = `${getReportAssetFunctionPath()}`; const handlerCode = await fs.readFile(`${functionTemplatePath}/simpleFunction.ts`); const testSmokeCode = await fs.readFile(`${functionTemplatePath}/simpleFunctionSmoke.test.ts`); if (!fs.existsSync(path.join(basePath, "src"))) { fs.mkdirSync(path.join(basePath, "src")); } if (!fs.existsSync(path.join(basePath, "src", "functions"))) { fs.mkdirSync(path.join(basePath, "src", "functions")); } await fs.writeFile(`${projectFunctionPath}/${options.title}.ts`, handlerCode .toString() .replaceAll("simpleFunction", options.title) .replaceAll("SimpleFunction", options.title.slice(0, 1).toUpperCase() + options.title.slice(1)) .replaceAll("functionName", options.title) .replace(`"async"`, `"${options.executionMode}"`) .replace("Function description", options.description)); await fs.writeFile(`${projectFunctionPath}/${options.title}Smoke.test.ts`, testSmokeCode .toString() .replaceAll("simpleFunction", options.title) .replace("./simpleFunction", `./${options.title}`)); const geoprocessingJson = JSON.parse(fs .readFileSync(path.join(basePath, "project", "geoprocessing.json")) .toString()); geoprocessingJson.geoprocessingFunctions = geoprocessingJson.geoprocessingFunctions || []; geoprocessingJson.geoprocessingFunctions.push(`src/functions/${options.title}.ts`); fs.writeFileSync(path.join(basePath, "project", "geoprocessing.json"), JSON.stringify(geoprocessingJson, null, " ")); spinner.succeed(`created ${options.title} function in ${projectFunctionPath}/`); spinner.succeed("Registered function in project/geoprocessing.json"); if (interactive) { console.log("\n"); console.log(chalk.blue(`Geoprocessing function: ${`${projectFunctionPath}/${options.title}.ts`}`)); console.log(chalk.blue(`Smoke test: ${`${projectFunctionPath}/${options.title}Smoke.test.ts`}`)); console.log(`\nNext Steps: * Update the geoprocessing function with your analysis * Populate examples/sketches folder with sketches for smoke test to run against * 'npm test' to smoke test your new geoprocessing function against all example sketches `); } } export async function makePreprocessingHandler(options, interactive = true, basePath = "./") { const spinner = interactive ? ora("Creating new preprocessing handler").start() : { start: () => false, stop: () => false, succeed: () => false }; spinner.start(`creating handler from templates`); // copy preprocessing function template const projectFunctionPath = getProjectFunctionPath(basePath); // rename metadata in function definition const baseFunctionPath = getBaseFunctionPath(); const handlerCode = await fs.readFile(`${baseFunctionPath}/clipToOceanEez.ts`); const testCode = await fs.readFile(`${baseFunctionPath}/clipToOceanEezSmoke.test.ts`); if (!fs.existsSync(path.join(basePath, "src"))) { fs.mkdirSync(path.join(basePath, "src")); } if (!fs.existsSync(path.join(basePath, "src", "functions"))) { fs.mkdirSync(path.join(basePath, "src", "functions")); } // add optional eez clip, unquoting the json string before insertion await fs.writeFile(`${projectFunctionPath}/${options.title}.ts`, handlerCode .toString() .replaceAll("clipToOceanEez", options.title) .replace("Example-description", options.description) .replace("EEZ_CLIP_OPERATION,", options.clipToEez === "no" ? "" : JSON.stringify({ datasourceId: "global-clipping-eez-land-union", operation: "intersection", options: { propertyFilter: { property: "UNION", values: [options.eez], }, }, }, null, 2).replaceAll(/"([^"]+)":/g, "$1:"))); await fs.writeFile(`${projectFunctionPath}/${options.title}Smoke.test.ts`, testCode.toString().replaceAll("clipToOceanEez", options.title)); const geoprocessingJson = JSON.parse(fs .readFileSync(path.join(basePath, "project", "geoprocessing.json")) .toString()); geoprocessingJson.preprocessingFunctions = geoprocessingJson.preprocessingFunctions || []; geoprocessingJson.preprocessingFunctions.push(`src/functions/${options.title}.ts`); fs.writeFileSync(path.join(basePath, "project", "geoprocessing.json"), JSON.stringify(geoprocessingJson, null, " ")); spinner.succeed(`created ${options.title} function in ${projectFunctionPath}/`); if (interactive) { console.log(chalk.blue(`\nPreprocessing function initialized`)); console.log(`\nNext Steps: * Update your function definition in ${`${projectFunctionPath}/${options.title}.ts`} * Smoke test in ${`${projectFunctionPath}/${options.title}Smoke.test.ts`} will be run the next time you use 'npm test' * Populate examples/sketches folder with sketches for smoke test to run against `); } } export { createFunction }; //# sourceMappingURL=createFunction.js.map