@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
JavaScript
"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