UNPKG

@storm-software/workspace-tools

Version:

Tools for managing a Storm workspace, including various Nx generators and executors for common development tasks.

470 lines (457 loc) • 15.4 kB
import { DEFAULT_CSS_BANNER, DEFAULT_JS_BANNER, addPackageDependencies, addWorkspacePackageJsonFields, copyAssets, getEnv } from "./chunk-I4X5CIJM.mjs"; import { withRunExecutor } from "./chunk-EYZGKQNH.mjs"; import { getWorkspaceConfig } from "./chunk-4PKTZSV2.mjs"; import { brandIcon, formatLogMessage, getStopwatch, writeDebug, writeError, writeFatal, writeInfo, writeSuccess, writeWarning } from "./chunk-MMA4S6LZ.mjs"; import { joinPaths } from "./chunk-TBW5MCN6.mjs"; // ../esbuild/src/assets.ts async function copyBuildAssets(context) { if (!context.result?.errors.length && context.options.assets?.length) { writeDebug( ` \u{1F4CB} Copying ${context.options.assets.length} asset files to output directory: ${context.outputPath}`, context.workspaceConfig ); const stopwatch = getStopwatch(`${context.options.name} asset copy`); await copyAssets( context.workspaceConfig, context.options.assets ?? [], context.outputPath, context.options.projectRoot, context.sourceRoot, true, false ); stopwatch(); } return context; } // ../esbuild/src/clean.ts import { rm } from "node:fs/promises"; async function cleanDirectories(directory) { await rm(directory, { recursive: true, force: true }); } // ../esbuild/src/context.ts import { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from "@nx/devkit"; import defu from "defu"; import { existsSync } from "node:fs"; import hf from "node:fs/promises"; import { findWorkspaceRoot } from "nx/src/utils/find-workspace-root"; // ../esbuild/src/config.ts var DEFAULT_BUILD_OPTIONS = { platform: "node", target: "node22", format: "esm", mode: "production", generatePackageJson: true, includeSrc: false, keepNames: true, metafile: false, treeshake: true, splitting: true, shims: false, watch: false, bundle: true, distDir: "dist", loader: { ".aac": "file", ".css": "file", ".eot": "file", ".flac": "file", ".gif": "file", ".jpeg": "file", ".jpg": "file", ".mp3": "file", ".mp4": "file", ".ogg": "file", ".otf": "file", ".png": "file", ".svg": "file", ".ttf": "file", ".wav": "file", ".webm": "file", ".webp": "file", ".woff": "file", ".woff2": "file" }, banner: { js: DEFAULT_JS_BANNER, css: DEFAULT_CSS_BANNER } }; // ../esbuild/src/context.ts async function resolveContext(userOptions) { const projectRoot = userOptions.projectRoot; const workspaceRoot = findWorkspaceRoot(projectRoot); if (!workspaceRoot) { throw new Error("Cannot find Nx workspace root"); } const workspaceConfig = await getWorkspaceConfig(true, { workspaceRoot: workspaceRoot.dir }); writeDebug(" \u2699\uFE0F Resolving build options", workspaceConfig); const stopwatch = getStopwatch("Build options resolution"); const projectGraph = await createProjectGraphAsync({ exitOnError: true }); const projectJsonPath = joinPaths( workspaceRoot.dir, projectRoot, "project.json" ); if (!existsSync(projectJsonPath)) { throw new Error("Cannot find project.json configuration"); } const projectJsonFile = await hf.readFile(projectJsonPath, "utf8"); const projectJson = JSON.parse(projectJsonFile); const projectName = projectJson.name || userOptions.name; const projectConfigurations = readProjectsConfigurationFromProjectGraph(projectGraph); if (!projectConfigurations?.projects?.[projectName]) { throw new Error( "The Build process failed because the project does not have a valid configuration in the project.json file. Check if the file exists in the root of the project." ); } const options = defu(userOptions, DEFAULT_BUILD_OPTIONS); options.name ??= projectName; const packageJsonPath = joinPaths( workspaceRoot.dir, options.projectRoot, "package.json" ); if (!existsSync(packageJsonPath)) { throw new Error("Cannot find package.json configuration"); } const env = getEnv("esbuild", options); const define = defu(options.define ?? {}, env ?? {}); const resolvedOptions = { ...options, tsconfig: userOptions.tsconfig === null ? void 0 : userOptions.tsconfig ? userOptions.tsconfig : joinPaths(workspaceRoot.dir, projectRoot, "tsconfig.json"), metafile: userOptions.mode === "development", clean: false, env, define: { STORM_FORMAT: JSON.stringify(options.format), ...Object.keys(define).filter((key) => define[key] !== void 0).reduce((res, key) => { const value = JSON.stringify(define[key]); const safeKey = key.replaceAll("(", "").replaceAll(")", ""); return { ...res, [`process.env.${safeKey}`]: value, [`import.meta.env.${safeKey}`]: value }; }, {}) } }; stopwatch(); const context = { options: resolvedOptions, clean: userOptions.clean !== false, workspaceConfig, projectConfigurations, projectName, projectGraph, sourceRoot: resolvedOptions.sourceRoot || projectJson.sourceRoot || joinPaths(resolvedOptions.projectRoot, "src"), outputPath: resolvedOptions.outputPath || joinPaths( workspaceConfig.workspaceRoot, "dist", resolvedOptions.projectRoot ), minify: resolvedOptions.minify || resolvedOptions.mode === "production" }; context.options.esbuildPlugins = [...context.options.esbuildPlugins ?? []]; if (context.options.verbose) { writeDebug( ` \u2699\uFE0F Build options resolved: ${formatLogMessage(context.options)}`, workspaceConfig ); } return context; } // ../esbuild/src/package-json.ts import { writeJsonFile } from "@nx/devkit"; import { existsSync as existsSync2 } from "node:fs"; import hf2 from "node:fs/promises"; async function generatePackageJson(context) { if (context.options.generatePackageJson !== false && existsSync2(joinPaths(context.options.projectRoot, "package.json"))) { writeDebug(" \u270D\uFE0F Writing package.json file", context.workspaceConfig); const stopwatch = getStopwatch("Write package.json file"); const packageJsonPath = joinPaths( context.options.projectRoot, "project.json" ); if (!existsSync2(packageJsonPath)) { throw new Error("Cannot find package.json configuration"); } const packageJsonFile = await hf2.readFile( joinPaths( context.workspaceConfig.workspaceRoot, context.options.projectRoot, "package.json" ), "utf8" ); let packageJson = JSON.parse(packageJsonFile); if (!packageJson) { throw new Error("Cannot find package.json configuration file"); } packageJson = await addPackageDependencies( context.workspaceConfig.workspaceRoot, context.options.projectRoot, context.projectName, packageJson ); packageJson = await addWorkspacePackageJsonFields( context.workspaceConfig, context.options.projectRoot, context.sourceRoot, context.projectName, false, packageJson ); if (context.options.entry) { packageJson.exports ??= {}; packageJson.exports["./package.json"] ??= "./package.json"; const entryPoints = Array.isArray(context.options.entry) ? context.options.entry : Object.keys(context.options.entry); if (entryPoints.length > 0) { const defaultEntry = entryPoints.includes("index") ? `.${context.options.distDir ? `/${context.options.distDir}` : ""}/index` : `.${context.options.distDir ? `/${context.options.distDir}` : ""}/${entryPoints[0]}`; const isEsm = Array.isArray(context.options.format) ? context.options.format.includes("esm") : context.options.format === "esm"; const isCjs = Array.isArray(context.options.format) ? context.options.format.includes("cjs") : context.options.format === "cjs"; const isDts = context.options.dts || context.options.experimentalDts; packageJson.exports["."] ??= `${defaultEntry}.${isEsm ? "mjs" : isCjs ? "cjs" : "js"}`; for (const entryPoint of entryPoints) { packageJson.exports[`./${entryPoint}`] ??= {}; if (isEsm) { if (isDts) { packageJson.exports[`./${entryPoint}`].import = { types: `./dist/${entryPoint}.d.mts`, default: `./dist/${entryPoint}.mjs` }; } else { packageJson.exports[`./${entryPoint}`].import = `./dist/${entryPoint}.mjs`; } if (isDts) { packageJson.exports[`./${entryPoint}`].default = { types: `./dist/${entryPoint}.d.mts`, default: `./dist/${entryPoint}.mjs` }; } else { packageJson.exports[`./${entryPoint}`].default = `./dist/${entryPoint}.mjs`; } } if (isCjs) { if (isDts) { packageJson.exports[`./${entryPoint}`].require = { types: `./dist/${entryPoint}.d.cts`, default: `./dist/${entryPoint}.cjs` }; } else { packageJson.exports[`./${entryPoint}`].require = `./dist/${entryPoint}.cjs`; } if (!isEsm) { if (isDts) { packageJson.exports[`./${entryPoint}`].default = { types: `./dist/${entryPoint}.d.cts`, default: `./dist/${entryPoint}.cjs` }; } else { packageJson.exports[`./${entryPoint}`].default = `./dist/${entryPoint}.cjs`; } } } if (!isEsm && !isCjs) { if (isDts) { packageJson.exports[`./${entryPoint}`].default = { types: `./dist/${entryPoint}.d.ts`, default: `./dist/${entryPoint}.js` }; } else { packageJson.exports[`./${entryPoint}`].default = `./dist/${entryPoint}.js`; } } } if (isEsm) { packageJson.module = `${defaultEntry}.mjs`; } else { packageJson.main = `${defaultEntry}.cjs`; } if (isDts) { packageJson.types = `${defaultEntry}.d.${isEsm ? "mts" : isCjs ? "cts" : "ts"}`; } packageJson.exports = Object.keys(packageJson.exports).reduce( (ret, key) => { if (key.endsWith("/index") && !ret[key.replace("/index", "")]) { ret[key.replace("/index", "")] = packageJson.exports[key]; } return ret; }, packageJson.exports ); } } await writeJsonFile( joinPaths(context.outputPath, "package.json"), packageJson ); stopwatch(); } return context; } // ../tsup/src/index.ts import { build as tsup } from "tsup"; async function build(options) { if (!options.silent) { writeDebug( ` \u{1F680} Running ${options.name || "tsup"} build`, options.workspaceConfig ); } const stopwatch = getStopwatch(`${options.name || "tsup"} build`); await tsup(options); if (!options.silent) { stopwatch(); } } // ../esbuild/src/tsup.ts async function executeTsup(context) { writeDebug( ` \u{1F680} Running ${context.options.name} build`, context.workspaceConfig ); const stopwatch = getStopwatch(`${context.options.name} build`); await build({ ...context.options, outDir: context.options.distDir ? joinPaths(context.outputPath, context.options.distDir) : context.outputPath, workspaceConfig: context.workspaceConfig }); stopwatch(); return context; } // ../esbuild/src/build.ts async function reportResults(context) { if (context.result?.errors.length === 0) { if (context.result.warnings.length > 0) { writeWarning( ` \u{1F6A7} The following warnings occurred during the build: ${context.result.warnings.map((warning) => warning.text).join("\n")}`, context.workspaceConfig ); } writeSuccess( ` \u{1F4E6} The ${context.options.name} build completed successfully`, context.workspaceConfig ); } else if (context.result?.errors && context.result?.errors.length > 0) { writeError( ` \u274C The ${context.options.name} build failed with the following errors: ${context.result.errors.map((error) => error.text).join("\n")}`, context.workspaceConfig ); throw new Error( `The ${context.options.name} build failed with the following errors: ${context.result.errors.map((error) => error.text).join("\n")}` ); } } async function cleanOutputPath(context) { if (context.clean !== false && context.outputPath) { writeDebug( ` \u{1F9F9} Cleaning ${context.options.name} output path: ${context.outputPath}`, context.workspaceConfig ); const stopwatch = getStopwatch(`${context.options.name} output clean`); await cleanDirectories(context.outputPath); stopwatch(); } return context; } async function build2(options) { writeDebug(` ${brandIcon()} Executing Storm ESBuild pipeline`); const stopwatch = getStopwatch("ESBuild pipeline"); try { const opts = Array.isArray(options) ? options : [options]; if (opts.length === 0) { throw new Error("No build options were provided"); } const context = await resolveContext(options); await cleanOutputPath(context); await Promise.all([ // dependencyCheck(context.options), generatePackageJson(context), copyBuildAssets(context), executeTsup(context) ]); await reportResults(context); writeSuccess(" \u{1F3C1} ESBuild pipeline build completed successfully"); } catch (error) { writeFatal( "Fatal errors that the build process could not recover from have occured. The build process has been terminated." ); throw error; } finally { stopwatch(); } } // src/executors/esbuild/executor.ts async function esbuildExecutorFn(options, context, config) { writeInfo("\u{1F4E6} Running Storm ESBuild executor on the workspace", config); if (!context.projectsConfigurations?.projects || !context.projectName || !context.projectsConfigurations.projects[context.projectName] || !context.projectsConfigurations.projects[context.projectName]?.root) { throw new Error( "The Build process failed because the context is not valid. Please run this command from a workspace." ); } await build2({ ...options, projectRoot: ( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion context.projectsConfigurations.projects?.[context.projectName].root ), name: context.projectName, sourceRoot: context.projectsConfigurations.projects?.[context.projectName]?.sourceRoot, format: options.format, platform: options.platform }); return { success: true }; } var executor_default = withRunExecutor( "Storm ESBuild build", esbuildExecutorFn, { skipReadingConfig: false, hooks: { applyDefaultOptions: async (options) => { options.entry ??= ["src/index.ts"]; options.outputPath ??= "dist/{projectRoot}"; options.tsconfig ??= "{projectRoot}/tsconfig.json"; return options; } } } ); export { esbuildExecutorFn, executor_default };