UNPKG

@seasketch/geoprocessing

Version:

Geoprocessing and reporting framework for SeaSketch 2.0

246 lines (220 loc) • 7.04 kB
import ora from "ora"; import fs from "fs-extra"; import chalk from "chalk"; import { BBox, Package, projectSchema } from "../../src/types/index.js"; import { promisify } from "node:util"; import { getGeoprocessingPath, getBaseProjectPath } from "../util/getPaths.js"; import { $ } from "zx"; import * as child from "node:child_process"; $.verbose = false; const exec = promisify(child.exec); export interface CreateProjectMetadata { name: string; description: string; author: string; email?: string; organization?: string; license: string; repositoryUrl: string; region: string; languages: string[]; gpVersion?: string; /** @deprecated */ bbox?: BBox; /** @deprecated */ bboxMinLng?: number; /** @deprecated */ bboxMaxLng?: number; /** @deprecated */ bboxMinLat?: number; /** @deprecated */ bboxMaxLat?: number; /** @deprecated */ planningAreaType?: string; /** @deprecated */ planningAreaId?: string; /** @deprecated */ planningAreaName?: string; /** @deprecated */ planningAreaNameQuestion?: string; } /** Create project at basePath. If should be created non-interactively then set interactive = false and provide all project creation metadata, otherwise will prompt for answers */ export async function createProject( metadata: CreateProjectMetadata, interactive = true, basePath = "", ) { const { organization, region, languages, email, gpVersion, name, description, author, license, repositoryUrl, } = metadata; // Installation path for new project const projectPath = `${basePath ? basePath + "/" : ""}${metadata.name}`; const spinner = interactive ? ora("Creating new project").start() : { start: () => false, stop: () => false, succeed: () => false, fail: () => false, }; spinner.start(`creating ${projectPath}`); await fs.ensureDir(projectPath); spinner.succeed(`created ${projectPath}/`); spinner.start("copying base project"); const baseProjectPath = getBaseProjectPath(); // Get version of geoprocessing currently running const curGpPackage: Package = JSON.parse( fs.readFileSync(`${getGeoprocessingPath()}/package.json`).toString(), ); const curGpVersion = curGpPackage.version; // Copy all files from base project template try { await fs.ensureDir(projectPath); await $`cp -r ${baseProjectPath}/* ${projectPath}`; await $`cp -r ${baseProjectPath}/. ${projectPath}`; await $`rm -f ${projectPath}/package-lock.json`; await $`rm -f ${projectPath}/project/geoprocessing.json`; await $`rm -rf ${projectPath}/examples/outputs/*.*`; await $`rm -rf ${projectPath}/examples/features/*.json`; await $`rm -rf ${projectPath}/examples/sketches/*.json`; } catch (error: unknown) { if (error instanceof Error) { console.log("Base project copy failed"); throw error; } } spinner.succeed("copied base files"); spinner.start("updating package.json with provided details"); const packageJSON: Package = { ...JSON.parse( fs.readFileSync(`${baseProjectPath}/package.json`).toString(), ), name, version: "0.1.0", description, author, license, repositoryUrl, // TODO: other repo types ...(/github/.test(metadata.repositoryUrl) ? { repository: { type: "git", url: "git+" + metadata.repositoryUrl + ".git", }, homepage: metadata.repositoryUrl + "#readme", bugs: { url: metadata.repositoryUrl + "/issues", }, } : {}), private: false, }; if (gpVersion) { spinner.start(`Installing user-defined GP version ${gpVersion}`); packageJSON.dependencies!["@seasketch/geoprocessing"] = gpVersion; spinner.succeed(`Installing user-defined GP version ${gpVersion}`); } else { packageJSON.dependencies!["@seasketch/geoprocessing"] = curGpVersion; } await fs.writeFile( `${projectPath}/package.json`, JSON.stringify(packageJSON, null, 2), ); spinner.succeed("updated package.json"); spinner.start("creating geoprocessing.json"); const geoAuthor = email ? `${metadata.author} <${email}>` : metadata.author; await fs.writeFile( `${projectPath}/project/geoprocessing.json`, JSON.stringify( { author: geoAuthor, organization: organization || "", region, clients: [], preprocessingFunctions: [], geoprocessingFunctions: [], }, null, " ", ), ); spinner.succeed("created geoprocessing.json"); spinner.start("updating basic.json"); const basic = fs.readJSONSync(`${projectPath}/project/basic.json`); const validBasic = projectSchema.parse({ ...basic, languages: ["EN", ...languages], // insert EN as required language }); await fs.writeJSONSync(`${projectPath}/project/basic.json`, validBasic, { spaces: 2, }); spinner.succeed("updated basic.json"); spinner.start("add .gitignore"); try { if (fs.existsSync(`${projectPath}/_gitignore`)) { fs.move(`${projectPath}/_gitignore`, `${projectPath}/.gitignore`); } spinner.succeed("added .gitignore"); } catch (error) { spinner.fail(".gitignore add failed"); console.error(error); } // recursively copy entire i18n directory to project space spinner.start("add i18n"); await fs.copy( `${getGeoprocessingPath()}/src/i18n`, projectPath + "/src/i18n", ); // Create i18n.json with project-specific config const configPath = `${projectPath}/project/i18n.json`; const i18nConfig = { localNamespace: "translation", remoteContext: packageJSON.name, }; await fs.writeJSON(configPath, i18nConfig, { spaces: 2 }); spinner.succeed("added i18n"); // Install dependencies including adding GP. if (interactive) { spinner.start("installing dependencies with npm"); try { await exec(`npm install`, { cwd: metadata.name, }); spinner.succeed("installed dependencies"); spinner.start("extracting translations"); await exec(`npm run extract:translation`, { cwd: metadata.name, }); } catch (error: unknown) { if (error instanceof Error) { console.log(error.message); console.log(error.stack); process.exit(); } } spinner.succeed("extracted initial translations"); } if (interactive) { console.log( chalk.blue(`\nYour geoprocessing project has been initialized!`), ); console.log(`\nNext Steps: * ${chalk.yellow( `Tutorials`, )} are available to create your first geoprocessing function and report client at https://github.com/seasketch/geoprocessing/wiki/Tutorials * ${chalk.yellow( `Translations`, )} need to be synced if you are using POEditor. Make sure POEDITOR_PROJECT and POEDITOR_API_TOKEN environemnt variables are set in your shell environment and then run 'npm run sync:translation'. See tutorials for more information `); } }