@sanity/pkg-utils
Version:
Simple utilities for modern npm packages.
718 lines (717 loc) • 35.3 kB
JavaScript
import { parsePackage, _typoMap, ZodError, parseExports } from "@sanity/parse-package-json";
import chalk from "chalk";
import fs from "node:fs/promises";
import path, { resolve } from "node:path";
import browserslist from "browserslist";
import { DEFAULT_BROWSERSLIST_QUERY } from "./defaults.js";
import { existsSync } from "node:fs";
import { isRecord } from "./handleError.js";
import ts from "@typescript/typescript6";
import { errorMap } from "zod-validation-error/v3";
import { z } from "zod/v3";
function createConsoleSpy(options) {
const { onRestored } = options || {}, original = {
log: console.log,
warn: console.warn,
error: console.error
}, messages = [];
return console.log = (...args) => messages.push({ type: "log", args }), console.warn = (...args) => messages.push({ type: "warn", args }), console.error = (...args) => messages.push({ type: "error", args }), {
messages,
restore: () => {
console.log = original.log, console.warn = original.warn, console.error = original.error, onRestored?.(messages);
}
};
}
const dependencyPlacementRules = [
{
name: "react-is",
option: "noReactIsPeerDependency",
disallowedIn: ["peerDependencies"],
allowedIn: ["dependencies", "devDependencies"]
},
{
name: "@sanity/ui",
option: "noSanityUiPeerDependency",
disallowedIn: ["peerDependencies"],
allowedIn: ["dependencies", "devDependencies"]
},
{
name: "@sanity/icons",
option: "noSanityIconsPeerDependency",
disallowedIn: ["peerDependencies"],
allowedIn: ["dependencies", "devDependencies"]
},
{
name: "sanity",
option: "noSanityDependency",
disallowedIn: ["dependencies"],
allowedIn: ["devDependencies", "peerDependencies"]
},
{
name: "styled-components",
option: "noStyledComponentsDependency",
disallowedIn: ["dependencies"],
allowedIn: ["devDependencies", "peerDependencies"]
},
{
name: "react",
option: "noReactDependency",
disallowedIn: ["dependencies"],
allowedIn: ["devDependencies", "peerDependencies"]
},
{
name: "react-dom",
option: "noReactDomDependency",
disallowedIn: ["dependencies"],
allowedIn: ["devDependencies", "peerDependencies"]
},
{
name: "@types/react",
option: "noReactTypesDependency",
disallowedIn: ["dependencies"],
allowedIn: ["devDependencies", "peerDependencies"],
requiredPeerVersion: "*"
},
{
name: "@types/react-dom",
option: "noReactDomTypesDependency",
disallowedIn: ["dependencies"],
allowedIn: ["devDependencies", "peerDependencies"],
requiredPeerVersion: "*"
},
{
name: "@types/node",
option: "noNodeTypesDependency",
disallowedIn: ["dependencies"],
allowedIn: ["devDependencies", "peerDependencies"],
requiredPeerVersion: "*"
},
{
name: "rxjs",
option: "noRxjsPeerDependency",
disallowedIn: ["peerDependencies"],
allowedIn: ["dependencies", "devDependencies"]
},
{
name: "@sanity/client",
option: "noSanityClientPeerDependency",
disallowedIn: ["peerDependencies"],
allowedIn: ["dependencies", "devDependencies"]
}
];
function formatFields(fields) {
const labels = fields.map((field) => `\`${field}\``);
return labels.length <= 1 ? labels.join("") : `${labels.slice(0, -1).join(", ")} or ${labels[labels.length - 1]}`;
}
function checkDependencyPlacement(options) {
const { pkg, logger, strictOptions: strictOptions2 } = options;
let shouldError = !1;
const report = (level, message) => {
level === "error" ? (shouldError = !0, logger.error(message)) : logger.warn(message);
};
for (const rule of dependencyPlacementRules) {
const level = strictOptions2[rule.option];
if (level !== "off") {
for (const field of rule.disallowedIn)
Object.hasOwn(pkg[field] ?? {}, rule.name) && report(
level,
`package.json: \`${rule.name}\` should not be in \`${field}\`. It should be in ${formatFields(
rule.allowedIn
)} instead.`
);
if (rule.requiredPeerVersion !== void 0) {
const peerDependencies = pkg.peerDependencies ?? {};
Object.hasOwn(peerDependencies, rule.name) && peerDependencies[rule.name] !== rule.requiredPeerVersion && report(
level,
`package.json: \`${rule.name}\` in \`peerDependencies\` should be set to "${rule.requiredPeerVersion}" (got "${peerDependencies[rule.name]}").`
);
}
}
}
return shouldError;
}
function assertLast(a, arr) {
const aIdx = arr.indexOf(a);
return aIdx === -1 ? !0 : aIdx === arr.length - 1;
}
function assertOrder(a, b, arr) {
const aIdx = arr.indexOf(a), bIdx = arr.indexOf(b);
return aIdx === -1 || bIdx === -1 ? !0 : aIdx < bIdx;
}
function validatePkg(input) {
const pkg = parsePackage(input), invalidKey = Object.keys(input).find((key) => {
const needle = key.toUpperCase();
return _typoMap.has(needle) ? _typoMap.get(needle) !== key : !1;
});
if (invalidKey)
throw new TypeError(
`
- package.json: "${invalidKey}" is not a valid key. Did you mean "${_typoMap.get(invalidKey.toUpperCase())}"?`
);
return pkg;
}
async function loadPkg(options) {
const { pkgPath } = options, raw = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
return validatePkg(raw), raw;
}
function areExportValuesEqual(value1, value2) {
if (typeof value1 == "string" && typeof value2 == "string")
return value1 === value2;
if (typeof value1 != typeof value2)
return !1;
if (typeof value1 == "object" && value1 !== null && typeof value2 == "object" && value2 !== null && !Array.isArray(value1) && !Array.isArray(value2)) {
const obj1 = value1, obj2 = value2, keys1 = Object.keys(obj1).filter(
(k) => k !== "source" && k !== "development" && k !== "monorepo"
), keys2 = Object.keys(obj2).filter(
(k) => k !== "source" && k !== "development" && k !== "monorepo"
);
if (keys1.length !== keys2.length)
return !1;
for (const key of keys1) {
if (!keys2.includes(key))
return !1;
const val1 = obj1[key], val2 = obj2[key];
if (val1 === void 0 || val2 === void 0 || !areExportValuesEqual(val1, val2))
return !1;
}
return !0;
}
return !1;
}
async function loadPkgWithReporting(options) {
const { pkgPath, logger, strict: strict2, strictOptions: strictOptions2 } = options;
try {
const pkg = await loadPkg({ pkgPath });
let shouldError = !1;
if (strict2) {
if (strictOptions2.preferModuleType !== "off")
if (pkg.type) {
if (pkg.type === "commonjs") {
const msg = 'package.json: `type` is set to "commonjs". Future versions of pkg-utils will require `"type": "module"`. Consider migrating to ES modules to prepare for this change.';
strictOptions2.preferModuleType === "error" ? (shouldError = !0, logger.error(msg)) : logger.warn(msg);
}
} else {
const msg = 'package.json: `type` field is missing. Future versions of pkg-utils will require `"type": "module"`. Consider adding `"type": "module"` to prepare for this change.';
strictOptions2.preferModuleType === "error" ? (shouldError = !0, logger.error(msg)) : logger.warn(msg);
}
if (strictOptions2.noPackageJsonBrowser !== "off" && pkg.browser) {
const msg = "package.json: the `browser` field is no longer needed. Use the `browser` condition in `exports` instead for better support across modern bundlers.";
strictOptions2.noPackageJsonBrowser === "error" ? (shouldError = !0, logger.error(msg)) : logger.warn(msg);
}
if (strictOptions2.noPackageJsonTypesVersions !== "off" && pkg.typesVersions) {
const msg = "package.json: the `typesVersions` field is no longer needed. TypeScript has long supported conditional exports and the `types` condition. Remove the `typesVersions` field and use the `types` condition in `exports` instead.";
strictOptions2.noPackageJsonTypesVersions === "error" ? (shouldError = !0, logger.error(msg)) : logger.warn(msg);
}
checkDependencyPlacement({ pkg, logger, strictOptions: strictOptions2 }) && (shouldError = !0);
}
if (pkg.exports) {
const _exports = Object.entries(pkg.exports);
for (const [expPath, exp] of _exports) {
if (typeof exp == "string" || expPath.endsWith(".css") || "svelte" in exp)
continue;
const keys = Object.keys(exp);
exp.types && (shouldError = !0, logger.error(
`exports["${expPath}"]: the \`types\` condition shouldn't be used as dts files are generated in such a way that both CJS and ESM is supported`
)), exp.module && (shouldError = !0, logger.error(
`exports["${expPath}"]: the \`module\` condition shouldn't be used as it's not well supported in all bundlers.`
)), exp.development && exp.source && exp.development !== exp.source && (shouldError = !0, logger.error(
`exports["${expPath}"]: the \`development\` condition must have the same value as \`source\` when both are present. Expected "${exp.source}" but got "${exp.development}"`
)), exp.monorepo && exp.source && exp.monorepo !== exp.source && (shouldError = !0, logger.error(
`exports["${expPath}"]: the \`monorepo\` condition must have the same value as \`source\` when both are present. Expected "${exp.source}" but got "${exp.monorepo}"`
)), exp.node ? (exp.import && exp.node.import && !assertOrder("node", "import", keys) && (shouldError = !0, logger.error(
`exports["${expPath}"]: the \`node\` property should come before the \`import\` property`
)), exp.node.module && (shouldError = !0, logger.error(
`exports["${expPath}"]: the \`node.module\` condition shouldn't be used as it's not well supported in all bundlers. A better strategy is to refactor the codebase to no longer be vulnerable to the "dual package hazard"`
)), !exp.node.source && exp.node.import && (exp.node.require || exp.require) && (exp.node.import.endsWith(".cjs.js") || exp.node.import.endsWith(".cjs.mjs")) && (shouldError = !0, logger.error(
`exports["${expPath}"]: the \`node.import\` re-export pattern shouldn't be used as it's not well supported in all bundlers. A better strategy is to refactor the codebase to no longer be vulnerable to the "dual package hazard"`
)), exp.require && exp.node.require && exp.require === exp.node.require ? (shouldError = !0, logger.error(
`exports["${expPath}"]: the \`node.require\` property isn't necessary as it's identical to \`require\``
)) : exp.require && exp.node.require && !assertOrder("node", "require", keys) && (shouldError = !0, logger.error(
`exports["${expPath}"]: the \`node\` property should come before the \`require\` property`
))) : assertOrder("import", "require", keys) || logger.warn(
`exports["${expPath}"]: the \`import\` property should come before the \`require\` property`
), assertLast("default", keys) || (shouldError = !0, logger.error(
`exports["${expPath}"]: the \`default\` property should be the last property`
));
}
}
if (strict2 && pkg.exports && Object.keys(pkg.exports).length > 0 && Object.entries(pkg.exports).some(([, exp]) => typeof exp == "string" || typeof exp == "object" && "svelte" in exp ? !1 : !!(exp.source || exp.development || exp.monorepo)))
if (pkg.publishConfig?.exports) {
const publishExports = pkg.publishConfig.exports;
for (const exportPath of Object.keys(pkg.exports))
exportPath in publishExports || (shouldError = !0, logger.error(
`publishConfig.exports: missing export path "${exportPath}" that exists in exports`
));
for (const exportPath of Object.keys(publishExports))
exportPath in pkg.exports || (shouldError = !0, logger.error(
`publishConfig.exports: unexpected export path "${exportPath}" that does not exist in exports`
));
for (const [exportPath, exp] of Object.entries(pkg.exports)) {
if (typeof exp == "string" || "svelte" in exp) {
const publishExp2 = publishExports[exportPath];
typeof publishExp2 != "string" && (typeof publishExp2 != "object" || !("svelte" in publishExp2)) && (shouldError = !0, logger.error(
`publishConfig.exports["${exportPath}"]: should be a string matching exports["${exportPath}"]`
));
continue;
}
const publishExp = publishExports[exportPath];
if (!publishExp)
continue;
if (typeof publishExp == "string") {
const conditions = Object.keys(exp).filter(
(k) => k !== "source" && k !== "development" && k !== "monorepo"
);
if (conditions.length !== 1 || conditions[0] !== "default")
shouldError = !0, logger.error(
`publishConfig.exports["${exportPath}"]: is a string but exports["${exportPath}"] has multiple conditions besides source/development/monorepo: ${conditions.join(", ")}`
);
else {
const expectedValue = exp.default;
publishExp !== expectedValue && (shouldError = !0, logger.error(
`publishConfig.exports["${exportPath}"]: should be "${expectedValue}" but got "${publishExp}"`
));
}
continue;
}
if ("svelte" in publishExp)
continue;
const exportConditions = Object.keys(exp).filter(
(k) => k !== "source" && k !== "development" && k !== "monorepo"
), publishConditions = Object.keys(publishExp);
"source" in publishExp && (shouldError = !0, logger.error(
`publishConfig.exports["${exportPath}"]: should not contain the \`source\` condition`
)), "development" in publishExp && (shouldError = !0, logger.error(
`publishConfig.exports["${exportPath}"]: should not contain the \`development\` condition`
)), "monorepo" in publishExp && (shouldError = !0, logger.error(
`publishConfig.exports["${exportPath}"]: should not contain the \`monorepo\` condition`
));
for (const condition of exportConditions)
condition in publishExp || (shouldError = !0, logger.error(
`publishConfig.exports["${exportPath}"]: missing \`${condition}\` condition that exists in exports["${exportPath}"]`
));
for (const condition of publishConditions)
exportConditions.includes(condition) || (shouldError = !0, logger.error(
`publishConfig.exports["${exportPath}"]: unexpected \`${condition}\` condition that does not exist in exports["${exportPath}"]`
));
for (const condition of exportConditions)
if (condition in publishExp) {
const exportValue = exp[condition], publishValue = publishExp[condition];
if (!areExportValuesEqual(exportValue, publishValue)) {
const exportValueStr = typeof exportValue == "string" ? exportValue : JSON.stringify(exportValue), publishValueStr = typeof publishValue == "string" ? publishValue : JSON.stringify(publishValue);
shouldError = !0, logger.error(
`publishConfig.exports["${exportPath}"].${condition}: should be ${exportValueStr} but got ${publishValueStr}`
);
}
}
}
} else {
const msg = "package.json: `publishConfig.exports` is missing. Adding it helps avoid publishing to npm with the `source`, `development`, or `monorepo` condition that points to code that cannot be used by the resolver. See https://tsdown.dev/options/package-exports#dev-exports for more information.";
strictOptions2.noPublishConfigExports === "error" ? (shouldError = !0, logger.error(msg)) : strictOptions2.noPublishConfigExports !== "off" && logger.warn(msg);
}
return shouldError && process.exit(1), pkg;
} catch (err) {
if (err instanceof ZodError)
for (const issue of err.issues) {
if (issue.code === "invalid_type") {
logger.error(
[
`\`${formatPath(issue.path)}\` `,
`in \`./package.json\` must be of type ${chalk.magenta(issue.expected)} `,
`(received ${chalk.magenta(issue.received)})`
].join("")
);
continue;
}
logger.error(issue);
}
else
logger.error(err);
return process.exit(1);
}
}
function formatPath(segments) {
return segments.map((s, idx) => idx === 0 ? s : typeof s == "number" ? `[${s}]` : s.startsWith(".") ? `["${s}"]` : `.${s}`).join("");
}
function browserslistToEsbuild(browserslistConfig, options = {}) {
if (!browserslistConfig) {
const path2 = process.cwd();
browserslistConfig = browserslist.loadConfig({ path: path2, ...options });
}
const SUPPORTED_ESBUILD_TARGETS = [
"es",
"chrome",
"edge",
"firefox",
"ios",
"node",
"safari",
"opera",
"ie"
], UNSUPPORTED = ["android 4"], replaces = {
ios_saf: "ios",
android: "chrome"
}, separator = " ";
return browserslist(browserslistConfig, options).filter((b) => !UNSUPPORTED.some((u) => b.startsWith(u))).map((b) => b === "safari TP" ? browserslist("last 1 safari version")[0] : b).map((b) => b.split(separator)).map((b) => (replaces[b[0]] && (b[0] = replaces[b[0]]), b)).map((b) => (b[1].includes("-") && (b[1] = b[1].slice(0, b[1].indexOf("-"))), b)).map((b) => (b[1].endsWith(".0") && (b[1] = b[1].slice(0, -2)), b)).filter((b) => /^\d+(\.\d+)*$/.test(b[1])).filter((b) => SUPPORTED_ESBUILD_TARGETS.includes(b[0])).reduce((acc, b) => {
const existingIndex = acc.findIndex((br) => br[0] === b[0]);
return existingIndex !== -1 ? acc[existingIndex][1] = b[1] : acc.push(b), acc;
}, []).map((b) => b.join(""));
}
function isPkgConfigPropertyResolver(prop) {
return typeof prop == "function";
}
function resolveConfigProperty(prop, initialValue) {
return prop ? isPkgConfigPropertyResolver(prop) ? prop(initialValue) : prop : initialValue;
}
function pathContains(containerPath, itemPath) {
return !path.relative(containerPath, itemPath).startsWith("..");
}
function findCommonDirPath(filePaths) {
let ret;
for (const filePath of filePaths) {
let dirPath = path.dirname(filePath);
if (!ret) {
ret = dirPath;
continue;
}
for (; dirPath !== ret; ) {
if (pathContains(dirPath, ret)) {
ret = dirPath;
break;
}
if (dirPath = path.dirname(dirPath), dirPath === ret)
break;
if (dirPath === ".") return;
}
}
return ret;
}
const fileEnding = /\.[mc]?js$/, dtsEnding = ".d.ts", defaultEnding = ".js", mjsEnding = ".mjs", cjsEnding = ".cjs", mtsEnding = ".d.mts", ctsEnding = ".d.cts";
function getTargetPaths(_type, expOrBundle) {
const type = _type === "module" ? "module" : "commonjs", set = /* @__PURE__ */ new Set();
return expOrBundle?.import && set.add(expOrBundle.import.replace(fileEnding, type === "module" ? dtsEnding : mtsEnding)), expOrBundle?.require && set.add(expOrBundle.require.replace(fileEnding, type === "commonjs" ? dtsEnding : ctsEnding)), isPkgExport$1(expOrBundle) && (expOrBundle.browser?.source || (expOrBundle.browser?.import && set.add(
expOrBundle.browser.import.replace(fileEnding, type === "module" ? dtsEnding : mtsEnding)
), expOrBundle.browser?.require && set.add(
expOrBundle.browser.require.replace(
fileEnding,
type === "commonjs" ? dtsEnding : ctsEnding
)
)), expOrBundle.node?.source || (expOrBundle.node?.import && set.add(
expOrBundle.node.import.replace(fileEnding, type === "module" ? dtsEnding : mtsEnding)
), expOrBundle.node?.require && set.add(
expOrBundle.node.require.replace(fileEnding, type === "commonjs" ? dtsEnding : ctsEnding)
))), Array.from(set);
}
function isPkgExport$1(exp) {
return exp?.browser || exp?.node || exp?.default;
}
const pkgExtMap = {
// pkg.type: "commonjs"
commonjs: {
commonjs: defaultEnding,
esm: mjsEnding
},
// pkg.type: "module"
module: {
commonjs: cjsEnding,
esm: defaultEnding
}
};
function validateExports(_exports, options) {
const { pkg } = options, type = pkg.type || "commonjs", ext = pkgExtMap[type], errors = [];
for (const exp of _exports)
exp._path === "." && (exp.require && pkg.main && exp.require !== pkg.main && errors.push(
'package.json: mismatch between "main" and "exports.require". These must be equal.'
), exp.import && pkg.module && exp.import !== pkg.module && errors.push(
'package.json: mismatch between "module" and "exports.import". These must be equal.'
)), exp.require && !exp.require.endsWith(ext.commonjs) && errors.push(
`package.json with \`type: "${type}"\` - \`exports["${exp._path}"].require\` must end with "${ext.commonjs}"`
), exp.import && !exp.import.endsWith(ext.esm) && errors.push(
`package.json with \`type: "${type}"\` - \`exports["${exp._path}"].import\` must end with "${ext.esm}"`
);
return errors;
}
function isTruthy$1(value) {
return !!value;
}
function parseAndValidateExports(options) {
const { cwd, pkg, strict: strict2, strictOptions: strictOptions2, logger } = options, type = pkg.type || "commonjs", errors = [], report = (kind, message) => {
kind === "warn" ? logger.warn(message) : errors.push(message);
};
if (!Array.isArray(pkg.files) && strict2 && strictOptions2.alwaysPackageJsonFiles !== "off" && report(
strictOptions2.alwaysPackageJsonFiles,
"package.json: `files` should be used over `.npmignore`"
), pkg.source) {
if (strict2 && pkg.exports?.["."] && typeof pkg.exports["."] == "object" && "source" in pkg.exports["."] && pkg.exports["."].source === pkg.source)
errors.push(
'package.json: the "source" property can be removed, as it is equal to exports["."].source.'
);
else if (!pkg.exports && pkg.main) {
const extMap = pkgExtMap[type], importExport = pkg.main.replace(fileEnding, extMap.esm), requireExport = pkg.main.replace(fileEnding, extMap.commonjs), defaultExport = pkg.main.replace(fileEnding, defaultEnding), maybeBrowserCondition = [];
if (pkg.browser) {
const browserConditions = [];
pkg.module && pkg.browser?.[pkg.module] ? browserConditions.push(
` "import": ${JSON.stringify(pkg.browser[pkg.module].replace(fileEnding, extMap.esm))}`
) : pkg.browser?.[pkg.main] && browserConditions.push(
` "import": ${JSON.stringify(pkg.browser[pkg.main].replace(fileEnding, extMap.esm))}`
), pkg.browser?.[pkg.main] && browserConditions.push(
` "require": ${JSON.stringify(pkg.browser[pkg.main].replace(fileEnding, extMap.commonjs))}`
), browserConditions.length && maybeBrowserCondition.push(
' "browser": {',
` "source": ${JSON.stringify(pkg.browser?.[pkg.source] || pkg.source)},`,
...browserConditions,
" }"
);
}
errors.push(
...[
"package.json: `exports` are missing, it should be:",
'"exports": {',
' ".": {',
` "source": ${JSON.stringify(pkg.source)},`,
// If browser conditions are detected then add them to the suggestion
...maybeBrowserCondition.length > 0 ? maybeBrowserCondition : [],
type === "commonjs" && ` "import": ${JSON.stringify(importExport)},`,
type === "module" && ` "require": ${JSON.stringify(requireExport)},`,
` "default": ${JSON.stringify(defaultExport)}`,
" },",
' "./package.json": "./package.json"',
"}"
].filter(isTruthy$1)
);
}
}
if (errors.length)
throw new Error(`
- ` + errors.join(`
- `));
if (!pkg.exports)
throw new Error(
`
- ` + [
"package.json: `exports` are missing, please set a minimal configuration, for example:",
'"exports": {',
' ".": {',
' "source": "./src/index.js",',
' "default": "./dist/index.js"',
" },",
' "./package.json": "./package.json"',
"}"
].join(`
- `)
);
const _exports = parseExports({ pkg });
strict2 && strictOptions2.noPackageJsonTypings !== "off" && "typings" in pkg && report(strictOptions2.noPackageJsonTypings, "package.json: `typings` should be `types`"), strict2 && strictOptions2.alwaysPackageJsonTypes !== "off" && !pkg.types && typeof pkg.exports?.["."] == "object" && "source" in pkg.exports["."] && pkg.exports["."].source?.endsWith(".ts") && report(
strictOptions2.alwaysPackageJsonTypes,
"package.json: `types` must be declared for the npm listing to show as a TypeScript module."
), strict2 && !pkg.exports["./package.json"] && errors.push('package.json: `exports["./package.json"] must be declared.');
for (const [exportPath, exportEntry] of Object.entries(pkg.exports))
if (exportPath.endsWith(".json") || typeof exportEntry == "string" && exportEntry.endsWith(".json"))
exportPath === "./package.json" && exportEntry !== "./package.json" && errors.push('package.json: `exports["./package.json"]` must be "./package.json".');
else if (exportPath.endsWith(".css"))
if (typeof exportEntry == "string")
existsSync(resolve(cwd, exportEntry)) || errors.push(
`package.json: \`exports[${JSON.stringify(exportPath)}]\`: file does not exist.`
);
else if (isRecord(exportEntry))
for (const [condition, target] of Object.entries(exportEntry))
typeof target != "string" && errors.push(
`package.json: \`exports[${JSON.stringify(exportPath)}][${JSON.stringify(condition)}]\`: must be a string path.`
);
else
errors.push(
`package.json: \`exports[${JSON.stringify(exportPath)}]\`: must be a string path or an object of export conditions.`
);
else isRecord(exportEntry) && "svelte" in exportEntry || (isPkgExport(exportEntry) ? ({
...exportEntry
}, exportPath === "." && (exportEntry.require && pkg.main && exportEntry.require !== pkg.main && errors.push(
'package.json: mismatch between "main" and "exports.require". These must be equal.'
), exportEntry.import && pkg.module && exportEntry.import !== pkg.module && errors.push(
'package.json: mismatch between "module" and "exports.import" These must be equal.'
))) : isRecord(exportEntry) || errors.push("package.json: exports must be an object"));
if (errors.push(...validateExports(_exports, { pkg })), errors.length)
throw new Error(`
- ` + errors.join(`
- `));
return _exports;
}
function isPkgExport(value) {
return isRecord(value) && "source" in value && typeof value.source == "string";
}
async function loadTSConfig(options) {
const { cwd, tsconfigPath } = options, configPath = ts.findConfigFile(cwd, ts.sys.fileExists, tsconfigPath);
if (!configPath)
return;
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
return ts.parseJsonConfigFileContent(configFile.config, ts.sys, cwd);
}
function resolveBrowserTarget(versions) {
const target = versions.filter(
(version) => version.startsWith("chrome") || version.startsWith("edge") || version.startsWith("firefox") || version.startsWith("ios") || version.startsWith("safari") || version.startsWith("opera")
);
if (target.length !== 0)
return target;
}
function resolveNodeTarget(versions) {
const target = versions.filter((version) => version.startsWith("node"));
if (target.length !== 0)
return target;
}
const toggle = z.union([z.literal("error"), z.literal("warn"), z.literal("off")]), strictOptions = z.object({
noPackageJsonTypings: toggle.default("error"),
noImplicitSideEffects: toggle.default("warn"),
noImplicitBrowsersList: toggle.default("warn"),
alwaysPackageJsonTypes: toggle.default("error"),
alwaysPackageJsonFiles: toggle.default("error"),
noCheckTypes: toggle.default("warn"),
noPackageJsonBrowser: toggle.default("warn"),
noPackageJsonTypesVersions: toggle.default("warn"),
preferModuleType: toggle.default("warn"),
noPublishConfigExports: toggle.default("warn"),
noReactIsPeerDependency: toggle.default("error"),
noSanityUiPeerDependency: toggle.default("error"),
noSanityIconsPeerDependency: toggle.default("error"),
noSanityDependency: toggle.default("error"),
noStyledComponentsDependency: toggle.default("error"),
noReactDependency: toggle.default("error"),
noReactDomDependency: toggle.default("error"),
noReactTypesDependency: toggle.default("error"),
noReactDomTypesDependency: toggle.default("error"),
noNodeTypesDependency: toggle.default("error"),
noRxjsPeerDependency: toggle.default("error"),
noSanityClientPeerDependency: toggle.default("error")
}).strict(), validationSchema = z.object({
strictOptions: strictOptions.default({})
});
function parseStrictOptions(input) {
return validationSchema.parse({ strictOptions: input }, { errorMap }).strictOptions;
}
var strict = /* @__PURE__ */ Object.freeze({
__proto__: null,
parseStrictOptions
});
function isTruthy(value) {
return !!value;
}
async function resolveBuildContext(options) {
const {
config,
cwd,
emitDeclarationOnly = !1,
logger,
pkg,
strict: strict2,
tsconfig: tsconfigPath
} = options, tsconfig = await loadTSConfig({ cwd, tsconfigPath }), strictOptions2 = parseStrictOptions(config?.strictOptions ?? {});
if (strictOptions2.noCheckTypes !== "off" && tsconfig?.options && config?.dts !== "rolldown" && tsconfig.options.noCheck !== !1 && !tsconfig.options.noCheck && config?.extract?.checkTypes !== !1) {
if (strictOptions2.noCheckTypes === "error")
throw new Error(
"`noCheck` is not set to `true` in the tsconfig.json file used by `package.config.ts`. This makes generating dts files slower than it needs to be, as it will perform type checking on the dts files while at it. You can set `noCheck: true` in tsconfig.json or set `extract: { checkTypes: false }` in package.config.ts to disable type checking."
);
logger.warn(
"`noCheck` is not set to `true` in the tsconfig.json file used by `package.config.ts`. This makes generating dts files slower than it needs to be, as it will perform type checking on the dts files while at it. You can set `noCheck: true` in tsconfig.json or set `extract: { checkTypes: false }` in package.config.ts to disable type checking."
);
}
let browserslist2 = pkg.browserslist;
if (!browserslist2) {
if (strict2 && strictOptions2.noImplicitBrowsersList !== "off") {
if (strictOptions2.noImplicitBrowsersList === "error")
throw new Error(
'\n- package.json: "browserslist" is missing, set it to `"browserslist": "extends @sanity/browserslist-config"`'
);
logger.warn(
'Could not detect a `browserslist` property in `package.json`, using default configuration. Add `"browserslist": "extends @sanity/browserslist-config"` to silence this warning.'
);
}
browserslist2 = DEFAULT_BROWSERSLIST_QUERY;
}
const targetVersions = browserslistToEsbuild(browserslist2);
if (strict2 && strictOptions2.noImplicitSideEffects !== "off" && typeof pkg.sideEffects > "u") {
const msg = "package.json: `sideEffects` is missing, see https://webpack.js.org/guides/tree-shaking/#clarifying-tree-shaking-and-sideeffects for how to define `sideEffects`";
if (strictOptions2.noImplicitSideEffects === "error")
throw new Error(msg);
logger.warn(msg);
}
const nodeTarget = resolveNodeTarget(targetVersions), webTarget = resolveBrowserTarget(targetVersions);
if (!nodeTarget)
throw new Error("no matching `node` target");
if (!webTarget)
throw new Error("no matching `web` target");
const target = {
"*": webTarget.concat(nodeTarget),
browser: webTarget,
node: nodeTarget
}, parsedExports = parseAndValidateExports({
cwd,
pkg,
strict: strict2,
strictOptions: strictOptions2,
logger
}).reduce(
(acc, { _path: exportPath, ...exportEntry }) => Object.assign(acc, { [exportPath]: exportEntry }),
{}
), exports = resolveConfigProperty(config?.exports, parsedExports), parsedExternal = [
...pkg.dependencies ? Object.keys(pkg.dependencies) : [],
...pkg.peerDependencies ? Object.keys(pkg.peerDependencies) : []
], external = config && Array.isArray(config.external) ? [...parsedExternal, ...config.external] : resolveConfigProperty(config?.external, parsedExternal), externalWithTypes = /* @__PURE__ */ new Set([pkg.name, ...external, ...external.map(transformPackageName)]), bundledDependencies = (pkg.devDependencies ? Object.keys(pkg.devDependencies) : []).filter(
// Do not bundle anything that is marked as external
(_) => !externalWithTypes.has(_)
), bundledPackages = config && Array.isArray(config.extract?.bundledPackages) ? [...bundledDependencies, ...config.extract.bundledPackages] : resolveConfigProperty(config?.extract?.bundledPackages, bundledDependencies), outputPaths = Object.values(exports).flatMap((exportEntry) => [
exportEntry.import,
exportEntry.require,
exportEntry.browser?.import,
exportEntry.browser?.require,
exportEntry.node?.source && exportEntry.node.import,
exportEntry.node?.source && exportEntry.node.require
].filter(isTruthy)).map((p) => path.resolve(cwd, p)), commonDistPath = findCommonDirPath(outputPaths);
if (commonDistPath === cwd)
throw new Error(
"all output files must share a common parent directory which is not the root package directory"
);
if (commonDistPath && !pathContains(cwd, commonDistPath))
throw new Error("all output files must be located within the package");
const configDistPath = config?.dist ? path.resolve(cwd, config.dist) : void 0;
if (configDistPath && commonDistPath && configDistPath !== commonDistPath && !pathContains(configDistPath, commonDistPath))
throw logger.log(`did you mean to configure \`dist: './${path.relative(cwd, commonDistPath)}'\`?`), new Error("all output files must be located with the configured `dist` path");
const distPath = configDistPath || commonDistPath;
if (!distPath)
throw new Error("could not detect `dist` path");
return {
config,
cwd,
distPath,
emitDeclarationOnly,
exports,
external,
bundledPackages,
files: [],
logger,
pkg,
runtime: config?.runtime ?? "*",
strict: strict2,
target,
ts: {
config: tsconfig,
configPath: tsconfigPath
},
dts: config?.dts === "rolldown" ? "rolldown" : "api-extractor"
};
}
function transformPackageName(packageName) {
if (packageName.startsWith("@types/"))
return packageName;
if (packageName.startsWith("@")) {
const [scope, name] = packageName.split("/");
return `@types/${scope?.slice(1)}__${name}`;
} else
return `@types/${packageName}`;
}
export {
createConsoleSpy,
getTargetPaths,
loadPkgWithReporting,
pkgExtMap,
resolveBuildContext,
resolveConfigProperty,
strict
};
//# sourceMappingURL=resolveBuildContext.js.map