zshy
Version:
Gold-standard build tool for TypeScript libraries
871 lines (869 loc) • 40.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.main = main;
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const arg_1 = __importDefault(require("arg"));
const globby_1 = require("globby");
const table_1 = require("table");
const ts = __importStar(require("typescript"));
const compile_js_1 = require("./compile.cjs");
const utils_js_1 = require("./utils.cjs");
async function main() {
///////////////////////////////////
/// parse command line args ///
///////////////////////////////////
// Parse command line arguments using arg library
let args;
try {
args = (0, arg_1.default)({
"--help": Boolean,
"--verbose": Boolean,
"--project": String,
"--dry-run": Boolean,
"--fail-threshold": String,
// "--attw": Boolean,
// Aliases
"-h": "--help",
"-p": "--project",
});
}
catch (error) {
if (error instanceof Error) {
(0, utils_js_1.emojiLog)("❌", error.message, "error");
}
console.error(`Use --help for usage information`);
process.exit(1);
}
// Handle help flag
if (args["--help"]) {
console.log(`
Usage: zshy [options]
Options:
-h, --help Show this help message
-p, --project <path> Path to tsconfig.json file
--verbose Enable verbose output
--dry-run Don't write any files, just log what would be done
--fail-threshold <threshold> When to exit with non-zero error code
"error" (default)
"warn"
"never"
Examples:
zshy # Run build
zshy --project ./tsconfig.build.json # Use specific tsconfig file (defaults to tsconfig.json)
zshy --verbose # Enable verbose logging
zshy --dry-run # Preview changes without writing files
`);
process.exit(0);
}
const userAgent = process.env.npm_config_user_agent;
let pmExec;
if (userAgent?.startsWith("pnpm")) {
pmExec = "pnpm exec";
}
else if (userAgent?.startsWith("yarn")) {
pmExec = "yarn exec";
}
else {
pmExec = "npx";
}
console.log(` ╔═══════════════════════════════════════════════╗`);
console.log(` ║ zshy » the bundler-free TypeScript build tool ║`);
console.log(` ╚═══════════════════════════════════════════════╝`);
(0, utils_js_1.emojiLog)("💎", "Starting build...");
const isVerbose = !!args["--verbose"];
const isDryRun = !!args["--dry-run"];
const failThreshold = args["--fail-threshold"] || "error"; // Default to 'error'
const isCjsInterop = true; // Enable CJS interop for testing
// Validate that the threshold value is one of the allowed values
if (failThreshold !== "never" && failThreshold !== "warn" && failThreshold !== "error") {
(0, utils_js_1.emojiLog)("❌", `Invalid value for --fail-threshold: "${failThreshold}". Valid values are "never", "warn", or "error"`, "error");
process.exit(1);
}
const isAttw = false; // args["--attw"];
if (isVerbose) {
(0, utils_js_1.emojiLog)("ℹ️", "Verbose mode enabled");
(0, utils_js_1.emojiLog)("📦", `Detected package manager: ${pmExec}`);
}
if (isDryRun) {
(0, utils_js_1.emojiLog)("🔍", "Dry run mode enabled. No files will be written or modified.");
}
// Display message about fail threshold setting
if (isVerbose) {
if (failThreshold === "never") {
(0, utils_js_1.emojiLog)("ℹ️", "Build will always succeed regardless of errors or warnings");
}
else if (failThreshold === "warn") {
(0, utils_js_1.emojiLog)("⚠️", "Build will fail on warnings or errors");
}
else {
(0, utils_js_1.emojiLog)("ℹ️", "Build will fail only on errors (default)");
}
}
///////////////////////////////////
/// find and read pkg json ///
///////////////////////////////////
// Find package.json by scanning up the file system
let packageJsonPath = "./package.json";
let currentDir = process.cwd();
while (currentDir !== path.dirname(currentDir)) {
const candidatePath = path.join(currentDir, "package.json");
if (fs.existsSync(candidatePath)) {
packageJsonPath = candidatePath;
break;
}
currentDir = path.dirname(currentDir);
}
if (!fs.existsSync(packageJsonPath)) {
(0, utils_js_1.emojiLog)("❌", "package.json not found in current directory or any parent directories", "error");
process.exit(1);
}
// read package.json and extract the "zshy" exports config
const pkgJsonRaw = fs.readFileSync(packageJsonPath, "utf-8");
// console.log("📦 Extracting entry points from package.json exports...");
const pkgJson = JSON.parse(pkgJsonRaw);
// Detect indentation from package.json to preserve it.
let indent = 2; // Default to 2 spaces
const indentMatch = pkgJsonRaw.match(/^([ \t]+)/m);
if (indentMatch?.[1]) {
indent = indentMatch[1];
}
else if (!pkgJsonRaw.includes("\n")) {
indent = 0; // minified
}
const pkgJsonDir = path.dirname(packageJsonPath);
const pkgJsonRelPath = (0, utils_js_1.relativePosix)(pkgJsonDir, packageJsonPath);
// print project root
(0, utils_js_1.emojiLog)("⚙️", `Detected project root: ${pkgJsonDir}`);
(0, utils_js_1.emojiLog)("📦", `Reading package.json from ./${pkgJsonRelPath}`);
/////////////////////////////////
/// parse zshy config ///
/////////////////////////////////
// const pkgJson = JSON.parse(fs.readFileSync("./package.json", "utf-8"));
const CONFIG_KEY = "zshy";
let rawConfig;
if (!pkgJson[CONFIG_KEY]) {
(0, utils_js_1.emojiLog)("❌", `No "${CONFIG_KEY}" key found in package.json`, "error");
process.exit(1);
}
if (typeof pkgJson[CONFIG_KEY] === "string") {
rawConfig = {
exports: { ".": pkgJson[CONFIG_KEY] },
};
}
else if (typeof pkgJson[CONFIG_KEY] === "object") {
rawConfig = { ...pkgJson[CONFIG_KEY] };
if (typeof rawConfig.exports === "string") {
rawConfig.exports = { ".": rawConfig.exports };
}
else if (typeof rawConfig.exports === "undefined") {
(0, utils_js_1.emojiLog)("❌", `Missing "exports" key in package.json#/${CONFIG_KEY}`, "error");
process.exit(1);
}
else if (typeof rawConfig.exports !== "object") {
(0, utils_js_1.emojiLog)("❌", `Invalid "exports" key in package.json#/${CONFIG_KEY}`, "error");
process.exit(1);
}
// Validate bin field if present
if (rawConfig.bin !== undefined) {
if (typeof rawConfig.bin === "string") {
// Keep string format - we'll handle this in entry point extraction
}
else if (typeof rawConfig.bin === "object" && rawConfig.bin !== null) {
// Object format is valid
}
else {
(0, utils_js_1.emojiLog)("❌", `Invalid "bin" key in package.json#/${CONFIG_KEY}, expected string or object`, "error");
process.exit(1);
}
}
// Validate conditions field if present
if (rawConfig.conditions !== undefined) {
if (typeof rawConfig.conditions === "object" && rawConfig.conditions !== null) {
// const { import: importCondition, require: requireCondition, ...rest } = config.conditions;
for (const [condition, value] of Object.entries(rawConfig.conditions)) {
if (value !== "esm" && value !== "cjs" && value !== "src") {
(0, utils_js_1.emojiLog)("❌", `Invalid condition value "${value}" for "${condition}" in package.json#/${CONFIG_KEY}/conditions. Valid values are "esm", "cjs", "src", or null`, "error");
process.exit(1);
}
}
}
else {
(0, utils_js_1.emojiLog)("❌", `Invalid "conditions" key in package.json#/${CONFIG_KEY}, expected object`, "error");
process.exit(1);
}
}
}
else if (typeof pkgJson[CONFIG_KEY] === "undefined") {
(0, utils_js_1.emojiLog)("❌", `Missing "${CONFIG_KEY}" key in package.json`, "error");
process.exit(1);
}
else {
(0, utils_js_1.emojiLog)("❌", `Invalid "${CONFIG_KEY}" key in package.json, expected string or object`, "error");
process.exit(1);
}
if (isVerbose) {
(0, utils_js_1.emojiLog)("🔧", `Parsed zshy config: ${(0, utils_js_1.formatForLog)(rawConfig)}`);
}
// Check for deprecated sourceDialects
if ("sourceDialects" in rawConfig) {
(0, utils_js_1.emojiLog)("❌", 'The "sourceDialects" option is no longer supported. Use "conditions" instead to configure custom export conditions.', "error");
process.exit(1);
}
const config = { ...rawConfig };
// Normalize cjs property
if (config.cjs === undefined) {
config.cjs = true; // Default to true if not specified
}
config.noEdit ?? (config.noEdit = false);
// Validate that if cjs is disabled, no conditions are set to "cjs"
if (config.cjs === false && config.conditions) {
const cjsConditions = Object.entries(config.conditions).filter(([_, value]) => value === "cjs");
if (cjsConditions.length > 0) {
const conditionNames = cjsConditions.map(([name]) => name).join(", ");
(0, utils_js_1.emojiLog)("❌", `CJS is disabled (cjs: false) but the following conditions are set to "cjs": ${conditionNames}. Either enable CJS or change these conditions.`, "error");
process.exit(1);
}
}
// Validate that if cjs is disabled, package.json type must be "module"
if (config.cjs === false && pkgJson.type !== "module") {
(0, utils_js_1.emojiLog)("❌", `CJS is disabled (cjs: false) but package.json#/type is not set to "module". When disabling CommonJS builds, you must set "type": "module" in your package.json.`, "error");
process.exit(1);
}
///////////////////////////
/// read tsconfig ///
///////////////////////////
// Determine tsconfig.json path
let tsconfigPath;
if (args["--project"]) {
// CLI flag takes priority
const resolvedProjectPath = path.resolve(process.cwd(), args["--project"]);
if (fs.existsSync(resolvedProjectPath)) {
if (fs.statSync(resolvedProjectPath).isDirectory()) {
(0, utils_js_1.emojiLog)("❌", `--project must point to a tsconfig.json file, not a directory: ${resolvedProjectPath}`, "error");
process.exit(1);
}
else {
// Use the file directly
tsconfigPath = resolvedProjectPath;
}
}
else {
(0, utils_js_1.emojiLog)("❌", `tsconfig.json file not found: ${resolvedProjectPath}`, "error");
process.exit(1);
}
}
else if (config.tsconfig) {
// Fallback to package.json config
const resolvedProjectPath = path.resolve(pkgJsonDir, config.tsconfig);
if (fs.existsSync(resolvedProjectPath)) {
if (fs.statSync(resolvedProjectPath).isDirectory()) {
(0, utils_js_1.emojiLog)("❌", `zshy.tsconfig must point to a tsconfig.json file, not a directory: ${resolvedProjectPath}`, "error");
process.exit(1);
}
else {
// Use the file directly
tsconfigPath = resolvedProjectPath;
}
}
else {
(0, utils_js_1.emojiLog)("❌", `Tsconfig file not found: ${resolvedProjectPath}`, "error");
process.exit(1);
}
}
else {
// Default to tsconfig.json in the package.json directory
tsconfigPath = path.join(pkgJsonDir, "tsconfig.json");
}
const _parsedConfig = (0, utils_js_1.readTsconfig)(tsconfigPath);
if (!fs.existsSync(tsconfigPath)) {
// Check if tsconfig.json exists
(0, utils_js_1.emojiLog)("❌", `tsconfig.json not found at ${(0, utils_js_1.toPosix)(path.resolve(tsconfigPath))}`, "error");
process.exit(1);
}
(0, utils_js_1.emojiLog)("📁", `Reading tsconfig from ./${(0, utils_js_1.relativePosix)(pkgJsonDir, tsconfigPath)}`);
// if (_parsedConfig.rootDir) {
// console.error(
// `❌ rootDir is determined from your set of entrypoints; you can remove it from your tsconfig.json.`,
// );
// process.exit(1);
// }
// if (_parsedConfig.declarationDir) {
// console.error(
// `❌ declarationDir is not supported in zshy; you should remove it from your tsconfig.json.`,
// );
// process.exit(1);
// }
// set/override compiler options
delete _parsedConfig.customConditions; // can't be set for CommonJS builds
const outDir = path.resolve(pkgJsonDir, _parsedConfig?.outDir || "./dist");
const relOutDir = (0, utils_js_1.relativePosix)(pkgJsonDir, outDir);
const declarationDir = path.resolve(pkgJsonDir, _parsedConfig?.declarationDir || relOutDir);
const relDeclarationDir = (0, utils_js_1.relativePosix)(pkgJsonDir, declarationDir);
const tsconfigJson = {
..._parsedConfig,
outDir,
target: _parsedConfig.target ?? ts.ScriptTarget.ES2020, // ensure compatible target for CommonJS
skipLibCheck: true, // skip library checks to reduce errors
declaration: true,
esModuleInterop: true,
noEmit: false,
emitDeclarationOnly: false,
rewriteRelativeImportExtensions: true,
verbatimModuleSyntax: false,
composite: false,
};
if (relOutDir === "") {
if (!pkgJson.files) {
(0, utils_js_1.emojiLog)("⚠️", 'You\'re building your code to the project root. This means your compiled files will be generated alongside your source files.\n ➜ Setting "files" in package.json to exclude TypeScript source from the published package.');
pkgJson.files = ["**/*.js", "**/*.mjs", "**/*.cjs", "**/*.d.ts", "**/*.d.mts", "**/*.d.cts"];
}
else {
(0, utils_js_1.emojiLog)(`⚠️`, `You\'re building your code to the project root. This means your compiled files will be generated alongside your source files.
Ensure that your "files" in package.json excludes TypeScript source files, or your users may experience .d.ts resolution issues in some environments:
"files": ["**/*.js", "**/*.mjs", "**/*.cjs", "**/*.d.ts", "**/*.d.mts", "**/*.d.cts"]`);
}
}
else {
if (!pkgJson.files) {
(0, utils_js_1.emojiLog)("⚠️", `The "files" key is missing in package.json. Setting to "${relOutDir}".`);
pkgJson.files = [relOutDir];
if (relOutDir !== relDeclarationDir) {
pkgJson.files.push(relDeclarationDir);
}
}
}
/////////////////////////////////
/// extract entry points ///
/////////////////////////////////
// Extract entry points from zshy exports config
(0, utils_js_1.emojiLog)("➡️", "Determining entrypoints...");
const entryPoints = [];
const rows = [["Subpath", "Entrypoint"]];
for (const [exportPath, sourcePath] of Object.entries(config.exports ?? {})) {
if (exportPath.includes("package.json"))
continue;
let cleanExportPath;
if (exportPath === ".") {
cleanExportPath = pkgJson.name;
}
else if (exportPath.startsWith("./")) {
cleanExportPath = pkgJson.name + "/" + exportPath.slice(2);
}
else {
(0, utils_js_1.emojiLog)("⚠️", `Invalid subpath export "${exportPath}" — should start with "./"`, "warn");
process.exit(1);
}
if (typeof sourcePath === "string") {
if (sourcePath.includes("*")) {
if (!sourcePath.endsWith("/*") && !sourcePath.endsWith("/**/*")) {
(0, utils_js_1.emojiLog)("❌", `Wildcard paths should end with /* or /**/* (for deep globs): ${sourcePath}`, "error");
process.exit(1);
}
let pattern;
if (sourcePath.endsWith("/**/*")) {
// Handle deep glob patterns like "./src/**/*"
pattern = sourcePath.slice(0, -5) + "/**/*.{ts,tsx,mts,cts}";
}
else {
// Handle shallow glob patterns like "./src/plugins/*"
pattern = sourcePath.slice(0, -2) + "/*.{ts,tsx,mts,cts}";
}
if (isVerbose) {
(0, utils_js_1.emojiLog)("🔍", `Matching glob: ${pattern}`);
}
const wildcardFiles = await (0, globby_1.globby)([pattern], {
ignore: ["**/*.d.ts", "**/*.d.mts", "**/*.d.cts"],
cwd: pkgJsonDir,
});
entryPoints.push(...wildcardFiles);
rows.push([`"${cleanExportPath}"`, `${sourcePath} (${wildcardFiles.length} matches)`]);
}
else if ((0, utils_js_1.isSourceFile)(sourcePath)) {
entryPoints.push(sourcePath);
rows.push([`"${cleanExportPath}"`, sourcePath]);
}
}
}
// Extract bin entry points from zshy bin config
if (config.bin) {
if (typeof config.bin === "string") {
// Single bin entry
if ((0, utils_js_1.isSourceFile)(config.bin)) {
entryPoints.push(config.bin);
rows.push([`bin:${pkgJson.name}`, config.bin]);
}
}
else {
// Multiple bin entries
for (const [binName, sourcePath] of Object.entries(config.bin)) {
if (typeof sourcePath === "string" && (0, utils_js_1.isSourceFile)(sourcePath)) {
entryPoints.push(sourcePath);
rows.push([`bin:${binName}`, sourcePath]);
}
}
}
}
console.log(" " +
(0, table_1.table)(rows, {
drawHorizontalLine: (lineIndex, rowCount) => {
return (lineIndex === 0 ||
lineIndex === 1 ||
// lineIndex === rowCount - 1 ||
lineIndex === rowCount);
},
})
.split("\n")
.join("\n ")
.trim());
// disallow .mts and .cts files
// if (entryPoints.some((ep) => ep.endsWith(".mts") || ep.endsWith(".cts"))) {
// emojiLog(
// "❌",
// "Source files with .mts or .cts extensions are not supported. Please use regular .ts files.",
// "error"
// );
// process.exit(1);
// }
if (entryPoints.length === 0) {
(0, utils_js_1.emojiLog)("❌", "No entry points found matching the specified patterns in package.json#/zshy exports or bin", "error");
process.exit(1);
}
///////////////////////////////
/// compute root dir ///
///////////////////////////////
// Compute common ancestor directory for all entry points
let rootDir;
if (tsconfigJson.rootDir) {
rootDir = path.resolve(tsconfigPath, tsconfigJson.rootDir);
}
else {
// compute rootDir from entrypoints
rootDir =
entryPoints.length > 0
? entryPoints.reduce((common, entryPoint) => {
const entryDir = path.dirname(path.resolve(entryPoint));
const commonDir = path.resolve(common);
// Find the longest common path
const entryParts = entryDir.split(path.sep);
const commonParts = commonDir.split(path.sep);
let i = 0;
while (i < entryParts.length && i < commonParts.length && entryParts[i] === commonParts[i]) {
i++;
}
return commonParts.slice(0, i).join(path.sep) || path.sep;
}, path.dirname(path.resolve(entryPoints[0])))
: process.cwd();
}
const relRootDir = (0, utils_js_1.relativePosix)(pkgJsonDir, rootDir);
//////////////////////////////////
/// display resolved paths ///
//////////////////////////////////
(0, utils_js_1.emojiLog)("🔧", "Resolved build paths:");
const pathRows = [["Location", "Resolved path"]];
pathRows.push(["rootDir", relRootDir ? `./${relRootDir}` : "."]);
pathRows.push(["outDir", relOutDir ? `./${relOutDir}` : "."]);
if (relDeclarationDir !== relOutDir) {
pathRows.push(["declarationDir", relDeclarationDir ? `./${relDeclarationDir}` : "."]);
}
console.log(" " +
(0, table_1.table)(pathRows, {
drawHorizontalLine: (lineIndex, rowCount) => {
return (lineIndex === 0 ||
lineIndex === 1 ||
// lineIndex === rowCount - 1 ||
lineIndex === rowCount);
},
})
.split("\n")
.join("\n ")
.trim());
const isTypeModule = pkgJson.type === "module";
if (isTypeModule) {
(0, utils_js_1.emojiLog)("🟨", `Package is an ES module (package.json#/type is "module")`);
}
else {
(0, utils_js_1.emojiLog)("🐢", `Package is a CommonJS module (${pkgJson.type === "commonjs" ? 'package.json#/type is "commonjs"' : 'package.json#/type not set to "module"'})`);
}
//////////////////////////////////////////////
/// clean up outDir and declarationDir ///
//////////////////////////////////////////////
const prefix = isDryRun ? "[dryrun] " : "";
if (relRootDir.startsWith(relOutDir)) {
(0, utils_js_1.emojiLog)("🗑️", `${prefix}Skipping cleanup of outDir as it contains source files`);
}
else {
// source files are in the outDir, so skip cleanup
// clean up outDir and declarationDir
(0, utils_js_1.emojiLog)("🗑️", `${prefix}Cleaning up outDir...`);
if (!isDryRun) {
fs.rmSync(outDir, { recursive: true, force: true });
// // print success message in verbose mode
if (isVerbose) {
if (fs.existsSync(outDir)) {
(0, utils_js_1.emojiLog)("❌", `Failed to clean up outDir: ${relOutDir}. Directory still exists.`, "error");
}
}
}
}
if (relDeclarationDir !== relOutDir) {
// already done
}
else if (relRootDir.startsWith(relDeclarationDir)) {
(0, utils_js_1.emojiLog)("🗑️", `${prefix}Skipping cleanup of declarationDir as it contains source files`);
}
else {
(0, utils_js_1.emojiLog)("🗑️", `${prefix}Cleaning up declarationDir...`);
if (!isDryRun) {
fs.rmSync(declarationDir, { recursive: true, force: true });
// // print success message in verbose mode
if (isVerbose) {
if (fs.existsSync(declarationDir)) {
(0, utils_js_1.emojiLog)("❌", `Failed to clean up declarationDir: ${relDeclarationDir}. Directory still exists.`, "error");
}
}
}
}
///////////////////////////////
/// compile tsc ///
///////////////////////////////
const uniqueEntryPoints = [...new Set(entryPoints)];
// try {
if (isVerbose) {
(0, utils_js_1.emojiLog)("→", `Resolved entrypoints: ${(0, utils_js_1.formatForLog)(uniqueEntryPoints)}`);
(0, utils_js_1.emojiLog)("→", `Resolved compilerOptions: ${(0, utils_js_1.formatForLog)({
...tsconfigJson,
module: ts.ModuleKind[tsconfigJson.module],
moduleResolution: ts.ModuleResolutionKind[tsconfigJson.moduleResolution],
target: ts.ScriptTarget[tsconfigJson.target],
})}`);
}
// Create a build context to track written files, copied assets, and compilation errors/warnings
const buildContext = {
writtenFiles: new Set(),
copiedAssets: new Set(),
errorCount: 0,
warningCount: 0,
};
// Check if CJS should be skipped
const skipCjs = config.cjs === false;
// CJS
if (!skipCjs) {
(0, utils_js_1.emojiLog)("🧱", `Building CJS...${isTypeModule ? ` (rewriting .ts -> .cjs/.d.cts)` : ``}`);
await (0, compile_js_1.compileProject)({
configPath: tsconfigPath,
ext: isTypeModule ? "cjs" : "js",
format: "cjs",
verbose: isVerbose,
dryRun: isDryRun,
pkgJsonDir,
rootDir,
cjsInterop: isCjsInterop,
compilerOptions: {
...tsconfigJson,
module: ts.ModuleKind.CommonJS,
moduleResolution: ts.ModuleResolutionKind.Node10,
outDir,
},
}, uniqueEntryPoints, buildContext);
}
else {
(0, utils_js_1.emojiLog)("⏭️", "Skipping CJS build (cjs: false)");
}
// ESM
(0, utils_js_1.emojiLog)("🧱", `Building ESM...${isTypeModule ? `` : ` (rewriting .ts -> .mjs/.d.mts)`}`);
await (0, compile_js_1.compileProject)({
configPath: tsconfigPath,
ext: isTypeModule ? "js" : "mjs",
format: "esm",
verbose: isVerbose,
dryRun: isDryRun,
pkgJsonDir,
rootDir,
cjsInterop: isCjsInterop,
compilerOptions: {
...tsconfigJson,
module: ts.ModuleKind.ESNext,
moduleResolution: ts.ModuleResolutionKind.Bundler,
outDir,
},
}, uniqueEntryPoints, buildContext);
///////////////////////////////////
/// display written files ///
///////////////////////////////////
// Display files that were written or would be written (only in verbose mode)
if (isVerbose && buildContext.writtenFiles.size > 0) {
(0, utils_js_1.emojiLog)("📜", `${prefix}Writing files (${buildContext.writtenFiles.size} total)...`);
// Sort files by relative path for consistent display
const sortedFiles = [...buildContext.writtenFiles]
.map((file) => (0, utils_js_1.relativePosix)(pkgJsonDir, file))
.sort()
.map((relPath) => (relPath.startsWith(".") ? relPath : `./${relPath}`));
sortedFiles.forEach((file) => {
console.log(` ${file}`);
});
}
///////////////////////////////
/// generate exports ///
///////////////////////////////
// generate package.json exports
if (config.noEdit) {
(0, utils_js_1.emojiLog)("📦", "[noedit] Skipping modification of package.json");
}
else {
// Generate exports based on zshy config
(0, utils_js_1.emojiLog)("📦", `${prefix}Updating package.json...`);
const newExports = {};
if (config.exports) {
for (const [exportPath, sourcePath] of Object.entries(config.exports)) {
if (exportPath.includes("package.json")) {
newExports[exportPath] = sourcePath;
continue;
}
const absSourcePath = path.resolve(pkgJsonDir, sourcePath);
const relSourcePath = path.relative(rootDir, absSourcePath);
const absJsPath = path.resolve(outDir, relSourcePath);
const absDtsPath = path.resolve(declarationDir, relSourcePath);
let relJsPath = "./" + (0, utils_js_1.relativePosix)(pkgJsonDir, absJsPath);
let relDtsPath = "./" + (0, utils_js_1.relativePosix)(pkgJsonDir, absDtsPath);
if (typeof sourcePath === "string") {
if (sourcePath.endsWith("/*") || sourcePath.endsWith("/**/*")) {
// Handle wildcard exports
const finalExportPath = exportPath;
if (finalExportPath.includes("**")) {
(0, utils_js_1.emojiLog)("❌", `Export keys cannot contain "**": ${finalExportPath}`, "error");
process.exit(1);
}
// Convert deep glob patterns to simple wildcard patterns in the final export
if (sourcePath.endsWith("/**/*")) {
// Also convert the output paths from /**/* to /*
if (relJsPath.endsWith("/**/*")) {
relJsPath = relJsPath.slice(0, -5) + "/*";
}
if (relDtsPath.endsWith("/**/*")) {
relDtsPath = relDtsPath.slice(0, -5) + "/*";
}
}
// Build exports object with proper condition ordering
const exportObj = {};
// Add custom conditions first in their original order
if (config.conditions) {
for (const [condition, value] of Object.entries(config.conditions)) {
if (value === "src") {
exportObj[condition] = sourcePath;
}
else if (value === "esm") {
exportObj[condition] = relJsPath;
}
else if (value === "cjs") {
exportObj[condition] = relJsPath;
}
}
}
// Add standard conditions
exportObj.types = relDtsPath;
exportObj.import = relJsPath;
if (!skipCjs) {
exportObj.require = relJsPath;
}
newExports[finalExportPath] = exportObj;
}
else if ((0, utils_js_1.isSourceFile)(sourcePath)) {
const esmPath = (0, utils_js_1.removeExtension)(relJsPath) + (isTypeModule ? `.js` : `.mjs`);
const cjsPath = (0, utils_js_1.removeExtension)(relJsPath) + (isTypeModule ? `.cjs` : `.js`);
// Use ESM type declarations when CJS is skipped, otherwise use CJS declarations
const dtsExt = skipCjs ? (isTypeModule ? ".d.ts" : ".d.mts") : isTypeModule ? ".d.cts" : ".d.ts";
const dtsPath = (0, utils_js_1.removeExtension)(relDtsPath) + dtsExt;
// Build exports object with proper condition ordering
const exportObj = {};
// Add custom conditions first in their original order
if (config.conditions) {
for (const [condition, value] of Object.entries(config.conditions)) {
if (value === "src") {
exportObj[condition] = sourcePath;
}
else if (value === "esm") {
exportObj[condition] = esmPath;
}
else if (value === "cjs") {
exportObj[condition] = cjsPath;
}
}
}
// Add standard conditions
exportObj.types = dtsPath;
exportObj.import = esmPath;
if (!skipCjs) {
exportObj.require = cjsPath;
}
newExports[exportPath] = exportObj;
if (exportPath === ".") {
if (!skipCjs) {
pkgJson.main = cjsPath;
pkgJson.module = esmPath;
pkgJson.types = dtsPath;
}
else {
// Only set module and types, not main
pkgJson.module = esmPath;
pkgJson.types = dtsPath;
}
if (isVerbose) {
(0, utils_js_1.emojiLog)("🔧", `Setting "main": ${(0, utils_js_1.formatForLog)(cjsPath)}`);
(0, utils_js_1.emojiLog)("🔧", `Setting "module": ${(0, utils_js_1.formatForLog)(esmPath)}`);
(0, utils_js_1.emojiLog)("🔧", `Setting "types": ${(0, utils_js_1.formatForLog)(dtsPath)}`);
}
}
}
}
}
pkgJson.exports = newExports;
if (isVerbose) {
(0, utils_js_1.emojiLog)("🔧", `Setting "exports": ${(0, utils_js_1.formatForLog)(newExports)}`);
}
}
///////////////////////////////
/// generate bin ///
///////////////////////////////
// Generate bin field based on zshy bin config
if (config.bin) {
const newBin = {};
// Convert config.bin to object format for processing
const binEntries = typeof config.bin === "string" ? [[pkgJson.name, config.bin]] : Object.entries(config.bin);
for (const [binName, sourcePath] of binEntries) {
if (typeof sourcePath === "string" && (0, utils_js_1.isSourceFile)(sourcePath)) {
const absSourcePath = path.resolve(pkgJsonDir, sourcePath);
const relSourcePath = path.relative(rootDir, absSourcePath);
const absJsPath = path.resolve(outDir, relSourcePath);
const relJsPath = "./" + (0, utils_js_1.relativePosix)(pkgJsonDir, absJsPath);
// Use ESM files for bin when CJS is skipped, otherwise use CJS
const binExt = skipCjs ? (isTypeModule ? ".js" : ".mjs") : isTypeModule ? ".cjs" : ".js";
const binPath = (0, utils_js_1.removeExtension)(relJsPath) + binExt;
newBin[binName] = binPath;
}
}
// If original config.bin was a string, output as string
if (typeof config.bin === "string") {
pkgJson.bin = Object.values(newBin)[0];
}
else {
// Output as object
pkgJson.bin = newBin;
}
if (isVerbose) {
(0, utils_js_1.emojiLog)("🔧", `Setting "bin": ${(0, utils_js_1.formatForLog)(pkgJson.bin)}`);
}
}
if (isDryRun) {
///////////////////////////////
/// write pkg json ///
///////////////////////////////
(0, utils_js_1.emojiLog)("📦", "[dryrun] Skipping package.json modification");
}
else {
fs.writeFileSync(packageJsonPath, JSON.stringify(pkgJson, null, indent) + "\n");
}
}
if (isAttw) {
// run `@arethetypeswrong/cli --pack .` to check types
(0, utils_js_1.emojiLog)("🔍", "Checking types with @arethetypeswrong/cli...");
const { execFile } = await Promise.resolve().then(() => __importStar(require("node:child_process")));
const { promisify } = await Promise.resolve().then(() => __importStar(require("node:util")));
const execFileAsync = promisify(execFile);
const [cmd, ...args] = `${pmExec} @arethetypeswrong/cli --pack ${pkgJsonDir} --format table-flipped`.split(" ");
console.dir([cmd, ...args], { depth: null });
let stdout = "";
let stderr = "";
let exitCode = 0;
try {
const result = await execFileAsync(cmd, args, {
cwd: pkgJsonDir,
encoding: "utf-8",
});
stdout = result.stdout;
stderr = result.stderr;
}
catch (error) {
stdout = error.stdout || "";
stderr = error.stderr || "";
exitCode = error.code || 1;
}
const output = stdout || stderr;
if (output) {
const indentedOutput = output
.split("\n")
.map((line) => ` ${line}`)
.join("\n");
if (exitCode === 0) {
console.log(indentedOutput);
}
else {
console.error(indentedOutput);
(0, utils_js_1.emojiLog)("⚠️", "ATTW found issues, but the build was not affected.", "warn");
}
}
}
// Report total compilation results
if (buildContext.errorCount > 0 || buildContext.warningCount > 0) {
(0, utils_js_1.emojiLog)("📊", `Compilation finished with ${buildContext.errorCount} error(s) and ${buildContext.warningCount} warning(s)`);
// Apply threshold rules for exit code
if (failThreshold !== "never" && buildContext.errorCount > 0) {
// Both 'warn' and 'error' thresholds cause failure on errors
(0, utils_js_1.emojiLog)("❌", `Build completed with errors`, "error");
process.exit(1);
}
else if (failThreshold === "warn" && buildContext.warningCount > 0) {
// Only 'warn' threshold causes failure on warnings
(0, utils_js_1.emojiLog)("⚠️", `Build completed with warnings (exiting with error due to --fail-threshold=warn)`, "warn");
process.exit(1);
}
else if (buildContext.errorCount > 0) {
// If we got here with errors, we're in 'never' mode
(0, utils_js_1.emojiLog)("⚠️", `Build completed with errors (continuing due to --fail-threshold=never)`, "warn");
}
else {
// Just warnings and not failing on them
(0, utils_js_1.emojiLog)("🎉", `Build complete with warnings`);
}
}
else {
(0, utils_js_1.emojiLog)("🎉", "Build complete! ✅");
}
}
//# sourceMappingURL=main.js.map