bunchee
Version:
zero config bundler for js/ts/jsx libraries
1,230 lines (1,208 loc) • 66.6 kB
JavaScript
#!/usr/bin/env node
var path = require('path');
var yargs = require('yargs');
var helpers = require('yargs/helpers');
var perf_hooks = require('perf_hooks');
var fs = require('fs');
var fsp = require('fs/promises');
var require$$0 = require('tty');
var tinyglobby = require('tinyglobby');
var picomatch = require('picomatch');
var index_js = require('../index.js');
require('module');
var prettyBytes = require('pretty-bytes');
var nanospinner = require('nanospinner');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var path__default = /*#__PURE__*/_interopDefault(path);
var yargs__default = /*#__PURE__*/_interopDefault(yargs);
var fs__default = /*#__PURE__*/_interopDefault(fs);
var fsp__default = /*#__PURE__*/_interopDefault(fsp);
var require$$0__default = /*#__PURE__*/_interopDefault(require$$0);
var picomatch__default = /*#__PURE__*/_interopDefault(picomatch);
var prettyBytes__default = /*#__PURE__*/_interopDefault(prettyBytes);
const availableExtensions = new Set([
'js',
'cjs',
'mjs',
'jsx',
'ts',
'tsx',
'cts',
'mts'
]);
// You can find the list of runtime keys here:
// https://runtime-keys.proposal.wintercg.org/
const runtimeExportConventions = new Set([
'electron',
'react-server',
'react-native',
'edge-light',
'node',
'deno',
'bun',
'workerd',
// Browser only
'browser'
]);
const optimizeConventions = new Set([
'development',
'production'
]);
const specialExportConventions = new Set([
...runtimeExportConventions,
...optimizeConventions
]);
const SRC = 'src';
const DIST = 'dist';
const dtsExtensionsMap = {
js: 'd.ts',
cjs: 'd.cts',
mjs: 'd.mts'
};
const tsExtensions = new Set([
'ts',
'tsx',
'cts',
'mts'
]);
const DEFAULT_TS_CONFIG = {
compilerOptions: {
target: 'ES2022',
module: 'ESNext',
moduleResolution: 'bundler'
},
include: [
'src'
]
};
const BINARY_TAG = '$binary';
const PRIVATE_GLOB_PATTERN = '**/_*/**';
const TESTS_GLOB_PATTERN = '**/{__tests__/**,__mocks__/**,*.{test,spec}.*}';
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var picocolors = {exports: {}};
var hasRequiredPicocolors;
function requirePicocolors () {
if (hasRequiredPicocolors) return picocolors.exports;
hasRequiredPicocolors = 1;
let tty = require$$0__default.default;
let isColorSupported = !("NO_COLOR" in process.env || process.argv.includes("--no-color")) && ("FORCE_COLOR" in process.env || process.argv.includes("--color") || process.platform === "win32" || tty.isatty(1) && process.env.TERM !== "dumb" || "CI" in process.env);
let formatter = (open, close, replace = open)=>(input)=>{
let string = "" + input;
let index = string.indexOf(close, open.length);
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
};
let replaceClose = (string, close, replace, index)=>{
let start = string.substring(0, index) + replace;
let end = string.substring(index + close.length);
let nextIndex = end.indexOf(close);
return ~nextIndex ? start + replaceClose(end, close, replace, nextIndex) : start + end;
};
let createColors = (enabled = isColorSupported)=>({
isColorSupported: enabled,
reset: enabled ? (s)=>`\x1b[0m${s}\x1b[0m` : String,
bold: enabled ? formatter("\x1b[1m", "\x1b[22m", "\x1b[22m\x1b[1m") : String,
dim: enabled ? formatter("\x1b[2m", "\x1b[22m", "\x1b[22m\x1b[2m") : String,
italic: enabled ? formatter("\x1b[3m", "\x1b[23m") : String,
underline: enabled ? formatter("\x1b[4m", "\x1b[24m") : String,
inverse: enabled ? formatter("\x1b[7m", "\x1b[27m") : String,
hidden: enabled ? formatter("\x1b[8m", "\x1b[28m") : String,
strikethrough: enabled ? formatter("\x1b[9m", "\x1b[29m") : String,
black: enabled ? formatter("\x1b[30m", "\x1b[39m") : String,
red: enabled ? formatter("\x1b[31m", "\x1b[39m") : String,
green: enabled ? formatter("\x1b[32m", "\x1b[39m") : String,
yellow: enabled ? formatter("\x1b[33m", "\x1b[39m") : String,
blue: enabled ? formatter("\x1b[34m", "\x1b[39m") : String,
magenta: enabled ? formatter("\x1b[35m", "\x1b[39m") : String,
cyan: enabled ? formatter("\x1b[36m", "\x1b[39m") : String,
white: enabled ? formatter("\x1b[37m", "\x1b[39m") : String,
gray: enabled ? formatter("\x1b[90m", "\x1b[39m") : String,
bgBlack: enabled ? formatter("\x1b[40m", "\x1b[49m") : String,
bgRed: enabled ? formatter("\x1b[41m", "\x1b[49m") : String,
bgGreen: enabled ? formatter("\x1b[42m", "\x1b[49m") : String,
bgYellow: enabled ? formatter("\x1b[43m", "\x1b[49m") : String,
bgBlue: enabled ? formatter("\x1b[44m", "\x1b[49m") : String,
bgMagenta: enabled ? formatter("\x1b[45m", "\x1b[49m") : String,
bgCyan: enabled ? formatter("\x1b[46m", "\x1b[49m") : String,
bgWhite: enabled ? formatter("\x1b[47m", "\x1b[49m") : String
});
picocolors.exports = createColors();
picocolors.exports.createColors = createColors;
return picocolors.exports;
}
var picocolorsExports = /*@__PURE__*/ requirePicocolors();
var pc = /*@__PURE__*/getDefaultExportFromCjs(picocolorsExports);
const defaultColorFn = (text)=>text;
function color(prefixColor) {
return pc.isColorSupported ? pc[prefixColor] : defaultColorFn;
}
let activeSpinner = null;
// Store original console methods
const originalConsole = {
log: console.log.bind(console),
warn: console.warn.bind(console),
error: console.error.bind(console),
info: console.info.bind(console)
};
function isSpinnerActive() {
if (!activeSpinner) return false;
const isSpinning = activeSpinner.isSpinning;
return typeof isSpinning === 'function' ? isSpinning() : isSpinning;
}
/**
* Wrap a console method to pause spinner before logging
*/ function wrapConsoleMethod(original) {
return (...args)=>{
if (isSpinnerActive() && activeSpinner) {
activeSpinner.clear();
original(...args);
activeSpinner.start();
} else {
original(...args);
}
};
}
/**
* Register a spinner so that ALL console output automatically pauses it.
* This intercepts console.log/warn/error/info globally.
* Call with `null` to unregister and restore original console methods.
*/ function setActiveSpinner(spinner) {
activeSpinner = spinner;
if (spinner) {
// Patch global console methods to pause spinner
console.log = wrapConsoleMethod(originalConsole.log);
console.warn = wrapConsoleMethod(originalConsole.warn);
console.error = wrapConsoleMethod(originalConsole.error);
console.info = wrapConsoleMethod(originalConsole.info);
} else {
// Restore original console methods
console.log = originalConsole.log;
console.warn = originalConsole.warn;
console.error = originalConsole.error;
console.info = originalConsole.info;
}
}
const logger = {
log (...arg) {
console.log(...arg);
},
warn (...arg) {
console.warn(color('yellow')('!'), ...arg);
},
error (...arg) {
console.error(color('red')('⨯'), ...arg);
},
info (...arg) {
console.log(color('green')('✓'), ...arg);
}
};
function posixRelativify(path) {
return path.startsWith('.') ? path : `./${path}`;
}
// Example: ./src/util/foo.development.ts -> foo.development
// Example: ./src/util/foo.react-server.ts -> foo.react-server
const baseNameWithoutExtension = (filePath)=>{
return path__default.default.basename(filePath, path__default.default.extname(filePath));
};
function validateEntryFiles(entryFiles) {
const fileBasePaths = new Set();
const duplicatePaths = new Set();
for (const filePath of entryFiles){
// Check if there are multiple files with the same base name
const filePathWithoutExt = filePath.slice(0, -path__default.default.extname(filePath).length).replace(/\\/g, '/');
const segments = filePathWithoutExt.split('/');
let lastSegment = segments[segments.length - 1];
while(lastSegment && (lastSegment === 'index' || lastSegment === '')){
segments.pop();
lastSegment = segments[segments.length - 1];
}
const fileBasePath = segments.join('/');
if (fileBasePaths.has(fileBasePath)) {
duplicatePaths.add(// Add a dot if the base name is empty, 'foo' -> './foo', '' -> '.'
'./' + filePath.replace(/\\/g, '/'));
}
fileBasePaths.add(fileBasePath);
}
if (duplicatePaths.size > 0) {
throw new Error(`Conflicted entry files found for entries: ${[
...duplicatePaths
].join(', ')}`);
}
}
function exit(err) {
logger.error(err);
throw typeof err === 'string' ? new Error(err) : err;
}
function hasPackageJson(cwd) {
return fileExists(path__default.default.resolve(cwd, 'package.json'));
}
async function getPackageMeta(cwd) {
const pkgFilePath = path__default.default.resolve(cwd, 'package.json');
let targetPackageJson = {};
try {
targetPackageJson = JSON.parse(await fsp__default.default.readFile(pkgFilePath, {
encoding: 'utf-8'
}));
} catch (_) {}
return targetPackageJson;
}
function isTypescriptFile(filename) {
const ext = path__default.default.extname(filename).slice(1);
return tsExtensions.has(ext);
}
function fileExists(filePath) {
return fs__default.default.existsSync(filePath);
}
const hasCjsExtension = (filename)=>path__default.default.extname(filename) === '.cjs';
const getMainFieldExportType = (pkg)=>{
const isEsmPkg = isESModulePackage(pkg.type);
const mainExportType = isEsmPkg && pkg.main ? hasCjsExtension(pkg.main) ? 'require' : 'import' : 'require';
return mainExportType;
};
const isTestFile = (filename)=>/\.(test|spec)$/.test(baseNameWithoutExtension(filename));
function joinRelativePath(...segments) {
// Normalize to forward slashes for cross-platform compatibility
// Export paths in package.json always use POSIX-style paths
let result = path__default.default.posix.join(...segments);
// If the first segment starts with '.', ensure the result does too.
if (segments[0] === '.' && !result.startsWith('.')) {
result = './' + result;
}
return result;
}
function isESModulePackage(packageType) {
return packageType === 'module';
}
function isBinExportPath(exportPath) {
return exportPath === BINARY_TAG || exportPath.startsWith(BINARY_TAG + '/');
}
function isTypeFile(filename) {
return filename.endsWith('.d.ts') || filename.endsWith('.d.mts') || filename.endsWith('.d.cts');
}
// shared.ts -> ./shared
// shared.<export condition>.ts -> ./shared.<export condition>
// index.ts -> ./index
// index.development.ts -> ./index.development
// foo/index.ts -> ./foo
function sourceFilenameToExportFullPath(filename) {
const ext = path__default.default.extname(filename);
const exportPath = filename.slice(0, -ext.length);
return posixRelativify(exportPath);
}
// If the file is matching the private module convention file export path.
// './lib/_foo' -> true
// './_util/index' -> true
// './lib/_foo/bar' -> true
// './foo' -> false
function isPrivateExportPath(exportPath) {
return /\/_/.test(exportPath);
}
function normalizePath(filePath) {
return filePath.replace(/\\/g, '/');
}
/**
* Check if an export key contains a wildcard pattern
*/ function hasWildcardPattern(exportKey) {
return exportKey.includes('*');
}
/**
* Replace wildcard in output path with matched subpath
* Example: "./dist/features/*.js" with "foo" -> "./dist/features/foo.js"
*/ function substituteWildcardInPath(outputPath, matchedSubpath) {
return outputPath.replace(/\*/g, matchedSubpath);
}
/**
* Expand a wildcard export pattern by finding matching source files
* Returns a map of concrete export paths to their matched subpaths
* Example: "./features/*" with files ["foo.ts", "bar.ts"] in src/features/
* -> { "./features/foo": "foo", "./features/bar": "bar" }
*/ async function expandWildcardPattern(wildcardPattern, cwd) {
const expanded = new Map();
const sourceDir = path__default.default.join(cwd, SRC);
if (!fileExists(sourceDir)) {
return expanded;
}
// Convert wildcard pattern to glob pattern
// "./features/*" -> "features/*"
const cleanPattern = wildcardPattern.replace(/^\.\//, '');
// Extract the base path before the wildcard
// "features/*" -> "features"
const basePathParts = cleanPattern.split('*');
const basePath = basePathParts[0].replace(/\/$/, '');
// Build glob pattern to match files
// "features/*" -> "features/*.{js,ts,tsx,...}"
const extPattern = `{${[
...availableExtensions
].join(',')}}`;
const globPatterns = [
`${cleanPattern}.${extPattern}`,
`${cleanPattern}/index.${extPattern}`
];
let matches = [];
try {
matches = await tinyglobby.glob(globPatterns, {
cwd: sourceDir,
ignore: [
PRIVATE_GLOB_PATTERN,
TESTS_GLOB_PATTERN
],
expandDirectories: false
});
} catch (error) {
logger.warn(`Failed to expand wildcard pattern ${wildcardPattern}: ${error}`);
return expanded;
}
for (const match of matches){
// Extract the matched subpath
// "features/foo.ts" -> "foo"
// "features/bar/index.ts" -> "bar"
const relativePath = normalizePath(match);
const ext = path__default.default.extname(relativePath);
const withoutExt = relativePath.slice(0, -ext.length);
// Remove the base path to get just the matched part
// "features/foo" -> "foo" (when basePath is "features")
let matchedPart = withoutExt;
if (basePath && matchedPart.startsWith(basePath + '/')) {
matchedPart = matchedPart.slice(basePath.length + 1);
} else if (basePath && matchedPart === basePath) {
continue;
}
// Handle index files
let matchedSubpath;
if (matchedPart.endsWith('/index')) {
matchedSubpath = matchedPart.slice(0, -6); // Remove "/index"
// If there's still a path, take the last segment
const lastSlash = matchedSubpath.lastIndexOf('/');
matchedSubpath = lastSlash >= 0 ? matchedSubpath.slice(lastSlash + 1) : matchedSubpath;
} else {
// Take the first segment (what matches the *)
const firstSlash = matchedPart.indexOf('/');
matchedSubpath = firstSlash >= 0 ? matchedPart.slice(0, firstSlash) : matchedPart;
}
// Build the concrete export path
// "./features/*" + "foo" -> "./features/foo"
const concreteExportPath = basePath ? `./${basePath}/${matchedSubpath}` : `./${matchedSubpath}`;
expanded.set(concreteExportPath, matchedSubpath);
}
return expanded;
}
/**
* Process export value for wildcard patterns, substituting wildcards in output paths
*/ async function processWildcardExportValue(exportValue, originalExportKey, currentPath, exportTypes, exportToDist, matchedSubpath) {
// End of searching, export value is file path.
// <export key>: <export value> (string)
if (typeof exportValue === 'string') {
const composedTypes = new Set(exportTypes);
const exportType = originalExportKey.startsWith('.') ? 'default' : originalExportKey;
composedTypes.add(exportType);
const exportInfo = exportToDist.get(mapExportFullPath(currentPath));
const exportCondition = Array.from(composedTypes).join('.');
// Substitute wildcard in output path
const substitutedPath = substituteWildcardInPath(exportValue, matchedSubpath);
if (!exportInfo) {
const outputConditionPair = [
substitutedPath,
exportCondition
];
addToExportDistMap(exportToDist, currentPath, [
outputConditionPair
]);
} else {
exportInfo.push([
substitutedPath,
exportCondition
]);
}
return;
}
const exportKeys = Object.keys(exportValue);
for (const exportKey of exportKeys){
// Clone the set to avoid modifying the parent set
const childExports = new Set(exportTypes);
// Normalize child export value to a map
const childExportValue = exportValue[exportKey];
// Substitute wildcard in nested string values
let processedChildValue = childExportValue;
if (typeof childExportValue === 'string') {
processedChildValue = substituteWildcardInPath(childExportValue, matchedSubpath);
} else if (typeof childExportValue === 'object' && childExportValue !== null) {
// Recursively process nested objects
const processed = {};
for (const [key, value] of Object.entries(childExportValue)){
if (typeof value === 'string') {
processed[key] = substituteWildcardInPath(value, matchedSubpath);
} else if (value !== null && value !== undefined) {
processed[key] = value;
}
}
processedChildValue = processed;
}
// Visit export path: ./subpath, ./subpath2, ...
if (exportKey.startsWith('.')) {
const childPath = joinRelativePath(currentPath, exportKey);
await processWildcardExportValue(processedChildValue, exportKey, childPath, childExports, exportToDist, matchedSubpath);
} else {
// Visit export type: import, require, ...
childExports.add(exportKey);
await processWildcardExportValue(processedChildValue, exportKey, currentPath, childExports, exportToDist, matchedSubpath);
}
}
}
function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) {
// End of searching, export value is file path.
// <export key>: <export value> (string)
if (typeof exportValue === 'string') {
const composedTypes = new Set(exportTypes);
const exportType = exportKey.startsWith('.') ? 'default' : exportKey;
composedTypes.add(exportType);
const exportInfo = exportToDist.get(mapExportFullPath(currentPath));
const exportCondition = Array.from(composedTypes).join('.');
if (!exportInfo) {
const outputConditionPair = [
exportValue,
exportCondition
];
addToExportDistMap(exportToDist, currentPath, [
outputConditionPair
]);
} else {
exportInfo.push([
exportValue,
exportCondition
]);
}
return;
}
const exportKeys = Object.keys(exportValue);
for (const exportKey of exportKeys){
// Clone the set to avoid modifying the parent set
const childExports = new Set(exportTypes);
// Normalize child export value to a map
const childExportValue = exportValue[exportKey];
// Visit export path: ./subpath, ./subpath2, ...
if (exportKey.startsWith('.')) {
const childPath = joinRelativePath(currentPath, exportKey);
collectExportPath(childExportValue, exportKey, childPath, childExports, exportToDist);
} else {
// Visit export type: import, require, ...
childExports.add(exportKey);
collectExportPath(childExportValue, exportKey, currentPath, childExports, exportToDist);
}
}
}
const mapExportFullPath = (exportPath)=>exportPath === '.' ? './index' : exportPath;
function addToExportDistMap(exportToDist, exportPath, outputConditionPairs) {
const fullPath = mapExportFullPath(exportPath);
const existingExportInfo = exportToDist.get(fullPath);
if (!existingExportInfo) {
exportToDist.set(fullPath, outputConditionPairs);
} else {
existingExportInfo.push(...outputConditionPairs);
}
}
/**
* parseExports - parse package.exports field and other fields like main,module to a map
*
* map from export path to output path and export conditions
*
* exportToDist: {
* './index': { development: ..., default: ... }
* './index.react-server': { development: ..., default: ... }
* }
*/ async function parseExports(pkg, cwd) {
var _pkg_exports;
const exportsField = (_pkg_exports = pkg.exports) != null ? _pkg_exports : {};
var _pkg_bin;
const bins = (_pkg_bin = pkg.bin) != null ? _pkg_bin : {};
const exportToDist = new Map();
const isEsmPkg = isESModulePackage(pkg.type);
const defaultCondition = isEsmPkg ? 'import' : 'require';
let currentPath = '.';
if (typeof exportsField === 'string') {
const outputConditionPair = [
exportsField,
defaultCondition
];
addToExportDistMap(exportToDist, currentPath, [
outputConditionPair
]);
} else {
// keys means unknown if they're relative path or export type
const exportConditionKeys = Object.keys(exportsField);
for (const exportKey of exportConditionKeys){
const exportValue = exportsField[exportKey];
const exportTypes = new Set();
const isExportPath = exportKey.startsWith('.');
// Handle wildcard patterns
if (isExportPath && hasWildcardPattern(exportKey) && cwd) {
// Expand wildcard pattern to concrete exports
const expanded = await expandWildcardPattern(exportKey, cwd);
for (const [concreteExportPath, matchedSubpath] of expanded){
const childPath = joinRelativePath(currentPath, concreteExportPath);
// Process the export value and substitute wildcards in output paths
await processWildcardExportValue(exportValue, exportKey, childPath, exportTypes, exportToDist, matchedSubpath);
}
continue;
}
const childPath = isExportPath ? joinRelativePath(currentPath, exportKey) : currentPath;
if (!isExportPath) {
exportTypes.add(exportKey);
}
collectExportPath(exportValue, exportKey, childPath, exportTypes, exportToDist);
}
}
if (typeof bins === 'string') {
const outputConditionPair = [
bins,
defaultCondition
];
addToExportDistMap(exportToDist, BINARY_TAG, [
outputConditionPair
]);
} else {
for (const binName of Object.keys(bins)){
const binDistPath = bins[binName];
const exportType = getExportTypeFromFile(binDistPath, pkg.type);
const exportPath = path.posix.join(BINARY_TAG, binName);
const outputConditionPair = [
binDistPath,
exportType
];
addToExportDistMap(exportToDist, exportPath, [
outputConditionPair
]);
}
}
// Handle package.json global exports fields
if (pkg.main || pkg.module || pkg.types) {
const mainExportPath = pkg.main;
const moduleExportPath = pkg.module;
const typesEntryPath = pkg.types;
addToExportDistMap(exportToDist, './index', [
Boolean(mainExportPath) && [
mainExportPath,
getMainFieldExportType(pkg)
],
Boolean(moduleExportPath) && [
moduleExportPath,
'module'
],
Boolean(typesEntryPath) && [
typesEntryPath,
'types'
]
].filter(Boolean));
}
return exportToDist;
}
function getExportTypeFromFile(filename, pkgType) {
const isESModule = isESModulePackage(pkgType);
const isCjsExt = filename.endsWith('.cjs');
const isEsmExt = filename.endsWith('.mjs');
const exportType = isEsmExt ? 'import' : isCjsExt ? 'require' : isESModule ? 'import' : 'require';
return exportType;
}
const matchFile = (matchingPattern, filePath)=>{
return matchingPattern.some((pattern)=>{
// pattern is always posix
const normalizedPattern = path.posix.normalize(pattern);
const expandedPattern = normalizedPattern.endsWith('/') ? `${normalizedPattern}**` : `${normalizedPattern}/**`;
const matcher = picomatch__default.default(expandedPattern);
const normalizedFilePath = path.posix.normalize(filePath);
return matcher(normalizedFilePath);
});
};
function validateTypesFieldCondition(pair) {
const [outputPath, composedExportType] = pair;
const exportTypes = new Set(composedExportType.split('.'));
if (!exportTypes.has('types') && isTypeFile(outputPath)) {
return true;
}
return false;
}
function validateFilesField(packageJson) {
const state = {
missingFiles: []
};
const filesField = packageJson.files || [
'*'
];
const exportsField = packageJson.exports || {};
const resolveExportsPaths = (exports)=>{
const paths = [];
if (typeof exports === 'string') {
paths.push(exports);
} else if (typeof exports === 'object') {
for(const key in exports){
paths.push(...resolveExportsPaths(exports[key]));
}
}
return paths;
};
const exportedPaths = resolveExportsPaths(exportsField).map((p)=>normalizePath(path__default.default.normalize(p)));
const commonFields = [
'main',
'module',
'types',
'module-sync'
];
for (const field of commonFields){
if (field in packageJson) {
exportedPaths.push(packageJson[field]);
}
}
state.missingFiles = exportedPaths.filter((exportPath)=>{
// Special case for package.json
if (exportPath === 'package.json') {
return false;
}
return !matchFile(filesField, exportPath);
});
return state;
}
async function lint$1(cwd) {
const pkg = await getPackageMeta(cwd);
const { name, main, exports } = pkg;
const isESM = isESModulePackage(pkg.type);
const parsedExports = await parseExports(pkg, cwd);
if (!name) {
logger.warn('Missing package name');
}
const exportsState = {
badMainExtension: false,
badMainExport: false,
invalidExportsFieldType: false,
badCjsRequireExport: {
value: false,
paths: []
},
badCjsImportExport: {
value: false,
paths: []
},
badEsmRequireExport: {
value: false,
paths: []
},
badEsmImportExport: {
value: false,
paths: []
},
badTypesExport: []
};
// Validate ESM package
if (isESM) {
if (exports) {
if (typeof exports === 'string') {
if (hasCjsExtension(exports)) {
exportsState.badMainExport = true;
}
} else if (typeof exports !== 'object') {
exportsState.invalidExportsFieldType = true;
} else {
parsedExports.forEach((outputPairs)=>{
for (const outputPair of outputPairs){
const [outputPath, composedExportType] = outputPair;
if (validateTypesFieldCondition([
outputPath,
composedExportType
])) {
exportsState.badTypesExport.push([
outputPath,
composedExportType
]);
}
const exportTypes = new Set(composedExportType.split('.'));
let requirePath = '';
let importPath = '';
if (exportTypes.has('require')) {
requirePath = outputPath;
}
if (exportTypes.has('import')) {
importPath = outputPath;
}
const requireExt = requirePath && path__default.default.extname(requirePath);
const importExt = importPath && path__default.default.extname(importPath);
if (requireExt === '.mjs' || requireExt === '.js') {
exportsState.badEsmRequireExport.value = true;
exportsState.badEsmRequireExport.paths.push(requirePath);
}
if (importExt === '.cjs') {
exportsState.badEsmImportExport.value = true;
exportsState.badEsmImportExport.paths.push(importPath);
}
}
});
}
}
} else {
// Validate CJS package
if (main && path__default.default.extname(main) === '.mjs') {
exportsState.badMainExtension = true;
}
if (exports) {
if (typeof exports === 'string') {
if (path__default.default.extname(exports) === '.mjs') {
exportsState.badMainExport = true;
}
} else if (typeof exports !== 'object') {
exportsState.invalidExportsFieldType = true;
} else {
parsedExports.forEach((outputPairs)=>{
for (const outputPair of outputPairs){
const [outputPath, composedExportType] = outputPair;
if (validateTypesFieldCondition([
outputPath,
composedExportType
])) {
exportsState.badTypesExport.push([
outputPath,
composedExportType
]);
}
const exportTypes = new Set(composedExportType.split('.'));
let requirePath = '';
let importPath = '';
if (exportTypes.has('require')) {
requirePath = outputPath;
}
if (exportTypes.has('import')) {
importPath = outputPath;
}
const requireExt = requirePath && path__default.default.extname(requirePath);
const importExt = importPath && path__default.default.extname(importPath);
if (requireExt === '.mjs') {
exportsState.badCjsRequireExport.value = true;
exportsState.badCjsRequireExport.paths.push(requirePath);
}
if (importExt === '.js' || importExt === '.cjs') {
exportsState.badCjsImportExport.value = true;
exportsState.badCjsImportExport.paths.push(importPath);
}
}
});
}
}
}
const fieldState = validateFilesField(pkg);
const warningsCount = exportsState.badTypesExport.length + fieldState.missingFiles.length;
if (warningsCount) {
logger.warn(`Lint: ${warningsCount} issues found.`);
}
if (fieldState.missingFiles.length) {
logger.warn('Missing files in package.json');
fieldState.missingFiles.forEach((p)=>{
logger.warn(` ${p}`);
});
}
if (exportsState.badMainExtension) {
logger.warn('Cannot export `main` field with .mjs extension in CJS package, only .js extension is allowed');
}
if (exportsState.badMainExport) {
if (isESM) {
logger.warn('Cannot export `exports` field with .cjs extension in ESM package, only .mjs and .js extensions are allowed');
} else {
logger.warn('Cannot export `exports` field with .mjs extension in CJS package, only .js and .cjs extensions are allowed');
}
}
if (exportsState.invalidExportsFieldType) {
logger.warn('Invalid exports field type, only object or string is allowed');
}
if (exportsState.badCjsRequireExport.value) {
logger.warn('Cannot export `require` field with .mjs extension in CJS package, only .cjs and .js extensions are allowed');
exportsState.badCjsRequireExport.paths.forEach((p)=>{
logger.warn(` ${p}`);
});
}
if (exportsState.badCjsImportExport.value) {
logger.warn('Cannot export `import` field with .js or .cjs extension in CJS package, only .mjs extensions are allowed');
exportsState.badCjsImportExport.paths.forEach((p)=>{
logger.warn(` ${p}`);
});
}
if (exportsState.badEsmRequireExport.value) {
logger.warn('Cannot export `require` field with .js or .mjs extension in ESM package, only .cjs extensions are allowed');
exportsState.badEsmRequireExport.paths.forEach((p)=>{
logger.warn(` ${p}`);
});
}
if (exportsState.badEsmImportExport.value) {
logger.warn('Cannot export `import` field with .cjs extension in ESM package, only .js and .mjs extensions are allowed');
exportsState.badEsmImportExport.paths.forEach((p)=>{
logger.warn(` ${p}`);
});
}
if (exportsState.badTypesExport.length) {
exportsState.badTypesExport.forEach(([outputPath, composedExportType])=>{
logger.error(`Bad export types field with ${composedExportType} in ${outputPath}, use "types" export condition for it`);
});
}
}
var version = "6.9.4";
async function writeDefaultTsconfig(tsConfigPath) {
await fs.promises.writeFile(tsConfigPath, JSON.stringify(DEFAULT_TS_CONFIG, null, 2), 'utf-8');
logger.log(`Detected using TypeScript but tsconfig.json is missing, created a ${pc.blue('tsconfig.json')} for you.`);
}
// ./index -> default
// ./index.development -> development
// ./index.react-server -> react-server
function getExportTypeFromExportPath(exportPath) {
// Skip the first two segments: `.` and `index`
const exportTypes = exportPath.split('.').slice(2);
return getExportTypeFromExportTypesArray(exportTypes);
}
function getSpecialExportTypeFromComposedExportPath(composedExportType) {
const exportTypes = composedExportType.split('.');
for (const exportType of exportTypes){
if (specialExportConventions.has(exportType)) {
return exportType;
}
}
return 'default';
}
function getExportTypeFromExportTypesArray(types) {
let exportType = 'default';
new Set(types).forEach((value)=>{
if (specialExportConventions.has(value)) {
exportType = value;
} else if (value === 'import' || value === 'require' || value === 'types') {
exportType = value;
}
});
return exportType;
}
// ./index -> .
// ./index.development -> .
// ./index.react-server -> .
// ./shared -> ./shared
// ./shared.development -> ./shared
// $binary -> $binary
// $binary/index -> $binary
// $binary/foo -> $binary/foo
function normalizeExportPath(exportPath) {
if (exportPath.startsWith(BINARY_TAG)) {
if (exportPath === `${BINARY_TAG}/index`) {
exportPath = BINARY_TAG;
}
return exportPath;
}
const baseName = exportPath.split('.').slice(0, 2).join('.');
if (baseName === './index') {
return '.';
}
return baseName;
}
async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpath, bins, exportsEntries) {
const isBinaryPath = isBinExportPath(originalSubpath);
const subpath = originalSubpath.replace(BINARY_TAG, 'bin');
const absoluteDirPath = path__default.default.join(sourceFolderPath, subpath);
const dirName = path__default.default.dirname(subpath) // Get directory name regardless of file/directory
;
const baseName = path__default.default.basename(subpath) // Get base name regardless of file/directory
;
const dirPath = path__default.default.join(sourceFolderPath, dirName);
// Match <name>{,/index}.{<ext>,<runtime>.<ext>}
const entryFilesPatterns = [
`${baseName}.{${[
...availableExtensions
].join(',')}}`,
`${baseName}.{${[
...runtimeExportConventions
].join(',')}}.{${[
...availableExtensions
].join(',')}}`,
`${baseName}/index.{${[
...availableExtensions
].join(',')}}`,
`${baseName}/index.{${[
...runtimeExportConventions
].join(',')}}.{${[
...availableExtensions
].join(',')}}`
];
const entryFiles = await tinyglobby.glob(entryFilesPatterns, {
cwd: dirPath,
ignore: [
PRIVATE_GLOB_PATTERN
],
expandDirectories: false
});
validateEntryFiles(entryFiles);
for (const file of entryFiles){
const ext = path__default.default.extname(file).slice(1);
if (!availableExtensions.has(ext) || isTestFile(file)) continue;
const sourceFileAbsolutePath = path__default.default.join(dirPath, file);
const exportPath = posixRelativify(fs.existsSync(absoluteDirPath) && (await fsp__default.default.stat(absoluteDirPath)).isDirectory() ? subpath : originalSubpath);
if (isBinaryPath) {
bins.set(normalizeExportPath(originalSubpath), sourceFileAbsolutePath);
} else {
const parts = path__default.default.basename(file).split('.');
const exportType = parts.length > 2 ? parts[1] : getExportTypeFromExportPath(exportPath);
const specialExportPath = exportType !== 'index' && parts.length > 2 ? exportPath + '.' + exportType : exportPath // Adjust for direct file matches
;
const sourceFilesMap = exportsEntries.get(specialExportPath) || {};
sourceFilesMap[exportType] = sourceFileAbsolutePath;
if (specialExportConventions.has(exportType)) {
const fallbackExportPath = sourceFilenameToExportFullPath(originalSubpath);
const fallbackSourceFilesMap = exportsEntries.get(fallbackExportPath) || {};
Object.assign(sourceFilesMap, fallbackSourceFilesMap);
}
exportsEntries.set(specialExportPath, sourceFilesMap);
}
}
}
// For `prepare` command
async function collectSourceEntries(sourceFolderPath) {
const bins = new Map();
const exportsEntries = new Map();
if (!fs.existsSync(sourceFolderPath)) {
return {
bins,
exportsEntries
};
}
// Match with global patterns
// bin/**/*.<ext>, bin/**/index.<ext>
const binPattern = `bin/**/*.{${[
...availableExtensions
].join(',')}}`;
const srcPattern = `**/*.{${[
...availableExtensions
].join(',')}}`;
const binMatches = await tinyglobby.glob(binPattern, {
cwd: sourceFolderPath,
ignore: [
PRIVATE_GLOB_PATTERN,
TESTS_GLOB_PATTERN
],
expandDirectories: false
});
const srcMatches = await tinyglobby.glob(srcPattern, {
cwd: sourceFolderPath,
ignore: [
PRIVATE_GLOB_PATTERN,
TESTS_GLOB_PATTERN
],
expandDirectories: false
});
for (const file of binMatches){
// convert relative path to export path
const exportPath = sourceFilenameToExportFullPath(normalizePath(file));
const binExportPath = exportPath.replace(/^\.[\//]bin/, BINARY_TAG);
await collectSourceEntriesByExportPath(sourceFolderPath, binExportPath, bins, exportsEntries);
}
for (const file of srcMatches){
const binExportPath = normalizePath(file).replace(/^bin/, BINARY_TAG)// Remove index.<ext> to [^index].<ext> to build the export path
.replace(/(\/index)?\.[^/]+$/, '');
await collectSourceEntriesByExportPath(sourceFolderPath, binExportPath, bins, exportsEntries);
}
return {
bins,
exportsEntries
};
}
// Output with posix style in package.json
function getDistPath(...subPaths) {
return posixRelativify(path.posix.join(DIST, ...subPaths));
}
function stripeBinaryTag(exportName) {
// Add \ to decode leading $
return exportName.replace(/\$binary\//, '');
}
const normalizeBaseNameToExportName = (name)=>{
const baseName = stripeBinaryTag(name);
return /^\.\/index(\.|$)/.test(baseName) ? '.' : posixRelativify(baseName);
};
function createExportCondition(exportName, sourceFile, moduleType, esmOnly = false) {
const isTsSourceFile = isTypescriptFile(sourceFile);
let cjsExtension = 'js';
let esmExtension = 'mjs';
if (moduleType === 'module') {
cjsExtension = 'cjs';
esmExtension = 'js';
}
if (exportName === '.') {
exportName = 'index';
}
const outputPathPrefix = esmOnly ? '' : 'es';
if (isTsSourceFile) {
const importCondition = esmOnly ? {
types: getDistPath(outputPathPrefix, `${exportName}.${dtsExtensionsMap[esmExtension]}`),
default: getDistPath(`${exportName}.${esmExtension}`)
} : {
types: getDistPath(outputPathPrefix, `${exportName}.${dtsExtensionsMap[esmExtension]}`),
default: getDistPath(outputPathPrefix, `${exportName}.${esmExtension}`)
};
if (esmOnly) {
return importCondition;
}
return {
import: importCondition,
require: {
types: getDistPath('cjs', `${exportName}.${dtsExtensionsMap[cjsExtension]}`),
default: getDistPath('cjs', `${exportName}.${cjsExtension}`)
}
};
}
const importPath = getDistPath(`${exportName}.${esmExtension}`);
if (esmOnly) {
return importPath;
}
return {
import: importPath,
require: getDistPath(`${exportName}.${cjsExtension}`)
};
}
function createExportConditionPair(exportName, sourceFile, moduleType, esmOnly = false) {
// <exportName>.<specialCondition>
let specialCondition;
const specialConditionName = getSpecialExportTypeFromComposedExportPath(exportName);
const normalizedExportPath = normalizeExportPath(exportName);
if (specialConditionName !== 'default') {
// e.g.
// ./index.develop -> index
// ./foo.react-server -> foo
const fileBaseName = exportName.split('.').slice(0, 2).join('.').replace('./', '');
const fileName = `${fileBaseName}-${specialConditionName}.mjs`;
specialCondition = {
[specialConditionName]: getDistPath(esmOnly ? '' : 'es', fileName)
};
return [
normalizedExportPath,
specialCondition
];
}
const exportCond = createExportCondition(exportName, sourceFile, moduleType, esmOnly);
return [
normalizedExportPath,
exportCond
];
}
function detectPackageManager(cwd) {
if (fs__default.default.existsSync(path__default.default.join(cwd, 'pnpm-lock.yaml'))) {
return 'pnpm';
}
if (fs__default.default.existsSync(path__default.default.join(cwd, 'yarn.lock'))) {
return 'yarn';
}
if (fs__default.default.existsSync(path__default.default.join(cwd, 'package-lock.json'))) {
return 'npm';
}
// Default to npm if no lock file found
return 'npm';
}
function addBuildScripts(pkgJson, cwd) {
if (!pkgJson.scripts) {
pkgJson.scripts = {};
}
const packageManager = detectPackageManager(cwd);
const buildCmd = packageManager === 'pnpm' ? 'pnpm build' : `${packageManager} run build`;
if (!pkgJson.scripts.build) {
pkgJson.scripts.build = 'bunchee';
}
if (!pkgJson.scripts.prepublishOnly) {
pkgJson.scripts.prepublishOnly = buildCmd;
}
}
async function prepare(cwd, options) {
const sourceFolder = path__default.default.resolve(cwd, SRC);
// Create src/index.ts if src folder doesn't exist
if (!fs__default.default.existsSync(sourceFolder)) {
await fsp__default.default.mkdir(sourceFolder, {
recursive: true
});
const indexPath = path__default.default.join(sourceFolder, 'index.ts');
await fsp__default.default.writeFile(indexPath, "export function index() {\n return 'index'\n}\n", 'utf-8');
}
let hasPackageJson = false;
const pkgJsonPath = path__default.default.join(cwd, 'package.json');
let pkgJson = {};
if (fs__default.default.existsSync(pkgJsonPath)) {
hasPackageJson = true;
const pkgJsonString = await fsp__default.default.readFile(pkgJsonPath, 'utf-8');
pkgJson = JSON.parse(pkgJsonString);
}
// configure `files` field with `dist`
const files = pkgJson.files || [];
if (!files.includes(DIST)) {
files.push(DIST);
}
pkgJson.files = files;
let isUsingTs = false;
// Collect bins and exports entries
const { bins, exportsEntries } = await collectSourceEntries(sourceFolder);
const tsConfigPath = path__default.default.join(cwd, 'tsconfig.json');
const exportsSourceFiles = [
...exportsEntries.values()
].reduce((acc, sourceFiles)=>{
Object.values(sourceFiles).forEach((sourceFile)=>acc.add(sourceFile));
return acc;
}, new Set());
const allSourceFiles = [
...exportsSourceFiles,
...bins.values()
].map((absoluteFilePath)=>absoluteFilePath);
const hasTypeScriptFiles = allSourceFiles.some((filename)=>isTypescriptFile(filename));
if (hasTypeScriptFiles) {
isUsingTs = true;
if (!fs__default.default.existsSync(tsConfigPath)) {
await writeDefaultTsconfig(tsConfigPath);
}
}
// Configure as ESM package by default if there's no package.json
// OR if --esm flag is explicitly set
if (!hasPackageJson || (options == null ? void 0 : options.esm)) {
pkgJson.type = 'module';
}
if (bins.size > 0) {
logger.log('Discovered binaries entries:');
const maxLengthOfBinName = Math.max(...Array.from(bins.keys()).map((binName)=>normalizeBaseNameToExportName(binName).length));
for (const [binName, binFile] of bins.entries()){
const spaces = ' '.repeat(Math.max(maxLengthOfBinName - normalizeBaseNameToExportName(binName).length, 0));
logger.log(` ${normalizeBaseNameToExportName(binName)}${spaces}: ${path__default.default.basename(binFile)}`);
}
if (bins.size === 1 && bins.has(BINARY_TAG)) {
pkgJson.bin = getDistPath('bin', 'index.js');
} else {
pkgJson.bin = {};
for (const [binOriginName] of bins.entries()){
const binName = stripeBinaryTag(binOriginName);
pkgJson.bin[binName === '.' ? pkgJson.name : binName] = getDistPath('bin', binName + '.js');
}
}
}
if (exportsEntries.size > 0) {
logger.log('Discovered exports entries:');
const maxLengthOfExportName = Math.max(...Array.from(exportsEntries.keys()).map((exportName)=>normalizeBaseNameToExportName(exportName).length));
for (const [exportName, sourceFilesMap] of exportsEntries.entries()){
const spaces = ' '.repeat(Math.max(maxLengthOfExportName - normalizeBaseNameToExportName(exportName).length, 0));
for (const exportFile of Object.values(sourceFilesMap)){
logger.log(` ${normalizeBaseNameToExportName(exportName)}${spaces}: ${path__default.default.basename(exportFile)}`);
}
}
const pkgExports = {};
const esmOnly = (options == null ? void 0 : options.esm) === true;
for (const [exportName, sourceFilesMap] of exportsEntries.entries()){
for (const sourceFile of Object.values(sourceFilesMap)){
const [normalizedExportPath, conditions] = createExportConditionPair(exportName, sourceFile, pkgJson.type, esmOnly);
// When esmOnly is true, conditions is either a string or an object with types/default (no import/require)
// When esmOnly is false, conditions is an object with import/require
if (esmOnly || typeof conditions === 'string' || typeof conditions === 'object' && conditions !== null && !('import' in conditions || 'require' in conditions)) {
pkgExports[normalizedExportPath] = conditions;
} else {
pkgExports[normalizedExportPath] = {
...conditions,
...pkgExports[normalizedExportPath]
};
}
}
}
// Configure node10 module resolution
if (exportsEntries.has('./index')) {
const isESM = pkgJson.type === 'module';
const mainExport = pkgExports['.'];
if (esmOnly) {
// When esmOnly is true, mainExport is either a string or an object with types/default
pkgJson.main = isUsingTs ? typeof mainExport === 'object' ? mainExport.default : mainExport : typeof mainExport === 'string' ? mainExport : mainExport.default;
if (isUsingTs && typeof mainExport === 'object') {