'use strict';

var constants = require('./constants.js');
var require$$0 = require('node:url');
var agent = require('../external/@socketsecurity/registry/lib/agent');
var debug = require('../external/@socketsecurity/registry/lib/debug');
var objects = require('../external/@socketsecurity/registry/lib/objects');
var spawn = require('../external/@socketsecurity/registry/lib/spawn');
var utils = require('./utils.js');

/**
 * Ensures stdio configuration includes IPC channel for process communication.
 * Converts various stdio formats to include 'ipc' as the fourth element.
 */
function ensureIpcInStdio(stdio) {
  if (typeof stdio === 'string') {
    return [stdio, stdio, stdio, 'ipc'];
  } else if (Array.isArray(stdio)) {
    if (!stdio.includes('ipc')) {
      return stdio.concat('ipc');
    }
    return stdio;
  } else {
    return ['pipe', 'pipe', 'pipe', 'ipc'];
  }
}

// Length of the '--node-options=' prefix, used to slice the value out of an
// npm '--node-options=<value>' argument.
const NODE_OPTIONS_FLAG_PREFIX_LENGTH = '--node-options='.length;

/**
 * Build the '--node-options=<value>' argument passed to npm so its lifecycle
 * scripts run with our Node permission flags.
 *
 * npm assigns this value to NODE_OPTIONS for the scripts it runs, REPLACING any
 * inherited NODE_OPTIONS. To avoid clobbering the user's configuration we merge,
 * in order, the caller's existing process.env.NODE_OPTIONS and any
 * '--node-options=<value>' npm argument ahead of our permission flags (issue
 * #1036).
 *
 * The value is intentionally NOT wrapped in quotes. shadowNpmBase spawns npm
 * without a shell, so any quotes would be passed literally to npm and become
 * part of the NODE_OPTIONS value. Consumers that re-tokenize NODE_OPTIONS on
 * whitespace (e.g. Next.js) then strip the leading quote off '--permission',
 * dropping it while keeping the '--allow-*' flags, which crashes Node >= 24
 * with ERR_MISSING_OPTION (issue #1160).
 */
function buildNpmNodeOptionsArg(envNodeOptions, nodeOptionsArg, permArgs) {
  const value = [envNodeOptions, nodeOptionsArg ? nodeOptionsArg.slice(NODE_OPTIONS_FLAG_PREFIX_LENGTH) : '', utils.cmdFlagsToString(permArgs)].filter(Boolean).join(' ');
  return `--node-options=${value}`;
}
async function shadowNpmBase(binName, args = process.argv.slice(2), options, extra) {
  const {
    env: spawnEnv,
    ipc,
    ...spawnOpts
  } = {
    __proto__: null,
    ...options
  };
  let cwd = objects.getOwn(spawnOpts, 'cwd') ?? process.cwd();
  if (cwd instanceof URL) {
    cwd = require$$0.fileURLToPath(cwd);
  }
  const isShadowNpm = binName === constants.NPM;
  const terminatorPos = args.indexOf('--');
  const rawBinArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos);
  const nodeOptionsArg = rawBinArgs.findLast(agent.isNpmNodeOptionsFlag);
  const progressArg = rawBinArgs.findLast(agent.isNpmProgressFlag) !== '--no-progress';
  const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos);
  const permArgs = isShadowNpm && constants.default.SUPPORTS_NODE_PERMISSION_FLAG ? ['--permission', '--allow-child-process',
  // '--allow-addons',
  // '--allow-wasi',
  // Allow all reads because npm walks up directories looking for config
  // and package.json files.
  '--allow-fs-read=*', `--allow-fs-write=${cwd}/*`, `--allow-fs-write=${constants.default.npmGlobalPrefix}/*`, `--allow-fs-write=${constants.default.npmCachePath}/*`] : [];
  const useAudit = rawBinArgs.includes('--audit');
  const useDebug = debug.isDebug('stdio');
  const useNodeOptions = nodeOptionsArg || permArgs.length;
  const binArgs = rawBinArgs.filter(a => !agent.isNpmAuditFlag(a) && !agent.isNpmProgressFlag(a));
  const isSilent = !useDebug && !binArgs.some(agent.isNpmLoglevelFlag);
  // The default value of loglevel is "notice". We default to "error" which is
  // two levels quieter.
  const logLevelArgs = isSilent ? [constants.FLAG_LOGLEVEL, 'error'] : [];
  const noAuditArgs = useAudit || !(await utils.findUp(constants.NODE_MODULES, {
    cwd,
    onlyDirectories: true
  })) ? [] : ['--no-audit'];
  const stdio = ensureIpcInStdio(objects.getOwn(spawnOpts, 'stdio'));
  const realBinPath = isShadowNpm ? await utils.installNpmLinks(constants.default.shadowBinPath) : await utils.installNpxLinks(constants.default.shadowBinPath);
  const spawnPromise = spawn.spawn(constants.default.execPath, [...constants.default.nodeNoWarningsFlags, ...constants.default.nodeDebugFlags, ...constants.default.nodeHardenFlags, ...constants.default.nodeMemoryFlags, ...(constants.default.ENV.INLINED_SOCKET_CLI_SENTRY_BUILD ? ['--require', constants.default.instrumentWithSentryPath] : []), '--require', constants.default.shadowNpmInjectPath, realBinPath, ...noAuditArgs, ...(useNodeOptions ? [buildNpmNodeOptionsArg(process.env['NODE_OPTIONS'], nodeOptionsArg, permArgs)] : []), '--no-fund',
  // Add '--no-progress' to fix input being swallowed by the npm spinner.
  '--no-progress',
  // Add '--loglevel=error' if a loglevel flag is not provided and the
  // SOCKET_CLI_DEBUG environment variable is not truthy.
  ...logLevelArgs, ...binArgs, ...otherArgs], {
    ...spawnOpts,
    env: {
      ...process.env,
      ...constants.default.processEnv,
      ...spawnEnv
    },
    stdio
  }, extra);
  spawnPromise.process.send({
    [constants.default.SOCKET_IPC_HANDSHAKE]: {
      [constants.default.SOCKET_CLI_SHADOW_API_TOKEN]: utils.getPublicApiToken(),
      [constants.default.SOCKET_CLI_SHADOW_BIN]: binName,
      [constants.default.SOCKET_CLI_SHADOW_PROGRESS]: progressArg,
      ...ipc
    }
  });
  return {
    spawnPromise
  };
}

async function shadowNpmBin(args = process.argv.slice(2), options, extra) {
  return await shadowNpmBase(constants.NPM, args, options, extra);
}

exports.ensureIpcInStdio = ensureIpcInStdio;
exports.shadowNpmBase = shadowNpmBase;
exports.shadowNpmBin = shadowNpmBin;
//# debugId=f17f4535-2c52-46f0-9dba-4ef3296c4d8e
//# sourceMappingURL=shadow-npm-bin2.js.map
