UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

1,552 lines • 76.6 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPnpmSpawnRegistryEnv = getPnpmSpawnRegistryEnv;
const fs_1 = require("fs");
const os_1 = require("os");
const path_1 = require("path");
const semver_1 = require("semver");
const pnpm_config_1 = require("../package-manager-config/pnpm-config");
const npmrc_1 = require("../package-manager-config/npmrc");
const fileutils_1 = require("../fileutils");
const logger_1 = require("../logger");
const utils_1 = require("./utils");
/*
 * pnpm registry resolution, by version line:
 *
 * - < 10.6.0: registry config lives only in the .npmrc chain and npm_config_*
 *   env vars, which a spawned npm resolves identically on its own. Nothing to
 *   bridge.
 * - 10.6.0 - 10.x: pnpm-workspace.yaml accepts every .npmrc setting in
 *   camelCase (https://github.com/pnpm/pnpm/pull/9211) and the parsed yaml
 *   object is Object.assign-ed over the npmrc-derived config, so a
 *   `registries` map (default/@scope keys) wholesale-replaces the
 *   npmrc/env/CLI registry selection. That config keeps npm's own tiers plus a
 *   `workspace` one, the .npmrc beside the workspace manifest, ranked under the
 *   project .npmrc and over the user one.
 * - >= 11.0.0: the config reader merges per key: registries =
 *   {...fromNpmrc, ...fromYaml}, then `pnpm_config_registry` env overrides
 *   only `registries.default`. npm_config_* env vars are no longer read (11.6.0
 *   restores the URL-scoped `//<dart>:<key>` ones alone), and .npmrc is
 *   restricted to auth/registry/network keys. The per-package lookup is
 *   registries[scope] ?? registries.default. An `auth.ini` file in pnpm's
 *   config dir layers between the user and workspace .npmrc. Because pnpm
 *   ignores npm_config_* here, the overlay this builds is consumed by the
 *   spawned `npm pack` (and a forced `npm view`), not by `pnpm view`, which
 *   resolves natively.
 * - >= 11.10.0: a JSON auth tier (`pnpm_config__auth` env over the global
 *   config.yaml `_auth`) layers credentials above the URL-scoped env tier and
 *   registries above the yaml.
 */
const DEFAULT_REGISTRY = 'https://registry.npmjs.org/';
const BARE_AUTH_KEYS = [
    '_authToken',
    '_auth',
    'username',
    '_password',
];
// pnpm's UNSCOPED_RESCOPABLE_KEYS: the credentials above plus the client TLS
// material and the helper, all of which it pins to the declaring file's own
// registry rather than leaving unscoped.
const PNPM_RESCOPABLE_KEYS = [
    ...BARE_AUTH_KEYS,
    'tokenHelper',
    'cert',
    'key',
];
/**
 * pnpm's normalize-registry-url as it reads from 11.15.1: a trailing slash on
 * whatever the URL parser makes of the value. Before that it appended one only
 * to a URL with no path at all, which nerfDart already resolves the same way, so
 * only this form needs reproducing.
 */
function normalizePnpmRegistry(registry) {
    let normalized = registry;
    try {
        normalized = new URL(registry).toString();
    }
    catch {
        // Left as written, the way pnpm leaves it; nerfDart rejects it next.
    }
    return normalized.endsWith('/') ? normalized : `${normalized}/`;
}
/**
 * The nerf dart pnpm keys a registry's own settings on. Normalizing first moves
 * it a path segment deeper than npm's plain dart: `https://h/api/npm` keys on
 * `//h/api/npm/`, not on its parent `//h/api/`, so an unscoped credential pinned
 * there no longer reaches a sibling path. 10.x still bundles the older
 * normalizer through 10.34.5, where the plain dart is what pnpm computes.
 */
function pnpmNerfDart(registry, pnpmVersion) {
    return (0, utils_1.nerfDart)((0, semver_1.gte)(pnpmVersion, '11.15.1') ? normalizePnpmRegistry(registry) : registry);
}
/**
 * pnpm's workspaceIsTrustedAuthFile: the workspace .npmrc doubling as the file
 * pnpm authenticates from. It then expands the references it withholds from a
 * project-controlled file. pnpm reads the coincident path at both tiers and
 * merges them, which the workspace copy wins outright, so it is read once here.
 */
function isTrustedWorkspaceNpmrc(workspaceDir, userConfigPath) {
    return (0, path_1.resolve)(workspaceDir, '.npmrc') === userConfigPath;
}
function getPnpmSpawnRegistryEnv(packageName, root, pnpmVersion) {
    const env = {};
    // Which surfaces this pnpm honors depends on the version, so an undetermined
    // one bridges nothing.
    if (!pnpmVersion || (0, semver_1.lt)(pnpmVersion, '10.6.0')) {
        return env;
    }
    const workspaceFile = findPnpmWorkspaceFile(root, pnpmVersion);
    const settings = readPnpmWorkspaceSettings(workspaceFile, pnpmVersion);
    const scope = (0, utils_1.getPackageScope)(packageName);
    // Kept identical to the predicate the caller hands mergeNpmConfigEnv at spawn
    // time, which drops the bridged ambient npm_config_* this answers true for
    // (settings outside the bridged set stay ambient either way).
    const managerIgnoresEnv = (0, utils_1.ignoresNpmConfigEnv)('pnpm', pnpmVersion);
    if ((0, semver_1.lt)(pnpmVersion, '11.0.0')) {
        // The replace wipes the npmrc/env/CLI selection outright, so the scoped key
        // is forced to the yaml default when the map has no entry for the scope. A
        // scoped-only map leaves pnpm no default at all, which crashes it on an
        // unscoped target but resolves a scoped one fine, so npm's own default is
        // left in place rather than aimed at a registry pnpm uses only for that
        // scope.
        const yamlDefault = pickYamlRegistry(settings, 'default', workspaceFile);
        if (yamlDefault) {
            (0, utils_1.setRegistry)(env, yamlDefault);
        }
        const pick = scope
            ? (pickYamlRegistry(settings, scope, workspaceFile) ?? yamlDefault)
            : undefined;
        if (scope && pick) {
            (0, utils_1.setScopedRegistry)(env, scope, pick);
        }
        // Both .npmrc files pnpm reads here, project first, which is the order both
        // the bridge and the bypass list resolve them in.
        const npmrcPaths = pnpmNpmrcPaths(root, workspaceFile);
        // On this version line pnpm's user config is npm's own (no auth.ini, no
        // npmrcAuthFile), always a file npm reads for itself, so it is a tier the
        // derived settings resolve across rather than one to bridge.
        const userConfigPath = getNpmUserConfigPath(root);
        const npmrcProxies = bridgeWorkspaceNpmrc(env, npmrcPaths, userConfigPath, scope, pnpmVersion);
        // auth.ini is an 11.x file, so the .npmrc chain is the whole of what can
        // need its bypass list re-spelled here.
        bridgeNoProxy(env, [...npmrcPaths, userConfigPath], pnpmVersion);
        // Applied last: pnpm assigns the yaml over the whole npmrc-derived config,
        // so what it declares outranks everything the files above contributed.
        applyYamlNetworkSettings(env, settings);
        applyResolvedProxies(env, [settings, npmrcProxies], root, scope, managerIgnoresEnv);
        reportCredentialDivergences(env, root, scope, userConfigPath, npmrcPaths.map((path) => ({ path, filtered: false })), pnpmVersion, managerIgnoresEnv);
        return env;
    }
    // The yaml-only keys go in at npm's env tier, where npm's per-key chain
    // reproduces pnpm's ordering: a project .npmrc @scope:registry still beats an
    // injected default, while an injected @scope:registry beats the project
    // .npmrc scoped key (yaml @scope > npmrc @scope in pnpm). JSON-auth
    // registries sit above the yaml and below the named env registry, which pnpm
    // applies onto registries.default after every spread.
    const jsonAuth = readJsonAuthTier(pnpmVersion);
    const globalSettings = readPnpmGlobalSettings(pnpmVersion);
    const globalPath = getGlobalConfigPath();
    const scopedRegistry = scope
        ? (jsonAuth?.registries[scope] ??
            pickYamlRegistry(settings, scope, workspaceFile) ??
            pickYamlRegistry(globalSettings, scope, globalPath))
        : undefined;
    if (scope && scopedRegistry) {
        (0, utils_1.setScopedRegistry)(env, scope, scopedRegistry);
    }
    // A top-level `registry` in a yaml file is an explicitly set key, which pnpm
    // applies onto registries.default on its own. Where it does that moved: from
    // 11.5.3 before the workspace file is even read, so only the global file's
    // reaches it and every map still outranks it, and from 11.10.0 again after
    // every map has merged, which puts both files' above the JSON auth tier.
    const lateScalar = (0, semver_1.gte)(pnpmVersion, '11.10.0')
        ? settings.registry || globalSettings.registry
        : undefined;
    const earlyScalar = (0, semver_1.gte)(pnpmVersion, '11.5.3') && (0, semver_1.lt)(pnpmVersion, '11.10.0')
        ? globalSettings.registry
        : undefined;
    const defaultRegistry = readPnpmEnvVar('registry', pnpmVersion) ??
        lateScalar ??
        jsonAuth?.registries['default'] ??
        pickYamlRegistry(settings, 'default', workspaceFile) ??
        earlyScalar ??
        pickYamlRegistry(globalSettings, 'default', globalPath);
    if (defaultRegistry) {
        (0, utils_1.setRegistry)(env, defaultRegistry);
    }
    const authIniPath = getAuthIniPath();
    const userConfigPath = getPnpmUserConfigPath(pnpmVersion, root);
    applyUrlScopedEnvConfig(env, pnpmVersion);
    applyJsonAuthCredentials(env, scope, jsonAuth);
    // From 11 pnpm reads one project .npmrc, and it is the one beside the
    // workspace file it walked up to, not the one in the directory it runs in
    // (loadNpmrcConfig's `workspaceDir ?? localPrefix`). npm reads the latter for
    // itself, so a nested workspace puts the two readers on different files.
    const workspaceDir = workspaceFile ? (0, path_1.dirname)(workspaceFile) : root;
    const npmrcProxies = bridgeNpmrcSources(env, root, workspaceDir, scope, authIniPath, userConfigPath, pnpmVersion, managerIgnoresEnv);
    reportCredentialDivergences(env, root, scope, userConfigPath, [
        {
            path: (0, path_1.join)(workspaceDir, '.npmrc'),
            filtered: !isTrustedWorkspaceNpmrc(workspaceDir, userConfigPath),
        },
        { path: authIniPath, filtered: false },
    ], pnpmVersion, managerIgnoresEnv);
    // resolveNoProxy takes the bypass list across every layer below, so the yaml
    // does not write it here.
    applyYamlNetworkSettings(env, globalSettings, false);
    applyYamlNetworkSettings(env, settings, false);
    applyEnvNetworkSettings(env, pnpmVersion);
    applyResolvedProxies(env, [envProxyDeclarations(pnpmVersion), settings, globalSettings, npmrcProxies], root, scope, managerIgnoresEnv);
    const noProxy = resolveNoProxy(settings, globalSettings, workspaceDir, authIniPath, userConfigPath, pnpmVersion);
    if (noProxy) {
        (0, utils_1.setProxies)(env, { noProxy });
    }
    return env;
}
/**
 * pnpm's own env reader: the lowercase prefix, then the uppercase one (which
 * only arrived in 11.0.6), with an empty value counting as undeclared.
 * See readEnvVar in pnpm's config reader.
 */
function readPnpmEnvVar(key, pnpmVersion) {
    const value = process.env[`pnpm_config_${key}`] ??
        ((0, semver_1.gte)(pnpmVersion, '11.0.6')
            ? process.env[`PNPM_CONFIG_${key.toUpperCase()}`]
            : undefined);
    return value || undefined;
}
/**
 * The URL-scoped entries pnpm >= 11.6.0 reads from the environment
 * (readUrlScopedEnvConfig): `p?npm_config_//<dart>:<key>`, case-insensitive
 * prefix, minus `:tokenHelper`, which pnpm refuses to take from it. The
 * `npm_config_` spellings reach the spawned npm ambiently (mergeNpmConfigEnv
 * keeps them for these versions); the `pnpm_config_` spellings are invisible to
 * npm, so re-spell those onto the overlay, which also reproduces pnpm merging
 * its own prefix above npm's for the same dart.
 */
function applyUrlScopedEnvConfig(env, pnpmVersion) {
    if ((0, semver_1.lt)(pnpmVersion, '11.6.0')) {
        return;
    }
    for (const [key, value] of Object.entries(process.env)) {
        // pnpm skips a null or empty value outright, matching npm's env tier.
        if (!value) {
            continue;
        }
        const match = /^pnpm_config_(\/\/.+)$/i.exec(key);
        if (!match || match[1].endsWith(':tokenHelper')) {
            continue;
        }
        // pnpm takes the value as written, so escape what npm's env tier would
        // otherwise expand out of a credential that carries a `${VAR}` of its own.
        env[`npm_config_${match[1]}`] = (0, utils_1.escapeNpmEnvExpr)(value);
    }
}
/**
 * The JSON auth tier pnpm reads from 11.10.0: `pnpm_config__auth` (then the
 * uppercase spelling, an empty value skipped) parsed as JSON, over the global
 * config.yaml's top-level `_auth`, merged per entry with the env winning.
 * Its registries outrank the workspace yaml and lose to the named env/CLI
 * registry; its credentials outrank the URL-scoped env tier and every file.
 * pnpm dies on a declaration it cannot parse, so that throws into the caller's
 * fall-open. See readJsonAuthEnv/parseJsonAuth in pnpm's config reader.
 */
function readJsonAuthTier(pnpmVersion) {
    if ((0, semver_1.lt)(pnpmVersion, '11.10.0')) {
        return null;
    }
    const raw = process.env['pnpm_config__auth'] ||
        process.env['PNPM_CONFIG__AUTH'] ||
        undefined;
    let envTier = null;
    if (raw !== undefined) {
        let parsed;
        try {
            parsed = JSON.parse(raw);
        }
        catch {
            throw new Error('The pnpm_config__auth environment variable is not valid JSON.');
        }
        envTier = parsePnpmJsonAuth(parsed, 'pnpm_config__auth', pnpmVersion);
    }
    const yamlAuth = readPnpmGlobalConfigYaml()?.['_auth'];
    const yamlTier = yamlAuth != null ? parsePnpmJsonAuth(yamlAuth, '_auth', pnpmVersion) : null;
    if (!envTier && !yamlTier) {
        return null;
    }
    const merged = new Map();
    for (const tier of [yamlTier, envTier]) {
        for (const entry of tier?.auth ?? []) {
            merged.set(`${entry.scope}\0${entry.dart}`, entry);
        }
    }
    return {
        auth: [...merged.values()],
        registries: {
            ...(yamlTier?.registries ?? {}),
            ...(envTier?.registries ?? {}),
        },
    };
}
/**
 * pnpm's parseJsonAuth: registry URL over scope, each leaf exactly
 * { authToken: string }, every violation fatal. Messages carry the setting
 * name and entry position rather than the entry itself, since a malformed
 * URL key can embed credentials.
 */
function parsePnpmJsonAuth(parsed, source, pnpmVersion) {
    if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
        throw new Error(`The pnpm ${source} setting must be a JSON object of registry URLs.`);
    }
    const auth = [];
    const registries = {};
    let entryNumber = 0;
    for (const [rawUrl, scopes] of Object.entries(parsed)) {
        entryNumber++;
        let url;
        try {
            url = new URL(rawUrl);
        }
        catch {
            throw invalidJsonAuthEntry(source, entryNumber);
        }
        if ((url.protocol !== 'https:' && url.protocol !== 'http:') ||
            url.hostname === '' ||
            url.username !== '' ||
            url.password !== '' ||
            url.search !== '' ||
            url.hash !== '') {
            throw invalidJsonAuthEntry(source, entryNumber);
        }
        const dart = pnpmNerfDart(url.href, pnpmVersion);
        if (!dart) {
            throw invalidJsonAuthEntry(source, entryNumber);
        }
        if (scopes === null ||
            typeof scopes !== 'object' ||
            Array.isArray(scopes)) {
            throw invalidJsonAuthScopes(source, entryNumber);
        }
        for (const [scope, creds] of Object.entries(scopes)) {
            const validScope = scope === '@' ||
                (scope.startsWith('@') &&
                    scope.length > 1 &&
                    !scope.includes('/') &&
                    !scope.includes(':'));
            if (!validScope ||
                creds === null ||
                typeof creds !== 'object' ||
                Array.isArray(creds) ||
                Object.keys(creds).some((field) => field !== 'authToken') ||
                typeof creds['authToken'] !== 'string') {
                throw invalidJsonAuthScopes(source, entryNumber);
            }
            auth.push({
                dart,
                scope,
                token: creds['authToken'],
            });
            registries[scope === '@' ? 'default' : scope] = url.href;
        }
    }
    return { auth, registries };
}
function invalidJsonAuthEntry(source, entryNumber) {
    return new Error(`Entry ${entryNumber} of the pnpm ${source} setting is not a plain http(s) registry URL.`);
}
function invalidJsonAuthScopes(source, entryNumber) {
    return new Error(`Entry ${entryNumber} of the pnpm ${source} setting must map scopes ("@" or "@org") to { "authToken": string } objects.`);
}
/**
 * npm has no scope-qualified auth key, so a scoped entry lands on the plain
 * dart, and only for the scope of the package being fetched: pnpm would not
 * send that token for anything else. Registry-wide entries go first so a
 * scoped token for the same registry wins, the way pnpm's per-scope credential
 * lookup prefers the specific entry. Every registry in the map is bridged, on
 * the same grounds as the auth.ini dart loop.
 */
function applyJsonAuthCredentials(env, scope, jsonAuth) {
    if (!jsonAuth) {
        return;
    }
    for (const wanted of scope ? ['@', scope] : ['@']) {
        for (const entry of jsonAuth.auth) {
            if (entry.scope === wanted) {
                // The token sits in a nested object, which pnpm's yaml replacer passes
                // through untouched, so it is escaped rather than expanded here: npm
                // would otherwise resolve a `${VAR}` pnpm sends as written.
                env[`npm_config_${entry.dart}:_authToken`] = (0, utils_1.escapeNpmEnvExpr)(entry.token);
            }
        }
    }
}
/**
 * The TLS settings pnpm >= 11 takes from its own `PNPM_CONFIG_*` prefix. They
 * outrank pnpm-workspace.yaml, so they are applied after it. `cafile` is left
 * out on purpose: pnpm accepts it and then never uses it for the fetch, the
 * same dead config as the yaml key.
 */
function applyEnvNetworkSettings(env, pnpmVersion) {
    const strictSsl = readPnpmEnvVar('strict_ssl', pnpmVersion);
    if (strictSsl !== undefined) {
        // parseField types this Boolean, so only an explicit 'false' turns
        // verification off.
        (0, utils_1.setStrictSsl)(env, strictSsl !== 'false');
    }
}
/** The proxy settings pnpm >= 11 takes from its own `PNPM_CONFIG_*` prefix. */
function envProxyDeclarations(pnpmVersion) {
    return {
        proxy: readPnpmEnvVar('proxy', pnpmVersion),
        httpProxy: readPnpmEnvVar('http_proxy', pnpmVersion),
        httpsProxy: readPnpmEnvVar('https_proxy', pnpmVersion),
    };
}
/**
 * The two proxies npm should end up with for `registry`. pnpm resolves each of
 * the three settings across every tier first, and only then falls back from
 * httpsProxy to the legacy proxy and from httpProxy to whichever of those won,
 * so the derivation cannot be done per tier: a workspace file's httpsProxy is
 * what an environment-supplied `proxy` leaves undeclared, and so still wins.
 *
 * npm has no http-only proxy, its `proxy` serving https too when `https-proxy`
 * is unset, so an http-only one is withheld unless http is what npm requests.
 * A value npm already resolves for itself under the same key is left to it.
 */
function resolveProxies(tiers, registry, npmSees) {
    const declared = (key) => tiers.map((tier) => tier[key]).find(Boolean);
    const httpsProxy = declared('httpsProxy') || declared('proxy');
    const httpProxy = declared('httpProxy') || httpsProxy;
    const send = (value, npmKey) => value === npmSees(npmKey) ? undefined : value;
    return {
        httpProxy: send(httpsProxy || registry.startsWith('http://') ? httpProxy : undefined, 'proxy'),
        httpsProxy: send(httpsProxy, 'https-proxy'),
    };
}
/**
 * The proxy-bypass list pnpm >= 11 ends up using. It reads the `no-proxy`
 * spelling and only falls back to `noproxy`, so the spelling decides before the
 * layer does: a workspace .npmrc `no-proxy` beats a pnpm-workspace.yaml
 * `noproxy`. Within one spelling the env sits above the yaml files, the
 * workspace one above the global one, and those above the npmrc-family files,
 * which only the `no-proxy` spelling reaches.
 * See createPackageManagerNetworkConfig in pnpm's config reader.
 */
function resolveNoProxy(settings, globalSettings, npmrcDir, authIniPath, userConfigPath, pnpmVersion) {
    const envNoProxy = readPnpmEnvVar('no_proxy', pnpmVersion);
    if (envNoProxy) {
        return envNoProxy;
    }
    const yamlNoProxy = settings.noProxy ?? globalSettings.noProxy;
    if (yamlNoProxy) {
        return yamlNoProxy;
    }
    const fromFiles = fileNoProxy([(0, path_1.join)(npmrcDir, '.npmrc'), authIniPath, userConfigPath], pnpmVersion);
    if (fromFiles) {
        return fromFiles;
    }
    return (readPnpmEnvVar('noproxy', pnpmVersion) ??
        settings.noproxy ??
        globalSettings.noproxy);
}
/**
 * pnpm looks pnpm-workspace.yaml up before a reader that only tolerates ENOENT,
 * so whatever this misses reads as absent while a file it finds and cannot open
 * aborts the command. 11.8.0 swapped find-up, which requires the match to be a
 * file, for a bare existence check, which is where a directory in the file's
 * place stops being looked past.
 */
function pnpmFindsWorkspaceFile(path, pnpmVersion) {
    return (0, semver_1.lt)(pnpmVersion, '11.8.0') ? (0, fileutils_1.fileExists)(path) : (0, fs_1.existsSync)(path);
}
const WORKSPACE_MANIFEST_FILENAME = 'pnpm-workspace.yaml';
/**
 * pnpm's INVALID_WORKSPACE_MANIFEST_FILENAME: names near enough the real one
 * that it looks for them alongside it and refuses to walk past one it finds.
 */
const MISSPELLED_WORKSPACE_MANIFEST_NAMES = [
    'pnpm-workspaces.yaml',
    'pnpm-workspaces.yml',
    'pnpm-workspace.yml',
];
/** 11.0.0 added the dot-prefixed spellings to that list. */
const MISSPELLED_WORKSPACE_MANIFEST_NAMES_11 = [
    ...MISSPELLED_WORKSPACE_MANIFEST_NAMES,
    '.pnpm-workspace.yaml',
    '.pnpm-workspace.yml',
    '.pnpm-workspaces.yaml',
    '.pnpm-workspaces.yml',
];
/**
 * pnpm resolves pnpm-workspace.yaml by walking up from the directory it runs
 * in and stopping at the nearest hit, so a workspace nested under another one
 * inherits the outer file's settings. Null when no directory on the way up has
 * one.
 * See findWorkspaceDir in pnpm's workspace root finder.
 */
function findPnpmWorkspaceFile(root, pnpmVersion) {
    // The env var names the directory outright, skipping the walk without
    // checking that the file is there, so a missing one reads as a workspace
    // declaring nothing rather than sending the lookup back up the tree.
    const fromEnv = (0, utils_1.readEnvVar)(process.env, 'NPM_CONFIG_WORKSPACE_DIR');
    if (fromEnv) {
        return (0, path_1.join)((0, path_1.resolve)(root, fromEnv), WORKSPACE_MANIFEST_FILENAME);
    }
    const misspelled = (0, semver_1.lt)(pnpmVersion, '11.0.0')
        ? MISSPELLED_WORKSPACE_MANIFEST_NAMES
        : MISSPELLED_WORKSPACE_MANIFEST_NAMES_11;
    for (const dir of [root, ...(0, utils_1.ancestorDirectories)(root)]) {
        const path = (0, path_1.join)(dir, WORKSPACE_MANIFEST_FILENAME);
        // Looked up first, because pnpm searches the names in this order within a
        // directory and takes the first hit: a correctly named file beside a
        // misspelled one is the one it reads.
        if (pnpmFindsWorkspaceFile(path, pnpmVersion)) {
            return path;
        }
        for (const name of misspelled) {
            const misspelledPath = (0, path_1.join)(dir, name);
            if (pnpmFindsWorkspaceFile(misspelledPath, pnpmVersion)) {
                // pnpm aborts the command here (BAD_WORKSPACE_MANIFEST_NAME) instead of
                // walking on, so there is no resolution left to reproduce. Propagating
                // to the caller's fall-open warns instead of silently resolving against
                // a correctly named file further up that pnpm never reaches.
                throw new Error(`The pnpm workspace manifest file should be named "${WORKSPACE_MANIFEST_FILENAME}". File found: ${misspelledPath}`);
            }
        }
    }
    return null;
}
function readPnpmWorkspaceSettings(path, pnpmVersion) {
    if (path === null) {
        return {};
    }
    const doc = (0, pnpm_config_1.readPnpmYamlConfig)(path);
    if (doc === null) {
        return {};
    }
    if (doc === 'unusable') {
        // pnpm aborts on this file, so there is no resolution left to reproduce.
        // Propagating to the caller's fall-open warns instead of silently treating
        // the workspace as declaring no registry.
        throw new Error(`The pnpm workspace file at ${path} could not be read.`);
    }
    return normalizePnpmWorkspaceSettings(resolveYamlEnv(doc, path, pnpmVersion, false), path);
}
const PNPM_REQUEST_PROXY_SCALARS = [
    'httpProxy',
    'httpsProxy',
    'noProxy',
    'proxy',
    'noproxy',
];
const PNPM_REQUEST_DESTINATION_SCALARS = new Set(['pnprServer', 'registry']);
const PNPM_REQUEST_DESTINATION_SCALARS_11_11 = new Set([
    ...PNPM_REQUEST_DESTINATION_SCALARS,
    ...PNPM_REQUEST_PROXY_SCALARS,
]);
const PNPM_REQUEST_DESTINATION_SCALARS_10 = new Set(['registry']);
const PNPM_REQUEST_DESTINATION_SCALARS_10_34_5 = new Set([
    ...PNPM_REQUEST_DESTINATION_SCALARS_10,
    ...PNPM_REQUEST_PROXY_SCALARS,
]);
/**
 * The scalar settings pnpm withholds from a file rather than expanding a
 * `${VAR}` into them, its REQUEST_DESTINATION_SCALAR_KEYS, null on a line that
 * withholds nothing. It covered the destination alone until 11.11.0 added the
 * proxies that carry a request there, and it was backported onto the 10 line,
 * where it never names the pnpr server and picked the proxies up in 10.34.5.
 *
 * Each line is decided before the next is consulted: a 10.x version must not
 * reach the 11 gates, which a plain `gte` ladder would let it do.
 */
function requestDestinationScalars(pnpmVersion) {
    if ((0, semver_1.lt)(pnpmVersion, '11.0.0')) {
        if ((0, semver_1.lt)(pnpmVersion, '10.34.2')) {
            return null;
        }
        return (0, semver_1.lt)(pnpmVersion, '10.34.5')
            ? PNPM_REQUEST_DESTINATION_SCALARS_10
            : PNPM_REQUEST_DESTINATION_SCALARS_10_34_5;
    }
    if ((0, semver_1.lt)(pnpmVersion, '11.5.3')) {
        return null;
    }
    return (0, semver_1.lt)(pnpmVersion, '11.11.0')
        ? PNPM_REQUEST_DESTINATION_SCALARS
        : PNPM_REQUEST_DESTINATION_SCALARS_11_11;
}
/**
 * A yaml settings file as pnpm's replaceEnvInSettings leaves it. Which
 * `${VAR}` it touches moved twice, and what it does with one it cannot resolve
 * moved once:
 *
 * - Keys, on every line from 10.7.0, and a key it resolves nothing for aborts
 *   the command. 10.6.0 has no replacer at all and takes the file verbatim.
 * - Top-level string values, on the same line, and likewise fatal.
 * - `registries` and `namedRegistries` values, from 11.1.0.
 * - A scalar naming a request destination is dropped instead of expanded, from
 *   10.34.2 on the 10 line and 11.5.3 on the 11 one
 *   (requestDestinationScalars). A `registries` entry holding one is dropped
 *   too, but from 11.5.3 alone: 10.x has no branch for that key and passes the
 *   map through whole. Both apply to a file a project controls; the global
 *   config.yaml is trusted and keeps expanding, and only 11 reads it anyway.
 *
 * A nested object elsewhere is passed through untouched on every line, so a
 * placeholder there is neither expanded nor fatal.
 *
 * Values come back in the form npm's own expansion turns back into what pnpm
 * resolved: a line that expands leaves an escaped reference for npm to consume
 * (expandPnpmEnvVars), and a line that does not escapes what it passes through.
 */
function resolveYamlEnv(doc, path, pnpmVersion, trusted) {
    const assertResolves = (value) => {
        const unresolved = (0, utils_1.unresolvedPnpmEnvVars)(value);
        if (unresolved.length > 0) {
            // pnpm aborts the command here, so there is no resolution left to
            // reproduce. Propagating to the caller's fall-open warns instead. Only the
            // references are named: the rest of the value can be a credential.
            throw new Error(`The pnpm configuration file at ${path} references an environment variable that is not set: ${unresolved.join(', ')}`);
        }
    };
    /** For a key, which is read rather than handed to npm. */
    const expandKey = (value) => {
        assertResolves(value);
        return (0, utils_1.expandPnpmEnvVars)(value);
    };
    /** For a value, which the spawned npm expands again. */
    const expandValue = (value) => {
        assertResolves(value);
        return (0, utils_1.bridgePnpmEnvVars)(value);
    };
    const expands = (0, semver_1.gte)(pnpmVersion, '10.7.0');
    const resolveScalar = expands ? expandValue : utils_1.escapeNpmEnvExpr;
    const resolveRegistry = (0, semver_1.gte)(pnpmVersion, '11.1.0')
        ? resolveScalar
        : utils_1.escapeNpmEnvExpr;
    const droppedScalars = trusted
        ? null
        : requestDestinationScalars(pnpmVersion);
    const dropsRegistries = !trusted && (0, semver_1.gte)(pnpmVersion, '11.5.3');
    const resolved = {};
    for (const [rawKey, value] of Object.entries(doc)) {
        const key = expands ? expandKey(rawKey) : rawKey;
        if (typeof value === 'string') {
            if (droppedScalars?.has(key) && PNPM_ENV_PLACEHOLDER.test(value)) {
                continue;
            }
            resolved[key] = resolveScalar(value);
        }
        else if (key === 'registries' || key === 'namedRegistries') {
            resolved[key] = mapYamlStrings(value, (entry) => dropsRegistries && PNPM_ENV_PLACEHOLDER.test(entry)
                ? undefined
                : resolveRegistry(entry));
        }
        else {
            resolved[key] = value;
        }
    }
    return resolved;
}
/**
 * `map` over the string values of a plain object, an entry it returns nothing
 * for dropped. Anything else is passed through, which is how pnpm's own two
 * mappers treat a shape they were not given.
 */
function mapYamlStrings(value, map) {
    if (value === null || typeof value !== 'object' || Array.isArray(value)) {
        return value;
    }
    const mapped = {};
    for (const [key, entry] of Object.entries(value)) {
        if (typeof entry !== 'string') {
            mapped[key] = entry;
            continue;
        }
        const result = map(entry);
        if (result !== undefined) {
            mapped[key] = result;
        }
    }
    return mapped;
}
/**
 * pnpm type-checks none of these, so its tolerance is uneven and each shape
 * here mirrors a measured 11.10.0 outcome. A truthy non-string proxy breaks
 * pnpm's own fetch, so it is fatal into the caller's fall-open; a falsy one is
 * never read as a proxy at all and drops here the way it drops there. A
 * wrong-shaped noProxy also survives pnpm, so it is dropped
 * rather than handed to the string-typed spawn env. `registries` and
 * `strictSsl` stay unnarrowed for the consumer that reads them, because pnpm
 * only reacts to the registry value it picks, and turns TLS verification off
 * for the boolean alone.
 */
function normalizePnpmWorkspaceSettings(doc, path) {
    const fail = (what) => {
        throw new Error(`The pnpm configuration file at ${path} declares ${what}.`);
    };
    if (doc.proxy && typeof doc.proxy !== 'string') {
        fail('a proxy that is not a string');
    }
    if (doc.httpProxy && typeof doc.httpProxy !== 'string') {
        fail('an httpProxy that is not a string');
    }
    if (doc.httpsProxy && typeof doc.httpsProxy !== 'string') {
        fail('an httpsProxy that is not a string');
    }
    const text = (key) => typeof doc[key] === 'string' ? doc[key] : undefined;
    return {
        registries: doc.registries,
        strictSsl: doc.strictSsl,
        registry: text('registry'),
        proxy: text('proxy'),
        httpProxy: text('httpProxy'),
        httpsProxy: text('httpsProxy'),
        noProxy: text('noProxy'),
        noproxy: text('noproxy'),
        ca: text('ca'),
        cert: text('cert'),
        key: text('key'),
    };
}
/**
 * The yaml registry for `key`, fatal when it exists with a non-string shape:
 * pnpm dies in `new URL` on the registry it picks (measured on 11.10.0), and an
 * entry it never picks harms nothing, so the check runs per pick rather than
 * over the whole map. A registries that is not a map declares no entry at all,
 * which is how pnpm resolves one.
 */
function pickYamlRegistry(settings, key, path) {
    const { registries } = settings;
    if (registries === null ||
        typeof registries !== 'object' ||
        Array.isArray(registries)) {
        return undefined;
    }
    const value = registries[key];
    if (value === undefined) {
        return undefined;
    }
    if (typeof value !== 'string') {
        throw new Error(`The pnpm configuration file at ${path} declares a registries["${key}"] that is not a string.`);
    }
    return value;
}
function getAuthIniPath() {
    return (0, path_1.join)((0, pnpm_config_1.getPnpmConfigDir)(process.env), 'auth.ini');
}
function getGlobalConfigPath() {
    return (0, path_1.join)((0, pnpm_config_1.getPnpmConfigDir)(process.env), 'config.yaml');
}
/**
 * The global config.yaml, null when absent. pnpm reads this one straight,
 * without the existence check it puts in front of pnpm-workspace.yaml, so every
 * command aborts on a file it cannot open or parse. That propagates to the
 * caller's fall-open instead of resolving on without the file's settings.
 */
function readPnpmGlobalConfigYaml() {
    const path = getGlobalConfigPath();
    const doc = (0, pnpm_config_1.readPnpmYamlConfig)(path);
    if (doc === 'unusable') {
        throw new Error(`The pnpm global configuration file at ${path} could not be read.`);
    }
    return doc;
}
/**
 * The settings pnpm >= 11 takes from that file. It applies them the way it
 * applies a workspace manifest, over the npmrc-derived config and under the
 * workspace file's own, but only for the keys it allows there: `registries` is
 * refused with a warning until 11.11.0, while every other key read here is an
 * npm setting name it has always allowed. The file is the user's own rather
 * than a project's, so a `${VAR}` naming a request destination is expanded
 * instead of withheld.
 */
function readPnpmGlobalSettings(pnpmVersion) {
    if ((0, semver_1.lt)(pnpmVersion, '11.0.0')) {
        return {};
    }
    const doc = readPnpmGlobalConfigYaml();
    if (doc === null) {
        return {};
    }
    const path = getGlobalConfigPath();
    const settings = normalizePnpmWorkspaceSettings(resolveYamlEnv(doc, path, pnpmVersion, true), path);
    if ((0, semver_1.lt)(pnpmVersion, '11.11.0')) {
        delete settings.registries;
    }
    return settings;
}
// pnpm keeps resolving from the remaining layers for an npmrc-family file it
// cannot read, so mirror the absent semantics. It stays silent on ENOENT and
// EISDIR and warns otherwise; we warn for the whole unreadable class, minus the
// ENOTDIR the reader already reports as absent, where pnpm warns and resolves
// on all the same.
const warnedUnreadableFiles = new Set();
function warnUnreadableFile(path) {
    if (warnedUnreadableFiles.has(path)) {
        return;
    }
    warnedUnreadableFiles.add(path);
    logger_1.logger.warn(`Could not read ${path}; resolving the pnpm registry configuration without it, the way pnpm itself does.`);
}
function readNpmrcEntriesOrWarn(path) {
    const entries = (0, npmrc_1.readNpmrcEntries)(path);
    if (entries !== 'unreadable') {
        return entries;
    }
    warnUnreadableFile(path);
    return null;
}
/** An npmrc-family file as written, null when it could not be read. */
function readNpmrcOrWarn(path) {
    const entries = readNpmrcEntriesOrWarn(path);
    return entries && (0, npmrc_1.npmrcEntriesToMap)(entries);
}
/**
 * The same file as pnpm ends up with it, which below 11 can be not at all: its
 * reader expands `${VAR}` in both halves of an entry through a function that
 * throws on a reference it resolves nothing for, and the config chain catches
 * that per file, so one bad reference costs every entry in the file and pnpm
 * carries on from the layers below. From 11 the lossy reader substitutes an
 * empty string per entry instead and the file survives.
 * See parseKey/parseField and Conf.addFile in pnpm's bundled npm-conf.
 */
function readPnpmNpmrcMap(path, pnpmVersion) {
    const entries = readNpmrcEntriesOrWarn(path);
    if (entries === null || (0, semver_1.gte)(pnpmVersion, '11.0.0')) {
        return entries && (0, npmrc_1.npmrcEntriesToMap)(entries);
    }
    // parseField hands a `key[]` array straight back, so the values under a key
    // ini collected into one are never expanded and never throw. The key is, and
    // one repeated line is enough to make every value under it an array.
    const arrayKeys = new Set(entries.filter((entry) => entry.array).map((entry) => entry.key));
    for (const { key, value } of entries) {
        if (!(0, utils_1.pnpmEnvVarsResolve)(key) ||
            (!arrayKeys.has(key) && !(0, utils_1.pnpmEnvVarsResolve)(value))) {
            return null;
        }
    }
    return (0, npmrc_1.npmrcEntriesToMap)(entries);
}
// pnpm's AUTH_VALUE_KEYS, which holds the same seven settings as its
// UNSCOPED_RESCOPABLE_KEYS. Aliased rather than spelled out again so the two
// cannot drift apart here while pnpm keeps them equal.
const PNPM_AUTH_VALUE_KEYS = PNPM_RESCOPABLE_KEYS;
// pnpm's hasEnvPlaceholder, which unlike its expander honors no escape.
const PNPM_ENV_PLACEHOLDER = /\$\{[^}]+\}/;
const isRegistryKey = (key) => key === 'registry' || (key.startsWith('@') && key.endsWith(':registry'));
const isAuthValueKey = (key) => PNPM_AUTH_VALUE_KEYS.some((k) => key === k || key.endsWith(`:${k}`));
/** pnpm's isRequestDestinationKey: the set its key-side test covers. */
const isRequestDestinationKey = (key) => isRegistryKey(key) || key.startsWith('//');
/** pnpm's isRequestDestinationValueKey: the set its value-side test covers. */
const isRequestDestinationValueKey = (key) => isRegistryKey(key) ||
    key === 'proxy' ||
    key === 'http-proxy' ||
    key === 'https-proxy';
/** Whether 11.5.3+ drops an entry rather than expanding its `${VAR}`. */
function pnpmDropsProjectEntry(rawKey, key, rawValue) {
    if (PNPM_ENV_PLACEHOLDER.test(rawKey) &&
        // Tested on both sides of the expansion, because a placeholder can carry a
        // key that is plain in neither set into one of them and vice versa.
        (isRequestDestinationKey(rawKey) ||
            isAuthValueKey(rawKey) ||
            isRequestDestinationKey(key) ||
            isAuthValueKey(key))) {
        return true;
    }
    return (PNPM_ENV_PLACEHOLDER.test(rawValue) &&
        (isRequestDestinationValueKey(key) || isAuthValueKey(key)));
}
/**
 * One npmrc-family file as pnpm reads it: both halves expanded, rebuilt in file
 * order so a later key that resolves to the same setting wins, the way pnpm's
 * own assignment does. npm's env-tier expansion uses a different grammar, which
 * is why the values are expanded here rather than left to the spawn.
 *
 * `filtered` applies the rule pnpm puts on the workspace file alone: until
 * 11.5.3 a `${VAR}` expanded there like anywhere else, so a placeholder-keyed
 * entry lands under the spelling it expands to; from 11.5.3 an entry naming a
 * host or carrying a credential is dropped instead of expanded when either half
 * holds one. See readAndFilterNpmrc in pnpm's config reader.
 */
function readPnpmNpmrcEntries(raw, pnpmVersion, filtered) {
    const drops = filtered && (0, semver_1.gte)(pnpmVersion, '11.5.3');
    const map = new Map();
    for (const [rawKey, rawValue] of raw) {
        const key = PNPM_ENV_PLACEHOLDER.test(rawKey)
            ? (0, utils_1.expandPnpmEnvVars)(rawKey)
            : rawKey;
        if (drops && pnpmDropsProjectEntry(rawKey, key, rawValue)) {
            continue;
        }
        map.set(key, rawValue);
    }
    // parseField decides a Boolean-typed setting from the literal value, before it
    // expands any `${VAR}`, so strict-ssl has to be read pre-expansion.
    const rawStrictSsl = map.get('strict-ssl');
    // What pnpm itself resolves this file's own registry to. The escaped form
    // below is text for npm to expand, not a URL: `\` is a path separator to the
    // URL parser, so darting it would key the credential a segment off.
    const fileRegistry = (0, utils_1.expandPnpmEnvVars)(map.get('registry') ?? '');
    for (const [key, value] of map) {
        // cafile is joined onto a directory before it is handed over, and on Windows
        // the backslashes an escape adds are separators that path normalization
        // collapses, leaving npm to consume one of them as the escape. It is escaped
        // once resolved instead.
        map.set(key, key === 'cafile' ? (0, utils_1.expandPnpmEnvVars)(value) : (0, utils_1.bridgePnpmEnvVars)(value));
    }
    // pnpm's getDefaultCreds applies a bare global _authToken/_auth/username/
    // _password (no nerf-dart prefix); npm honors auth only in the nerf-darted
    // form, so re-key each onto the registry this file itself carries, or npmjs
    // when it carries none. pnpm does this per file and before the merge
    // (rescopeUnscopedCreds), which is why two files can each contribute a bare
    // credential under a different dart, and why a dart the same file spells out
    // keeps its own value. From 11.4.0 that is pnpm's own pin; earlier 11.x paired
    // the credential with whichever registry won overall, which let a
    // workspace-local .npmrc or pnpm-workspace.yaml aim a user-level credential at
    // a host of its choosing (CVE-2026-50017), so the pin is applied there too
    // rather than reproducing the hole.
    const dart = pnpmNerfDart(fileRegistry || DEFAULT_REGISTRY, pnpmVersion);
    const rescoped = [];
    for (const bareKey of PNPM_RESCOPABLE_KEYS) {
        const value = map.get(bareKey);
        if (value === undefined) {
            continue;
        }
        map.delete(bareKey);
        // An unparseable registry leaves pnpm nowhere safe to pin them, so it drops
        // them outright.
        if (!dart) {
            continue;
        }
        // Re-keyed on presence, an empty value included, because that is how pnpm
        // does it: an emptied credential goes on to shadow the same key in every
        // file below, which is how a workspace clears one it inherits.
        if (!map.has(`${dart}:${bareKey}`)) {
            map.set(`${dart}:${bareKey}`, value);
        }
        // Nothing is withheld from npm when the credential was empty to begin with.
        if (value && BARE_AUTH_KEYS.includes(bareKey)) {
            rescoped.push(bareKey);
        }
    }
    return { map, rawStrictSsl, rescoped, dart };
}
/**
 * The .npmrc files pnpm reads below its own environment, highest first: the one
 * beside the package.json the command runs from, then the one beside the
 * workspace manifest it walked up to. They are the same file for a workspace
 * that is its own root, and the second is the tier npm has none of.
 */
function pnpmNpmrcPaths(root, workspaceFile) {
    const project = (0, path_1.join)(root, '.npmrc');
    const workspaceDir = workspaceFile ? (0, path_1.dirname)(workspaceFile) : root;
    return workspaceDir === root
        ? [project]
        : [project, (0, path_1.join)(workspaceDir, '.npmrc')];
}
/**
 * An npmrc-family file with its keys expanded, rebuilt in file order so a later
 * key that expands onto the same setting wins the way pnpm's own assignment
 * does. Values stay as written, because parseField types a Boolean setting from
 * the literal before any `${VAR}` in it is expanded.
 */
function expandPnpmNpmrcKeys(raw) {
    const map = new Map();
    for (const [rawKey, rawValue] of raw) {
        map.set((0, utils_1.expandPnpmEnvVars)(rawKey), rawValue);
    }
    return map;
}
/**
 * A registry the yaml or the environment already forced in outranks these files
 * in pnpm, so it keeps winning here.
 */
function applyPnpmFileRegistry(env, scope, bridged) {
    const registry = bridged('registry');
    if (!env['npm_config_registry'] && registry) {
        (0, utils_1.setRegistry)(env, registry);
    }
    const scopedRegistry = scope ? bridged(`${scope}:registry`) : undefined;
    if (scope && !env[`npm_config_${scope}:registry`] && scopedRegistry) {
        (0, utils_1.setScopedRegistry)(env, scope, scopedRegistry);
    }
}
/**
 * `raw` is the value as written, before any `${VAR}` expansion: strict-ssl is
 * typed Boolean-only, so parseField turns just 'true'/'false' (plus '' -> true
 * and the null/undefined literals) into non-strings and leaves everything else
 * a truthy string. '0', 'no' and 'off' all keep TLS verification on in pnpm;
 * only an explicit 'false' turns it off.
 */
function applyPnpmStrictSsl(env, raw) {
    if (raw !== undefined) {
        (0, utils_1.setStrictSsl)(env, raw !== 'false');
    }
}
/**
 * The workspace .npmrc pnpm below 11 layers under the project one. npm has no
 * tier for it at all, so a setting the project file leaves undeclared has to
 * reach npm through the environment; one the project file declares npm resolves
 * for itself, and injecting the workspace value would put it above that file
 * rather than below it. The ambient npm_config_* both readers honor on this line
 * outranks either file, so a setting declared there is left alone as well.
 *
 * Nothing is bridged out of `userConfigPath`, which npm reads for itself, but
 * the proxies pnpm derives from one another are resolved across it: a tier
 * missing from the lookup makes an `https-proxy` it declares read as
 * undeclared, and the caller then derives one from a legacy `proxy` a file
 * above it set.
 *
 * A bare credential is deliberately not bridged. pnpm has no per-file rescoping
 * here and pins one to nerfDart(allSettings.registry), the registry the npmrc
 * chain resolves rather than the one the pnpm-workspace.yaml sends the fetch to,
 * so npm's nerf-darted form cannot be derived from what this can see.
 */
function bridgeWorkspaceNpmrc(env, npmrcPaths, userConfigPath, scope, pnpmVersion) {
    const [projectPath, workspacePath] = npmrcPaths;
    // pnpm's view of the shadowing tier: a file its reader discarded shadows
    // nothing, even though npm goes on reading that same file for itself.
    const tiers = [...npmrcPaths, userConfigPath].map((path) => {
        const raw = readPnpmNpmrcMap(path, pnpmVersion);
        return raw && expandPnpmNpmrcKeys(raw);
    });
    const projectNpmrc = tiers[0];
    const workspaceNpmrc = workspacePath ? tiers[1] : null;
    /** What pnpm resolves from these files and the env tier over them. */
    const resolved = (key) => (0, utils_1.bridgePnpmEnvVars)((0, utils_1.readNpmConfigEnv)(process.env, key) ??
        tiers.find((tier) => tier?.has(key))?.get(key) ??
        '') || undefined;
    const proxies = {
        proxy: resolved('proxy'),
        httpProxy: resolved('http-proxy'),
        httpsProxy: resolved('https-proxy'),
    };
    if (!workspaceNpmrc) {
        return proxies;
    }
    /** The value as written, unless a tier above the workspace file declares one. */
    const declared = (key) => projectNpmrc?.has(key) || (0, utils_1.readNpmConfigEnv)(process.env, key) !== undefined
        ? undefined
        : workspaceNpmrc.get(key);
    // An empty value declares nothing to derive from: pnpm's own readers re-check
    // for an empty registry, and npm skips an empty env value outright. Deriving
    // from one is what does damage (an empty cafile resolves to its own directory).
    const bridged = (key) => (0, utils_1.bridgePnpmEnvVars)(declared(key) ?? '') || undefined;
    applyPnpmFileRegistry(env, scope, bridged);
    // Every dart is copied, not just the contacted registry's: npm resolves auth
    // per fetched URI and sends only the matching key, so a tarball served from a
    // second authenticated host keeps working. Filtering here would strip it.
    for (const key of workspaceNpmrc.keys()) {
        // npm has no tokenHelper setting, and pnpm takes one from its user config
        // alone, so a scoped helper here stands for no credential the fetch had.
        // `:cert`/`:key` carry inline PEM, which neither tool reads in scoped form
        // (pnpm's getNetworkConfigs pairs a registry with `:certfile`/`:keyfile`
        // paths, the same keys npm resolves per URI, and those do go through).
        if (!key.startsWith('//') ||
            key.endsWith(':tokenHelper') ||
            key.endsWith(':cert') ||
            key.endsWith(':key')) {
            continue;
        }
        const value = bridged(key);
        if (value) {
            env[`npm_config_${key}`] = value;
        }
    }
    // Resolved before the npm-facing escape: on Windows the backslashes it adds
    // are separators, which normalization collapses and npm then reads one of them
    // as the escape.
    const cafile = (0, utils_1.expandPnpmEnvVars)(declared('cafile') ?? '') || undefined;
    if (cafile) {
        // pnpm's only reader on this line is loadCAFile, a bare readFileSync on the
        // raw value, so a relative one resolves against the cwd the command runs in,
        // which is the root the spawn uses. It expands no leading `~`, and npm
        // ignores a cafile it cannot open, so getting the base wrong drops the trust
        // anchor with no diagnostic at all. (11.2.0 moved that base to the directory
        // of the declaring file.)
        (0, utils_1.setCafile)(env, (0, utils_1.escapeNpmEnvExpr)((0, path_1.resolve)((0, path_1.dirname)(projectPath), cafile)));
    }
    // Flat keys on this line: pnpm pins neither trust anchors nor client TLS
    // material to a registry before 11, and npm reads all three the same way.
    for (const key of ['ca', 'cert', 'key']) {
        const value = bridged(key);
        if (value) {
            env[`npm_config_${key}`] = value;
        }
    }
    applyPnpmStrictSsl(env, declared('strict-ssl'));
    (0, utils_1.setProxies)(env, {
        // The spelling npm reads natively, which it can still only read from its own
        // project config. pnpm prefers `no-proxy` across every layer over `noproxy`
        // across every layer, so bridgeNoProxy runs after this and overwrites it.
        noProxy: bridged('noproxy'),
    });
    return proxies;
}
function bridgeNpmrcSources(env, root, workspaceDir, scope, authIniPath, userConfigPath, pnpmVersion, managerIgnoresEnv) {
    // The file npm resolves as its project config, beside the package.json the
    // spawn runs from.
    const projectRaw = readNpmrcOrWarn((0, path_1.join)(root, '.npmrc'));
    // pnpm reads exactly one workspace .npmrc, beside the workspace manifest it
    // walked up to, and merges it over auth.ini. That file is npm's own only when
    // the two directories coincide; above the spawn's, it is a source only pnpm
    // reads, so its entries have to be bridged rather than left to npm.
    const workspaceRaw = workspaceDir === root
        ? projectRaw
        : readNpmrcOrWarn((0, path_1.join)(workspaceDir, '.npmrc'));
    const authIniRaw = readNpmrcOrWarn(authIniPath);
    const trustedWorkspace = isTrustedWorkspaceNpmrc(workspaceDir, userConfigPath);
    // The same file at the workspace tier already, where pnpm trusts and reads it.
    const userRaw = trustedWorkspace ? null : readNpmrcOrWarn(userConfigPath);
    // Highest pnpm precedence first, matching the order it assigns them in
    // (workspace .npmrc over auth.ini over the file it authenticates from).
    const sources = [];
    if (workspaceRaw) {
        sources.push({
            dir: workspaceDir,
            npmNative: workspaceDir === root,
            ...readPnpmNpmrcEntries(workspaceRaw, pnpmVersion, !trustedWorkspace),
        });
    }
    if (authIniRaw) {
        sources.push({
            dir: (0, path_1.dirname)(authIniPath),
            npmNative: false,
            ...readPnpmNpmrcEntries(authIniRaw, pnpmVersion, false),
        });
    }
    if (userRaw) {
        sources.push({
            dir: (0, path_1.dirname)(userConfigPath),
            // pnpm authenticates from the file `npmrcAuthFile`/`userconfig` selects.
            // npm opens that same file only where its own `userconfig` lands on it;
            // anywhere else it is a source npm never reads, so its entries need
            // bridging like auth.ini's.
            npmNative: userConfigPath === getNpmUserConfigPath(root),
            ...readPnpmNpmrcEntries(userRaw, pnpmVersion, false),
        });
    }
    if (sources.length === 0) {
        return {};
    }
    const projectNpmrc = projectRaw ?? new Map();
    /** The highest source declaring `key`; an empty value still shadows the rest. */
    const declaringSource = (key) => sources.find((source) => source.map.has(key));
    /**
     * What pnpm resolves from these files, npm-native or not: a value it derives
     * another setting from is one npm does not derive for itself.
     */
    const declaredValue = (key) => declaringSource(key)?.map.get(key) || undefined;
    /** That source, unless npm reads it for itself and needs no bridging. */
    const bridging = (key) => {
        const source = declaringSource(key);
        return source?.npmNative === false ? source : undefined;
    };
    // An empty value declares nothing to derive from: pnpm's own readers re-check
    // for an empty registry, and npm skips an empty env value outright. Deriving
    // from one is what does damage (an empty cafile resolves to its own directory).
    const bridgedValue = (key) => bridging(key)?.map.get(key) || undefined;
    applyPnpmFileRegistry(env, scope, bridgedValue);
    // Every dart is copied, not just the contacted registry's: npm resolves auth
    // per fetched URI and sends only the matching key, so a tarball served from a
    // second authenticated host keeps working. Filtering here would strip it.
    for (const source of sources) {
        for (const [key, value] of source.map) {
            // A key a higher source declares is that source's to decide, whether it
            // bridges the value or leaves it to npm.
            if (!key.startsWith('//') || declaringSource(key) !== source) {
                continue;
            }
            // npm has no tokenHelper setting and pnpm ignores one that arrives through
            // the environment, so bridging it would only put a command line in the
            // child's environment. pnpm also refuses to run a helper from any file but
            // its user auth config, so one here stands for no credential the fetch
            // would have had. `:cert`/`:key` carry inline PEM, which npm has no
            // registry-scoped form for, so they go in flat below instead.
            if (source.npmNative ||
                key.endsWith(':tokenHelper') ||
                key.endsWith(':cert') ||
                key.endsWith(':key')) {
                continue;
            }
            // The env checks keep the URL-scoped env tier above these files, matching
            // pnpm's merge order: pnpm_config_ spellings are already in the overlay
            // (applyUrlScopedEnvConfig); ambient npm_config_ ones pnpm reads must stay
            // unbridged, or the overlaid value would shadow them out of the merge.
            if (env[`npm_config_${key}`] !== undefined ||
                (!managerIgnoresEnv(key) &&
                    (0, utils_1.readNpmConfigEnv)(process.env, key) !== undefined)) {
                continue;
            }
            env[`npm_config_${key}`] = value;
        }
    }
    // The rescoped credentials went in through the dart loop above, since that is
    // the form they carry by the time pnpm merges them. What is left is naming the
    // ones npm will not get: a file npm reads itself contributes none, because npm
    // rejects bare auth in its own config (ERR_INVALID_AUTH) before any overlay
    // matters.
    const bareKeys = new Set();
    const credentialDarts = new Set();
    for (const source of sources) {
        if (source.npmNative || !source.dart) {
            continue;
        }
        for (const key of source.rescoped) {
            bareKeys.add(key);
            credentialDarts.add(source.dart);
        }
    }
    const requestDart = (0, utils_1.requestNerfDart)(contactedRegistry(env, projectNpmrc, scope, managerIgnoresEnv));
    const requestKeys = requestDart ? (0, utils_1.registryKeysFor)(requestDart) : [];
    // A withheld credential is invisible in npm's own error, so name it, unless
    // npm already finds one for that registry among the sources visible here. A
    // user-level ~/.npmrc is not one, so the message states only what was
    // withheld rather than predicting how the request will fail.
    if (bareKeys.size > 0 &&
        requestDart &&
        !requestKeys.some((key) => credentialDarts.has(key)) &&
        !hasCredentials(env, projectNpmrc, requestDart, managerIgnoresEnv)) {
        warnUnscopedCredential(requestDart, [...bareKeys]);
    }
    // Flat TLS/proxy keys are part of pnpm's auth-config inheritance set
    // (RAW_AUTH_CFG_KEYS) and are written to auth.ini by `pnpm config set`, so
    // bridge them too.
    const cafileSource = bridging('cafile');
    const cafile = bridgedValue('cafile');
    if (cafileSource && cafile) {
        // From 11.2.0 pnpm resolves a relative cafile against the directory of the
        // file that declared it, not the workspace root; before that its only reader
        // is loadCAFile, a bare readFileSync on the raw value, so it lands on the
        // cwd the command runs in (the workspace root for a migrate). Neither
        // expands a leading `~`. npm ignores a cafile it cannot open, so getting the
        // base wrong drops the trust anchor with no diagnostic at all.
        const base = (0, semver_1.gte)(pnpmVersion, '11.2.0') ? cafileSource.dir : root;
        (0, utils_1.setCafile)(env, (0, utils_1.escapeNpmEnvExpr)((0, path_1.resolve)(base, cafile)));
    }
    // npm reads inline `ca` PEM only as a flat (global) key, and pnpm does not
    // source-scope trust anchors, so it needs no pin check.
    const ca = bridgedValue('ca');
    if (ca) {
        env['npm_config_ca'] = ca;
    }
    // `cert`/`key` are client TLS material, which pnpm pins to a registry the same
    // way it pins credentials, so by here they are darted. npm has no
    // registry-scoped inline form (its //host/:certfile / :keyfile keys take
    // paths, not PEM) and npm_config_cert presents the certificate to every host
    // npm contacts, so only the pair pinned to the registry npm will actually
    // contact can go in, and it goes in flat.
    // Every tier is read here, the URL-scoped env one above the files and the
    // project file among them: npm's own registry-scoped TLS keys take paths
    // (certfile/keyfile), so inline PEM cannot reach it in scoped form from any of
    // them, npm-native file included. Which tier declares it settles the value
    // first and the nearest dart declaring one then wins, the order pnpm resolves
    // them in (pickSettingByUrl walks a map every tier has already merged into).
    const pinnedTls = (dartKey) => env[`npm_config_${dartKey}`] ||
        // The same ambient tier the dart loop honors: from 11.6.0 pnpm reads a
        // URL-scoped npm_config_ entry the spawn would otherwise pass straight
        // through in a form npm makes no use of.
        (managerIgnoresEnv(dartKey)
            ? undefined
            : (0, utils_1.readNpmConfigEnv)(process.env, dartKey)) ||
        declaringSource(dartKey)?.map.get(dartKey);
    if (requestDart) {
        for (const key of ['cert', 'key']) {
            const value = requestKeys
                .map((regKey) => pinnedTls(`${regKey}:${key}`))
                .find(Boolean);
            if (value) {
                env[`npm_config_${key}`] = value;
            }
            else if (projectRaw &&
                // Read as npm resolves it: it expands a `${VAR}` in the key before it
                // looks the setting up, so a placeholder-spelled one still reaches it.
                (0, utils_1.readExpandedKey)(projectRaw, key, utils_1.expandNpmEnvVars) !== undefined) {
                // npm reads this one out of its own project config and presents it to
                // every host it contacts, where pnpm pinned it to a registry this fetch
                // never reaches. The `null` literal is what cancels a file value at npm's
                // env tier; an empty one leaves the file's in place (measured on npm 9,
                // 10 and 11).
                env[`npm_config_${key}`] = 'null';
            }
        }
    }
    applyPnpmStrictSsl(env, bridging('strict-ssl')?.rawStrictSsl);
    return {
        proxy: declaredValue('proxy'),
        httpProxy: declaredValue('http-proxy'),
        httpsProxy: declaredValue('https-proxy'),
    };
}
/**
 * The proxy-bypass list is the one npmrc key whose spelling differs. In these
 * files pnpm 11 honors `no-proxy` and ignores `noproxy`, where npm does the
 * exact opposite (it warns about `no-proxy` as an unknown config and moves on).
 * pnpm 10.x honors both, so only the spelling npm cannot read needs bridging on
 * either line. Either way pnpm's `no-proxy` never reaches the spawned npm from
 * any file it reads, so the layer that wins in pnpm has to be re-spelled. A
 * `noProxy` in pnpm-workspace.yaml outranks every one of them and is applied
 * after this.
 */
function bridgeNoProxy(env, npmrcPaths, pnpmVersion) {
    const value = fileNoProxy(npmrcPaths, pnpmVersion);
    if (value) {
        (0, utils_1.setProxies)(env, { noProxy: value });
    }
}
/** The bypass list the highest of `npmrcPaths` to declare one contributes. */
function fileNoProxy(npmrcPaths, pnpmVersion) {
    for (const path of npmrcPaths) {
        const npmrc = readPnpmNpmrcMap(path, pnpmVersion);
        // Declaring the key empty is pnpm's way of clearing a list it inherits, so
        // presence settles the layer and an empty value stops the search here.
        if (!npmrc?.has('no-proxy')) {
            continue;
        }
        const value = npmrc.get('no-proxy');
        // npm ignores `no-proxy` in the file it does read, so the value never goes
        // through npm's own expansion under that key; expand it with pnpm's grammar.
        return value ? (0, utils_1.bridgePnpmEnvVars)(value) : undefined;
    }
    return undefined;
}
/**
 * The registry the spawned npm will contact, as far as this process can see: a
 * scoped registry for the package, else the default, else npm's own. A registry
 * declared only in a user-level ~/.npmrc is not visible here, which leaves the
 * comparison covering the sources that can redirect the request to a host the
 * user never configured.
 */
function contactedRegistry(env, projectNpmrc, scope, managerIgnoresEnv) {
    // npm's pickRegistry falls through on a falsy value, so a setting that
    // expanded to nothing lands on the next one rather than on an empty host.
    return ((scope
        ? npmResolved(env, projectNpmrc, `${scope}:registry`, managerIgnoresEnv)
        : undefined) ||
        npmResolved(env, projectNpmrc, 'registry', managerIgnoresEnv) ||
        DEFAULT_REGISTRY);
}
/**
 * Writes the proxy pair pnpm ends up with, once every tier has declared. The
 * registry npm is about to contact decides whether an http-only proxy is worth
 * bridging, and what npm reads for itself decides whether a value needs to be.
 */
function applyResolvedProxies(env, tiers, root, scope, managerIgnoresEnv) {
    const projectNpmrc = readNpmrcOrWarn((0, path_1.join)(root, '.npmrc')) ?? new Map();
    (0, utils_1.setProxies)(env, resolveProxies(tiers, contactedRegistry(env, projectNpmrc, scope, managerIgnoresEnv), 
    // The user config counts as what npm sees: a proxy it holds there is one
    // npm resolves for itself, and overwriting it with a value derived from
    // another key would put the overlay above a file npm was already reading.
    npmVisibleReader(env, root, projectNpmrc, managerIgnoresEnv)));
}
function npmResolved(env, projectNpmrc, key, managerIgnoresEnv) {
    // npm's env tier outranks the .npmrc, but the spawn strips a bridged ambient
    // npm_config_* the manager ignores (mergeNpmConfigEnv), and every key read
    // here is bridged, so a value npm never sees is not counted either.
    const ambient = managerIgnoresEnv(key)
        ? undefined
        : (0, utils_1.readNpmConfigEnv)(process.env, key);
    const declared = env[`npm_config_${key}`] ??
        ambient ??
        (0, utils_1.readExpandedKey)(projectNpmrc, key, utils_1.expandNpmEnvVars);
    // npm trims a value before it expands one (parseField), so a blank value
    // collapses while a padded reference still resolves.
    return declared === undefined ? undefined : (0, utils_1.expandNpmEnvVars)(declared.trim());
}
/**
 * What the spawned npm resolves a key to on its own: its env tier, minus the
 * bridged spellings the spawn strips, then the two .npmrc files it opens here.
 * An unreadable one is silently absent, which is what npm makes of it.
 */
function npmVisibleReader(env, root, projectNpmrc, managerIgnoresEnv) {
    const userConfig = (0, npmrc_1.readNpmrcMap)(getNpmUserConfigPath(root));
    return (key) => {
        const declared = npmResolved(env, projectNpmrc, key, managerIgnoresEnv);
        if (declared !== undefined || !(userConfig instanceof Map)) {
            return declared;
        }
        const value = (0, utils_1.readExpandedKey)(userConfig, key, utils_1.expandNpmEnvVars);
        return value === undefined ? undefined : (0, utils_1.expandNpmEnvVars)(value.trim());
    };
}
/**
 * What pnpm itself ends up with for a key, across the tiers the overlay already
 * carries and the npmrc-family files it reads for itself, each as its own
 * reader leaves it: a file it discarded whole declares nothing, and neither
 * does an entry it withheld. A key it answers is one npm answering the same way
 * reproduces rather than diverges from.
 */
function pnpmResolvedReader(env, files, pnpmVersion, managerIgnoresEnv) {
    const maps = files.map(({ path, filtered }) => {
        const raw = readPnpmNpmrcMap(path, pnpmVersion);
        return raw && readPnpmNpmrcEntries(raw, pnpmVersion, filtered).map;
    });
    return (key) => 
    // The overlay is pnpm's own resolution of every tier above these files.
    env[`npm_config_${key}`] ||
        (managerIgnoresEnv(key) ? undefined : (0, utils_1.readNpmConfigEnv)(process.env, key)) ||
        maps.find((map) => map?.get(key))?.get(key);
}
function hasCredentials(env, projectNpmrc, dart, managerIgnoresEnv) {
    return (0, utils_1.hasCredentialFor)(dart, (key) => npmResolved(env, projectNpmrc, key, managerIgnoresEnv));
}
/** pnpm accepts `~/` and `~\` on every platform; npm accepts `~\` on Windows only. */
const PNPM_HOME_PATH = /^~[/\\]/;
const NPM_HOME_PATH = process.platform === 'win32' ? /^~[/\\]/ : /^~\//;
/** Both tools normalize a config path this way: a leading `~/` (or `~\`) for
 *  the home directory, else the cwd the command runs in. That cwd is the config
 *  root the spawn uses, not this process's, which a migrate from a subdirectory
 *  differs from. */
function resolveConfigPath(value, homePattern, root) {
    return homePattern.test(value)
        ? (0, path_1.resolve)((0, os_1.homedir)(), value.slice(2))
        : (0, path_1.resolve)(root, value);
}
/**
 * The file pnpm >= 11 authenticates from. Its selection chain is followed here
 * minus the two CLI links, which nx never passes.
 * See loadNpmrcConfig in pnpm's config reader.
 */
function getPnpmUserConfigPath(pnpmVersion, root) {
    // Read first: pnpm parses the global config.yaml before the selector
    // applies, so a malformed one aborts even when the env names the auth file.
    const globalYaml = readPnpmGlobalConfigYaml();
    let selected = readPnpmEnvVar('npmrc_auth_file', pnpmVersion) ??
        readPnpmEnvVar('userconfig', pnpmVersion);
    if (selected === undefined) {
        const fromYaml = globalYaml?.['npmrcAuthFile'];
        selected =
            (typeof fromYaml === 'string' ? fromYaml : undefined) ||
                // The last link is npm's own setting, which npm then reads for itself.
                (0, utils_1.readEnvVar)(process.env, 'npm_config_userconfig') ||
                undefined;
    }
    return selected
        ? resolveConfigPath(selected, PNPM_HOME_PATH, root)
        : (0, path_1.join)((0, os_1.homedir)(), '.npmrc');
}
/**
 * The file npm resolves as its own user config. npm documents `userconfig` as
 * settable from the environment and the command line only, never from another
 * config file, so its env tier over the `~/.npmrc` default is the whole chain.
 */
function getNpmUserConfigPath(root) {
    const configured = (0, utils_1.readNpmConfigEnv)(process.env, 'userconfig');
    return configured
        ? resolveConfigPath((0, utils_1.expandNpmEnvVars)(configured.trim()), NPM_HOME_PATH, root)
        : (0, path_1.join)((0, os_1.homedir)(), '.npmrc');
}
/**
 * Reports the two credentials the overlay cannot reproduce for the registry npm
 * is about to contact: one pnpm produces by running a token helper, which npm
 * has no setting for, and one npm holds in a file of its own that pnpm would
 * not send. Both supported lines take a helper only from the user config pnpm
 * resolves (10.x getAuthHeadersFromConfig reads it from userSettings alone; 11
 * additionally aborts the command outright with TOKEN_HELPER_IN_PROJECT_CONFIG
 * when one reaches it from any other file), so `userConfigPath` is the one
 * place worth reading for it.
 */
function reportCredentialDivergences(env, root, scope, userConfigPath, npmrcFiles, pnpmVersion, managerIgnoresEnv) {
    const projectNpmrc = readNpmrcOrWarn((0, path_1.join)(root, '.npmrc')) ?? new Map();
    const requestDart = (0, utils_1.requestNerfDart)(contactedRegistry(env, projectNpmrc, scope, managerIgnoresEnv));
    const npmVisible = npmVisibleReader(env, root, projectNpmrc, managerIgnoresEnv);
    if (requestDart) {
        // pnpm goes without one where it resolved the registry but not a credential
        // for it: a file it discards whole over an unresolvable reference, an entry
        // it withholds from 11.5.3, or the .npmrc beside an outer workspace file
        // that it reads in place of the one npm opens here.
        const pnpmSends = pnpmResolvedReader(env, [...npmrcFiles, { path: userConfigPath, filtered: false }], pnpmVersion, managerIgnoresEnv);
        (0, utils_1.warnNativeCredential)(env, requestDart, 'pnpm', 'Declare it where pnpm reads it too if it should authenticate there, or remove it from .npmrc if npm should not.', (key) => (pnpmSends(key) ? undefined : npmVisible(key)));
    }
    const userConfig = readPnpmNpmrcMap(userConfigPath, pnpmVersion);
    if (!userConfig || !requestDart) {
        return;
    }
    // Where pnpm pins a `tokenHelper` written without a registry prefix.
    const pinnedDart = (0, semver_1.gte)(pnpmVersion, '11.4.0')
        ? // From 11.4.0 it rescopes per file, onto the registry that same file
            // declares (rescopeUnscopedCreds), expanding `${VAR}` before reading it
            // off.
            pnpmNerfDart((0, utils_1.expandPnpmEnvVars)(userConfig.get('registry') ?? '') || DEFAULT_REGISTRY, pnpmVersion)
        : // 10.x and 11.0-11.3 pin it onto the registry that wins overall instead
            // (getAuthHeadersFromCreds keys the unscoped credential on the resolved
            // `registry`). The default registry, never a scoped one: pnpm keys the
            // helper on `registry` alone, so a scoped package goes elsewhere without
            // it.
            (0, utils_1.nerfDart)(npmResolved(env, projectNpmrc, 'registry', managerIgnoresEnv) ||
                DEFAULT_REGISTRY);
    const requestKeys = (0, utils_1.registryKeysFor)(requestDart);
    if (!declaresTokenHelper(userConfig, requestKeys, pinnedDart)) {
        return;
    }
    // A plain credential npm holds beside the helper is one npm still sends, so
    // there is nothing to report about the helper it never runs.
    if (!(0, utils_1.hasCredentialFor)(requestDart, npmVisible)) {
        warnTokenHelper(requestDart);
    }
}
/**
 * Whether the credential pnpm presents for a request comes from a token helper.
 * A helper outranks every other credential for that registry, whichever layer it
 * came from (credsToHeader), so finding one settles what pnpm sends.
 * `requestKeys` is the dart chain pnpm walks, so an unscoped helper counts when
 * the dart it was pinned to is one of them.
 */
function declaresTokenHelper(userConfig, requestKeys, pinnedDart) {
    // pnpm expands `${VAR}` in this file's values as well as its keys, and a value
    // resolving to nothing declares no helper at all.
    const declared = (key) => (0, utils_1.expandPnpmEnvVars)((0, utils_1.readExpandedKey)(userConfig, key, utils_1.expandPnpmEnvVars) ?? '');
    return requestKeys.some((key) => !!declared(`${key}:tokenHelper`) ||
        (key === pinnedDart && !!declared('tokenHelper')));
}
let warnedTokenHelper = false;
// The nerf dart only, for the same reason warnUnscopedCredential uses it. The
// helper's command line stays out too: it is what produces the credential.
function warnTokenHelper(dart) {
    if (warnedTokenHelper) {
        return;
    }
    warnedTokenHelper = true;
    logger_1.logger.warn(`pnpm runs a token helper to authenticate with ${dart}, which npm cannot do, so packages will be fetched from there without that credential. Store the token the helper returns as "${dart}:_authToken=..." in a file npm reads if it should authenticate there.`);
}
let warnedUnscopedCredential = false;
// The nerf dart, not the registry URL: a registry URL can carry its own basic
// auth, which would then be in every console and CI log the warning reaches.
function warnUnscopedCredential(dart, keys) {
    if (warnedUnscopedCredential) {
        return;
    }
    warnedUnscopedCredential = true;
    const scoped = keys.map((key) => `"${dart}:${key}=..."`).join(', ');
    logger_1.logger.warn(`A credential in pnpm's auth.ini is not scoped to a registry, so it was not used for ${dart} when fetching packages. pnpm pins an unscoped credential to the registry that same file declares, and has deprecated the unscoped form. Scope it (${scoped}) to use it with this registry.`);
}
/**
 * Network settings pnpm honors from a yaml configuration file. `caFile`/
 * `cafile` is the one it accepts and then never uses (it loads a CA file from
 * the npmrc-family files alone, measured on 10.18.0 and 11.20.0), so that key
 * is deliberately not bridged; inline `ca`/`cert`/`key` declared here do reach
 * its fetch, unpinned, exactly as npm's own spelling of them does.
 */
function applyYamlNetworkSettings(env, settings, applyNoProxy = true) {
    // Only the boolean turns verification off (rejectUnauthorized is
    // `strictSsl ?? true`, and the agent that carries it is built for
    // `strictSsl === false`), so any other declared value restores npm's default
    // over a `strict-ssl=false` from a file below.
    if (settings.strictSsl !== undefined) {
        (0, utils_1.setStrictSsl)(env, settings.strictSsl !== false);
    }
    if (settings.ca) {
        env['npm_config_ca'] = settings.ca;
    }
    for (const key of ['cert', 'key']) {
        if (settings[key]) {
            env[`npm_config_${key}`] = settings[key];
        }
    }
    // pnpm honors either spelling and prefers noProxy when both are set. The
    // proxies themselves are resolved with every other tier's rather than here.
    if (applyNoProxy) {
        (0, utils_1.setProxies)(env, { noProxy: settings.noProxy ?? settings.noproxy });
    }
}