mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-31 01:37:24 +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>
145 lines
3.5 KiB
TypeScript
145 lines
3.5 KiB
TypeScript
import { isString } from '@sindresorhus/is';
|
|
import ini from 'ini';
|
|
import { regEx } from '../../../util/regex.ts';
|
|
import type {
|
|
DetectedNpmrcLineEnding,
|
|
NpmrcDocument,
|
|
NpmrcLine,
|
|
NpmrcLineEnding,
|
|
} from './types.ts';
|
|
|
|
/**
|
|
* Follow `ini.parse`: indented section-like lines are settings, not sections.
|
|
*/
|
|
const npmrcSectionRegex = regEx(/^\[(?<section>[^\]]*)\]\s*$/);
|
|
const npmrcSettingRegex = regEx(/^(?<key>[^=]+)(?:=(?<value>.*))?$/);
|
|
|
|
/**
|
|
* Reuse `ini.parse`'s token decoder while retaining raw lines for lossless
|
|
* rendering.
|
|
*
|
|
* `@types/ini` declares a string result, but single-quoted JSON literals can
|
|
* decode to other types, so callers must narrow it.
|
|
*/
|
|
function decodeNpmrcText(raw: string): unknown {
|
|
return ini.unsafe(raw);
|
|
}
|
|
|
|
function decodeNpmrcValue(raw: string): unknown {
|
|
const value = decodeNpmrcText(raw);
|
|
if (value === 'true') {
|
|
return true;
|
|
}
|
|
if (value === 'false') {
|
|
return false;
|
|
}
|
|
if (value === 'null') {
|
|
return null;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function parseNpmrcKey(decodedKey: string): {
|
|
key: string;
|
|
isArray: boolean;
|
|
} {
|
|
if (!decodedKey.endsWith('[]')) {
|
|
return { key: decodedKey, isArray: false };
|
|
}
|
|
|
|
if (decodedKey === '[]') {
|
|
return { key: decodedKey, isArray: false };
|
|
}
|
|
|
|
return { key: decodedKey.slice(0, -2), isArray: true };
|
|
}
|
|
|
|
function parseNpmrcLine(
|
|
raw: string,
|
|
lineEnding: NpmrcLineEnding,
|
|
section: string | null,
|
|
): NpmrcLine {
|
|
const trimmed = raw.trim();
|
|
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith(';')) {
|
|
return { type: 'other', raw, lineEnding };
|
|
}
|
|
|
|
const rawSectionName = npmrcSectionRegex.exec(raw)?.groups?.section;
|
|
if (rawSectionName !== undefined) {
|
|
const sectionName = decodeNpmrcText(rawSectionName);
|
|
if (!isString(sectionName)) {
|
|
return { type: 'other', raw, lineEnding };
|
|
}
|
|
|
|
return {
|
|
type: 'section',
|
|
name: sectionName,
|
|
raw,
|
|
lineEnding,
|
|
};
|
|
}
|
|
|
|
const setting = npmrcSettingRegex.exec(raw)?.groups;
|
|
if (!setting) {
|
|
return { type: 'other', raw, lineEnding };
|
|
}
|
|
|
|
const decodedKey = decodeNpmrcText(setting.key);
|
|
if (!isString(decodedKey)) {
|
|
return { type: 'other', raw, lineEnding };
|
|
}
|
|
|
|
const { key, isArray } = parseNpmrcKey(decodedKey);
|
|
|
|
let value: unknown = true;
|
|
if (setting.value !== undefined) {
|
|
value = decodeNpmrcValue(setting.value);
|
|
}
|
|
|
|
return {
|
|
type: 'setting',
|
|
section,
|
|
key,
|
|
isArray,
|
|
value,
|
|
raw,
|
|
lineEnding,
|
|
};
|
|
}
|
|
|
|
export function parseNpmrc(content: string): NpmrcDocument {
|
|
const lines: NpmrcLine[] = [];
|
|
let detectedLineEnding: DetectedNpmrcLineEnding | null = null;
|
|
let section: string | null = null;
|
|
// oxlint-disable-next-line prefer-named-capture-group -- split needs captured separators, not named groups.
|
|
const parts = content.split(regEx(/(\r\n|\r|\n)/));
|
|
|
|
for (let index = 0; index < parts.length; index += 2) {
|
|
const raw = parts[index];
|
|
const lineEnding = (parts[index + 1] ?? '') as NpmrcLineEnding;
|
|
if (!raw && !lineEnding) {
|
|
continue;
|
|
}
|
|
|
|
if (lineEnding && !detectedLineEnding) {
|
|
detectedLineEnding = lineEnding;
|
|
}
|
|
|
|
const line = parseNpmrcLine(raw, lineEnding, section);
|
|
lines.push(line);
|
|
if (line.type === 'section') {
|
|
section = line.name;
|
|
}
|
|
}
|
|
|
|
return {
|
|
lines,
|
|
detectedLineEnding,
|
|
trailingLineEnding: lines.at(-1)?.lineEnding ?? '',
|
|
};
|
|
}
|
|
|
|
export function renderNpmrc(lines: NpmrcLine[]): string {
|
|
return lines.map((line) => `${line.raw}${line.lineEnding}`).join('');
|
|
}
|