'use strict';

var fs = require('node:fs');
var path = require('node:path');
var require$$0 = require('node:url');
var require$$9 = require('../external/@socketsecurity/registry/lib/debug');
var logger = require('../external/@socketsecurity/registry/lib/logger');
var spawn = require('../external/@socketsecurity/registry/lib/spawn');
var constants = require('./constants.js');
var utils = require('./utils.js');
var shadowNpmBin = require('./shadow-npm-bin2.js');

/**
 * Extract package PURLs from add/dlx command arguments.
 */
function extractPackagePurlsFromArgs(command, rawArgs, dlxCommands) {
  const packagePurls = [];
  const isDlxCommand = dlxCommands?.has(command);
  if (command === 'add' || isDlxCommand) {
    // For 'add package1 package2@version' or 'dlx package', get packages from args.
    const packageArgs = rawArgs.slice(1).filter(arg => !arg.startsWith('-') && arg !== '--');
    for (const pkgSpec of packageArgs) {
      const purl = utils.safeNpmSpecToPurl(pkgSpec);
      if (purl) {
        packagePurls.push(purl);
      }
    }
  }
  return packagePurls;
}

/**
 * Extract package PURLs from package.json for install/update commands.
 */
async function extractPackagePurlsFromPackageJson(cwd) {
  const packagePurls = [];
  try {
    const packageJsonContent = await fs.promises.readFile(`${cwd}/package.json`, 'utf8');
    const packageJson = JSON.parse(packageJsonContent);
    const allDeps = {
      ...packageJson.dependencies,
      ...packageJson.devDependencies,
      ...packageJson.optionalDependencies,
      ...packageJson.peerDependencies
    };
    for (const [name, version] of Object.entries(allDeps)) {
      const purl = utils.safeNpmSpecToPurl(typeof version === 'string' ? `${name}@${version}` : name);
      if (purl) {
        packagePurls.push(purl);
      }
    }
    utils.debugScan('start', packagePurls.length);
  } catch (e) {
    require$$9.debugFn('warn', 'Package.json not found or invalid during dependency scanning');
    require$$9.debugDir('error', e);
  }
  return packagePurls;
}
/**
 * Scan packages and log alerts if found.
 */
async function scanPackagesAndLogAlerts(options) {
  const {
    acceptRisks,
    command,
    dlxCommands,
    installCommands,
    managerName,
    nothrow = true,
    rawArgs,
    spinner,
    viewAllRisks
  } = options;
  let {
    cwd = process.cwd()
  } = options;
  if (cwd instanceof URL) {
    cwd = require$$0.fileURLToPath(cwd);
  }

  // Check if this is a command that needs security scanning.
  const isDlxCommand = dlxCommands && command && dlxCommands.has(command);
  const isInstallCommand = command && installCommands.has(command);
  const needsScanning = isDlxCommand || isInstallCommand;
  if (!needsScanning || rawArgs.includes(constants.FLAG_DRY_RUN)) {
    return {
      shouldExit: false
    };
  }

  // Extract package names from command arguments before any downloads.
  let packagePurls = [];
  if (command === 'add' || isDlxCommand) {
    packagePurls = extractPackagePurlsFromArgs(command, rawArgs, dlxCommands);
  } else if (isInstallCommand) {
    // For install/update, scan dependencies from package.json.
    // Note: This scans direct dependencies only.
    packagePurls = await extractPackagePurlsFromPackageJson(cwd);
  }
  if (!packagePurls.length) {
    return {
      shouldExit: false
    };
  }
  utils.debugScan('start', packagePurls.length);
  require$$9.debugDir('inspect', {
    packagePurls
  });
  try {
    const alertsMap = await utils.getAlertsMapFromPurls(packagePurls, {
      filter: acceptRisks ? {
        actions: ['error'],
        blocked: true
      } : {
        actions: ['error', 'monitor', 'warn']
      },
      nothrow,
      spinner
    });
    if (alertsMap.size) {
      process.exitCode = 1;
      spinner?.stop();
      utils.logAlertsMap(alertsMap, {
        hideAt: viewAllRisks ? 'none' : 'middle',
        output: process.stderr
      });
      const errorMessage = `Socket ${managerName} exiting due to risks.${viewAllRisks ? '' : `\nView all risks - Rerun with environment variable ${constants.default.SOCKET_CLI_VIEW_ALL_RISKS}=1.`}${acceptRisks ? '' : `\nAccept risks - Rerun with environment variable ${constants.default.SOCKET_CLI_ACCEPT_RISKS}=1.`}`.trim();
      logger.logger.error(errorMessage);
      return {
        alertsMap,
        shouldExit: true
      };
    }
  } catch (e) {
    spinner?.stop();
    // Re-throw process.exit errors from tests.
    if (e instanceof Error && e.message === 'process.exit called') {
      throw e;
    }
    utils.debugScan('error', undefined, e);
    // Continue with installation if scanning fails.
  }
  utils.debugScan('complete', packagePurls.length);
  require$$9.debugDir('inspect', {
    args: rawArgs.slice(1)
  });
  return {
    shouldExit: false
  };
}

const DLX_COMMANDS = new Set(['dlx']);
const INSTALL_COMMANDS = new Set(['add', 'i', 'install', 'install-test', 'it', 'update', 'up']);
async function shadowPnpmBin(args = process.argv.slice(2), options, extra) {
  const opts = {
    __proto__: null,
    ...options
  };
  const {
    env: spawnEnv,
    ipc,
    ...spawnOpts
  } = opts;
  const {
    spinner
  } = opts;
  const wasSpinning = !!spinner?.isSpinning;
  spinner?.start();
  let {
    cwd = process.cwd()
  } = opts;
  if (cwd instanceof URL) {
    cwd = require$$0.fileURLToPath(cwd);
  }
  const terminatorPos = args.indexOf('--');
  const rawPnpmArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos);
  const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos);

  // Check if this is a command that needs security scanning.
  const command = rawPnpmArgs[0];
  const isDlxCommand = command && DLX_COMMANDS.has(command);
  const isInstallCommand = command && INSTALL_COMMANDS.has(command);
  const needsScanning = isDlxCommand || isInstallCommand;
  if (needsScanning && !rawPnpmArgs.includes(constants.FLAG_DRY_RUN)) {
    const acceptRisks = !!constants.default.ENV.SOCKET_CLI_ACCEPT_RISKS;
    const viewAllRisks = !!constants.default.ENV.SOCKET_CLI_VIEW_ALL_RISKS;

    // Handle add and dlx commands with shared utility.
    if (command === 'add' || isDlxCommand) {
      const scanResult = await scanPackagesAndLogAlerts({
        acceptRisks,
        command,
        cwd,
        dlxCommands: DLX_COMMANDS,
        installCommands: INSTALL_COMMANDS,
        managerName: 'pnpm',
        rawArgs: rawPnpmArgs,
        spinner,
        viewAllRisks
      });
      if (scanResult.shouldExit) {
        // eslint-disable-next-line n/no-process-exit
        process.exit(1);
        // This line is never reached in production, but helps tests.
        throw new Error('process.exit called');
      }
    } else if (['install', 'i', 'update', 'up'].includes(command)) {
      // For install/update, scan all dependencies from pnpm-lock.yaml
      const pnpmLockPath = path.join(cwd, constants.PNPM_LOCK_YAML);
      if (fs.existsSync(pnpmLockPath)) {
        try {
          const lockfileContent = await utils.readPnpmLockfile(pnpmLockPath);
          if (lockfileContent) {
            const lockfile = utils.parsePnpmLockfile(lockfileContent);
            if (lockfile) {
              // Use existing function to scan the entire lockfile
              require$$9.debugFn('notice', `scanning: all dependencies from ${constants.PNPM_LOCK_YAML}`);
              const alertsMap = await utils.getAlertsMapFromPnpmLockfile(lockfile, {
                nothrow: true,
                filter: acceptRisks ? {
                  actions: ['error'],
                  blocked: true
                } : {
                  actions: ['error', 'monitor', 'warn']
                }
              });
              spinner?.stop();
              if (alertsMap.size) {
                process.exitCode = 1;
                utils.logAlertsMap(alertsMap, {
                  hideAt: viewAllRisks ? 'none' : 'middle',
                  output: process.stderr
                });
                const errorMessage = `Socket pnpm exiting due to risks.${viewAllRisks ? '' : `\nView all risks - Rerun with environment variable ${constants.default.SOCKET_CLI_VIEW_ALL_RISKS}=1.`}${acceptRisks ? '' : `\nAccept risks - Rerun with environment variable ${constants.default.SOCKET_CLI_ACCEPT_RISKS}=1.`}`.trim();
                logger.logger.error(errorMessage);
                // eslint-disable-next-line n/no-process-exit
                process.exit(1);
                // This line is never reached in production, but helps tests.
                throw new Error('process.exit called');
              }

              // Return early since we've already done the scanning
              require$$9.debugFn('notice', 'complete: lockfile scanning, proceeding with install');
            }
          }
        } catch (e) {
          require$$9.debugFn('error', 'PNPM lockfile scanning failed');
          require$$9.debugDir('error', e);
        }
      } else {
        require$$9.debugFn('notice', 'skip: no pnpm-lock.yaml found, skipping bulk install scanning');
      }
    }
    require$$9.debugFn('notice', 'complete: scanning, proceeding with install');
  }
  const realPnpmPath = await utils.installPnpmLinks(constants.default.shadowBinPath);
  spinner?.stop();
  const suffixArgs = [...rawPnpmArgs, ...otherArgs];
  require$$9.debugFn('notice', `spawn: ${constants.PNPM} shadow bin ${realPnpmPath} ${utils.cmdFlagsToString(suffixArgs)}`);
  if (wasSpinning) {
    spinner?.start();
  }

  // Set up stdio with IPC channel.
  const stdio = shadowNpmBin.ensureIpcInStdio(spawnOpts.stdio);
  const spawnPromise = spawn.spawn(realPnpmPath, suffixArgs, {
    ...spawnOpts,
    env: {
      ...process.env,
      ...spawnEnv
    },
    stdio,
    // On Windows, pnpm is often a .cmd file that requires shell execution.
    // The spawn function from @socketsecurity/registry will handle this properly
    // when shell is true.
    shell: constants.default.WIN32
  }, extra);

  // Send IPC handshake.
  spawnPromise.process.send({
    [constants.default.SOCKET_IPC_HANDSHAKE]: {
      [constants.default.SOCKET_CLI_SHADOW_API_TOKEN]: utils.getPublicApiToken(),
      [constants.default.SOCKET_CLI_SHADOW_BIN]: constants.PNPM,
      [constants.default.SOCKET_CLI_SHADOW_PROGRESS]: true,
      ...ipc
    }
  });
  return {
    spawnPromise
  };
}

exports.scanPackagesAndLogAlerts = scanPackagesAndLogAlerts;
exports.shadowPnpmBin = shadowPnpmBin;
//# debugId=1e3148ec-bac2-494c-808c-c14f2bc99e12
//# sourceMappingURL=shadow-pnpm-bin2.js.map
