// src/actions/package/platformPublint.ts
import fs3 from "node:fs";
import { builtinModules } from "node:module";
import path4 from "node:path";
import chalk5 from "chalk";
import { globSync } from "glob";
import ts from "typescript";

// src/actions/package/exportMapPublint.ts
import fs2 from "node:fs";
import path3 from "node:path";
import chalk4 from "chalk";

// src/actions/package/expectedCompileOutputs.ts
import fs from "node:fs";
import path2 from "node:path";
import { cosmiconfig as cosmiconfig2 } from "cosmiconfig";
import { TypeScriptLoader as TypeScriptLoader2 } from "cosmiconfig-typescript-loader";

// src/lib/deepMerge.ts
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
function isPlainObject(value) {
  if (typeof value !== "object" || value === null) return false;
  const proto = Object.getPrototypeOf(value);
  return proto === Object.prototype || proto === null;
}
function isMergeable(value) {
  return Array.isArray(value) || isPlainObject(value);
}
function cloneValue(value) {
  if (Array.isArray(value)) {
    return value.map((item) => cloneValue(item));
  }
  if (isPlainObject(value)) {
    const result = {};
    for (const [key, item] of Object.entries(value)) {
      if (UNSAFE_KEYS.has(key)) continue;
      result[key] = cloneValue(item);
    }
    return result;
  }
  return value;
}
function mergeObject(target, source, options) {
  const destination = {};
  if (isPlainObject(target)) {
    for (const [key, item] of Object.entries(target)) {
      if (UNSAFE_KEYS.has(key)) continue;
      destination[key] = cloneValue(item);
    }
  }
  for (const [key, sourceValue] of Object.entries(source)) {
    if (UNSAFE_KEYS.has(key)) continue;
    if (Object.hasOwn(target, key) && isMergeable(sourceValue)) {
      const customMerge = options.customMerge?.(key);
      destination[key] = (customMerge ?? deepMerge)(target[key], sourceValue, options);
    } else {
      destination[key] = cloneValue(sourceValue);
    }
  }
  return destination;
}
function deepMerge(target, source, options = {}) {
  const isSourceIsArray = Array.isArray(source);
  const isTargetIsArray = Array.isArray(target);
  if (isSourceIsArray !== isTargetIsArray) {
    return cloneValue(source);
  }
  if (isSourceIsArray) {
    return [...target, ...source].map((item) => cloneValue(item));
  }
  return mergeObject(target, source, options);
}

// src/lib/loadConfig.ts
import chalk from "chalk";
import { cosmiconfig } from "cosmiconfig";
import { TypeScriptLoader } from "cosmiconfig-typescript-loader";
var configState = {
  config: void 0,
  rootConfigPath: void 0
};
function createExplorer() {
  return cosmiconfig("xy", { cache: true, loaders: { ".ts": TypeScriptLoader() } });
}
var loadConfig = async (params) => {
  if (configState.config === void 0) {
    const cosmicConfigResult = await createExplorer().search();
    configState.config = cosmicConfigResult?.config ?? {};
    configState.rootConfigPath = cosmicConfigResult?.filepath;
    const configFilePath = cosmicConfigResult?.filepath;
    if (configFilePath !== void 0) {
      console.log(chalk.green(`Loaded config from ${configFilePath}`));
      if (Boolean(configState.config.verbose)) {
        console.log(chalk.gray(JSON.stringify(configState.config, null, 2)));
      }
    }
  }
  return deepMerge(configState.config, params ?? {});
};

// src/actions/package/compile/inputs.ts
import chalk2 from "chalk";
import { glob } from "glob";
var getAllInputs = (srcDir, isVerbose = false) => {
  return glob.sync(`${srcDir}/**/*.{ts,tsx,mts,cts}`, { posix: true }).map((file) => {
    const result = file.slice(Math.max(0, srcDir.length + 1));
    if (isVerbose) {
      console.log(chalk2.gray(`getAllInputs: ${JSON.stringify(result, null, 2)}`));
    }
    return result;
  });
};

// src/actions/package/compile/buildEntries.ts
var bareSpecOrStoryRe = /(?:^|\/)(?:spec|stories)\.(?:ts|tsx|mts|cts)$/;
var isSpecOrStory = (entry) => {
  return entry.includes(".spec.") || entry.includes(".stories.") || entry.startsWith("spec/") || entry.includes("/spec/") || entry.startsWith("stories/") || entry.includes("/stories/") || bareSpecOrStoryRe.test(entry);
};
var isDeclaration = (entry) => /\.d\.(?:ts|mts|cts)$/.test(entry);
var buildEntries = (srcDir, entryMode = "single", options, shouldExcludeSpecAndStories = true, isVerbose = false) => {
  let entries;
  switch (entryMode) {
    case "platform": {
      entries = ["index-node.ts", "index-browser.ts"];
      break;
    }
    case "all": {
      entries = (shouldExcludeSpecAndStories ? getAllInputs(srcDir).filter((entry) => !isSpecOrStory(entry)) : getAllInputs(srcDir)).filter((entry) => !isDeclaration(entry));
      break;
    }
    case "custom": {
      entries = [];
      break;
    }
    default: {
      entries = ["index.ts"];
      break;
    }
  }
  if (typeof options !== "boolean" && Array.isArray(options?.entry)) {
    entries.push(...options.entry);
  }
  if (isVerbose) console.log(`buildEntries [${entryMode}] ${entries.length}`);
  return entries;
};

// src/actions/package/compile/entryOutputName.ts
import path from "node:path";
var platformSourceSegments = /* @__PURE__ */ new Set(["browser", "neutral", "node"]);
var toPosixPath = (value) => value.split(path.sep).join("/");
var normalizeEntryPath = (entry) => toPosixPath(entry).replace(/^\.?\//u, "");
var customEntryOutputName = (entry) => {
  const parsed = path.posix.parse(normalizeEntryPath(entry));
  const withoutExtension = path.posix.join(parsed.dir, parsed.name);
  const segments = withoutExtension.split("/");
  return segments.length > 1 && platformSourceSegments.has(segments[0]) ? segments.slice(1).join("/") : withoutExtension;
};
var entryOutputBasename = (entry, entryMode) => {
  if (entryMode === "custom") return customEntryOutputName(entry);
  return path.posix.parse(normalizeEntryPath(entry)).name;
};

// src/actions/package/compile/resolveCompilePlatforms.ts
import chalk3 from "chalk";
var resolvePlatformRecord = (value) => {
  if (value === void 0 || value === true) return { src: {} };
  if (value === false) return {};
  const { esbuildOptions: _legacyEsbuildOptions, ...entries } = value;
  return entries;
};
var classifyPlatform = (value) => {
  if (value === void 0) {
    return {
      deprecatedEmptyObject: false,
      entries: {},
      state: "unset"
    };
  }
  if (typeof value === "boolean") {
    return {
      deprecatedEmptyObject: false,
      entries: value ? { src: {} } : {},
      state: value ? "build" : "off"
    };
  }
  const isDeprecatedEmptyObject = Object.keys(value).length === 0;
  const entries = resolvePlatformRecord(value);
  return {
    deprecatedEmptyObject: isDeprecatedEmptyObject,
    entries,
    state: Object.keys(entries).length > 0 ? "build" : "off"
  };
};
var resolveCompilePlatforms = (compile) => {
  const node = classifyPlatform(compile?.node);
  const browser = classifyPlatform(compile?.browser);
  const neutral = classifyPlatform(compile?.neutral);
  const isAnyBuild = node.state === "build" || browser.state === "build" || neutral.state === "build";
  const neutralEntries = neutral.state === "build" ? neutral.entries : !isAnyBuild && neutral.state === "unset" ? { src: {} } : {};
  const deprecatedEmptyObjectTargets = [
    ...node.deprecatedEmptyObject ? ["node"] : [],
    ...browser.deprecatedEmptyObject ? ["browser"] : [],
    ...neutral.deprecatedEmptyObject ? ["neutral"] : []
  ];
  return {
    browser: browser.state === "build" ? browser.entries : {},
    deprecatedEmptyObjectTargets,
    neutral: neutralEntries,
    node: node.state === "build" ? node.entries : {}
  };
};

// src/actions/package/expectedCompileOutputs.ts
var CONFIG_FILE_NAMES = [
  "xy.config.ts",
  "xy.config.mts",
  "xy.config.cts",
  "xy.config.js",
  "xy.config.mjs",
  "xy.config.cjs"
];
var PRIMARY_INDEX_BASENAMES = /* @__PURE__ */ new Set([
  "index",
  "index-browser",
  "index-neutral",
  "index-node",
  "index.browser",
  "index.node",
  "index.neutral",
  "browser-index",
  "neutral-index",
  "node-index"
]);
function ensureExportsPath(value) {
  return value.startsWith("./") ? value : `./${value}`;
}
function isPrimaryIndexBasename(basename) {
  return PRIMARY_INDEX_BASENAMES.has(basename);
}
function getRootOutDir(options) {
  return options.outDir ?? options.outdir ?? "dist";
}
function pairedDeclarationFile(runtimeFile) {
  const candidates = runtimeFile.endsWith(".mjs") ? [runtimeFile.replace(/\.mjs$/, ".d.mts"), runtimeFile.replace(/\.mjs$/, ".d.ts")] : runtimeFile.endsWith(".cjs") ? [runtimeFile.replace(/\.cjs$/, ".d.cts"), runtimeFile.replace(/\.cjs$/, ".d.ts")] : runtimeFile.endsWith(".js") ? [runtimeFile.replace(/\.js$/, ".d.ts")] : [];
  return candidates.find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile());
}
function relativeExportsTarget(pkgDir, filePath) {
  return ensureExportsPath(toPosixPath(path2.relative(pkgDir, filePath)));
}
async function loadMergedWorkspaceConfig(pkgDir) {
  const root = await loadConfig();
  const explorer = cosmiconfig2("xy", { cache: true, loaders: { ".ts": TypeScriptLoader2() } });
  const result = await explorer.search(pkgDir);
  if (result?.config === void 0) return root;
  return deepMerge(root, result.config);
}
function hasWorkspaceConfig(pkgDir) {
  return CONFIG_FILE_NAMES.some((name) => fs.existsSync(path2.join(pkgDir, name)));
}
function outputForEntry(pkgDir, platform, rootOutDir, entry, entryMode) {
  const basename = entryOutputBasename(entry, entryMode ?? "single");
  const runtimeAbs = path2.join(pkgDir, rootOutDir, platform, `${basename}.mjs`);
  if (!fs.existsSync(runtimeAbs)) return void 0;
  const runtime = relativeExportsTarget(pkgDir, runtimeAbs);
  const declarationFile = pairedDeclarationFile(runtimeAbs);
  return {
    entry,
    isPrimaryIndex: false,
    platform,
    runtime,
    types: declarationFile === void 0 ? void 0 : relativeExportsTarget(pkgDir, declarationFile)
  };
}
function collectOutputsForPlatform(pkgDir, platform, entries, compile) {
  const outputs = [];
  const entryMode = compile?.entryMode ?? "single";
  for (const [srcDir, options] of Object.entries(entries)) {
    const rootOutDir = getRootOutDir(options);
    const builtEntries = buildEntries(srcDir, entryMode, options, true, false);
    for (const entry of builtEntries) {
      const output = outputForEntry(pkgDir, platform, rootOutDir, entry, entryMode);
      if (output !== void 0) outputs.push(output);
    }
  }
  const byBasename = /* @__PURE__ */ new Map();
  for (const output of outputs) {
    const basename = path2.posix.parse(output.runtime).name;
    const group = byBasename.get(basename) ?? [];
    group.push(output);
    byBasename.set(basename, group);
  }
  for (const output of outputs) {
    const basename = path2.posix.parse(output.runtime).name;
    const group = byBasename.get(basename) ?? [];
    output.isPrimaryIndex = isPrimaryIndexBasename(basename) || group.length === 1;
  }
  return outputs;
}
async function collectExpectedCompileOutputs(pkgDir) {
  if (!hasWorkspaceConfig(pkgDir)) return [];
  const config = await loadMergedWorkspaceConfig(pkgDir);
  const compile = config.compile;
  if (compile === void 0) return [];
  const platforms = resolveCompilePlatforms(compile);
  return [
    ...collectOutputsForPlatform(pkgDir, "node", platforms.node, compile),
    ...collectOutputsForPlatform(pkgDir, "browser", platforms.browser, compile),
    ...collectOutputsForPlatform(pkgDir, "neutral", platforms.neutral, compile)
  ];
}

// src/actions/package/exportMapPublint.ts
var nodeConditionNames = /* @__PURE__ */ new Set(["node", "node-addons"]);
var browserConditionNames = /* @__PURE__ */ new Set(["browser", "web", "webworker", "worker"]);
var neutralConditionNames = /* @__PURE__ */ new Set(["neutral"]);
var platformSegments = /* @__PURE__ */ new Set(["browser", "neutral", "node"]);
function emptyResult() {
  return {
    errors: 0,
    modified: false,
    warnings: 0
  };
}
function toPosixPath2(value) {
  return value.split(path3.sep).join("/");
}
function isSubpathExportKey(key) {
  return key === "." || key.startsWith("./");
}
function formatExportPath(pathParts) {
  return pathParts.map((part, index) => {
    if (index === 0) return part;
    if (part.startsWith("[")) return part;
    return /^[a-zA-Z_$][\w$]*$/.test(part) ? `.${part}` : `[${JSON.stringify(part)}]`;
  }).join("");
}
function ensureExportsPath2(value) {
  return value.startsWith("./") ? value : `./${value}`;
}
function isRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
function normalizeTarget(target) {
  return ensureExportsPath2(toPosixPath2(target));
}
function conditionPlatform(key) {
  if (nodeConditionNames.has(key)) return "node";
  if (browserConditionNames.has(key)) return "browser";
  if (neutralConditionNames.has(key)) return "neutral";
  return void 0;
}
function getConditionPlatform(conditionPath) {
  for (const condition of conditionPath.toReversed()) {
    const platform = conditionPlatform(condition);
    if (platform) return platform;
  }
  return void 0;
}
function getPathPlatform(target) {
  const segments = target.replace(/^\.\//, "").split(/[\\/]+/);
  const distIndex = segments.indexOf("dist");
  const platform = distIndex === -1 ? void 0 : segments[distIndex + 1];
  return platformSegments.has(platform) ? platform : void 0;
}
function collectExportLeaves(value, subpath = ".", pathParts = ["exports"], conditionPath = []) {
  if (typeof value === "string") {
    if (!value.startsWith("./")) return [];
    const leafKey = pathParts.at(-1);
    const kind = leafKey === "types" ? "types" : "default";
    return [{
      conditionPath,
      conditionPlatform: getConditionPlatform(conditionPath),
      exportPath: formatExportPath(pathParts),
      kind,
      subpath,
      target: value
    }];
  }
  if (Array.isArray(value)) {
    return value.flatMap((entry, index) => collectExportLeaves(entry, subpath, [...pathParts, `[${index}]`], conditionPath));
  }
  if (!isRecord(value)) return [];
  return Object.entries(value).flatMap(([key, nested]) => {
    if (isSubpathExportKey(key)) {
      return collectExportLeaves(nested, key, [...pathParts, key], conditionPath);
    }
    return collectExportLeaves(nested, subpath, [...pathParts, key], [...conditionPath, key]);
  });
}
function isCompatiblePlatform(expected, leaf) {
  const pathPlatform = getPathPlatform(leaf.target);
  if (pathPlatform !== void 0 && pathPlatform !== expected) return false;
  if (leaf.conditionPlatform === void 0) return true;
  if (expected === "neutral") return leaf.conditionPlatform === "neutral";
  return leaf.conditionPlatform === expected;
}
function findExportedLeaf(leaves, target, kind, platform) {
  const normalized = normalizeTarget(target);
  return leaves.find((leaf) => leaf.kind === kind && normalizeTarget(leaf.target) === normalized && isCompatiblePlatform(platform, leaf));
}
function hasTypesOnlyExport(leaves, expected) {
  if (expected.types === void 0) return false;
  const typesLeaf = findExportedLeaf(leaves, expected.types, "types", expected.platform);
  if (typesLeaf === void 0) return false;
  return findExportedLeaf(leaves, expected.runtime, "default", expected.platform) === void 0;
}
function hasTypesForRuntimeSubpath(leaves, runtimeLeaf, pkgDir) {
  return leaves.some((leaf) => {
    if (leaf.subpath !== runtimeLeaf.subpath || leaf.kind !== "types") return false;
    const absolute = path3.resolve(pkgDir, leaf.target);
    return fs2.existsSync(absolute);
  });
}
function exportMapDiagnostics(pkgDir, pkg, expectedOutputs) {
  const leaves = collectExportLeaves(pkg.exports);
  const diagnostics = [];
  for (const expected of expectedOutputs) {
    const runtimeLeaf = findExportedLeaf(leaves, expected.runtime, "default", expected.platform);
    const isTypesOnly = hasTypesOnlyExport(leaves, expected);
    if (runtimeLeaf === void 0 && !isTypesOnly) {
      diagnostics.push({
        entry: expected.entry,
        kind: "export-map-missing-runtime",
        platform: expected.platform,
        target: expected.runtime
      });
    }
    if (expected.types === void 0) continue;
    const typesLeaf = findExportedLeaf(leaves, expected.types, "types", expected.platform);
    if (typesLeaf !== void 0) continue;
    if (runtimeLeaf !== void 0 && hasTypesForRuntimeSubpath(leaves, runtimeLeaf, pkgDir)) continue;
    if (isTypesOnly) continue;
    diagnostics.push({
      entry: expected.entry,
      kind: "export-map-missing-types",
      platform: expected.platform,
      target: expected.types
    });
  }
  return diagnostics;
}
function renderDiagnostic(diagnostic) {
  switch (diagnostic.kind) {
    case "export-map-missing-runtime": {
      return `Publint [platform]: compiled ${diagnostic.platform} entry "${diagnostic.entry}" (${diagnostic.target}) is not exported`;
    }
    case "export-map-missing-types": {
      return `Publint [platform]: compiled ${diagnostic.platform} entry "${diagnostic.entry}" is missing types export for ${diagnostic.target}`;
    }
    case "export-map-wrong-target": {
      return `Publint [platform]: ${diagnostic.detail}`;
    }
  }
}
function diagnosticKey(diagnostic) {
  return [
    diagnostic.kind,
    diagnostic.platform ?? "",
    diagnostic.target ?? "",
    diagnostic.entry ?? "",
    diagnostic.detail ?? ""
  ].join("\0");
}
function dedupeDiagnostics(diagnostics) {
  const seen = /* @__PURE__ */ new Set();
  const result = [];
  for (const diagnostic of diagnostics) {
    const key = diagnosticKey(diagnostic);
    if (seen.has(key)) continue;
    seen.add(key);
    result.push(diagnostic);
  }
  return result;
}
function mergeResult(target, source) {
  target.errors += source.errors;
  target.warnings += source.warnings;
  target.modified ||= source.modified;
}
function hasSubpathKeys(exports) {
  return Object.keys(exports).some(isSubpathExportKey);
}
function shouldNormalizeExportsForFix(pkg) {
  if (pkg.exports === void 0) {
    pkg.exports = {};
    return true;
  }
  if (typeof pkg.exports === "string") {
    pkg.exports = { ".": pkg.exports };
    return true;
  }
  if (isRecord(pkg.exports) && !hasSubpathKeys(pkg.exports)) {
    pkg.exports = { ".": pkg.exports };
    return true;
  }
  return false;
}
function isRecordObject(value) {
  return isRecord(value);
}
function ensureSubpath(exports, subpath) {
  const existing = exports[subpath];
  if (isRecordObject(existing)) return existing;
  const created = {};
  exports[subpath] = created;
  return created;
}
function setFlatExport(subpathExport, kind, target) {
  if (subpathExport[kind] === void 0) {
    subpathExport[kind] = target;
  }
}
function setConditionExport(subpathExport, platform, kind, target) {
  const conditionKey = platform === "neutral" ? "neutral" : platform;
  const existing = subpathExport[conditionKey];
  const conditionExport = isRecordObject(existing) ? existing : {};
  if (!isRecordObject(existing)) subpathExport[conditionKey] = conditionExport;
  if (conditionExport[kind] === void 0) conditionExport[kind] = target;
}
function preferredSubpath(expected) {
  if (expected.isPrimaryIndex) return ".";
  const basename = path3.posix.parse(expected.runtime).name;
  return `./${basename}`;
}
function activePlatforms(outputs) {
  return [...new Set(outputs.map((output) => output.platform))];
}
function fixMissingExport(pkg, expected, allOutputs) {
  const result = emptyResult();
  const isNormalized = shouldNormalizeExportsForFix(pkg);
  if (!isRecordObject(pkg.exports)) {
    result.modified = isNormalized;
    return result;
  }
  const subpath = preferredSubpath(expected);
  const subpathExport = ensureSubpath(pkg.exports, subpath);
  const subpathOutputs = allOutputs.filter((output) => preferredSubpath(output) === subpath);
  const platforms = activePlatforms(subpathOutputs);
  const isUseConditions = platforms.length > 1 || subpath === "." && expected.platform !== "neutral";
  if (isUseConditions) {
    setConditionExport(subpathExport, expected.platform, "default", expected.runtime);
    if (expected.types !== void 0) setConditionExport(subpathExport, expected.platform, "types", expected.types);
  } else {
    setFlatExport(subpathExport, "default", expected.runtime);
    if (expected.types !== void 0) setFlatExport(subpathExport, "types", expected.types);
  }
  console.warn(chalk4.yellow(
    `Publint [platform]: added export for ${expected.runtime} at exports[${JSON.stringify(subpath)}]`
  ));
  result.modified = true;
  result.warnings++;
  result.modified ||= isNormalized;
  return result;
}
async function checkExportMappings(pkgDir, pkg, shouldFix) {
  const result = emptyResult();
  const expectedOutputs = await collectExpectedCompileOutputs(pkgDir);
  if (expectedOutputs.length === 0) return result;
  const diagnostics = dedupeDiagnostics(exportMapDiagnostics(pkgDir, pkg, expectedOutputs));
  if (shouldFix) {
    for (const diagnostic of diagnostics) {
      if (diagnostic.kind !== "export-map-missing-runtime" && diagnostic.kind !== "export-map-missing-types") continue;
      const expected = expectedOutputs.find((output) => output.entry === diagnostic.entry && output.platform === diagnostic.platform);
      if (expected === void 0) continue;
      mergeResult(result, fixMissingExport(pkg, expected, expectedOutputs));
    }
    return result;
  }
  for (const diagnostic of diagnostics) {
    console.error(chalk4.red(renderDiagnostic(diagnostic)));
    result.errors++;
  }
  return result;
}

// src/actions/package/platformPublint.ts
var platformSegments2 = /* @__PURE__ */ new Set(["browser", "neutral", "node"]);
var nodeConditionNames2 = /* @__PURE__ */ new Set(["node", "node-addons"]);
var browserConditionNames2 = /* @__PURE__ */ new Set(["browser", "web", "webworker", "worker"]);
var neutralConditionNames2 = /* @__PURE__ */ new Set(["neutral"]);
var builtinModuleNames = new Set(
  builtinModules.map((mod) => mod.replace(/^node:/, "")).filter((mod) => !mod.startsWith("_"))
);
var codeExtensions = [".mjs", ".cjs", ".js"];
var declarationExtensions = [".d.mts", ".d.cts", ".d.ts"];
function toPosixPath3(value) {
  return value.split(path4.sep).join("/");
}
function relativePath(pkgDir, filePath) {
  return toPosixPath3(path4.relative(pkgDir, filePath));
}
function isSubpathExportKey2(key) {
  return key === "." || key.startsWith("./");
}
function formatExportPath2(pathParts) {
  return pathParts.map((part, index) => {
    if (index === 0) return part;
    if (part.startsWith("[")) return part;
    return /^[a-zA-Z_$][\w$]*$/.test(part) ? `.${part}` : `[${JSON.stringify(part)}]`;
  }).join("");
}
function conditionPlatform2(key) {
  if (nodeConditionNames2.has(key)) return "node";
  if (browserConditionNames2.has(key)) return "browser";
  if (neutralConditionNames2.has(key)) return "neutral";
  return void 0;
}
function getConditionPlatform2(conditionPath) {
  for (const condition of conditionPath.toReversed()) {
    const platform = conditionPlatform2(condition);
    if (platform) return platform;
  }
  return void 0;
}
function getPathPlatform2(target) {
  const segments = target.replace(/^\.\//, "").split(/[\\/]+/);
  const distIndex = segments.indexOf("dist");
  const platform = distIndex === -1 ? void 0 : segments[distIndex + 1];
  return platformSegments2.has(platform) ? platform : void 0;
}
function collectExportTargets(value, pathParts = ["exports"], conditionPath = []) {
  if (typeof value === "string") {
    if (!value.startsWith("./")) return [];
    return [{
      conditionPath,
      conditionPlatform: getConditionPlatform2(conditionPath),
      exportPath: formatExportPath2(pathParts),
      pathPlatform: getPathPlatform2(value),
      target: value
    }];
  }
  if (Array.isArray(value)) {
    return value.flatMap((entry, index) => collectExportTargets(entry, [...pathParts, `[${index}]`], conditionPath));
  }
  if (typeof value !== "object" || value === null) return [];
  return Object.entries(value).flatMap(([key, nested]) => {
    const nextConditionPath = isSubpathExportKey2(key) ? conditionPath : [...conditionPath, key];
    return collectExportTargets(nested, [...pathParts, key], nextConditionPath);
  });
}
function isDeclarationFile(filePath) {
  return declarationExtensions.some((ext) => filePath.endsWith(ext));
}
function isCodeFile(filePath) {
  return codeExtensions.some((ext) => filePath.endsWith(ext));
}
function hasCodeOrDeclarationExtension(filePath) {
  return isCodeFile(filePath) || isDeclarationFile(filePath);
}
function pathCandidates(filePath, extensions) {
  const candidates = [filePath];
  const parsed = path4.parse(filePath);
  const withoutExtension = parsed.ext ? filePath.slice(0, -parsed.ext.length) : filePath;
  for (const ext of extensions) {
    candidates.push(`${withoutExtension}${ext}`, `${filePath}${ext}`, path4.join(filePath, `index${ext}`));
  }
  return [...new Set(candidates)];
}
function declarationCandidates(filePath) {
  const extensionCandidates = filePath.endsWith(".mjs") ? [filePath.replace(/\.mjs$/, ".d.mts"), filePath.replace(/\.mjs$/, ".d.ts")] : filePath.endsWith(".cjs") ? [filePath.replace(/\.cjs$/, ".d.cts"), filePath.replace(/\.cjs$/, ".d.ts")] : filePath.endsWith(".js") ? [filePath.replace(/\.js$/, ".d.ts")] : [];
  return [.../* @__PURE__ */ new Set([filePath, ...extensionCandidates, ...pathCandidates(filePath, declarationExtensions)])];
}
function resolveExistingFile(filePath, hasDeclaration) {
  const candidates = hasDeclaration ? declarationCandidates(filePath) : pathCandidates(filePath, codeExtensions);
  return candidates.find((candidate) => fs3.existsSync(candidate) && fs3.statSync(candidate).isFile());
}
function resolveExportTargetFiles(pkgDir, target) {
  if (!hasCodeOrDeclarationExtension(target) && !target.includes("*")) return [];
  const relativeTarget = target.replace(/^\.\//, "");
  if (relativeTarget.includes("*")) {
    return globSync(relativeTarget, {
      cwd: pkgDir,
      nodir: true,
      absolute: true
    }).filter(hasCodeOrDeclarationExtension);
  }
  const absoluteTarget = path4.resolve(pkgDir, relativeTarget);
  const exact = fs3.existsSync(absoluteTarget) && fs3.statSync(absoluteTarget).isFile() ? absoluteTarget : resolveExistingFile(absoluteTarget, isDeclarationFile(relativeTarget));
  return exact != null && exact.length > 0 ? [exact] : [];
}
function isNodeBuiltin(specifier) {
  const normalized = specifier.replace(/^node:/, "");
  return specifier === "node" || specifier.startsWith("node:") || builtinModuleNames.has(normalized) || builtinModuleNames.has(normalized.split("/", 1)[0] ?? "");
}
function parseSourceFile(filePath) {
  const sourceCode = fs3.readFileSync(filePath, "utf8");
  const scriptKind = isCodeFile(filePath) ? ts.ScriptKind.JS : ts.ScriptKind.TS;
  return ts.createSourceFile(path4.basename(filePath), sourceCode, ts.ScriptTarget.Latest, true, scriptKind);
}
function stringLiteralText(node) {
  return node && ts.isStringLiteral(node) ? node.text : void 0;
}
function collectModuleSpecifiers(sourceFile) {
  const specifiers = [];
  function addSpecifier(node) {
    const text = stringLiteralText(node);
    if (text != null && text.length > 0) specifiers.push(text);
  }
  function visit(node) {
    if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
      addSpecifier(node.moduleSpecifier);
    } else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
      addSpecifier(node.arguments[0]);
    } else if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length > 0) {
      addSpecifier(node.arguments[0]);
    } else if (ts.isImportTypeNode(node)) {
      if (ts.isLiteralTypeNode(node.argument)) addSpecifier(node.argument.literal);
    } else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)) {
      addSpecifier(node.moduleReference.expression);
    }
    ts.forEachChild(node, visit);
  }
  visit(sourceFile);
  for (const ref of sourceFile.typeReferenceDirectives) {
    specifiers.push(ref.fileName);
  }
  return specifiers;
}
function resolveLocalSpecifier(filePath, specifier, hasDeclaration) {
  if (!specifier.startsWith(".")) return void 0;
  return resolveExistingFile(path4.resolve(path4.dirname(filePath), specifier), hasDeclaration);
}
function nodeAmbientGlobalFromName(name) {
  if (name === "NodeJS") return "NodeJS";
  if (name === "Buffer") return "Buffer";
  if (name === "process") return "process";
  return void 0;
}
function getQualifiedRootName(name) {
  return ts.isIdentifier(name) ? name.text : getQualifiedRootName(name.left);
}
function getPropertyAccessRootName(expression) {
  let current = expression;
  while (ts.isPropertyAccessExpression(current)) {
    current = current.expression;
  }
  return ts.isIdentifier(current) ? current.text : "";
}
function getNodeAmbientGlobal(sourceFile) {
  let ambient;
  function visit(node) {
    if (ambient != null && ambient.length > 0) return;
    if (ts.isTypeReferenceNode(node)) {
      ambient = nodeAmbientGlobalFromName(getQualifiedRootName(node.typeName));
    } else if (ts.isExpressionWithTypeArguments(node) && ts.isIdentifier(node.expression)) {
      ambient = nodeAmbientGlobalFromName(node.expression.text);
    } else if (ts.isExpressionWithTypeArguments(node) && ts.isPropertyAccessExpression(node.expression)) {
      ambient = nodeAmbientGlobalFromName(getPropertyAccessRootName(node.expression));
    } else if (ts.isTypeQueryNode(node)) {
      ambient = nodeAmbientGlobalFromName(getQualifiedRootName(node.exprName));
    }
    ts.forEachChild(node, visit);
  }
  visit(sourceFile);
  return ambient;
}
function scanFileGraph(rootFile, platform, target) {
  const isDeclaration2 = isDeclarationFile(rootFile);
  const queue = [rootFile];
  const seen = /* @__PURE__ */ new Set();
  const diagnostics = [];
  while (queue.length > 0) {
    const file = queue.shift();
    if (!(file != null && file.length > 0) || seen.has(file)) continue;
    seen.add(file);
    const sourceFile = parseSourceFile(file);
    for (const specifier of collectModuleSpecifiers(sourceFile)) {
      if (isNodeBuiltin(specifier)) {
        diagnostics.push({
          detail: specifier,
          exportPath: target.exportPath,
          file,
          kind: "node-builtin-import",
          platform,
          target: target.target
        });
      }
      const resolved = resolveLocalSpecifier(file, specifier, isDeclaration2);
      if (resolved != null && resolved.length > 0 && !seen.has(resolved)) queue.push(resolved);
    }
    if (isDeclaration2) {
      const ambient = getNodeAmbientGlobal(sourceFile);
      if (ambient != null && ambient.length > 0) {
        diagnostics.push({
          detail: ambient,
          exportPath: target.exportPath,
          file,
          kind: "node-ambient-type",
          platform,
          target: target.target
        });
      }
    }
  }
  return diagnostics;
}
function shouldScanForNodeUsage(target) {
  if (target.pathPlatform === "browser" || target.pathPlatform === "neutral") return target.pathPlatform;
  if (target.conditionPlatform === "browser" || target.conditionPlatform === "neutral") return target.conditionPlatform;
  return void 0;
}
function collectConditionTargetDiagnostics(targets) {
  const diagnostics = [];
  for (const target of targets) {
    const { conditionPlatform: condition, pathPlatform: output } = target;
    if (!condition || !output) continue;
    if (condition === "browser" && output === "node" || condition === "node" && output === "browser" || condition === "neutral" && output !== "neutral") {
      diagnostics.push({
        detail: `${condition} condition points to ${output} output`,
        exportPath: target.exportPath,
        kind: "condition-target-mismatch",
        platform: condition,
        target: target.target
      });
    }
  }
  return diagnostics;
}
function isRuntimeTarget(target) {
  return codeExtensions.some((ext) => target.target.endsWith(ext));
}
function isPortableTarget(target) {
  return target.conditionPlatform === "browser" || target.conditionPlatform === "neutral" || target.pathPlatform === "browser" || target.pathPlatform === "neutral" || !target.conditionPlatform && !target.pathPlatform;
}
function isNodeTarget(target) {
  return target.conditionPlatform === "node" || target.pathPlatform === "node";
}
function hasNodeTarget(targets) {
  return targets.some(isNodeTarget);
}
function isNodeOnlyPackage(pkg, targets) {
  const runtimeTargets = targets.filter(isRuntimeTarget);
  const platformTargets = runtimeTargets.length > 0 ? runtimeTargets : targets;
  if (platformTargets.length === 0) return pkg.bin !== void 0;
  return platformTargets.some(isNodeTarget) && !platformTargets.some(isPortableTarget);
}
function readRootTsconfigTypes(pkgDir) {
  const configPath = path4.join(pkgDir, "tsconfig.json");
  if (!fs3.existsSync(configPath)) return [];
  const parsed = ts.parseConfigFileTextToJson(configPath, fs3.readFileSync(configPath, "utf8"));
  const config = parsed.config;
  const types = config?.compilerOptions?.types;
  return Array.isArray(types) ? types.filter((value) => typeof value === "string") : [];
}
function collectTsconfigDiagnostics(pkgDir, pkg, targets) {
  if (targets.length === 0) return [];
  if (hasNodeTarget(targets) || isNodeOnlyPackage(pkg, targets)) return [];
  const types = readRootTsconfigTypes(pkgDir);
  return types.includes("node") ? [{
    detail: 'compilerOptions.types includes "node"',
    file: path4.join(pkgDir, "tsconfig.json"),
    kind: "tsconfig-node-types"
  }] : [];
}
function diagnosticKey2(pkgDir, diagnostic) {
  return [
    diagnostic.kind,
    diagnostic.platform ?? "",
    diagnostic.target ?? "",
    diagnostic.exportPath ?? "",
    diagnostic.file != null && diagnostic.file.length > 0 ? relativePath(pkgDir, diagnostic.file) : "",
    diagnostic.detail ?? ""
  ].join("\0");
}
function dedupeDiagnostics2(pkgDir, diagnostics) {
  const seen = /* @__PURE__ */ new Set();
  const result = [];
  for (const diagnostic of diagnostics) {
    const key = diagnosticKey2(pkgDir, diagnostic);
    if (seen.has(key)) continue;
    seen.add(key);
    result.push(diagnostic);
  }
  return result;
}
function renderDiagnostic2(pkgDir, diagnostic) {
  switch (diagnostic.kind) {
    case "condition-target-mismatch": {
      return `Publint [platform]: ${diagnostic.detail} at ${diagnostic.exportPath} (${diagnostic.target})`;
    }
    case "node-builtin-import": {
      const file = relativePath(pkgDir, diagnostic.file ?? "");
      return `Publint [platform]: ${diagnostic.platform} export ${diagnostic.target} imports Node builtin "${diagnostic.detail}" in ${file} (${diagnostic.exportPath})`;
    }
    case "node-ambient-type": {
      const file = relativePath(pkgDir, diagnostic.file ?? "");
      return `Publint [platform]: ${diagnostic.platform} hasDeclaration ${diagnostic.target} exposes Node ambient "${diagnostic.detail}" in ${file} (${diagnostic.exportPath})`;
    }
    case "tsconfig-node-types": {
      return 'Publint [platform]: non-node package tsconfig.json should not include compilerOptions.types "node"';
    }
  }
}
async function checkPlatform(pkgDir, pkg, shouldFix = false) {
  const result = pkg.private === true ? {
    errors: 0,
    modified: false,
    warnings: 0
  } : await checkExportMappings(pkgDir, pkg, shouldFix);
  const targets = collectExportTargets(pkg.exports);
  const diagnostics = [...collectConditionTargetDiagnostics(targets)];
  for (const target of targets) {
    const platform = shouldScanForNodeUsage(target);
    if (!platform) continue;
    for (const file of resolveExportTargetFiles(pkgDir, target.target)) {
      diagnostics.push(...scanFileGraph(file, platform, target));
    }
  }
  diagnostics.push(...collectTsconfigDiagnostics(pkgDir, pkg, targets));
  for (const diagnostic of dedupeDiagnostics2(pkgDir, diagnostics)) {
    console.error(chalk5.red(renderDiagnostic2(pkgDir, diagnostic)));
    result.errors++;
  }
  return result;
}
export {
  checkPlatform
};
//# sourceMappingURL=platformPublint.mjs.map
