UNPKG

@storm-software/tsdown

Version:

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

477 lines (467 loc) 22.6 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 __name = (target, value) => __defProp(target, "name", { value, configurable: true }); 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; } __name(isPlainObject, "isPlainObject"); 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; } __name(_defu, "_defu"); function createDefu(merger) { return (...arguments_) => ( // eslint-disable-next-line unicorn/no-array-reduce arguments_.reduce((p, c) => _defu(p, c, "", merger), {}) ); } __name(createDefu, "createDefu"); 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(); } __name(clean, "clean"); async function cleanDirectories(name = "TSDown", directory, config) { await (0, import_promises.rm)(directory, { recursive: true, force: true }); } __name(cleanDirectories, "cleanDirectories"); // 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 = /* @__PURE__ */ __name(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.emitTypes === true ? { transformer: "oxc" } : userOptions.emitTypes, bundleDts: userOptions.emitTypes, 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; }, "resolveOptions"); 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; } __name(generatePackageJson, "generatePackageJson"); 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; } __name(executeTSDown, "executeTSDown"); 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; } __name(copyBuildAssets, "copyBuildAssets"); async function reportResults(options) { (0, import_console2.writeSuccess)(` \u{1F4E6} The ${options.name} build completed successfully`, options.config); } __name(reportResults, "reportResults"); 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; } __name(cleanOutputPath, "cleanOutputPath"); 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(); } } __name(build, "build"); // 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 emitTypesOption = 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(emitTypesOption).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); } } __name(createProgram, "createProgram"); var buildAction = /* @__PURE__ */ __name((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); } }, "buildAction"); var cleanAction = /* @__PURE__ */ __name((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); } }, "cleanAction"); 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(); } })();