#!/usr/bin/env node
/*!
 * name: nuget-server
 * version: 0.19.0
 * description: NuGet server on Node.js
 * author: Kouji Matsui (@kekyo@mi.kekyo.net)
 * license: MIT
 * repository.url: https://github.com/kekyo/nuget-server.git
 * git.commit.hash: 5b9501882b352a0490b3091210ed1740cfb81389
 */
"use strict";
const commander = require("commander");
const server = require("./server-RSJzFMg6.cjs");
const dayjs = require("dayjs");
const readline = require("readline");
const path = require("path");
const fs = require("fs/promises");
const fs$1 = require("fs");
const AdmZip = require("adm-zip");
const xml2js = require("xml2js");
function _interopNamespaceDefault(e) {
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
  if (e) {
    for (const k in e) {
      if (k !== "default") {
        const d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: () => e[k]
        });
      }
    }
  }
  n.default = e;
  return Object.freeze(n);
}
const readline__namespace = /* @__PURE__ */ _interopNamespaceDefault(readline);
const nowDate = () => dayjs().format("YYYY/MM/DD HH:mm:ss.SSS");
const logLevelPriority = {
  debug: 0,
  info: 1,
  warn: 2,
  error: 3,
  ignore: 4
};
const createConsoleLogger = (prefix, logLevel = "info") => {
  const currentLogLevel = logLevelPriority[logLevel];
  const shouldLog = (level) => {
    return logLevelPriority[level] >= currentLogLevel;
  };
  const noop = () => {
  };
  return {
    debug: shouldLog("debug") ? (msg) => console.debug(`[${prefix}]: [${nowDate()}]: [debug]: ${msg}`) : noop,
    info: shouldLog("info") ? (msg) => console.info(`[${prefix}]: [${nowDate()}]: [info]: ${msg}`) : noop,
    warn: shouldLog("warn") ? (msg) => console.warn(`[${prefix}]: [${nowDate()}]: [warning]: ${msg}`) : noop,
    error: shouldLog("error") ? (msg) => console.error(`[${prefix}]: [${nowDate()}]: [error]: ${msg}`) : noop
  };
};
const promptInput = (rl, prompt, defaultValue) => {
  return new Promise((resolve) => {
    const displayPrompt = defaultValue ? `${prompt} [${defaultValue}]: ` : `${prompt}: `;
    rl.question(displayPrompt, (answer) => {
      resolve(answer.trim() || defaultValue || "");
    });
  });
};
const promptPassword = (prompt) => {
  return new Promise((resolve, reject) => {
    if (!process.stdin.isTTY) {
      const rl = readline__namespace.createInterface({
        input: process.stdin,
        output: process.stdout,
        terminal: false
      });
      process.stdout.write(`${prompt}: `);
      rl.once("line", (input) => {
        process.stdout.write("\n");
        rl.close();
        resolve(input);
      });
      return;
    }
    process.stdout.write(`${prompt}: `);
    process.stdin.setRawMode(true);
    process.stdin.resume();
    let password = "";
    const onData = (chunk) => {
      const str = chunk.toString("utf8");
      for (let i = 0; i < str.length; i++) {
        const char = str[i];
        switch (char) {
          case "":
            process.stdout.write("\n");
            process.stdin.setRawMode(false);
            process.stdin.pause();
            process.stdin.removeListener("data", onData);
            reject(new Error("Cancelled by user"));
            return;
          case "\r":
          case "\n":
            process.stdout.write("\n");
            process.stdin.setRawMode(false);
            process.stdin.pause();
            process.stdin.removeListener("data", onData);
            resolve(password);
            return;
          case "":
          // Backspace (DEL)
          case "\b":
            if (password.length > 0) {
              password = password.slice(0, -1);
              process.stdout.write("\b \b");
            }
            break;
          default:
            if (char && char.charCodeAt(0) >= 32 && char.charCodeAt(0) < 127) {
              password += char;
              process.stdout.write("*");
            }
            break;
        }
      }
    };
    process.stdin.on("data", onData);
  });
};
const promptConfirm = (rl, prompt, defaultValue = false) => {
  return new Promise((resolve) => {
    const defaultStr = defaultValue ? "Y/n" : "y/N";
    rl.question(`${prompt} [${defaultStr}]: `, (answer) => {
      const normalized = answer.trim().toLowerCase();
      if (normalized === "") {
        resolve(defaultValue);
      } else {
        resolve(normalized === "y" || normalized === "yes");
      }
    });
  });
};
const checkUsersFileExists = async (usersFilePath) => {
  try {
    await fs.access(usersFilePath, fs$1.constants.F_OK);
    return true;
  } catch {
    return false;
  }
};
const ensureDirectoryForFile = async (filePath) => {
  const dirPath = filePath.substring(0, filePath.lastIndexOf("/")) || filePath.substring(0, filePath.lastIndexOf("\\")) || ".";
  try {
    await fs.access(dirPath, fs$1.constants.F_OK);
  } catch {
    await fs.mkdir(dirPath, { recursive: true });
  }
};
const runAuthInit = async (config, logger) => {
  const { configDir, usersFile } = config;
  logger.info("Initializing authentication...");
  try {
    const usersFilePath = usersFile || path.join(configDir || "./", "users.json");
    if (await checkUsersFileExists(usersFilePath)) {
      logger.error(
        `${usersFilePath} already exists. Please remove it first to initialize authentication.`
      );
      process.exit(1);
    }
    await ensureDirectoryForFile(usersFilePath);
    logger.debug(`Using users file: ${usersFilePath}`);
    const rl = readline__namespace.createInterface({
      input: process.stdin,
      output: process.stdout
    });
    try {
      const username = await promptInput(rl, "Enter admin username");
      if (!username || username.trim().length === 0) {
        logger.error("Username is required and cannot be empty");
        process.exit(1);
      }
      rl.close();
      let password;
      let confirmPassword;
      let attempts = 0;
      const maxAttempts = 3;
      while (attempts < maxAttempts) {
        try {
          password = await promptPassword("Enter password");
          if (!password || password.length < 4) {
            logger.error("Password must be at least 4 characters long");
            attempts++;
            continue;
          }
          if (config.passwordStrengthCheck !== false) {
            const strength = server.checkPasswordStrength(password, [username]);
            const minScore = server.getMinPasswordScore(config);
            if (strength.score < minScore) {
              const strengthLabel = [
                "Weak",
                "Fair",
                "Good",
                "Strong",
                "Very Strong"
              ][minScore];
              logger.error(
                `Password is too weak (${strength.strength}). Minimum required: ${strengthLabel}.`
              );
              if (strength.feedback.warning) {
                logger.info(`Hint: ${strength.feedback.warning}`);
              } else if (strength.feedback.suggestions.length > 0) {
                logger.info(`Hint: ${strength.feedback.suggestions[0]}`);
              }
              attempts++;
              continue;
            }
            logger.info(`Password strength: ${strength.strength}`);
          }
          confirmPassword = await promptPassword("Confirm password");
          if (password !== confirmPassword) {
            logger.error("Passwords do not match. Please try again.");
            attempts++;
            continue;
          }
          break;
        } catch (error) {
          if (error.message === "Cancelled by user") {
            logger.info("Authentication initialization cancelled.");
            process.exit(0);
          }
          throw error;
        }
      }
      if (attempts >= maxAttempts) {
        logger.error("Maximum password attempts exceeded.");
        process.exit(1);
      }
      const userService = server.createUserService({
        configDir: configDir || "./",
        usersFile: config.usersFile,
        logger,
        serverConfig: config
      });
      await userService.initialize();
      logger.info("Creating admin user...");
      const user = await userService.createUser({
        username,
        password,
        role: "admin"
      });
      console.log("\n" + "=".repeat(60));
      console.log("Admin user created successfully!");
      console.log("=".repeat(60));
      console.log(`Username: ${user.username}`);
      console.log(`Role: ${user.role}`);
      console.log("=".repeat(60));
      console.log(
        "\nNote: You need to generate an API password for NuGet client authentication."
      );
      console.log(
        "You can do this through the web UI after logging in with your username and password."
      );
      console.log("=".repeat(60) + "\n");
      logger.info("Authentication initialization completed.");
      userService.destroy();
    } catch (error) {
      rl.close();
      throw error;
    }
  } catch (error) {
    logger.error(`Authentication initialization failed: ${error.message}`);
    process.exit(1);
  }
};
const createNuGetClient = (config) => {
  const { baseUrl, username, password, timeout = 6e4 } = config;
  const authHeader = username && password ? `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}` : void 0;
  const fetchWithAuth = async (url, options = {}) => {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    try {
      const headers = {
        ...options.headers
      };
      if (authHeader) {
        headers["Authorization"] = authHeader;
      }
      const response = await fetch(url, {
        ...options,
        headers,
        signal: controller.signal
      });
      clearTimeout(timeoutId);
      if (response.status === 301 || response.status === 302) {
        const location = response.headers.get("Location");
        if (location) {
          return fetchWithAuth(location, options);
        }
      }
      if (!response.ok) {
        throw new Error(
          `HTTP ${response.status}: ${response.statusText} for ${url}`
        );
      }
      return response;
    } catch (error) {
      clearTimeout(timeoutId);
      if (error.name === "AbortError") {
        throw new Error(`Request timeout after ${timeout}ms for ${url}`);
      }
      throw error;
    }
  };
  return {
    /**
     * Get the service index from the NuGet server
     */
    getServiceIndex: async () => {
      const indexUrl = baseUrl.endsWith("/") ? `${baseUrl}index.json` : `${baseUrl}/index.json`;
      const response = await fetchWithAuth(indexUrl);
      return await response.json();
    },
    /**
     * Search for packages
     */
    searchPackages: async (searchUrl, skip, take) => {
      const url = new URL(searchUrl);
      url.searchParams.set("q", "");
      url.searchParams.set("skip", skip.toString());
      url.searchParams.set("take", take.toString());
      url.searchParams.set("prerelease", "true");
      url.searchParams.set("semVerLevel", "2.0.0");
      const response = await fetchWithAuth(url.toString());
      return await response.json();
    },
    /**
     * Get all versions of a package
     */
    getPackageVersions: async (packageBaseUrl, packageId) => {
      const normalizedId = packageId.toLowerCase();
      const url = `${packageBaseUrl}/${normalizedId}/index.json`;
      const response = await fetchWithAuth(url);
      const data = await response.json();
      return data.versions;
    },
    /**
     * Download a package
     */
    downloadPackage: async (contentUrl, packageId, version) => {
      const normalizedId = packageId.toLowerCase();
      const normalizedVersion = version.toLowerCase();
      const url = `${contentUrl}/${normalizedId}/${normalizedVersion}/${normalizedId}.${normalizedVersion}.nupkg`;
      const response = await fetchWithAuth(url);
      const arrayBuffer = await response.arrayBuffer();
      return Buffer.from(arrayBuffer);
    }
  };
};
const parseNuspec = async (nuspecContent) => {
  const parser = new xml2js.Parser({ explicitArray: false });
  const result = await parser.parseStringPromise(nuspecContent);
  const metadata = result.package?.metadata;
  if (!metadata) {
    throw new Error("Invalid nuspec: missing metadata");
  }
  return {
    id: metadata.id,
    version: metadata.version,
    icon: metadata.icon
  };
};
const packageExists = async (packageDir, packageId, version) => {
  const packagePath = path.join(
    packageDir,
    packageId,
    version,
    `${packageId}.${version}.nupkg`
  );
  try {
    await fs.access(packagePath, fs$1.constants.F_OK);
    return true;
  } catch {
    return false;
  }
};
const savePackage = async (packageData, packageDir, logger) => {
  let zip;
  try {
    zip = new AdmZip(packageData);
  } catch (error) {
    throw new Error("Invalid package format - not a valid ZIP file");
  }
  const nuspecEntry = zip.getEntries().find((entry) => entry.entryName.endsWith(".nuspec"));
  if (!nuspecEntry) {
    throw new Error("Package does not contain a .nuspec file");
  }
  const nuspecContent = nuspecEntry.getData().toString("utf8");
  const metadata = await parseNuspec(nuspecContent);
  const versionDir = path.join(packageDir, metadata.id, metadata.version);
  await fs.mkdir(versionDir, { recursive: true });
  const nupkgPath = path.join(
    versionDir,
    `${metadata.id}.${metadata.version}.nupkg`
  );
  await fs.writeFile(nupkgPath, packageData);
  const nuspecPath = path.join(versionDir, `${metadata.id}.nuspec`);
  await fs.writeFile(nuspecPath, nuspecContent);
  if (metadata.icon) {
    try {
      const iconEntry = zip.getEntries().find((entry) => entry.entryName === metadata.icon);
      if (iconEntry) {
        const iconData = iconEntry.getData();
        const iconExtension = metadata.icon.split(".").pop()?.toLowerCase() || "png";
        const iconFileName = `icon.${iconExtension}`;
        const iconPath = path.join(versionDir, iconFileName);
        await fs.writeFile(iconPath, iconData);
        logger.debug(
          `Extracted icon: ${iconFileName} for ${metadata.id} ${metadata.version}`
        );
      }
    } catch (error) {
      logger.warn(
        `Failed to extract icon for ${metadata.id} ${metadata.version}: ${error}`
      );
    }
  }
};
const createImportService = (config) => {
  const { sourceUrl, username, password, packageDir, logger, onProgress } = config;
  const client = createNuGetClient({
    baseUrl: sourceUrl,
    username,
    password
  });
  return {
    /**
     * Discover all packages from the source server
     */
    discoverPackages: async () => {
      logger.info("Fetching service index...");
      const serviceIndex = await client.getServiceIndex();
      const searchService = serviceIndex.resources.find((r) => {
        const types = Array.isArray(r["@type"]) ? r["@type"] : [r["@type"]];
        return types.some((t) => t.includes("SearchQueryService"));
      });
      const packageBaseService = serviceIndex.resources.find((r) => {
        const types = Array.isArray(r["@type"]) ? r["@type"] : [r["@type"]];
        return types.some((t) => t.includes("PackageBaseAddress"));
      });
      if (!searchService || !packageBaseService) {
        throw new Error("Required NuGet services not found in service index");
      }
      const searchUrl = searchService["@id"].replace(/\/$/, "");
      const packageBaseUrl = packageBaseService["@id"].replace(/\/$/, "");
      logger.info("Discovering packages...");
      const allPackages = [];
      let skip = 0;
      const take = 100;
      let totalHits = 0;
      do {
        logger.debug(`Fetching packages ${skip} to ${skip + take}...`);
        const searchResult = await client.searchPackages(searchUrl, skip, take);
        totalHits = searchResult.totalHits;
        for (const pkg of searchResult.data) {
          try {
            logger.debug(`Getting versions for ${pkg.id}...`);
            const versions = await client.getPackageVersions(
              packageBaseUrl,
              pkg.id
            );
            allPackages.push({
              id: pkg.id,
              versions
            });
          } catch (error) {
            logger.warn(`Failed to get versions for ${pkg.id}: ${error}`);
          }
        }
        skip += take;
      } while (skip < totalHits);
      const totalVersions = allPackages.reduce(
        (sum, p) => sum + p.versions.length,
        0
      );
      logger.info(
        `Found ${allPackages.length} packages with ${totalVersions} total versions`
      );
      return allPackages;
    },
    /**
     * Import packages from the source server
     */
    importPackages: async (packages) => {
      const serviceIndex = await client.getServiceIndex();
      const packageContentService = serviceIndex.resources.find((r) => {
        const types = Array.isArray(r["@type"]) ? r["@type"] : [r["@type"]];
        return types.some((t) => t.includes("PackageBaseAddress"));
      });
      if (!packageContentService) {
        throw new Error("Package content service not found");
      }
      const packageContentUrl = packageContentService["@id"].replace(/\/$/, "");
      const result = {
        totalPackages: packages.length,
        totalVersions: packages.reduce((sum, p) => sum + p.versions.length, 0),
        successfulVersions: 0,
        failedVersions: 0,
        failures: []
      };
      const progress = {
        totalPackages: result.totalPackages,
        totalVersions: result.totalVersions,
        downloadedVersions: 0,
        failedVersions: 0
      };
      for (const pkg of packages) {
        for (const version of pkg.versions) {
          progress.currentPackage = pkg.id;
          progress.currentVersion = version;
          try {
            let overwrite = false;
            if (await packageExists(packageDir, pkg.id, version)) {
              logger.debug(
                `Overwriting existing package: ${pkg.id}@${version}`
              );
              overwrite = true;
            }
            logger.debug(`Downloading ${pkg.id}@${version}...`);
            const packageData = await client.downloadPackage(
              packageContentUrl,
              pkg.id,
              version
            );
            await savePackage(packageData, packageDir, logger);
            result.successfulVersions++;
            progress.downloadedVersions++;
            logger.info(
              `Successfully imported ${pkg.id}@${version}${overwrite ? " (overwrite)" : ""}`
            );
          } catch (error) {
            logger.error(
              `Failed to import ${pkg.id}@${version}: ${error.message}`
            );
            result.failedVersions++;
            progress.failedVersions++;
            result.failures.push({
              packageId: pkg.id,
              version,
              error: error.message
            });
          }
          if (onProgress) {
            onProgress(progress);
          }
        }
      }
      return result;
    }
  };
};
const runImportPackages = async (config, logger) => {
  const { packageDir } = config;
  logger.info("Starting package import...");
  const rl = readline__namespace.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  try {
    const sourceUrl = await promptInput(
      rl,
      "Enter source NuGet server URL",
      "http://host.example.com/repository/nuget/"
    );
    if (!sourceUrl) {
      logger.error("Server URL cannot be empty");
      process.exit(1);
    }
    try {
      new URL(sourceUrl);
    } catch {
      logger.error("Invalid URL format");
      process.exit(1);
    }
    const needsAuth = await promptConfirm(
      rl,
      "Does the server require authentication?",
      false
    );
    let username;
    let password;
    if (needsAuth) {
      username = await promptInput(rl, "Enter username");
      if (!username) {
        logger.error(
          "Username cannot be empty when authentication is required"
        );
        process.exit(1);
      }
      rl.close();
      try {
        password = await promptPassword("Enter password");
        if (!password) {
          logger.error(
            "Password cannot be empty when authentication is required"
          );
          process.exit(1);
        }
      } catch (error) {
        if (error.message === "Cancelled by user") {
          logger.info("Import cancelled by user.");
          process.exit(0);
        }
        throw error;
      }
      const rl2 = readline__namespace.createInterface({
        input: process.stdin,
        output: process.stdout
      });
      logger.info("=".repeat(60));
      logger.info("Import Configuration:");
      logger.info(`Source: ${sourceUrl}`);
      logger.info(`Target: ${packageDir}`);
      logger.info(`Authentication: ${username} (password hidden)`);
      logger.info("=".repeat(60));
      const confirmed = await promptConfirm(
        rl2,
        "Start importing packages? (existing packages will be overwritten)",
        false
      );
      rl2.close();
      if (!confirmed) {
        logger.info("Import cancelled by user.");
        process.exit(0);
      }
    } else {
      logger.info("=".repeat(60));
      logger.info("Import Configuration:");
      logger.info(`Source: ${sourceUrl}`);
      logger.info(`Target: ${packageDir}`);
      logger.info(`Authentication: None`);
      logger.info("=".repeat(60));
      const confirmed = await promptConfirm(
        rl,
        "Start importing packages? (existing packages will be overwritten)",
        false
      );
      rl.close();
      if (!confirmed) {
        logger.info("Import cancelled by user.");
        process.exit(0);
      }
    }
    let lastPercentage = -1;
    const importService = createImportService({
      sourceUrl,
      username,
      password,
      packageDir,
      logger,
      onProgress: (progress) => {
        const percentage = progress.totalVersions > 0 ? Math.floor(
          progress.downloadedVersions / progress.totalVersions * 100
        ) : 0;
        if (percentage !== lastPercentage || progress.downloadedVersions === progress.totalVersions) {
          lastPercentage = percentage;
          const packageInfo = progress.currentPackage && progress.currentVersion ? ` - ${progress.currentPackage}@${progress.currentVersion}` : "";
          logger.info(
            `Progress: ${progress.downloadedVersions}/${progress.totalVersions} packages (${percentage}%)${packageInfo}`
          );
        }
      }
    });
    logger.info("Discovering packages from source server...");
    const packages = await importService.discoverPackages();
    if (packages.length === 0) {
      logger.info("No packages found on the source server.");
      process.exit(0);
    }
    const totalVersions = packages.reduce(
      (sum, p) => sum + p.versions.length,
      0
    );
    logger.info(
      `Found ${packages.length} packages with ${totalVersions} versions total.`
    );
    logger.info("Starting package import...");
    const startTime = Date.now();
    const result = await importService.importPackages(packages);
    const elapsedTime = ((Date.now() - startTime) / 1e3).toFixed(1);
    logger.info("=".repeat(60));
    logger.info("Import Complete!");
    logger.info("=".repeat(60));
    logger.info(`Total packages: ${result.totalPackages}`);
    logger.info(`Total versions: ${result.totalVersions}`);
    logger.info(`Successfully imported: ${result.successfulVersions}`);
    logger.info(`Failed: ${result.failedVersions}`);
    logger.info(`Time elapsed: ${elapsedTime} seconds`);
    if (result.failures.length > 0) {
      logger.info("Failed imports:");
      for (const failure of result.failures.slice(0, 10)) {
        logger.error(
          `  - ${failure.packageId}@${failure.version}: ${failure.error}`
        );
      }
      if (result.failures.length > 10) {
        logger.info(`  ... and ${result.failures.length - 10} more`);
      }
    }
    logger.info("=".repeat(60));
    logger.info("Package import completed.");
  } catch (error) {
    rl.close();
    logger.error(`Package import failed: ${error.message}`);
    process.exit(1);
  }
};
const validateConfig = (config, configDir, logger) => {
  const validated = {};
  if (typeof config.port === "number" && config.port > 0 && config.port <= 65535) {
    validated.port = config.port;
  } else if (config.port !== void 0) {
    logger?.warn(`Invalid port in config.json: ${config.port}`);
  }
  if (typeof config.baseUrl === "string") {
    validated.baseUrl = config.baseUrl;
  }
  if (typeof config.packageDir === "string") {
    validated.packageDir = path.resolve(configDir, config.packageDir);
  }
  if (typeof config.usersFile === "string") {
    validated.usersFile = path.resolve(configDir, config.usersFile);
  }
  if (typeof config.realm === "string") {
    validated.realm = config.realm;
  }
  if (typeof config.logLevel === "string") {
    const validLevels = [
      "debug",
      "info",
      "warn",
      "error",
      "ignore"
    ];
    if (validLevels.includes(config.logLevel)) {
      validated.logLevel = config.logLevel;
    } else {
      logger?.warn(`Invalid logLevel in config.json: ${config.logLevel}`);
    }
  }
  if (Array.isArray(config.trustedProxies)) {
    const validProxies = config.trustedProxies.filter(
      (ip) => typeof ip === "string"
    );
    if (validProxies.length > 0) {
      validated.trustedProxies = validProxies;
    }
    if (validProxies.length !== config.trustedProxies.length) {
      logger?.warn(
        "Some invalid trusted proxy IPs in config.json were ignored"
      );
    }
  }
  if (typeof config.authMode === "string") {
    const validModes = ["none", "publish", "full"];
    if (validModes.includes(config.authMode)) {
      validated.authMode = config.authMode;
    } else {
      logger?.warn(`Invalid authMode in config.json: ${config.authMode}`);
    }
  }
  if (typeof config.sessionSecret === "string") {
    validated.sessionSecret = config.sessionSecret;
    if (logger) {
      logger.warn(
        "Session secret found in config.json. Consider using environment variable NUGET_SERVER_SESSION_SECRET instead for better security."
      );
    }
  }
  if (typeof config.passwordMinScore === "number" && config.passwordMinScore >= 0 && config.passwordMinScore <= 4) {
    validated.passwordMinScore = config.passwordMinScore;
  } else if (config.passwordMinScore !== void 0) {
    logger?.warn(
      `Invalid passwordMinScore in config.json: ${config.passwordMinScore}. Must be 0-4.`
    );
  }
  if (typeof config.passwordStrengthCheck === "boolean") {
    validated.passwordStrengthCheck = config.passwordStrengthCheck;
  }
  if (typeof config.duplicatePackagePolicy === "string") {
    const validPolicies = [
      "overwrite",
      "ignore",
      "error"
    ];
    if (validPolicies.includes(
      config.duplicatePackagePolicy
    )) {
      validated.duplicatePackagePolicy = config.duplicatePackagePolicy;
    } else {
      logger?.warn(
        `Invalid duplicatePackagePolicy in config.json: ${config.duplicatePackagePolicy}`
      );
    }
  }
  return validated;
};
const loadConfigFromPath = async (configPath, logger) => {
  try {
    const content = await fs.readFile(configPath, "utf-8");
    const config = JSON.parse(content);
    logger?.debug(`Loaded configuration from ${configPath}`);
    const configDir = configPath.substring(0, configPath.lastIndexOf("/")) || configPath.substring(0, configPath.lastIndexOf("\\")) || ".";
    return validateConfig(config, configDir, logger);
  } catch (error) {
    if (error.code === "ENOENT") {
      logger?.debug(`No config file found at ${configPath}`);
      return {};
    } else if (error instanceof SyntaxError) {
      logger?.warn(`Failed to parse config file: ${error.message}`);
      return {};
    } else {
      logger?.warn(`Failed to load config file: ${error.message}`);
      return {};
    }
  }
};
const getPortFromEnv = () => {
  const port = process.env.NUGET_SERVER_PORT;
  return port ? parseInt(port, 10) : void 0;
};
const getPackageDirFromEnv = () => {
  return process.env.NUGET_SERVER_PACKAGE_DIR;
};
const getConfigFileFromEnv = () => {
  return process.env.NUGET_SERVER_CONFIG_FILE;
};
const getRealmFromEnv = () => {
  return process.env.NUGET_SERVER_REALM;
};
const getLogLevelFromEnv = () => {
  const level = process.env.NUGET_SERVER_LOG_LEVEL;
  const validLevels = ["debug", "info", "warn", "error", "ignore"];
  return validLevels.includes(level) ? level : void 0;
};
const getAuthModeFromEnv = () => {
  const authMode = process.env.NUGET_SERVER_AUTH_MODE;
  if (authMode === "publish" || authMode === "full" || authMode === "none") {
    return authMode;
  }
  return void 0;
};
const getSessionSecretFromEnv = () => {
  return process.env.NUGET_SERVER_SESSION_SECRET;
};
const getPasswordMinScoreFromEnv = () => {
  const value = process.env.NUGET_SERVER_PASSWORD_MIN_SCORE;
  if (value) {
    const score = parseInt(value, 10);
    if (!isNaN(score) && score >= 0 && score <= 4) {
      return score;
    }
  }
  return void 0;
};
const getPasswordStrengthCheckFromEnv = () => {
  const value = process.env.NUGET_SERVER_PASSWORD_STRENGTH_CHECK;
  if (value) {
    return value.toLowerCase() !== "false";
  }
  return void 0;
};
const getUsersFileFromEnv = () => {
  return process.env.NUGET_SERVER_USERS_FILE;
};
const getDuplicatePackagePolicyFromEnv = () => {
  const policy = process.env.NUGET_SERVER_DUPLICATE_PACKAGE_POLICY;
  if (policy === "overwrite" || policy === "ignore" || policy === "error") {
    return policy;
  }
  return void 0;
};
const program = new commander.Command();
program.name(server.name).description(server.description).version(`${server.version}-${server.git_commit_hash}`).option("-p, --port <port>", "port number").option(
  "-b, --base-url <url>",
  "fixed base URL for API endpoints (overrides auto-detection)"
).option("-d, --package-dir <dir>", "package storage directory").option("-c, --config-file <path>", "path to config.json file").option("-u, --users-file <path>", "path to users.json file").option("-r, --realm <realm>", `authentication realm`).option(
  "-l, --log-level <level>",
  "log level (debug, info, warn, error, ignore)"
).option(
  "--trusted-proxies <ips>",
  "comma-separated list of trusted proxy IPs"
).option("--auth-mode <mode>", "authentication mode (none, publish, full)").option(
  "--auth-init",
  "initialize authentication with interactive admin user creation"
).option(
  "--import-packages",
  "import packages from another NuGet server interactively"
).action(async (options) => {
  const configFilePath = options.configFile || getConfigFileFromEnv() || "./config.json";
  const tempLogger = createConsoleLogger(server.name, "warn");
  const configFile = await loadConfigFromPath(configFilePath, tempLogger);
  const configDir = path.dirname(configFilePath);
  const port = options.port !== void 0 ? parseInt(options.port, 10) : getPortFromEnv() || configFile.port || 5963;
  const baseUrl = options.baseUrl || server.getBaseUrlFromEnv() || configFile.baseUrl;
  const packageDir = options.packageDir || getPackageDirFromEnv() || configFile.packageDir || "./packages";
  const realm = options.realm || getRealmFromEnv() || configFile.realm || `${server.name} ${server.version}`;
  const logLevel = options.logLevel || getLogLevelFromEnv() || configFile.logLevel || "info";
  const trustedProxies = options.trustedProxies ? options.trustedProxies.split(",").map((ip) => ip.trim()) : server.getTrustedProxiesFromEnv() || configFile.trustedProxies;
  const authMode = options.authMode || getAuthModeFromEnv() || configFile.authMode || "none";
  const sessionSecret = getSessionSecretFromEnv() || configFile.sessionSecret;
  const passwordMinScore = getPasswordMinScoreFromEnv() ?? configFile.passwordMinScore ?? 2;
  const passwordStrengthCheck = getPasswordStrengthCheckFromEnv() ?? configFile.passwordStrengthCheck ?? true;
  const usersFile = options.usersFile || getUsersFileFromEnv() || configFile.usersFile;
  const duplicatePackagePolicy = getDuplicatePackagePolicyFromEnv() || configFile.duplicatePackagePolicy || "ignore";
  const validLogLevels = [
    "debug",
    "info",
    "warn",
    "error",
    "ignore"
  ];
  if (!validLogLevels.includes(logLevel)) {
    console.error(
      `Invalid log level: ${logLevel}. Valid levels are: ${validLogLevels.join(", ")}`
    );
    process.exit(1);
  }
  const logger = createConsoleLogger(server.name, logLevel);
  const validAuthModes = ["none", "publish", "full"];
  if (!validAuthModes.includes(authMode)) {
    console.error(
      `Invalid auth mode: ${authMode}. Valid modes are: ${validAuthModes.join(", ")}`
    );
    process.exit(1);
  }
  if (isNaN(port) || port <= 0 || port > 65535) {
    console.error("Invalid port number");
    process.exit(1);
  }
  logger.info(`${server.name} [${server.version}-${server.git_commit_hash}] Starting...`);
  logger.info(`Port: ${port}`);
  if (baseUrl) {
    logger.info(`Base URL: ${baseUrl} (fixed)`);
  } else {
    logger.info(`Base URL: http://localhost:${port} (auto-detected)`);
  }
  logger.info(`Package directory: ${packageDir}`);
  logger.info(`Config file: ${configFilePath}`);
  if (usersFile) {
    logger.info(`Users file: ${usersFile}`);
  }
  logger.info(`Realm: ${realm}`);
  logger.info(`Authentication mode: ${authMode}`);
  logger.info(`Log level: ${logLevel}`);
  if (trustedProxies && trustedProxies.length > 0) {
    logger.info(`Trusted proxies: ${trustedProxies.join(", ")}`);
  }
  if (configFile && Object.keys(configFile).length > 0) {
    logger.info(`Configuration loaded from ${configFilePath}`);
  }
  const config = {
    port,
    baseUrl,
    packageDir,
    configDir,
    usersFile,
    realm,
    authMode,
    trustedProxies,
    logLevel,
    sessionSecret,
    passwordMinScore,
    passwordStrengthCheck,
    duplicatePackagePolicy
  };
  if (options.authInit) {
    await runAuthInit(config, logger);
    process.exit(0);
  }
  if (options.importPackages) {
    await runImportPackages(config, logger);
    process.exit(0);
  }
  try {
    logger.info("Starting Fastify server...");
    const server$1 = await server.startFastifyServer(config, logger);
    const gracefulShutdown = async () => {
      logger.info("Shutting down server...");
      try {
        await server$1.close();
        process.exit(0);
      } catch (error) {
        logger.error(`Error during shutdown: ${error}`);
        process.exit(1);
      }
    };
    process.on("SIGTERM", gracefulShutdown);
    process.on("SIGINT", gracefulShutdown);
  } catch (error) {
    logger.error(`Failed to start server: ${error}`);
    process.exit(1);
  }
});
program.parse();
//# sourceMappingURL=cli.cjs.map
