@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
JavaScript
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
};