UNPKG

@sanity/pkg-utils

Version:

Simple utilities for modern npm packages.

718 lines (717 loc) 35.3 kB
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