// src/lib/updo/fetchRegistryInfo.ts
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import chalk from "chalk";

// src/lib/concurrency.ts
import { AsyncLocalStorage } from "node:async_hooks";
var outputStorage = new AsyncLocalStorage();
async function runWithConcurrency(items, concurrency, fn) {
  let next = 0;
  async function worker() {
    while (next < items.length) {
      const i = next++;
      await fn(items[i]);
    }
  }
  await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
}

// src/lib/crossSpawn.ts
import crossSpawn from "cross-spawn";
function spawn(command, args, options) {
  return crossSpawn(command, args, options);
}
function spawnSync(command, args, options) {
  return crossSpawn.sync(command, args, options);
}

// src/lib/initCwd.ts
function INIT_CWD() {
  return process.env.INIT_CWD ?? process.cwd();
}

// src/lib/updo/fetchRegistryInfo.ts
var REGISTRY_BASE = "https://registry.npmjs.org";
var VIEW_FIELDS = ["versions", "dist-tags", "time", "--json"];
function parseNpmrc(filePath, into) {
  let text;
  try {
    text = fs.readFileSync(filePath, "utf8");
  } catch {
    return;
  }
  for (const rawLine of text.split("\n")) {
    const line = rawLine.trim();
    if (!line || line.startsWith("#") || line.startsWith(";")) continue;
    const tokenMatch = /^\/\/([^:]+?):_authToken=(.+)$/.exec(line);
    if (tokenMatch) {
      const hostPath = tokenMatch[1];
      const token = tokenMatch[2].trim().replaceAll(/^["']|["']$/g, "");
      const host = hostPath.split("/", 1)[0];
      into.tokens.set(`https://${host}`, token);
      continue;
    }
    const scopeMatch = /^(@[^:]+):registry=(.+)$/.exec(line);
    if (scopeMatch) {
      into.scopeRegistries.set(scopeMatch[1], scopeMatch[2].trim().replace(/\/$/, ""));
    }
  }
}
var npmrcConfigCache = { config: void 0 };
function loadNpmrcConfig() {
  if (npmrcConfigCache.config) return npmrcConfigCache.config;
  const config = { scopeRegistries: /* @__PURE__ */ new Map(), tokens: /* @__PURE__ */ new Map() };
  parseNpmrc(path.join(INIT_CWD(), ".npmrc"), config);
  parseNpmrc(path.join(os.homedir(), ".npmrc"), config);
  npmrcConfigCache.config = config;
  return config;
}
function resolveRegistry(packageName, config) {
  if (packageName.startsWith("@")) {
    const scope = packageName.split("/", 1)[0];
    const scoped = config.scopeRegistries.get(scope);
    if (scoped != null && scoped.length > 0) return scoped;
  }
  return REGISTRY_BASE;
}
function combineOutput(stdout, stderr) {
  return [stdout, stderr].filter(Boolean).join("\n").trim();
}
function isRegistryNotFoundOutput(output) {
  return /\bE404\b|\b404\b|not found/i.test(output);
}
function outputSnippet(output) {
  if (!output) return "(no output)";
  return output.length > 1200 ? `${output.slice(0, 1200)}...` : output;
}
function lookupErrorMessage(error) {
  return [
    `Updo: failed to fetch registry metadata for ${error.packageName}.`,
    `Command: ${error.command}`,
    `Exit status: ${error.status ?? "unknown"}`,
    outputSnippet(error.output)
  ].join("\n");
}
function coerceStringRecord(value) {
  const out = {};
  if (value == null || typeof value !== "object" || Array.isArray(value)) return out;
  for (const [key, raw] of Object.entries(value)) {
    if (typeof raw === "string") out[key] = raw;
  }
  return out;
}
function coerceVersions(value) {
  if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
  if (value != null && typeof value === "object") return Object.keys(value);
  return [];
}
function parsePackageViewJson(stdout, options = {}) {
  const data = JSON.parse(stdout);
  const versions = coerceVersions(data.versions);
  const result = {
    distTags: coerceStringRecord(data["dist-tags"] ?? data.distTags),
    versions
  };
  if (options.wantTimes ?? false) {
    const publishTimes = coerceStringRecord(data.time);
    if (Object.keys(publishTimes).length > 0) result.publishTimes = publishTimes;
  }
  return result;
}
function isPackageManagerViewSupported(pm) {
  return pm?.name === "npm" || pm?.name === "pnpm";
}
function packageManagerName(pm) {
  return pm?.name ?? "unknown";
}
function viewCommand(pm, packageName) {
  return ["view", packageName, ...VIEW_FIELDS];
}
function appendChunk(current, chunk) {
  return current + (typeof chunk === "string" ? chunk : chunk.toString("utf8"));
}
function runPackageManagerView(pm, args) {
  return new Promise((resolve) => {
    const child = spawn(pm.command, args, {
      cwd: INIT_CWD(),
      env: { FORCE_COLOR: "3", ...process.env },
      stdio: ["ignore", "pipe", "pipe"]
    });
    let stdout = "";
    let stderr = "";
    let isSettled = false;
    child.stdout?.on("data", (chunk) => {
      stdout = appendChunk(stdout, chunk);
    });
    child.stderr?.on("data", (chunk) => {
      stderr = appendChunk(stderr, chunk);
    });
    child.on("error", (error) => {
      if (isSettled) return;
      isSettled = true;
      resolve({
        stderr: [stderr, error.message].filter(Boolean).join("\n"),
        stdout,
        status: null
      });
    });
    child.on("close", (status) => {
      if (isSettled) return;
      isSettled = true;
      resolve({
        stderr,
        status,
        stdout
      });
    });
  });
}
async function fetchRegistryInfoWithPackageManager(packageName, options) {
  const pm = options.packageManager;
  if (!isPackageManagerViewSupported(pm)) {
    return {
      command: "unknown view",
      output: `Unsupported package manager for command-backed updo lookup: ${packageManagerName(pm)}`,
      packageName,
      status: 1,
      type: "fatal"
    };
  }
  const args = viewCommand(pm, packageName);
  const result = await runPackageManagerView(pm, args);
  const output = combineOutput(result.stdout, result.stderr);
  if (result.status !== 0) {
    return isRegistryNotFoundOutput(output) ? {
      output,
      packageName,
      status: result.status,
      type: "not-found"
    } : {
      command: `${pm.command} ${args.join(" ")}`,
      output,
      packageName,
      status: result.status,
      type: "fatal"
    };
  }
  try {
    return {
      info: parsePackageViewJson(result.stdout, options),
      packageName,
      type: "ok"
    };
  } catch (error) {
    return {
      command: `${pm.command} ${args.join(" ")}`,
      output: error instanceof Error ? `${error.message}
${output}` : output,
      packageName,
      status: result.status,
      type: "fatal"
    };
  }
}
async function fetchRegistryInfoDirect(packageName, options = {}) {
  try {
    const config = loadNpmrcConfig();
    const registry = resolveRegistry(packageName, config);
    const url = `${registry}/${packageName}`;
    const headers = {};
    if (!(options.wantTimes ?? false)) {
      headers.Accept = "application/vnd.npm.install-v1+json";
    }
    const token = config.tokens.get(registry);
    if (token != null && token.length > 0) headers.Authorization = `Bearer ${token}`;
    const res = await fetch(url, { headers });
    if (!res.ok) {
      return { distTags: {}, versions: [] };
    }
    const data = await res.json();
    const versionKeys = Object.keys(data.versions ?? {});
    const result = {
      distTags: data["dist-tags"] ?? {},
      versions: versionKeys
    };
    if ((options.wantTimes ?? false) && data.time) {
      const publishTimes = {};
      for (const v of versionKeys) {
        const t = data.time[v];
        if (typeof t === "string") publishTimes[v] = t;
      }
      result.publishTimes = publishTimes;
    }
    return result;
  } catch {
    return { distTags: {}, versions: [] };
  }
}
async function fetchRegistryInfo(packageName, options = {}) {
  if (isPackageManagerViewSupported(options.packageManager)) {
    const result = await fetchRegistryInfoWithPackageManager(packageName, options);
    if (result.type === "ok") return result.info;
    if (result.type === "not-found") return { distTags: {}, versions: [] };
    throw new Error(lookupErrorMessage(result));
  }
  return await fetchRegistryInfoDirect(packageName, options);
}
function runAuthCommand(pm, args, stdio) {
  return spawnSync(pm.command, args, {
    cwd: INIT_CWD(),
    encoding: "utf8",
    env: { FORCE_COLOR: "3", ...process.env },
    stdio
  });
}
function shouldCheckWhoami(pm) {
  const result = runAuthCommand(pm, ["whoami"], "pipe");
  return result.status === 0;
}
function ensureUpdoRegistryAuthenticated(pm) {
  console.log(chalk.gray(`Updo: checking ${pm.command} authentication after registry 404`));
  if (shouldCheckWhoami(pm)) {
    console.log(chalk.gray(`Updo: ${pm.command} authentication is active`));
    return;
  }
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
    throw new Error(
      `Updo: ${pm.command} whoami failed and this is not an interactive terminal. Run "${pm.command} login" and retry xy updo.`
    );
  }
  console.warn(chalk.yellow(`Updo: ${pm.command} whoami failed; starting ${pm.command} login.`));
  const login = runAuthCommand(pm, ["login"], "inherit");
  if (login.status !== 0) {
    throw new Error(`Updo: ${pm.command} login failed. Run "${pm.command} login" manually and retry xy updo.`);
  }
  if (!shouldCheckWhoami(pm)) {
    throw new Error(`Updo: still not authenticated after ${pm.command} login. Run "${pm.command} login" manually and retry xy updo.`);
  }
  console.log(chalk.gray(`Updo: ${pm.command} authentication is active`));
}
function normalizedConcurrency(value) {
  return Math.max(1, Math.floor(value ?? 16));
}
function logFetchStart(total, concurrency, source) {
  if (total === 0) {
    console.log(chalk.gray("  No registry metadata to fetch"));
    return;
  }
  console.log(chalk.gray(`  Fetching metadata for ${total} package(s) via ${source} (${concurrency} concurrent)`));
}
function logFetchComplete(total) {
  if (total > 0) {
    console.log(chalk.gray(`  Finished registry metadata lookup for ${total} package(s)`));
  }
}
function writeFetchProgress(completed, total) {
  if (completed % 25 === 0 || completed === total) {
    process.stderr.write(`\r  Fetched ${completed}/${total} package metadata records...`);
  }
}
async function fetchAllRegistryInfoDirect(packageNames, options = {}) {
  const results = /* @__PURE__ */ new Map();
  const concurrency = normalizedConcurrency(options.concurrency);
  let completed = 0;
  const total = packageNames.length;
  logFetchStart(total, concurrency, "direct registry fetch");
  await runWithConcurrency(packageNames, concurrency, async (name) => {
    results.set(name, await fetchRegistryInfoDirect(name, { wantTimes: options.wantTimes }));
    completed++;
    writeFetchProgress(completed, total);
  });
  if (total > 0) {
    process.stderr.write("\n");
  }
  logFetchComplete(total);
  return results;
}
async function fetchAllRegistryInfoWithPackageManager(packageNames, options) {
  const pm = options.packageManager;
  if (!isPackageManagerViewSupported(pm)) {
    throw new Error(`Updo: command-backed registry lookup is not supported for ${packageManagerName(pm)}.`);
  }
  const results = /* @__PURE__ */ new Map();
  const notFound = [];
  const concurrency = normalizedConcurrency(options.concurrency);
  let completed = 0;
  const total = packageNames.length;
  logFetchStart(total, concurrency, `${pm.command} view`);
  await runWithConcurrency(packageNames, concurrency, async (name) => {
    const result = await fetchRegistryInfoWithPackageManager(name, options);
    if (result.type === "ok") {
      results.set(name, result.info);
    } else if (result.type === "not-found") {
      notFound.push(result);
    } else {
      throw new Error(lookupErrorMessage(result));
    }
    completed++;
    writeFetchProgress(completed, total);
  });
  if (total > 0) {
    process.stderr.write("\n");
  }
  logFetchComplete(total);
  if (notFound.length === 0) return results;
  console.warn(chalk.yellow(
    `Updo: ${notFound.length} declared package(s) returned 404 from ${pm.command} view; checking registry authentication.`
  ));
  ensureUpdoRegistryAuthenticated(pm);
  console.log(chalk.gray(`  Retrying metadata fetch for ${notFound.length} package(s) after authentication`));
  const stillNotFound = [];
  await runWithConcurrency(notFound, concurrency, async (missing) => {
    const result = await fetchRegistryInfoWithPackageManager(missing.packageName, options);
    if (result.type === "ok") {
      results.set(missing.packageName, result.info);
    } else if (result.type === "not-found") {
      stillNotFound.push(missing.packageName);
    } else {
      throw new Error(lookupErrorMessage(result));
    }
  });
  if (stillNotFound.length > 0) {
    throw new Error(
      `Updo: ${pm.command} view still returned 404 for declared package(s) after authentication: ` + stillNotFound.join(", ")
    );
  }
  return results;
}
async function fetchAllRegistryInfo(packageNames, options = {}) {
  if (isPackageManagerViewSupported(options.packageManager)) {
    return await fetchAllRegistryInfoWithPackageManager(packageNames, options);
  }
  return await fetchAllRegistryInfoDirect(packageNames, options);
}
export {
  fetchAllRegistryInfo,
  fetchRegistryInfo,
  isRegistryNotFoundOutput,
  parsePackageViewJson
};
//# sourceMappingURL=fetchRegistryInfo.mjs.map
