UNPKG

bunchee

Version:

zero config bundler for js/ts/jsx libraries

1,230 lines (1,208 loc) 66.6 kB
#!/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') {