mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 18:19:51 +00:00
2ee9a26675
* fix(manager/npm): Parse npmrc settings before sanitizing * Refactor NPMRC parsing and sanitization * fix(manager/npm): Restore npmrc after artifact updates * test(manager/npm): cover npmrc failure paths * refactor(manager/npm): simplify npmrc parsing * Update npmrc-parser.spec.ts * refactor(manager/npm): clarify npmrc parsing * refactor(manager/npm): make npmrc regexes RE2-compatible * chore: retrigger CI * Update lib/modules/manager/npm/npmrc-parser.ts Co-authored-by: Michael Kriese <michael.kriese@visualon.de> * fix(manager/npm): preserve captured npmrc line endings --------- Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
161 lines
4.1 KiB
TypeScript
161 lines
4.1 KiB
TypeScript
import { isString } from '@sindresorhus/is';
|
|
import { GlobalConfig } from '../../../config/global.ts';
|
|
import { logger } from '../../../logger/index.ts';
|
|
import {
|
|
findLocalSiblingOrParent,
|
|
readLocalFile,
|
|
} from '../../../util/fs/index.ts';
|
|
import { regEx } from '../../../util/regex.ts';
|
|
import { parseNpmrc, renderNpmrc } from './npmrc-parser.ts';
|
|
import type {
|
|
NpmrcDocument,
|
|
NpmrcLine,
|
|
NpmrcLineEnding,
|
|
NpmrcResult,
|
|
NpmrcSettingLine,
|
|
} from './types.ts';
|
|
|
|
interface SanitizedRepoNpmrc {
|
|
content: string;
|
|
detectedLineEnding: NpmrcDocument['detectedLineEnding'];
|
|
}
|
|
|
|
/**
|
|
* Mirrors npm's environment-reference grammar. Escape handling remains
|
|
* procedural because RE2 does not support lookbehind.
|
|
*/
|
|
const environmentVariableReferenceRegex = regEx(
|
|
// oxlint-disable-next-line prefer-named-capture-group -- Only the full match and its position are used.
|
|
/\$\{([^${}?]+)(\?)?\}/g,
|
|
);
|
|
|
|
function containsEnvironmentVariableReference(value: unknown): boolean {
|
|
if (!isString(value)) {
|
|
return false;
|
|
}
|
|
|
|
for (const match of value.matchAll(environmentVariableReferenceRegex)) {
|
|
let escapeCount = 0;
|
|
let escapeIndex = match.index - 1;
|
|
while (value[escapeIndex] === '\\') {
|
|
escapeCount += 1;
|
|
escapeIndex -= 1;
|
|
}
|
|
|
|
if (escapeCount % 2 === 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function hasEnvironmentVariableReference(line: NpmrcSettingLine): boolean {
|
|
return (
|
|
containsEnvironmentVariableReference(line.key) ||
|
|
containsEnvironmentVariableReference(line.value)
|
|
);
|
|
}
|
|
|
|
function sanitizeRepoNpmrc(
|
|
repoNpmrc: string,
|
|
npmrcFileName: string,
|
|
): SanitizedRepoNpmrc {
|
|
const document = parseNpmrc(repoNpmrc);
|
|
const retainedLines: NpmrcLine[] = [];
|
|
const allowEnvironmentVariableReferences = GlobalConfig.get('exposeAllEnv');
|
|
let removedEnvironmentVariableReferenceLine = false;
|
|
let removedPackageLockSetting = false;
|
|
|
|
for (const line of document.lines) {
|
|
if (line.type !== 'setting' || line.section !== null) {
|
|
retainedLines.push(line);
|
|
continue;
|
|
}
|
|
|
|
if (line.key === 'package-lock') {
|
|
removedPackageLockSetting = true;
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
!allowEnvironmentVariableReferences &&
|
|
hasEnvironmentVariableReference(line)
|
|
) {
|
|
removedEnvironmentVariableReferenceLine = true;
|
|
continue;
|
|
}
|
|
|
|
retainedLines.push(line);
|
|
}
|
|
|
|
if (removedPackageLockSetting) {
|
|
logger.debug('Stripping package-lock setting from .npmrc');
|
|
}
|
|
|
|
if (removedEnvironmentVariableReferenceLine) {
|
|
logger.debug(
|
|
{ npmrcFileName },
|
|
'Stripping .npmrc file of lines with variables',
|
|
);
|
|
}
|
|
|
|
return {
|
|
content: renderNpmrc(retainedLines),
|
|
detectedLineEnding: document.detectedLineEnding,
|
|
};
|
|
}
|
|
|
|
function mergeNpmrcDocuments(
|
|
configNpmrc: string | undefined,
|
|
sanitizedRepoNpmrc: SanitizedRepoNpmrc,
|
|
): string {
|
|
if (!configNpmrc) {
|
|
return sanitizedRepoNpmrc.content;
|
|
}
|
|
|
|
const configDocument = parseNpmrc(configNpmrc);
|
|
if (configDocument.trailingLineEnding) {
|
|
return `${configNpmrc}${sanitizedRepoNpmrc.content}`;
|
|
}
|
|
|
|
const separator: Exclude<NpmrcLineEnding, ''> =
|
|
configDocument.detectedLineEnding ??
|
|
sanitizedRepoNpmrc.detectedLineEnding ??
|
|
'\n';
|
|
|
|
return `${configNpmrc}${separator}${sanitizedRepoNpmrc.content}`;
|
|
}
|
|
|
|
export async function resolveNpmrc(
|
|
packageFile: string,
|
|
config: { npmrc?: string; npmrcMerge?: boolean },
|
|
): Promise<NpmrcResult> {
|
|
const npmrcFileName = await findLocalSiblingOrParent(packageFile, '.npmrc');
|
|
if (!npmrcFileName) {
|
|
return {
|
|
npmrc: isString(config.npmrc) ? config.npmrc : undefined,
|
|
npmrcFileName,
|
|
};
|
|
}
|
|
|
|
const repoNpmrc = await readLocalFile(npmrcFileName, 'utf8');
|
|
|
|
if (!isString(repoNpmrc)) {
|
|
return { npmrc: undefined, npmrcFileName };
|
|
}
|
|
|
|
if (isString(config.npmrc) && !config.npmrcMerge) {
|
|
logger.info(
|
|
{ npmrcFileName },
|
|
'Repo .npmrc file is ignored due to config.npmrc with config.npmrcMerge=false',
|
|
);
|
|
|
|
return { npmrc: config.npmrc, npmrcFileName };
|
|
}
|
|
|
|
const sanitizedRepoNpmrc = sanitizeRepoNpmrc(repoNpmrc, npmrcFileName);
|
|
const npmrc = mergeNpmrcDocuments(config.npmrc, sanitizedRepoNpmrc);
|
|
return { npmrc, npmrcFileName };
|
|
}
|