UNPKG

@storm-software/tsdown

Version:

A package containing `tsdown` utilities for building Storm Software libraries and applications

600 lines (590 loc) 22.7 kB
#!/usr/bin/env node 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(); } })();