@storm-software/tsdown
Version:
A package containing `tsdown` utilities for building Storm Software libraries and applications
600 lines (590 loc) • 22.7 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// bin/tsdown.ts
var import_get_config2 = require("@storm-software/config-tools/get-config");
var import_console3 = require("@storm-software/config-tools/logger/console");
var import_find_workspace_root2 = require("@storm-software/config-tools/utilities/find-workspace-root");
var import_process_handler = require("@storm-software/config-tools/utilities/process-handler");
var import_commander = require("commander");
// src/build.ts
var import_devkit = require("@nx/devkit");
var import_build_tools = require("@storm-software/build-tools");
var import_get_config = require("@storm-software/config-tools/get-config");
var import_console2 = require("@storm-software/config-tools/logger/console");
var import_get_log_level = require("@storm-software/config-tools/logger/get-log-level");
var import_correct_paths = require("@storm-software/config-tools/utilities/correct-paths");
// ../../node_modules/.pnpm/defu@6.1.4/node_modules/defu/dist/defu.mjs
function isPlainObject(value) {
if (value === null || typeof value !== "object") {
return false;
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) {
return false;
}
if (Symbol.iterator in value) {
return false;
}
if (Symbol.toStringTag in value) {
return Object.prototype.toString.call(value) === "[object Module]";
}
return true;
}
function _defu(baseObject, defaults, namespace = ".", merger) {
if (!isPlainObject(defaults)) {
return _defu(baseObject, {}, namespace, merger);
}
const object = Object.assign({}, defaults);
for (const key in baseObject) {
if (key === "__proto__" || key === "constructor") {
continue;
}
const value = baseObject[key];
if (value === null || value === void 0) {
continue;
}
if (merger && merger(object, key, value, namespace)) {
continue;
}
if (Array.isArray(value) && Array.isArray(object[key])) {
object[key] = [...value, ...object[key]];
} else if (isPlainObject(value) && isPlainObject(object[key])) {
object[key] = _defu(
value,
object[key],
(namespace ? `${namespace}.` : "") + key.toString(),
merger
);
} else {
object[key] = value;
}
}
return object;
}
function createDefu(merger) {
return (...arguments_) => (
// eslint-disable-next-line unicorn/no-array-reduce
arguments_.reduce((p, c) => _defu(p, c, "", merger), {})
);
}
var defu = createDefu();
var defuFn = createDefu((object, key, currentValue) => {
if (object[key] !== void 0 && typeof currentValue === "function") {
object[key] = currentValue(object[key]);
return true;
}
});
var defuArrayFn = createDefu((object, key, currentValue) => {
if (Array.isArray(object[key]) && typeof currentValue === "function") {
object[key] = currentValue(object[key]);
return true;
}
});
// src/build.ts
var import_node_fs = require("fs");
var import_promises2 = __toESM(require("fs/promises"), 1);
var import_find_workspace_root = require("nx/src/utils/find-workspace-root");
var import_tsdown = require("tsdown");
// src/clean.ts
var import_console = require("@storm-software/config-tools/logger/console");
var import_promises = require("fs/promises");
async function clean(name = "TSDown", directory, config) {
(0, import_console.writeDebug)(` \u{1F9F9} Cleaning ${name} output path: ${directory}`, config);
const stopwatch = (0, import_console.getStopwatch)(`${name} output clean`);
await cleanDirectories(name, directory, config);
stopwatch();
}
async function cleanDirectories(name = "TSDown", directory, config) {
await (0, import_promises.rm)(directory, { recursive: true, force: true });
}
// src/config.ts
var DEFAULT_BUILD_OPTIONS = {
platform: "node",
target: "node22",
format: ["esm", "cjs"],
tsconfig: "tsconfig.json",
mode: "production",
globalName: "globalThis",
unused: { level: "error" },
injectShims: true,
watch: false,
bundle: true,
treeshake: true,
clean: true,
debug: false
};
// src/build.ts
var resolveOptions = async (userOptions) => {
const projectRoot = userOptions.projectRoot;
const workspaceRoot = (0, import_find_workspace_root.findWorkspaceRoot)(projectRoot);
if (!workspaceRoot) {
throw new Error("Cannot find Nx workspace root");
}
const config = await (0, import_get_config.getConfig)(workspaceRoot.dir);
(0, import_console2.writeDebug)(" \u2699\uFE0F Resolving build options", config);
const stopwatch = (0, import_console2.getStopwatch)("Build options resolution");
const projectGraph = await (0, import_devkit.createProjectGraphAsync)({
exitOnError: true
});
const projectJsonPath = (0, import_correct_paths.joinPaths)(
workspaceRoot.dir,
projectRoot,
"project.json"
);
if (!(0, import_node_fs.existsSync)(projectJsonPath)) {
throw new Error("Cannot find project.json configuration");
}
const projectJsonFile = await import_promises2.default.readFile(projectJsonPath, "utf8");
const projectJson = JSON.parse(projectJsonFile);
const projectName = projectJson.name;
const projectConfigurations = (0, import_devkit.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}-${options.format}`;
options.target ??= import_build_tools.DEFAULT_TARGET;
const packageJsonPath = (0, import_correct_paths.joinPaths)(
workspaceRoot.dir,
options.projectRoot,
"package.json"
);
if (!(0, import_node_fs.existsSync)(packageJsonPath)) {
throw new Error("Cannot find package.json configuration");
}
const env = (0, import_build_tools.getEnv)("tsdown", options);
const result = {
...options,
config,
...userOptions,
tsconfig: (0, import_correct_paths.joinPaths)(
projectRoot,
userOptions.tsconfig ? userOptions.tsconfig.replace(projectRoot, "") : "tsconfig.json"
),
format: options.format || "cjs",
entryPoints: await (0, import_build_tools.getEntryPoints)(
config,
projectRoot,
projectJson.sourceRoot,
userOptions.entry || ["./src/index.ts"],
userOptions.emitOnAll
),
outdir: userOptions.outputPath || (0, import_correct_paths.joinPaths)("dist", projectRoot),
plugins: [],
name: userOptions.name || projectName,
projectConfigurations,
projectName,
projectGraph,
sourceRoot: userOptions.sourceRoot || projectJson.sourceRoot || (0, import_correct_paths.joinPaths)(projectRoot, "src"),
minify: userOptions.minify || !userOptions.debug,
verbose: userOptions.verbose || (0, import_get_log_level.isVerbose)() || userOptions.debug === true,
includeSrc: userOptions.includeSrc === true,
metafile: userOptions.metafile !== false,
generatePackageJson: userOptions.generatePackageJson !== false,
clean: userOptions.clean !== false,
emitOnAll: userOptions.emitOnAll === true,
dts: userOptions.dts === true ? { transformer: "oxc" } : userOptions.dts,
bundleDts: userOptions.dts,
assets: userOptions.assets ?? [],
shims: userOptions.injectShims !== true,
bundle: userOptions.bundle !== false,
watch: userOptions.watch === true,
define: {
STORM_FORMAT: JSON.stringify(options.format || "cjs"),
...options.format === "cjs" && options.injectShims ? {
"import.meta.url": "importMetaUrl"
} : {},
...Object.keys(env || {}).reduce((res, key) => {
const value = JSON.stringify(env[key]);
return {
...res,
[`process.env.${key}`]: value,
[`import.meta.env.${key}`]: value
};
}, {}),
...options.define
}
};
stopwatch();
return result;
};
async function generatePackageJson(options) {
if (options.generatePackageJson !== false && (0, import_node_fs.existsSync)((0, import_correct_paths.joinPaths)(options.projectRoot, "package.json"))) {
(0, import_console2.writeDebug)(" \u270D\uFE0F Writing package.json file", options.config);
const stopwatch = (0, import_console2.getStopwatch)("Write package.json file");
const packageJsonPath = (0, import_correct_paths.joinPaths)(options.projectRoot, "project.json");
if (!(0, import_node_fs.existsSync)(packageJsonPath)) {
throw new Error("Cannot find package.json configuration");
}
const packageJsonFile = await import_promises2.default.readFile(
(0, import_correct_paths.joinPaths)(
options.config.workspaceRoot,
options.projectRoot,
"package.json"
),
"utf8"
);
if (!packageJsonFile) {
throw new Error("Cannot find package.json configuration file");
}
let packageJson = JSON.parse(packageJsonFile);
packageJson = await (0, import_build_tools.addPackageDependencies)(
options.config.workspaceRoot,
options.projectRoot,
options.projectName,
packageJson
);
packageJson = await (0, import_build_tools.addWorkspacePackageJsonFields)(
options.config,
options.projectRoot,
options.sourceRoot,
options.projectName,
false,
packageJson
);
packageJson.exports ??= {};
packageJson.exports["./package.json"] ??= "./package.json";
packageJson.exports["."] ??= (0, import_build_tools.addPackageJsonExport)(
"index",
packageJson.type,
options.sourceRoot
);
let entryPoints = [{ in: "./src/index.ts", out: "./src/index.ts" }];
if (options.entryPoints) {
if (Array.isArray(options.entryPoints)) {
entryPoints = options.entryPoints.map(
(entryPoint) => typeof entryPoint === "string" ? { in: entryPoint, out: entryPoint } : entryPoint
);
}
for (const entryPoint of entryPoints) {
const split = entryPoint.out.split(".");
split.pop();
const entry = split.join(".").replaceAll("\\", "/");
packageJson.exports[`./${entry}`] ??= (0, import_build_tools.addPackageJsonExport)(
entry,
packageJson.type,
options.sourceRoot
);
}
}
packageJson.main = packageJson.type === "commonjs" ? "./dist/index.js" : "./dist/index.cjs";
packageJson.module = packageJson.type === "module" ? "./dist/index.js" : "./dist/index.mjs";
packageJson.types = "./dist/index.d.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 (0, import_devkit.writeJsonFile)((0, import_correct_paths.joinPaths)(options.outdir, "package.json"), packageJson);
stopwatch();
}
return options;
}
async function executeTSDown(options) {
(0, import_console2.writeDebug)(` \u{1F680} Running ${options.name} build`, options.config);
const stopwatch = (0, import_console2.getStopwatch)(`${options.name} build`);
await (0, import_tsdown.build)({
...options,
entry: options.entryPoints,
outDir: options.outdir,
config: false
});
stopwatch();
return options;
}
async function copyBuildAssets(options) {
(0, import_console2.writeDebug)(
` \u{1F4CB} Copying asset files to output directory: ${options.outdir}`,
options.config
);
const stopwatch = (0, import_console2.getStopwatch)(`${options.name} asset copy`);
await (0, import_build_tools.copyAssets)(
options.config,
options.assets ?? [],
options.outdir,
options.projectRoot,
options.sourceRoot,
true,
false
);
stopwatch();
return options;
}
async function reportResults(options) {
(0, import_console2.writeSuccess)(
` \u{1F4E6} The ${options.name} build completed successfully`,
options.config
);
}
async function cleanOutputPath(options) {
if (options.clean !== false && options.outdir) {
(0, import_console2.writeDebug)(
` \u{1F9F9} Cleaning ${options.name} output path: ${options.outdir}`,
options.config
);
const stopwatch = (0, import_console2.getStopwatch)(`${options.name} output clean`);
await cleanDirectories(options.name, options.outdir, options.config);
stopwatch();
}
return options;
}
async function build(options) {
(0, import_console2.writeDebug)(` \u26A1 Executing Storm TSDown pipeline`);
const stopwatch = (0, import_console2.getStopwatch)("TSDown pipeline");
try {
const opts = Array.isArray(options) ? options : [options];
if (opts.length === 0) {
throw new Error("No build options were provided");
}
const resolved = await Promise.all(
opts.map(async (opt) => await resolveOptions(opt))
);
if (resolved.length > 0) {
await cleanOutputPath(resolved[0]);
await generatePackageJson(resolved[0]);
await Promise.all(
resolved.map(async (opt) => {
await executeTSDown(opt);
await copyBuildAssets(opt);
await reportResults(opt);
})
);
} else {
(0, import_console2.writeWarning)(
" \u{1F6A7} No options were passed to TSBuild. Please check the parameters passed to the `build` function."
);
}
(0, import_console2.writeSuccess)(" \u{1F3C1} TSDown pipeline build completed successfully");
} catch (error) {
(0, import_console2.writeFatal)(
"Fatal errors that the build process could not recover from have occured. The build process has been terminated."
);
throw error;
} finally {
stopwatch();
}
}
// bin/tsdown.ts
async function createProgram(config) {
try {
(0, import_console3.writeInfo)("\u26A1 Running Storm TSDown pipeline", config);
const root = (0, import_find_workspace_root2.findWorkspaceRootSafe)();
process.env.STORM_WORKSPACE_ROOT ??= root;
process.env.NX_WORKSPACE_ROOT_PATH ??= root;
if (root) {
process.chdir(root);
}
const program = new import_commander.Command("storm-tsdown");
program.version("1.0.0", "-v --version", "display CLI version");
program.description("\u26A1 Run the Storm TSDown pipeline").showHelpAfterError().showSuggestionAfterError();
const projectRootOption = new import_commander.Option(
"-p --project-root <path>",
"The path to the root of the project to build. This path is defined relative to the workspace root."
).makeOptionMandatory(true);
const sourceRootOption = new import_commander.Option(
"-s --source-root <path>",
"The path of the project's source folder to build"
);
const nameOption = new import_commander.Option(
"-n --name <value>",
"The name of the project to build"
);
const outputPathOption = new import_commander.Option(
"-o --output-path <path>",
"The path of the project's source folder to build"
).default("dist/{projectRoot}");
const platformOption = new import_commander.Option(
"-p --platform <value>",
"The platform to build the distribution for"
).choices(["node", "neutral", "browser"]).default("node");
const formatOption = new import_commander.Option(
"-f, --format <value...>",
"The format to build the distribution in"
).choices(["esm", "cjs", "iife"]).argParser((value, previous) => {
if (previous === void 0) {
return [value];
} else if (!previous.includes(value)) {
previous.push(value);
}
return previous;
}).default("esm");
const cleanOption = new import_commander.Option(
"-c --clean",
"Should the output directory be cleaned before building"
).default(true);
const noCleanOption = new import_commander.Option(
"--no-clean",
"Should the output directory be cleaned before building"
).default(false);
const bundleOption = new import_commander.Option(
"-b --bundle",
"Should the output be bundled"
).default(true);
const noBundleOption = new import_commander.Option(
"--no-bundle",
"Should the output be bundled"
).default(false);
const targetOption = new import_commander.Option(
"-t --target <value>",
"The target to build the distribution for"
).choices([
"ESNext",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020",
"ES2021",
"ES2022",
"ES2023"
]).default("ESNext");
const watchOption = new import_commander.Option(
"-w --watch",
"Should the build process watch for changes"
).default(false);
const debugOption = new import_commander.Option(
"-d --debug",
"Should the build process run in debug mode"
).default(false);
const bannerOption = new import_commander.Option(
"--banner <value>",
"The banner to prepend to the output"
);
const footerOption = new import_commander.Option(
"--footer <value>",
"The footer to prepend to the output"
);
const splittingOption = new import_commander.Option(
"--splitting",
"Should the output be split into multiple files"
).default(true);
const treeShakingOption = new import_commander.Option(
"--tree-shaking",
"Should tree shaking be enabled"
).default(true);
const generatePackageJsonOption = new import_commander.Option(
"--generate-package-json",
"Should a package.json be generated for the output"
).default(true);
const emitOnAllOption = new import_commander.Option(
"--emit-on-all",
"Should the output be emitted on all platforms"
).default(false);
const metafileOption = new import_commander.Option(
"--metafile",
"Should a metafile be generated for the output"
).default(true);
const minifyOption = new import_commander.Option(
"--minify",
"Should the output be minified"
).default(true);
const includeSrcOption = new import_commander.Option(
"--include-src",
"Should the source files be included in the output"
).default(false);
const verboseOption = new import_commander.Option(
"--verbose",
"Should the build process be verbose"
).default(false);
const injectShimsOption = new import_commander.Option(
"--inject-shims",
"Should shims be injected into the output"
).default(true);
const dtsOption = new import_commander.Option(
"--emit-types",
"Should types be emitted for the output"
).default(true);
program.command("build", { isDefault: true }).alias("bundle").description(
"Run a TypeScript build using TSDown, API-Extractor, and TSC (for type generation)."
).addOption(nameOption).addOption(projectRootOption).addOption(sourceRootOption).addOption(outputPathOption).addOption(platformOption).addOption(formatOption).addOption(targetOption).addOption(bundleOption).addOption(noBundleOption).addOption(cleanOption).addOption(noCleanOption).addOption(watchOption).addOption(debugOption).addOption(bannerOption).addOption(footerOption).addOption(splittingOption).addOption(treeShakingOption).addOption(generatePackageJsonOption).addOption(emitOnAllOption).addOption(metafileOption).addOption(minifyOption).addOption(includeSrcOption).addOption(verboseOption).addOption(injectShimsOption).addOption(dtsOption).action(buildAction(config));
program.command("clean").alias("clear").description(
"Clean the output directory of the project. This command will remove the 'dist' folder."
).addOption(nameOption).action(cleanAction(config));
return program;
} catch (e) {
(0, import_console3.writeFatal)(
`A fatal error occurred while running the program: ${e.message}`,
config
);
process.exit(1);
}
}
var buildAction = (config) => async (options) => {
try {
await build({
...options,
format: options.format
});
} catch (e) {
(0, import_console3.writeFatal)(
`A fatal error occurred while cleaning the TSDown output directory: ${e.message}`,
config
);
(0, import_process_handler.exitWithError)(config);
process.exit(1);
}
};
var cleanAction = (config) => async (options) => {
try {
await clean(options.name, options.output, config);
} catch (e) {
(0, import_console3.writeFatal)(
`A fatal error occurred while cleaning the TSDown output directory: ${e.message}`,
config
);
(0, import_process_handler.exitWithError)(config);
process.exit(1);
}
};
void (async () => {
const config = await (0, import_get_config2.getConfig)();
const stopwatch = (0, import_console3.getStopwatch)("Storm TSDown executable");
try {
(0, import_process_handler.handleProcess)(config);
const program = await createProgram(config);
await program.parseAsync(process.argv);
(0, import_console3.writeSuccess)(
`\u{1F389} Storm TSDown executable has completed successfully!`,
config
);
(0, import_process_handler.exitWithSuccess)(config);
} catch (error) {
(0, import_console3.writeFatal)(
`A fatal error occurred while running Storm TSDown executable:
${error.message}`,
config
);
(0, import_process_handler.exitWithError)(config);
process.exit(1);
} finally {
stopwatch();
}
})();