UNPKG

@pakk/core

Version:

The core library of pakk, that can manage your package.json for library development.

1,153 lines (1,150 loc) 43.8 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const common = require("@alexaegis/common"); const fs = require("@alexaegis/fs"); const p = require("node:path"); const workspaceTools = require("@alexaegis/workspace-tools"); const node_fs = require("node:fs"); const promises = require("node:fs/promises"); const posix = require("node:path/posix"); const globby = require("globby"); const logging = require("@alexaegis/logging"); const sort = require("@alexaegis/workspace-tools/sort"); const DEFAULT_OUT_DIR = "dist"; const DEFAULT_SRC_DIR = "src"; const DEFAULT_BIN_DIR = "bin"; const DEFAULT_BIN_GLOB = "*"; const DEFAULT_BINSHIM_DIR = "shims"; const DEFAULT_PACKAGE_EXPORTS = "*"; const DEFAULT_PACKAGE_EXPORT_BASEDIR = "."; const DEFAULT_PACKAGE_EXPORT_IGNORES = ["*.(spec|test).*"]; const DEFAULT_STATIC_EXPORT_GLOBS = ["readme.md", "static/**/*", "export/**/*"]; const DEFAULT_EXPORT_FORMATS = ["es", "cjs"]; const ALL_ROLLUP_MODULE_FORMATS = [ "es", "cjs", "amd", "umd", "iife", "system" ]; const ALL_VITE_LIBRARY_FORMATS = [ "es", "cjs", "umd", "iife" ]; const PACKAGE_JSON_KIND = { /** * Used in the repository as the source packageJson */ DEVELOPMENT: "development", /** * The packageJson that will be in the distributed package */ DISTRIBUTION: "distribution" }; const isPackageJsonKindType = (s) => { return Object.values(PACKAGE_JSON_KIND).includes(s); }; var PackageJsonExportTarget = /* @__PURE__ */ ((PackageJsonExportTarget2) => { PackageJsonExportTarget2["SOURCE"] = "source"; PackageJsonExportTarget2["DIST"] = "dist"; PackageJsonExportTarget2["SHIM"] = "shim"; return PackageJsonExportTarget2; })(PackageJsonExportTarget || {}); const NPM_INSTALL_HOOKS = [ "preinstall", "install", "postinstall", "prepublish", "preprepare", "prepare", "postprepare" ]; const ALL_NPM_HOOKS = [ ...NPM_INSTALL_HOOKS, "prepare", "prepack", "postpack", "prepublishOnly", "publish", "postpublish", "prerestart", "restart", "postrestart" ]; const dtsExtension = ".d.ts"; const stripFileExtension = (name, options) => { const extension = name.endsWith(dtsExtension) && options?.stripDts !== false ? dtsExtension : p.extname(name); return name.replace(new RegExp(`${extension}$`), ""); }; const createExportMapFromPaths = (pathsFromBase, options) => { const basePath = options.basePath ?? "."; const exportMap = {}; for (const path of pathsFromBase) { const key = options.keyKind === "extensionless-filename-only" ? stripFileExtension(p.basename(path)) : "./" + stripFileExtension(path); const pathVariants = { "development-to-source": "./" + p.posix.join(options.srcDir, basePath, path), // The original full path, not used by default but there's an option if preferred "development-to-dist": "./" + p.posix.join(options.outDir, path), // It is assumed that files in the outDir replicate their folder structure from the srcDir "distribution-to-dist": "./" + path }; if (options.shimDir) { pathVariants["development-to-shim"] = "./" + p.join(options.shimDir, path); } exportMap[key] = pathVariants; } return exportMap; }; const enterPathPosix = (path, enterCount = 1) => { const explodedPath = p.posix.normalize(path).split(p.posix.sep); const directoryCount = explodedPath.length - 1; explodedPath.splice(0, Math.min(enterCount, directoryCount)); const prefix = path.startsWith("./") ? "./" : ""; return prefix + p.posix.join(...explodedPath); }; const normalizeAutoBinOptions = (options) => { return { binBaseDir: options?.binBaseDir ?? DEFAULT_BIN_DIR, bins: options?.bins ?? DEFAULT_BIN_GLOB, shimDir: options?.shimDir ?? DEFAULT_BINSHIM_DIR, defaultBinIgnore: options?.defaultBinIgnore ?? DEFAULT_PACKAGE_EXPORT_IGNORES, binIgnore: options?.binIgnore ?? [], enabledNpmHooks: options?.enabledNpmHooks ?? ALL_NPM_HOOKS }; }; const normalizePackageName = (packageName) => { return packageName?.replace(/^@/, "").replace("/", "-") ?? ""; }; const mapObject = (o, map) => { return Object.fromEntries( Object.entries(o).map(([key, value]) => { return [key, map(value, key)]; }) ); }; const allBinPathCombinations = [ `${PACKAGE_JSON_KIND.DEVELOPMENT}-to-${PackageJsonExportTarget.SOURCE}`, `${PACKAGE_JSON_KIND.DEVELOPMENT}-to-${PackageJsonExportTarget.DIST}`, `${PACKAGE_JSON_KIND.DISTRIBUTION}-to-${PackageJsonExportTarget.DIST}`, `${PACKAGE_JSON_KIND.DEVELOPMENT}-to-${PackageJsonExportTarget.SHIM}` ]; const mapPathMapToFormat = (binPaths, format, fileNameFn) => { return mapObject(binPaths, (kindsOfPaths, _binName) => { return mapObject(kindsOfPaths, (path, _pathKind) => { const fileName = posix.basename(path); const extensionlessFileName = stripFileExtension(fileName); const dir = posix.dirname(path); return posix.join( dir, format === "SOURCE" ? fileName : fileNameFn(format, extensionlessFileName) ); }); }); }; const markComment = " # autogenerated"; class AutoBin { order = 3; options; context; outDirAbs; shimDirAbs; outBinDirAbs; binPathMap = {}; existingManualBinEntries = {}; constructor(context, options) { this.options = normalizeAutoBinOptions(options); this.context = context; this.outDirAbs = fs.toAbsolute(this.context.outDir, this.context); this.shimDirAbs = posix.join(this.context.cwd, this.options.shimDir); this.outBinDirAbs = posix.join(this.outDirAbs, this.options.binBaseDir); } collectManualBinEntries(workspacePackage) { return Object.fromEntries( Object.entries(workspacePackage.packageJson.bin ?? {}).filter( ([, path]) => !path.startsWith("." + posix.sep + posix.normalize(this.options.shimDir)) || !path.endsWith("js") || path.includes("manual") ) ); } async examinePackage(workspacePackage) { this.existingManualBinEntries = this.collectManualBinEntries(workspacePackage); this.context.logger.trace("existingManualBinEntries", this.existingManualBinEntries); const absoluteBinBaseDir = fs.toAbsolute(posix.join(this.context.srcDir, this.options.binBaseDir), { cwd: workspacePackage.packagePath }); const binFiles = await globby.globby(this.options.bins, { cwd: absoluteBinBaseDir, ignore: [...this.options.binIgnore, ...this.options.defaultBinIgnore], onlyFiles: true, dot: true }); this.binPathMap = createExportMapFromPaths(binFiles, { outDir: this.context.outDir, shimDir: this.options.shimDir, srcDir: this.context.srcDir, basePath: this.options.binBaseDir, keyKind: "extensionless-filename-only" }); for (const binPath of binFiles) { const binName = stripFileExtension(posix.basename(binPath)); this.binPathMap[binName] = { "development-to-source": posix.join( this.context.srcDir, this.options.binBaseDir, binPath ), "development-to-dist": posix.join(this.context.outDir, this.options.binBaseDir, binPath), "distribution-to-dist": posix.join(this.options.binBaseDir, binPath), "development-to-shim": posix.join(this.options.shimDir, binPath) }; } const packageJsonUpdates = {}; packageJsonUpdates.bin = void 0; for (const script in packageJsonUpdates.scripts) { if (packageJsonUpdates.scripts[script]?.endsWith(markComment)) { packageJsonUpdates.scripts[script] = void 0; } } return { packageJsonUpdates, bundlerEntryFiles: binFiles.reduce((acc, binFile) => { const path = posix.join(this.context.srcDir, this.options.binBaseDir, binFile); const alias = posix.join(this.options.binBaseDir, stripFileExtension(binFile)); acc[alias] = path; return acc; }, {}) }; } /** * for module based packages, bins are modules too and the adjust path * step only acts for the 'es' format */ async process(packageJson, pathContext) { if (this.context.primaryFormat === pathContext.format) { const binPathMapForFormat = mapPathMapToFormat( this.binPathMap, this.context.primaryFormat, this.context.fileName ); const packageName = normalizePackageName(packageJson.name); await this.ensureEsmBinEntriesRenamed(); if (pathContext.packageJsonKind === PACKAGE_JSON_KIND.DEVELOPMENT) { await this.createShims( Object.values(binPathMapForFormat).map( (pathKinds) => pathKinds["development-to-shim"] ), this.context.primaryFormat ); } await Promise.allSettled( Object.values(binPathMapForFormat).flatMap((pathKinds) => [ pathKinds["development-to-dist"], pathKinds["development-to-shim"] ]).filter((executable) => node_fs.existsSync(executable)).map( (executable) => fs.turnIntoExecutable(executable, { cwd: this.context.cwd, logger: this.context.logger }) ) ); await this.preLink( mapObject(binPathMapForFormat, (pathKinds) => pathKinds["development-to-dist"]), packageName ); const update = Object.entries(binPathMapForFormat).reduce( (result, [key, value]) => { if (result.scripts && this.options.enabledNpmHooks.includes(key)) { if (!packageJson.scripts?.[key] || packageJson.scripts[key]?.endsWith(markComment)) { if (pathContext.packageJsonKind === PACKAGE_JSON_KIND.DISTRIBUTION) { result.scripts[key] = value["distribution-to-dist"] + markComment; } else if (NPM_INSTALL_HOOKS.includes(key)) { result.scripts[key] = "# local install hooks are disabled" + markComment; } else { result.scripts[key] = value["development-to-shim"] + markComment; } } key = packageName + "-" + key; } if (key.endsWith("index")) { key = key.replace("index", ""); } if (key === "") { const packageJsonName = workspaceTools.getPackageJsonTemplateVariables(packageJson); key = packageJsonName.packageNameWithoutOrg; } if (!result.bin) { result.bin = {}; } result.bin[key] = "." + posix.sep + (pathContext.packageJsonKind === PACKAGE_JSON_KIND.DISTRIBUTION ? value["distribution-to-dist"] : value["development-to-shim"]); return result; }, { bin: this.existingManualBinEntries, scripts: {} } ); if (typeof update.bin === "object" && Object.keys(update.bin).length === 0) { delete update.bin; } if (typeof update.scripts === "object" && Object.keys(update.scripts).length === 0) { delete update.scripts; } return [{ bin: void 0 }, update]; } else { return void 0; } } /** * Ensures shimDir exists and creates simple javascript files that are * importing their counterpart from `outDir` */ async createShims(shimPaths, format) { if (this.context.packageType === "module" && format === "es" || this.context.packageType === "commonjs" && format !== "es") { this.context.logger.info( `Creating shims for bins in ${format}/${this.context.packageType} format` ); await promises.rm(this.shimDirAbs, { force: true, recursive: true }); const shimDirToOutBin = posix.relative(this.shimDirAbs, this.outBinDirAbs); const formatJs = await fs.getPrettierFormatter(); const shimPathsToMake = await Promise.all( shimPaths.map( (path) => promises.readFile(fs.toAbsolute(path, this.context), { encoding: "utf8" }).then( (content) => content.includes("// autogenerated") ? path : void 0 ).catch(() => path) ) ).then((results) => results.filter((result) => result !== void 0)); if (shimPathsToMake.length > 0) { this.context.logger.info(`create shims for ${shimPathsToMake.join("; ")}`); await Promise.allSettled( shimPathsToMake.map(async (path) => { const outBinPath = enterPathPosix(path, 1); const builtBinFromShims = shimDirToOutBin + posix.sep + outBinPath; const formattedESShimContent = await formatJs( `// autogenerated-by-pakk export * from '${builtBinFromShims}'; ` ); const formattedCJSShimContent = await formatJs( `// autogenerated-by-pakk, as seen from tsc /* eslint-disable unicorn/prefer-module */ /* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable no-prototype-builtins */ var __createBinding = function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }; var __exportStar = function(m, exports) { for (var p in m) if (p !== 'default' && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); }; __exportStar(require('${builtBinFromShims}'), exports); ` ); const shimPathAbs = posix.join(this.shimDirAbs, outBinPath); try { await promises.mkdir(posix.dirname(shimPathAbs), { recursive: true }); await promises.writeFile( shimPathAbs, format === "es" ? formattedESShimContent : formattedCJSShimContent ); } catch (error) { this.context.logger.error( "Couldn't write", shimPathAbs, "error happened", error ); } }) ); } } } /** * Ensures that all .js files in the dist folder are renamed to the * expected name this plugin added them to the bin entry. */ async ensureEsmBinEntriesRenamed() { if (this.context.packageType === "module") { const esBinPathsMap = mapPathMapToFormat(this.binPathMap, "es", this.context.fileName); const data = Object.entries(esBinPathsMap).flatMap(([_binName, binPath]) => { const extensionlessPath = stripFileExtension(binPath["development-to-dist"]); return [ { binPath: extensionlessPath + ".js", newBinPath: binPath["development-to-dist"] }, { binPath: extensionlessPath + ".js.map", newBinPath: binPath["development-to-dist"] + ".map" } ]; }); await Promise.all( data.filter(({ binPath }) => node_fs.existsSync(binPath)).map( ({ binPath, newBinPath }) => promises.rename(binPath, newBinPath).catch(() => false) ) ); } } // TODO: something is funky, there are extensionless files in the distbin dir and they are not executable. /** * */ async preLink(binRecord, packageName) { const workspaceBinDirectoryPath = posix.join( this.context.rootWorkspacePackage.packagePath, "node_modules", ".bin" ); const packageBinDirectoryPath = fs.toAbsolute(posix.join("node_modules", ".bin"), this.context); const symlinksToMake = Object.entries(binRecord).flatMap(([binName, binPath]) => { if (this.options.enabledNpmHooks.includes(binName)) { binName = packageName + "-" + binName; } return [ posix.join(workspaceBinDirectoryPath, binName), posix.join(packageBinDirectoryPath, binName) ].map((targetFilePath) => { const relativeFromTargetBackToFile = posix.relative(posix.dirname(targetFilePath), binPath); return { relativeFromTargetBackToFile, targetFilePath }; }); }); await Promise.all( symlinksToMake.map(async ({ targetFilePath, relativeFromTargetBackToFile }) => { try { await promises.symlink(relativeFromTargetBackToFile, targetFilePath); this.context.logger.info( `symlinked ${targetFilePath} to ${relativeFromTargetBackToFile}` ); } catch { this.context.logger.info(`${targetFilePath} is already present`); } }) ); } } class AutoCopyLicense { order = 4; context; licensePath; constructor(context, _options) { this.context = context; } examinePackage(workspacePackage) { const pathsOfInterest = [ workspacePackage.packagePath, this.context.rootWorkspacePackage.packagePath ]; const possibleLiceseFileNames = ["license", "LICENSE"]; const possibleLicenseFileLocations = pathsOfInterest.flatMap( (path) => possibleLiceseFileNames.map((fileName) => p.join(path, fileName)) ); this.licensePath = possibleLicenseFileLocations.find((path) => node_fs.existsSync(path)); if (this.licensePath) { this.context.logger.trace("found license file at", this.licensePath); } else { this.context.logger.warn( "no license file found in the following locations", possibleLicenseFileLocations ); } return {}; } async process(_packageJson, pathContext) { if (pathContext.packageJsonKind === PACKAGE_JSON_KIND.DISTRIBUTION) { if (!this.licensePath) { this.context.logger.warn("No license file found!"); return; } const licenseFileDestination = p.join( fs.toAbsolute(this.context.outDir, this.context), p.basename(this.licensePath) ); try { await promises.cp(this.licensePath, licenseFileDestination); this.context.logger.info("Copied license file from", this.licensePath); } catch (error) { this.context.logger.error( "Couldn't copy license file from", this.licensePath, "to", this.context.outDir, "error happened", error ); } } } } const pakkDirectivePrefix = "pakk:"; const pakkDirectiveNotDistributed = `${pakkDirectivePrefix}not-distributed`; const everyPakkDirective = [pakkDirectiveNotDistributed]; class AutoDirective { order = 5; context; constructor(context) { this.context = context; this.context.logger.info("enabled directives:"); this.context.logger.info(pakkDirectiveNotDistributed); } postprocess(workspacePackage, packageJsonKind) { return common.deepMapObject(workspacePackage.packageJson, (key, value) => { if (packageJsonKind === "development" && typeof key === "string" && key.includes(pakkDirectivePrefix) && everyPakkDirective.every((directive) => !key.includes(directive))) { this.context.logger.warn( "key contains a pakk directive that is not recognized", key ); } if (packageJsonKind === "development" && typeof value === "string" && value.includes(pakkDirectivePrefix) && everyPakkDirective.every((directive) => !value.includes(directive))) { this.context.logger.warn( "value contains a pakk directive that is not recognized", value ); } return packageJsonKind === "distribution" && (typeof key === "string" && key.includes(pakkDirectiveNotDistributed) || typeof value === "string" && value.includes(pakkDirectiveNotDistributed)) ? void 0 : value; }); } } const DEFAULT_PACKAGE_JSON_EXPORT_PATH = "./package.json"; const normalizeAutoExportOptions = (options) => { return { exports: options?.exports ?? DEFAULT_PACKAGE_EXPORTS, exportsIgnore: options?.exportsIgnore ?? [], defaultExportsIgnore: options?.defaultExportsIgnore ?? DEFAULT_PACKAGE_EXPORT_IGNORES, exportBaseDir: options?.exportBaseDir ?? DEFAULT_PACKAGE_EXPORT_BASEDIR, developmentPackageJsonExportsTarget: options?.developmentPackageJsonExportsTarget ?? PackageJsonExportTarget.DIST, svelte: options?.svelte ?? false, exportPackageJson: options?.exportPackageJson ?? true }; }; const normalizeAutoExportStaticOptions = (options) => { return { staticExports: options?.staticExports ?? DEFAULT_STATIC_EXPORT_GLOBS, exportPackageJson: options?.exportPackageJson ?? true }; }; class AutoExportStatic { order = 2; options; context; staticExports = {}; constructor(context, options) { this.options = normalizeAutoExportStaticOptions(options); this.context = context; } static collectFileMap = async (cwd, globs) => { const globbyResult = await globby.globby(globs, { cwd, dot: true }); return globbyResult.reduce((accumulator, next) => { const key = `.${posix.sep}${stripFileExtension(posix.basename(next))}`; accumulator[key] = `.${posix.sep}${next}`; return accumulator; }, {}); }; static copyAll = async (cwd, relativeSourceFiles, outDirectory) => { await Promise.allSettled( relativeSourceFiles.map((sourceFile) => ({ sourceFile: posix.join(cwd, sourceFile), targetFile: posix.join(cwd, outDirectory, sourceFile) })).filter( ({ sourceFile, targetFile }) => node_fs.existsSync(sourceFile) && !node_fs.existsSync(targetFile) ).map( ({ sourceFile, targetFile }) => promises.cp(sourceFile, targetFile, { preserveTimestamps: true, recursive: true }) ) ); }; async examinePackage(_workspacePackage) { this.staticExports = await AutoExportStatic.collectFileMap( this.context.workspacePackage.packagePath, this.options.staticExports ); if (this.options.exportPackageJson) { this.staticExports[DEFAULT_PACKAGE_JSON_EXPORT_PATH] = DEFAULT_PACKAGE_JSON_EXPORT_PATH; } return {}; } async process(_packageJson, pathContext) { if (pathContext.packageJsonKind === PACKAGE_JSON_KIND.DISTRIBUTION) { const staticFilePaths = Object.values(this.staticExports); this.context.logger.info("copy all static files", staticFilePaths); await AutoExportStatic.copyAll( this.context.workspacePackage.packagePath, staticFilePaths, this.context.outDir ); } return { exports: this.staticExports }; } postprocess(workspacePackage) { if (workspacePackage.packageJson.exports) { for (const [key, value] of Object.entries(workspacePackage.packageJson.exports)) { if (typeof value === "string" && !this.staticExports[key]) { delete workspacePackage.packageJson.exports[key]; } } } return workspacePackage.packageJson; } } const allExportPathCombinations = [ `${PACKAGE_JSON_KIND.DEVELOPMENT}-to-${PackageJsonExportTarget.SOURCE}`, `${PACKAGE_JSON_KIND.DEVELOPMENT}-to-${PackageJsonExportTarget.DIST}`, `${PACKAGE_JSON_KIND.DISTRIBUTION}-to-${PackageJsonExportTarget.DIST}` ]; class AutoExport { order = 1; options; context; exportMap = {}; constructor(context, options) { this.context = context; this.options = normalizeAutoExportOptions(options); } async examinePackage(_packageJson) { const absoluteExportBaseDir = fs.toAbsolute( p.posix.join(this.context.srcDir, this.options.exportBaseDir), { cwd: this.context.workspacePackage.packagePath } ); const ignore = [...this.options.exportsIgnore, ...this.options.defaultExportsIgnore]; this.context.logger.trace("ignoring exports", ignore); const entryFiles = await globby.globby(this.options.exports, { cwd: absoluteExportBaseDir, ignore, onlyFiles: true, dot: true }); this.context.logger.info("detected package exports", entryFiles); this.exportMap = createExportMapFromPaths(entryFiles, { outDir: this.context.outDir, srcDir: this.context.srcDir, basePath: this.options.exportBaseDir, keyKind: "extensionless-relative-path-from-base" }); this.context.logger.trace("exportMap", this.exportMap); return { bundlerEntryFiles: entryFiles.reduce((acc, entryFile) => { const path = p.posix.join( this.context.srcDir, this.options.exportBaseDir, entryFile ); const alias = stripFileExtension(entryFile); acc[alias] = path; return acc; }, {}) }; } /** * This plugin compiles the exports object for a packageJson file * * For the distributed packageJson it should always contain paths that are * targeting the dist folder from the dist folder. * * For development packageJson the types always target the source for * immediate feedback by the LSP by local consumers of the package. * The actual code that's being imported by node has two options, * by default they target the outDir and expect libraries to be built * before actually running them in a local setting. * There's an alternative mode however that will target the source files. */ process(_packageJson, pathContext) { const entryExports = {}; if (this.options.exportPackageJson) { this.context.logger.debug("adding the package.json export entry"); entryExports[DEFAULT_PACKAGE_JSON_EXPORT_PATH] = DEFAULT_PACKAGE_JSON_EXPORT_PATH; } for (const [key, pathVariants] of Object.entries(this.exportMap)) { let path; let typesPath = pathVariants["development-to-source"]; const isSvelteFile = pathVariants["distribution-to-dist"].endsWith(".svelte"); const developmentPackageJsonExportsTarget = this.options.svelte ? PackageJsonExportTarget.SOURCE : this.options.developmentPackageJsonExportsTarget; if (pathContext.packageJsonKind === PACKAGE_JSON_KIND.DISTRIBUTION) { path = pathVariants["distribution-to-dist"]; if (isSvelteFile) { typesPath = pathVariants["distribution-to-dist"] + ".d.ts"; } else if (pathVariants["distribution-to-dist"].endsWith(".ts")) { typesPath = stripFileExtension(pathVariants["distribution-to-dist"]) + ".d.ts"; } else { typesPath = pathVariants["distribution-to-dist"]; } } else if (developmentPackageJsonExportsTarget === PackageJsonExportTarget.SOURCE) { if (isSvelteFile) { typesPath = pathVariants["development-to-dist"] + ".d.ts"; } path = pathVariants["development-to-source"]; } else { path = pathVariants["development-to-dist"]; } const fileName = p.basename(path); const dir = posix.dirname(path); const extensionlessFileName = stripFileExtension(fileName); const exportConditions = { types: typesPath }; if (this.context.formats.includes("cjs")) { exportConditions.require = "./" + p.posix.join(dir, this.context.fileName("cjs", extensionlessFileName)); } else { if (this.context.formats.includes("umd")) { exportConditions.require = "./" + p.posix.join(dir, this.context.fileName("umd", extensionlessFileName)); } else if (this.context.formats.includes("iife")) { exportConditions.require = "./" + p.posix.join(dir, this.context.fileName("iife", extensionlessFileName)); } } if (this.context.formats.includes("es")) { exportConditions.import = "./" + p.posix.join(dir, this.context.fileName("es", extensionlessFileName)); } if (this.context.formats.includes(this.context.primaryFormat)) { exportConditions.default = "./" + p.posix.join( dir, isSvelteFile ? fileName : this.context.fileName( this.context.primaryFormat, extensionlessFileName ) ); } if (this.options.svelte) { exportConditions["svelte"] = "./" + p.posix.join( dir, // Let svelte import the source file regardless isSvelteFile ? fileName : this.context.fileName("es", extensionlessFileName) ); if (isSvelteFile) { delete exportConditions.import; delete exportConditions.require; } } const indexNormalizedKey = key.replace(/\/index$/, "/").replace(/^.\/$/, "."); entryExports[indexNormalizedKey] = exportConditions; } return [{ exports: void 0 }, { exports: entryExports }]; } } const createDefaultViteFileNameFn = (packageType) => (format, extensionlessFileName) => extensionlessFileName + getDefaultViteBundleFileExtension(format, packageType); const getDefaultViteBundleFileExtension = (format, packageType = "commonjs") => { switch (format) { case "es": case "esm": { return packageType === "module" ? ".js" : ".mjs"; } case "cjs": { return packageType === "commonjs" ? ".js" : ".cjs"; } default: { throw new Error( `Cannot determine default fileName for format: ${format} only esm and cjs can be auto determined.` ); } } }; const DEFAULT_AUTO_METADATA_KEYS_FROM_WORKSPACE = [ "license", "author", "homepage", "bugs", "keywords", "config", "engines", "repository" ]; const DEFAULT_AUTO_METADATA_MANDATORY_KEYS = ["name", "description", "version"]; const normalizeAutoMetadataOptions = (options) => { return { ...logging.normalizeLoggerOption(options), keysFromWorkspace: options?.keysFromWorkspace ?? DEFAULT_AUTO_METADATA_KEYS_FROM_WORKSPACE, mandatoryKeys: options?.mandatoryKeys ?? DEFAULT_AUTO_METADATA_MANDATORY_KEYS, fallbackEntries: options?.fallbackEntries ?? {}, overrideEntries: options?.overrideEntries ?? {} }; }; class AutoMetadata { order = 6; options; context; metadataFromWorkspacePackageJson; constructor(context, rawOptions) { this.context = context; this.options = normalizeAutoMetadataOptions(rawOptions); } examinePackage(workspacePackage) { this.context.logger.trace( "collecting keys from workspace:", this.options.keysFromWorkspace ); this.metadataFromWorkspacePackageJson = Object.fromEntries( Object.entries(this.context.rootWorkspacePackage.packageJson).filter( ([key]) => this.options.keysFromWorkspace.includes(key) && !Object.hasOwn(workspacePackage.packageJson, key) ) ); return {}; } postprocess(workspacePackage, packageJsonKind) { if (packageJsonKind === PACKAGE_JSON_KIND.DISTRIBUTION) { this.context.logger.info("filling metadata for distributed packageJson"); this.context.logger.trace("fallbackEntries", this.options.fallbackEntries); this.context.logger.trace( "metadataFromWorkspacePackageJson", this.metadataFromWorkspacePackageJson ); this.context.logger.trace("overrideEntries", this.options.overrideEntries); const filledPackageJson = common.deepMerge([ this.options.fallbackEntries, workspacePackage.packageJson, this.metadataFromWorkspacePackageJson, this.options.overrideEntries ]); if (typeof filledPackageJson.repository === "object") { filledPackageJson.repository.directory = workspacePackage.packagePathFromRootPackage; } const missingKeys = this.options.mandatoryKeys.filter( (mandatoryKey) => !Object.hasOwn(filledPackageJson, mandatoryKey) ); if (missingKeys.length > 0) { const errorMessage = `Some keys are missing! Please define the following keys in your packageJson file: ${missingKeys.join(", ")}`; this.context.logger.error(errorMessage); throw new Error(errorMessage); } return filledPackageJson; } else { return workspacePackage.packageJson; } } } class AutoPeer { order = 5; context; constructor(context) { this.context = context; } postprocess(workspacePackage, packageJsonKind) { if (packageJsonKind === PACKAGE_JSON_KIND.DISTRIBUTION && workspacePackage.packageJson.dependencies && workspacePackage.packageJson.peerDependencies) { this.context.logger.info("removing dependencies that are also peerDependencies..."); const peerDependencies = Object.keys(workspacePackage.packageJson.peerDependencies); const deduplicatedDependencies = Object.fromEntries( Object.entries(workspacePackage.packageJson.dependencies).filter( ([dependency]) => !peerDependencies.includes(dependency) ) ); return { ...workspacePackage.packageJson, dependencies: Object.values(deduplicatedDependencies).length > 0 ? deduplicatedDependencies : void 0 }; } else { return workspacePackage.packageJson; } } } const removeWorkspaceVersionDirective = (version, pkg) => { const result = version.replace(/^workspace:/, ""); return pkg?.packageJson.version && (result.length === 0 || result === "^" || result === "~") ? result + pkg.packageJson.version : result; }; class AutoRemoveWorkspaceDirective { order = 6; context; constructor(context) { this.context = context; } postprocess(workspacePackage, packageJsonKind) { if (packageJsonKind === PACKAGE_JSON_KIND.DISTRIBUTION) { this.context.logger.info("removing the workspace: specifier from dependencies..."); return workspaceTools.PACKAGE_JSON_DEPENDENCY_FIELDS.reduce( (packageJson, dependencyField) => { const dependencies = packageJson[dependencyField]; if (dependencies) { packageJson[dependencyField] = common.mapObject( dependencies, (value, key) => value ? removeWorkspaceVersionDirective( value, this.context.allWorkspacePackages.find( (pkg) => pkg.packageJson.name === key ) ) : value ); } return packageJson; }, { ...workspacePackage.packageJson } ); } else { return workspacePackage.packageJson; } } } const normalizeAutoSortPackageJsonOptions = (options) => { return { ...fs.normalizeCwdOption(options), sortingPreference: options?.sortingPreference }; }; class AutoSort { order = 7; context; options; sortingNormalizer; constructor(context, options) { this.context = context; this.options = normalizeAutoSortPackageJsonOptions(options); } async examinePackage() { this.sortingNormalizer = await sort.createJsonSortingPreferenceNormalizer( "package.json", this.options ); return {}; } postprocess(workspacePackage) { this.context.logger.info("sorting packageJson..."); return common.sortObject( workspacePackage.packageJson, this.sortingNormalizer(this.options.sortingPreference) ); } } const findCurrentAndRootWorkspacePackage = async (rawOptions) => { const options = fs.normalizeCwdOption(rawOptions); const packageDirName = options.cwd.slice(Math.max(0, options.cwd.lastIndexOf(p.sep))); const workspace = await workspaceTools.collectWorkspacePackages(options); const rootWorkspacePackage = workspace.find( (workspacePackage2) => workspacePackage2.packageKind === "root" ); const workspacePackage = workspace.find( (workspacePackage2) => workspacePackage2.packageKind === "regular" && workspacePackage2.packagePath.includes(options.cwd) && (workspacePackage2.packagePath + p.sep).includes(packageDirName + p.sep) ); if (!rootWorkspacePackage || !workspacePackage) { throw new Error("Package could not be determined"); } return { workspacePackage, rootWorkspacePackage, allWorkspacePackages: workspace }; }; const normalizePakkOptions = (options) => { const logLevelOptions = logging.normalizeLogLevelOption(options); return { ...fs.normalizeCwdOption(options), ...logLevelOptions, ...fs.normalizeWriteJsonOptions(options), ...normalizeAutoBinOptions(options), ...normalizeAutoExportOptions(options), ...normalizeAutoExportStaticOptions(options), ...normalizeAutoMetadataOptions(options), ...normalizeAutoSortPackageJsonOptions(options), logger: options?.logger ?? logging.createLogger({ name: "pakk", minLevel: logLevelOptions.logLevel }), sourcePackageJson: options?.sourcePackageJson ?? "package.json", srcDir: options?.srcDir ?? DEFAULT_SRC_DIR, outDir: options?.outDir ?? DEFAULT_OUT_DIR, enabledFeatures: options?.enabledFeatures ?? [], disabledFeatures: options?.disabledFeatures ?? [], autoPrettier: options?.autoPrettier ?? true, dts: options?.dts ?? true, preserveImportAttributes: options?.preserveImportAttributes ?? "assert", targetPackageJsonKind: options?.targetPackageJsonKind }; }; const createIsFeatureEnabled = (enabledFeatures, disabledFeatures) => (feature) => { const isEnabled = enabledFeatures.length === 0 || enabledFeatures.includes(feature); const isDisabled = disabledFeatures.includes(feature); return isEnabled && !isDisabled; }; const pakkFeatures = [ "bin", "copy-license", "export", "export-static", "metadata", "peer", "sort", "directive", "remove-workspace-directive" ]; class Pakk { options; context; features = []; constructor(context, options) { this.context = context; this.options = options; if (this.options.svelte) { this.options.logger.info("svelte mode: forcing 'es' only output format"); this.context.formats = ["es"]; } const isFeatureEnabled = createIsFeatureEnabled( this.options.enabledFeatures, this.options.disabledFeatures ); const pakkFeatureMap = { bin: AutoBin, "copy-license": AutoCopyLicense, export: AutoExport, "export-static": AutoExportStatic, metadata: AutoMetadata, peer: AutoPeer, sort: AutoSort, directive: AutoDirective, "remove-workspace-directive": AutoRemoveWorkspaceDirective }; this.features = Object.entries(pakkFeatureMap).filter(([featureName]) => isFeatureEnabled(featureName)).map(([featureName, feature]) => { return new feature( { ...this.context, logger: options.logger.getSubLogger({ name: featureName }) }, options ); }).sort((a, b) => a.order - b.order); this.options.logger.trace("features enabled:", this.features.length); this.options.logger.trace("context", { ...this.context, logger: "SKIPPED FROM LOG", rootWorkspacePackage: { ...this.context.rootWorkspacePackage, packageJson: "SKIPPED FROM LOG" }, workspacePackage: { ...this.context.workspacePackage, packageJson: "SKIPPED FROM LOG" } }); } getLogger() { return this.options.logger; } getTargetPackageJsonKinds() { return this.options.targetPackageJsonKind ? [this.options.targetPackageJsonKind] : Object.values(PACKAGE_JSON_KIND); } static async withContext(manualContext, rawOptions) { const options = normalizePakkOptions(rawOptions); const workspaceContext = await findCurrentAndRootWorkspacePackage(options); const primaryFormat = Pakk.primaryLibraryFormat( workspaceContext.workspacePackage.packageJson ); const packageType = workspaceContext.workspacePackage.packageJson.type === "module" ? "module" : "commonjs"; const pakk = new Pakk( { ...workspaceContext, ...manualContext, primaryFormat, packageType, fileName: manualContext.fileName ?? createDefaultViteFileNameFn(packageType), outDir: options.outDir, srcDir: options.srcDir, cwd: options.cwd, logger: options.logger }, options ); return pakk; } static primaryLibraryFormat(packageJson) { return packageJson.type === "module" ? "es" : "cjs"; } /** * 1st step, examining the package. This step does not write anything. * It can be done before the build takes place as it's only supposed to * take a look at your source code. */ async examinePackage(workspacePackage = this.context.workspacePackage) { const detectedExports = await common.asyncFilterMap( this.features, async (plugin) => await plugin.examinePackage?.(workspacePackage) ); return common.deepMerge([ { bundlerEntryFiles: {}, packageJsonUpdates: {} }, ...detectedExports ]); } /** * Will return a path adjusted packageJson object based on the content of * the workspace for both the SOURCE and DISTRIBUTION packageJson files. * * And also returns the path where it should be written to. */ async createUpdatedPackageJson(packageJsonKind) { const packageJsonUpdates = await common.asyncFilterMap( this.features, async (plugin) => await plugin.process?.(structuredClone(this.context.workspacePackage.packageJson), { packageJsonKind, format: this.context.primaryFormat }) ); let updatedPackageJson = common.deepMerge([ this.context.workspacePackage.packageJson, ...packageJsonUpdates.flat(1) ]); updatedPackageJson = this.features.reduce( (packageJson, plugin) => plugin.postprocess?.( { ...this.context.workspacePackage, packageJson }, packageJsonKind ) ?? packageJson, updatedPackageJson ); const path = packageJsonKind === PACKAGE_JSON_KIND.DISTRIBUTION ? fs.toAbsolute( p.join( this.context.workspacePackage.packagePath, this.options.outDir, "package.json" ), this.options ) : fs.toAbsolute( p.join(this.context.workspacePackage.packagePath, "package.json"), this.options ); return { updatedPackageJson, path }; } } exports.ALL_NPM_HOOKS = ALL_NPM_HOOKS; exports.ALL_ROLLUP_MODULE_FORMATS = ALL_ROLLUP_MODULE_FORMATS; exports.ALL_VITE_LIBRARY_FORMATS = ALL_VITE_LIBRARY_FORMATS; exports.AutoBin = AutoBin; exports.AutoCopyLicense = AutoCopyLicense; exports.AutoDirective = AutoDirective; exports.AutoExport = AutoExport; exports.AutoExportStatic = AutoExportStatic; exports.AutoMetadata = AutoMetadata; exports.AutoPeer = AutoPeer; exports.AutoRemoveWorkspaceDirective = AutoRemoveWorkspaceDirective; exports.AutoSort = AutoSort; exports.DEFAULT_AUTO_METADATA_KEYS_FROM_WORKSPACE = DEFAULT_AUTO_METADATA_KEYS_FROM_WORKSPACE; exports.DEFAULT_AUTO_METADATA_MANDATORY_KEYS = DEFAULT_AUTO_METADATA_MANDATORY_KEYS; exports.DEFAULT_BINSHIM_DIR = DEFAULT_BINSHIM_DIR; exports.DEFAULT_BIN_DIR = DEFAULT_BIN_DIR; exports.DEFAULT_BIN_GLOB = DEFAULT_BIN_GLOB; exports.DEFAULT_EXPORT_FORMATS = DEFAULT_EXPORT_FORMATS; exports.DEFAULT_OUT_DIR = DEFAULT_OUT_DIR; exports.DEFAULT_PACKAGE_EXPORTS = DEFAULT_PACKAGE_EXPORTS; exports.DEFAULT_PACKAGE_EXPORT_BASEDIR = DEFAULT_PACKAGE_EXPORT_BASEDIR; exports.DEFAULT_PACKAGE_EXPORT_IGNORES = DEFAULT_PACKAGE_EXPORT_IGNORES; exports.DEFAULT_PACKAGE_JSON_EXPORT_PATH = DEFAULT_PACKAGE_JSON_EXPORT_PATH; exports.DEFAULT_SRC_DIR = DEFAULT_SRC_DIR; exports.DEFAULT_STATIC_EXPORT_GLOBS = DEFAULT_STATIC_EXPORT_GLOBS; exports.NPM_INSTALL_HOOKS = NPM_INSTALL_HOOKS; exports.PACKAGE_JSON_KIND = PACKAGE_JSON_KIND; exports.PackageJsonExportTarget = PackageJsonExportTarget; exports.Pakk = Pakk; exports.allBinPathCombinations = allBinPathCombinations; exports.allExportPathCombinations = allExportPathCombinations; exports.createIsFeatureEnabled = createIsFeatureEnabled; exports.everyPakkDirective = everyPakkDirective; exports.isPackageJsonKindType = isPackageJsonKindType; exports.mapPathMapToFormat = mapPathMapToFormat; exports.normalizeAutoBinOptions = normalizeAutoBinOptions; exports.normalizeAutoExportOptions = normalizeAutoExportOptions; exports.normalizeAutoExportStaticOptions = normalizeAutoExportStaticOptions; exports.normalizeAutoMetadataOptions = normalizeAutoMetadataOptions; exports.normalizeAutoSortPackageJsonOptions = normalizeAutoSortPackageJsonOptions; exports.normalizePakkOptions = normalizePakkOptions; exports.pakkDirectiveNotDistributed = pakkDirectiveNotDistributed; exports.pakkDirectivePrefix = pakkDirectivePrefix; exports.pakkFeatures = pakkFeatures; //# sourceMappingURL=index.cjs.map