110 lines
3.3 KiB
TypeScript
110 lines
3.3 KiB
TypeScript
/**
|
|
* This file sets you up with structure needed for an ESLint rule.
|
|
*
|
|
* It leverages utilities from @typescript-eslint to allow TypeScript to
|
|
* provide autocompletions etc for the configuration.
|
|
*
|
|
* Your rule's custom logic will live within the create() method below
|
|
* and you can learn more about writing ESLint rules on the official guide:
|
|
*
|
|
* https://eslint.org/docs/developer-guide/working-with-rules
|
|
*
|
|
* You can also view many examples of existing rules here:
|
|
*
|
|
* https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin/src/rules
|
|
*/
|
|
|
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
import { closeSync, openSync, readSync } from 'node:fs';
|
|
|
|
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-ensure-pnpm-lock-version"
|
|
export const RULE_NAME = 'ensure-pnpm-lock-version';
|
|
|
|
export const rule = ESLintUtils.RuleCreator(() => __filename)({
|
|
name: RULE_NAME,
|
|
meta: {
|
|
type: 'problem',
|
|
docs: {
|
|
description: ``,
|
|
},
|
|
schema: [
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
version: {
|
|
type: 'string',
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
messages: {
|
|
unparseableLockfileVersion:
|
|
'Could not parse lockfile version from pnpm-lock.yaml, the file may be corrupted or the ensure-pnpm-lock-version lint rule may need to be updated.',
|
|
incorrectLockfileVersion:
|
|
'pnpm-lock.yaml has a lockfileVersion of {{version}}, but {{expectedVersion}} is required.',
|
|
},
|
|
},
|
|
defaultOptions: [],
|
|
create(context) {
|
|
// Read upon creation of the rule, the contents should not change during linting
|
|
const lockfileFirstLine = readFirstLineSync('pnpm-lock.yaml');
|
|
// Extract the version number, it will be a string in single quotes
|
|
const lockfileVersion = lockfileFirstLine.match(
|
|
/lockfileVersion:\s*'([^']+)'/
|
|
)?.[1];
|
|
|
|
const options = context.options as { version: string }[];
|
|
if (!Array.isArray(options) || options.length === 0) {
|
|
throw new Error('Expected an array of options with a version property');
|
|
}
|
|
const expectedLockfileVersion = options[0].version;
|
|
return {
|
|
Program(node) {
|
|
if (!lockfileVersion) {
|
|
context.report({
|
|
node,
|
|
messageId: 'unparseableLockfileVersion',
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (lockfileVersion !== expectedLockfileVersion) {
|
|
context.report({
|
|
node,
|
|
messageId: 'incorrectLockfileVersion',
|
|
data: {
|
|
version: lockfileVersion,
|
|
expectedVersion: expectedLockfileVersion,
|
|
},
|
|
});
|
|
}
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|
|
/**
|
|
* pnpm-lock.yaml is a huge file, so only read the first line as efficiently as possible
|
|
* for optimum linting performance.
|
|
*/
|
|
function readFirstLineSync(filePath: string) {
|
|
const BUFFER_SIZE = 64; // Optimized for the expected line length
|
|
const buffer = Buffer.alloc(BUFFER_SIZE);
|
|
let line = '';
|
|
let bytesRead: number;
|
|
let fd: number;
|
|
try {
|
|
fd = openSync(filePath, 'r');
|
|
bytesRead = readSync(fd, buffer, 0, BUFFER_SIZE, 0);
|
|
line = buffer.toString('utf8', 0, bytesRead).split('\n')[0];
|
|
} catch (err) {
|
|
throw err; // Re-throw to allow caller to handle
|
|
} finally {
|
|
if (fd !== undefined) {
|
|
closeSync(fd);
|
|
}
|
|
}
|
|
return line;
|
|
}
|