77 lines
2 KiB
TypeScript
77 lines
2 KiB
TypeScript
// Copyright 2022 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import { File, Rule } from 'endanger';
|
|
import semver from 'semver';
|
|
|
|
function isPinnedVersion(version: string): boolean {
|
|
if (version.startsWith('https:')) {
|
|
return version.includes('#');
|
|
}
|
|
return semver.valid(version) !== null;
|
|
}
|
|
|
|
async function getLineContaining(file: File, text: string) {
|
|
let lines = await file.lines();
|
|
for (let line of lines) {
|
|
if (await line.contains(text)) {
|
|
return line;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
let dependencyTypes = [
|
|
'dependencies',
|
|
'devDependencies',
|
|
'peerDependencies',
|
|
'optionalDependencies',
|
|
];
|
|
|
|
export default function packageJsonVersionsShouldBePinned() {
|
|
return new Rule({
|
|
match: {
|
|
files: ['**/package.json', '!**/node_modules/**'],
|
|
},
|
|
messages: {
|
|
packageJsonVersionsShouldBePinned: `
|
|
**Pin package.json versions**
|
|
All package.json versions should be pinned to a specific version.
|
|
See {depName}@{depVersion} in {filePath}#{dependencyType}.
|
|
`,
|
|
},
|
|
async run({ files, context }) {
|
|
for (let file of files.modifiedOrCreated) {
|
|
let pkg = await file.json();
|
|
for (let dependencyType of dependencyTypes) {
|
|
let deps = pkg[dependencyType];
|
|
if (deps == null) {
|
|
continue;
|
|
}
|
|
for (let depName of Object.keys(deps)) {
|
|
let depVersion = deps[depName];
|
|
if (!isPinnedVersion(depVersion)) {
|
|
let line = await getLineContaining(
|
|
file,
|
|
`"${depName}": "${depVersion}"`
|
|
);
|
|
context.warn(
|
|
'packageJsonVersionsShouldBePinned',
|
|
{
|
|
file,
|
|
line: line ?? undefined,
|
|
},
|
|
{
|
|
depName,
|
|
depVersion,
|
|
filePath: file.path,
|
|
dependencyType,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
});
|
|
}
|