feat(linter): add rule for dependency checking (#17581)
This commit is contained in:
parent
1891addc09
commit
ef8c4ed095
@ -30,8 +30,12 @@
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/executors/**/schema.json", "**/generators/**/schema.json"],
|
||||
"files": ["*.json"],
|
||||
"parser": "jsonc-eslint-parser",
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["**/executors/**/schema.json", "**/generators/**/schema.json"],
|
||||
"rules": {
|
||||
"@nx/workspace/valid-schema-description": "error"
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
runCLI,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e/utils';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
@ -435,6 +436,92 @@ describe('Linter', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dependency checks', () => {
|
||||
beforeAll(() => {
|
||||
updateJson(`libs/${mylib}/.eslintrc.json`, (json) => {
|
||||
json.overrides = [
|
||||
...json.overrides,
|
||||
{
|
||||
files: ['*.json'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
rules: {
|
||||
'@nx/dependency-checks': 'error',
|
||||
},
|
||||
},
|
||||
];
|
||||
return json;
|
||||
});
|
||||
updateJson(`libs/${mylib}/project.json`, (json) => {
|
||||
json.targets.lint.options.lintFilePatterns = [
|
||||
`libs/${mylib}/**/*.ts`,
|
||||
`libs/${mylib}/project.json`,
|
||||
`libs/${mylib}/package.json`,
|
||||
];
|
||||
return json;
|
||||
});
|
||||
});
|
||||
|
||||
it('should report dependency check issues', () => {
|
||||
const rootPackageJson = readJson('package.json');
|
||||
const nxVersion = rootPackageJson.devDependencies.nx;
|
||||
const swcCoreVersion = rootPackageJson.devDependencies['@swc/core'];
|
||||
const swcHelpersVersion = rootPackageJson.dependencies['@swc/helpers'];
|
||||
|
||||
let out = runCLI(`lint ${mylib}`, { silenceError: true });
|
||||
expect(out).toContain('All files pass linting');
|
||||
|
||||
// make an explict dependency to nx
|
||||
updateFile(
|
||||
`libs/${mylib}/src/lib/${mylib}.ts`,
|
||||
(content) =>
|
||||
`import { names } from '@nx/devkit';\n\n` +
|
||||
content.replace(/return .*;/, `return names(${mylib}).className;`)
|
||||
);
|
||||
|
||||
// output should now report missing dependencies section
|
||||
out = runCLI(`lint ${mylib}`, { silenceError: true });
|
||||
expect(out).toContain(
|
||||
'Dependency sections are missing from the "package.json"'
|
||||
);
|
||||
|
||||
// should fix the missing section issue
|
||||
out = runCLI(`lint ${mylib} --fix`, { silenceError: true });
|
||||
expect(out).toContain(
|
||||
`Successfully ran target lint for project ${mylib}`
|
||||
);
|
||||
const packageJson = readJson(`libs/${mylib}/package.json`);
|
||||
expect(packageJson).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependencies": {
|
||||
"@nx/devkit": "${nxVersion}",
|
||||
"@swc/core": "${swcCoreVersion}",
|
||||
"@swc/helpers": "${swcHelpersVersion}",
|
||||
"nx": "${nxVersion}",
|
||||
},
|
||||
"name": "@proj/${mylib}",
|
||||
"type": "commonjs",
|
||||
"version": "0.0.1",
|
||||
}
|
||||
`);
|
||||
|
||||
// intentionally set the invalid version
|
||||
updateJson(`libs/${mylib}/package.json`, (json) => {
|
||||
json.dependencies['@nx/devkit'] = '100.0.0';
|
||||
return json;
|
||||
});
|
||||
out = runCLI(`lint ${mylib}`, { silenceError: true });
|
||||
expect(out).toContain(
|
||||
`The version specifier does not contain the installed version of "@nx/devkit" package: ${nxVersion}`
|
||||
);
|
||||
|
||||
// should fix the version mismatch issue
|
||||
out = runCLI(`lint ${mylib} --fix`, { silenceError: true });
|
||||
expect(out).toContain(
|
||||
`Successfully ran target lint for project ${mylib}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Root projects migration', () => {
|
||||
|
||||
1
nx.json
1
nx.json
@ -105,6 +105,7 @@
|
||||
"{projectRoot}/generators.json",
|
||||
"{projectRoot}/executors.json",
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/project.json",
|
||||
"{projectRoot}/migrations.json"
|
||||
]
|
||||
},
|
||||
|
||||
@ -39,6 +39,7 @@
|
||||
"@typescript-eslint/utils": "^5.58.0",
|
||||
"chalk": "^4.1.0",
|
||||
"confusing-browser-globals": "^1.0.9",
|
||||
"jsonc-eslint-parser": "^2.1.0",
|
||||
"semver": "7.3.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@ -15,6 +15,10 @@ import nxPluginChecksRule, {
|
||||
RULE_NAME as nxPluginChecksRuleName,
|
||||
} from './rules/nx-plugin-checks';
|
||||
|
||||
import dependencyChecks, {
|
||||
RULE_NAME as dependencyChecksRuleName,
|
||||
} from './rules/dependency-checks';
|
||||
|
||||
// Resolve any custom rules that might exist in the current workspace
|
||||
import { workspaceRules } from './resolve-workspace-rules';
|
||||
|
||||
@ -32,6 +36,7 @@ module.exports = {
|
||||
rules: {
|
||||
[enforceModuleBoundariesRuleName]: enforceModuleBoundaries,
|
||||
[nxPluginChecksRuleName]: nxPluginChecksRule,
|
||||
[dependencyChecksRuleName]: dependencyChecks,
|
||||
...workspaceRules,
|
||||
},
|
||||
};
|
||||
|
||||
1458
packages/eslint-plugin/src/rules/dependency-checks.spec.ts
Normal file
1458
packages/eslint-plugin/src/rules/dependency-checks.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
364
packages/eslint-plugin/src/rules/dependency-checks.ts
Normal file
364
packages/eslint-plugin/src/rules/dependency-checks.ts
Normal file
@ -0,0 +1,364 @@
|
||||
import { AST } from 'jsonc-eslint-parser';
|
||||
import { normalizePath, workspaceRoot } from '@nx/devkit';
|
||||
import { createESLintRule } from '../utils/create-eslint-rule';
|
||||
import { readProjectGraph } from '../utils/project-graph-utils';
|
||||
import { findProject, getSourceFilePath } from '../utils/runtime-lint-utils';
|
||||
import { join } from 'path';
|
||||
import { findProjectsNpmDependencies } from '@nx/js/src/internal';
|
||||
import { satisfies } from 'semver';
|
||||
import { getHelperDependenciesFromProjectGraph } from '@nx/js';
|
||||
import {
|
||||
getAllDependencies,
|
||||
removePackageJsonFromFileMap,
|
||||
} from '../utils/package-json-utils';
|
||||
|
||||
export type Options = [
|
||||
{
|
||||
buildTargets?: string[];
|
||||
checkMissingDependencies?: boolean;
|
||||
checkObsoleteDependencies?: boolean;
|
||||
checkVersionMismatches?: boolean;
|
||||
checkMissingPackageJson?: boolean;
|
||||
ignoredDependencies?: string[];
|
||||
}
|
||||
];
|
||||
|
||||
export type MessageIds =
|
||||
| 'missingDependency'
|
||||
| 'obsoleteDependency'
|
||||
| 'versionMismatch'
|
||||
| 'missingDependencySection';
|
||||
|
||||
export const RULE_NAME = 'dependency-checks';
|
||||
|
||||
export default createESLintRule<Options, MessageIds>({
|
||||
name: RULE_NAME,
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: `Checks dependencies in project's package.json for version mismatches`,
|
||||
recommended: 'error',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
buildTargets: [{ type: 'string' }],
|
||||
ignoreDependencies: [{ type: 'string' }],
|
||||
checkMissingDependencies: { type: 'boolean' },
|
||||
checkObsoleteDependencies: { type: 'boolean' },
|
||||
checkVersionMismatches: { type: 'boolean' },
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
messages: {
|
||||
missingDependency: `The "{{projectName}}" uses the package "{{packageName}}", but it is missing from the project's "package.json".`,
|
||||
obsoleteDependency: `The "{{packageName}}" package is not used by "{{projectName}}".`,
|
||||
versionMismatch: `The version specifier does not contain the installed version of "{{packageName}}" package: {{version}}.`,
|
||||
missingDependencySection: `Dependency sections are missing from the "package.json" but following dependencies were detected:{{dependencies}}`,
|
||||
},
|
||||
},
|
||||
defaultOptions: [
|
||||
{
|
||||
buildTargets: ['build'],
|
||||
checkMissingDependencies: true,
|
||||
checkObsoleteDependencies: true,
|
||||
checkVersionMismatches: true,
|
||||
ignoredDependencies: [],
|
||||
},
|
||||
],
|
||||
create(
|
||||
context,
|
||||
[
|
||||
{
|
||||
buildTargets,
|
||||
ignoredDependencies,
|
||||
checkMissingDependencies,
|
||||
checkObsoleteDependencies,
|
||||
checkVersionMismatches,
|
||||
},
|
||||
]
|
||||
) {
|
||||
if (!(context.parserServices as any).isJSON) {
|
||||
return {};
|
||||
}
|
||||
const fileName = normalizePath(context.getFilename());
|
||||
// support only package.json
|
||||
if (!fileName.endsWith('/package.json')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const projectPath = normalizePath(globalThis.projectPath || workspaceRoot);
|
||||
const sourceFilePath = getSourceFilePath(fileName, projectPath);
|
||||
const { projectGraph, projectRootMappings, projectFileMap } =
|
||||
readProjectGraph(RULE_NAME);
|
||||
|
||||
if (!projectGraph) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const sourceProject = findProject(
|
||||
projectGraph,
|
||||
projectRootMappings,
|
||||
sourceFilePath
|
||||
);
|
||||
|
||||
// check if source project exists
|
||||
if (!sourceProject) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// check if library has a build target
|
||||
const buildTarget = buildTargets.find(
|
||||
(t) => sourceProject.data.targets?.[t]
|
||||
);
|
||||
if (!buildTarget) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// gather helper dependencies for @nx/js executors
|
||||
const helperDependencies = getHelperDependenciesFromProjectGraph(
|
||||
workspaceRoot,
|
||||
sourceProject.name,
|
||||
projectGraph
|
||||
);
|
||||
|
||||
// find all dependencies for the project
|
||||
const npmDeps = findProjectsNpmDependencies(
|
||||
sourceProject,
|
||||
projectGraph,
|
||||
buildTarget,
|
||||
{
|
||||
helperDependencies: helperDependencies.map((dep) => dep.target),
|
||||
},
|
||||
removePackageJsonFromFileMap(projectFileMap)
|
||||
);
|
||||
const projDependencies = {
|
||||
...npmDeps.dependencies,
|
||||
...npmDeps.peerDependencies,
|
||||
};
|
||||
const expectedDependencyNames = Object.keys(projDependencies);
|
||||
|
||||
const projPackageJsonPath = join(
|
||||
workspaceRoot,
|
||||
sourceProject.data.root,
|
||||
'package.json'
|
||||
);
|
||||
|
||||
globalThis.projPackageJsonDeps ??= getAllDependencies(projPackageJsonPath);
|
||||
const projPackageJsonDeps: Record<string, string> =
|
||||
globalThis.projPackageJsonDeps;
|
||||
const rootPackageJsonDeps = getAllDependencies(
|
||||
join(workspaceRoot, 'package.json')
|
||||
);
|
||||
|
||||
function validateMissingDependencies(node: AST.JSONProperty) {
|
||||
if (!checkMissingDependencies) {
|
||||
return;
|
||||
}
|
||||
const missingDeps = expectedDependencyNames.filter(
|
||||
(d) => !projPackageJsonDeps[d]
|
||||
);
|
||||
|
||||
missingDeps.forEach((d) => {
|
||||
if (!ignoredDependencies.includes(d)) {
|
||||
context.report({
|
||||
node: node as any,
|
||||
messageId: 'missingDependency',
|
||||
data: { packageName: d, projectName: sourceProject.name },
|
||||
fix(fixer) {
|
||||
projPackageJsonDeps[d] =
|
||||
rootPackageJsonDeps[d] || projDependencies[d];
|
||||
|
||||
const deps = (node.value as AST.JSONObjectExpression).properties;
|
||||
if (deps.length) {
|
||||
return fixer.insertTextAfter(
|
||||
deps[deps.length - 1] as any,
|
||||
`,\n "${d}": "${projPackageJsonDeps[d]}"`
|
||||
);
|
||||
} else {
|
||||
return fixer.replaceTextRange(
|
||||
[node.value.range[0] + 1, node.value.range[1] - 1],
|
||||
`\n "${d}": "${projPackageJsonDeps[d]}"\n `
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateVersionMatchesInstalled(
|
||||
node: AST.JSONProperty,
|
||||
packageName: string,
|
||||
packageRange: string
|
||||
) {
|
||||
if (!checkVersionMismatches) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
projDependencies[packageName] === '*' ||
|
||||
satisfies(projDependencies[packageName], packageRange)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: node as any,
|
||||
messageId: 'versionMismatch',
|
||||
data: {
|
||||
packageName: packageName,
|
||||
version: projDependencies[packageName],
|
||||
},
|
||||
fix: (fixer) =>
|
||||
fixer.replaceText(
|
||||
node as any,
|
||||
`"${packageName}": "${
|
||||
rootPackageJsonDeps[packageName] || projDependencies[packageName]
|
||||
}"`
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
function reportObsoleteDependency(
|
||||
node: AST.JSONProperty,
|
||||
packageName: string
|
||||
) {
|
||||
if (!checkObsoleteDependencies) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: node as any,
|
||||
messageId: 'obsoleteDependency',
|
||||
data: { packageName: packageName, projectName: sourceProject.name },
|
||||
fix: (fixer) => {
|
||||
const isLastProperty =
|
||||
node.parent.properties[node.parent.properties.length - 1] === node;
|
||||
const index = node.parent.properties.findIndex((n) => n === node);
|
||||
|
||||
if (index > 0) {
|
||||
const previousNode = node.parent.properties[index - 1];
|
||||
return fixer.removeRange([
|
||||
previousNode.range[1] + 1,
|
||||
node.range[1] + (isLastProperty ? 0 : 1),
|
||||
]);
|
||||
} else {
|
||||
const parent = node.parent;
|
||||
|
||||
// it's the only property
|
||||
if (isLastProperty) {
|
||||
return fixer.removeRange([
|
||||
parent.range[0] + 1,
|
||||
parent.range[1] - 1,
|
||||
]);
|
||||
} else {
|
||||
return fixer.removeRange([
|
||||
parent.range[0] + 1,
|
||||
node.range[1] + 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// remove 4 spaces, new line and potential comma from previous line
|
||||
const shouldRemoveSiblingComma =
|
||||
isLastProperty && node.parent.properties.length > 1;
|
||||
const leadingChars = 5 + (shouldRemoveSiblingComma ? 1 : 0);
|
||||
return fixer.removeRange([
|
||||
node.range[0] - leadingChars,
|
||||
node.range[1] + (isLastProperty ? 0 : 1),
|
||||
]);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function validateDependenciesSectionExistance(
|
||||
node: AST.JSONObjectExpression
|
||||
) {
|
||||
if (
|
||||
!expectedDependencyNames.length ||
|
||||
!expectedDependencyNames.some((d) => !ignoredDependencies.includes(d))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!node.properties ||
|
||||
!node.properties.some((p) =>
|
||||
[
|
||||
'dependencies',
|
||||
'peerDependencies',
|
||||
'devDependencies',
|
||||
'optionalDependencies',
|
||||
].includes((p.key as any).value)
|
||||
)
|
||||
) {
|
||||
context.report({
|
||||
node: node as any,
|
||||
messageId: 'missingDependencySection',
|
||||
data: {
|
||||
dependencies: expectedDependencyNames
|
||||
.map((d) => `\n- "${d}"`)
|
||||
.join(),
|
||||
},
|
||||
fix: (fixer) => {
|
||||
expectedDependencyNames.sort().reduce((acc, d) => {
|
||||
acc[d] = rootPackageJsonDeps[d] || projDependencies[d];
|
||||
return acc;
|
||||
}, projPackageJsonDeps);
|
||||
|
||||
const dependencies = Object.keys(projPackageJsonDeps)
|
||||
.map((d) => `\n "${d}": "${projPackageJsonDeps[d]}"`)
|
||||
.join(',');
|
||||
|
||||
if (!node.properties.length) {
|
||||
return fixer.replaceText(
|
||||
node as any,
|
||||
`{\n "dependencies": {${dependencies}\n }\n}`
|
||||
);
|
||||
} else {
|
||||
return fixer.insertTextAfter(
|
||||
node.properties[node.properties.length - 1] as any,
|
||||
`,\n "dependencies": {${dependencies}\n }`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i]'](
|
||||
node: AST.JSONProperty
|
||||
) {
|
||||
return validateMissingDependencies(node);
|
||||
},
|
||||
['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i] > JSONObjectExpression > JSONProperty'](
|
||||
node: AST.JSONProperty
|
||||
) {
|
||||
const packageName = (node.key as any).value;
|
||||
const packageRange = (node.value as any).value;
|
||||
|
||||
if (ignoredDependencies.includes(packageName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (expectedDependencyNames.includes(packageName)) {
|
||||
return validateVersionMatchesInstalled(
|
||||
node,
|
||||
packageName,
|
||||
packageRange
|
||||
);
|
||||
} else {
|
||||
return reportObsoleteDependency(node, packageName);
|
||||
}
|
||||
},
|
||||
['JSONExpressionStatement > JSONObjectExpression'](
|
||||
node: AST.JSONObjectExpression
|
||||
) {
|
||||
return validateDependenciesSectionExistance(node);
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
@ -1,11 +1,6 @@
|
||||
import 'nx/src/utils/testing/mock-fs';
|
||||
|
||||
import type {
|
||||
FileData,
|
||||
ProjectFileMap,
|
||||
ProjectGraph,
|
||||
ProjectGraphDependency,
|
||||
} from '@nx/devkit';
|
||||
import type { FileData, ProjectFileMap, ProjectGraph } from '@nx/devkit';
|
||||
import { DependencyType } from '@nx/devkit';
|
||||
import * as parser from '@typescript-eslint/parser';
|
||||
import { TSESLint } from '@typescript-eslint/utils';
|
||||
|
||||
26
packages/eslint-plugin/src/utils/package-json-utils.ts
Normal file
26
packages/eslint-plugin/src/utils/package-json-utils.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { ProjectFileMap, readJsonFile } from '@nx/devkit';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
export function getAllDependencies(path: string): Record<string, string> {
|
||||
if (existsSync(path)) {
|
||||
const packageJson = readJsonFile(path);
|
||||
return {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
...packageJson.peerDependencies,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export function removePackageJsonFromFileMap(
|
||||
projectFileMap: ProjectFileMap
|
||||
): ProjectFileMap {
|
||||
const newFileMap = {};
|
||||
Object.keys(projectFileMap).forEach((key) => {
|
||||
newFileMap[key] = projectFileMap[key].filter(
|
||||
(f) => !f.file.endsWith('/package.json')
|
||||
);
|
||||
});
|
||||
return newFileMap;
|
||||
}
|
||||
@ -6,3 +6,5 @@ export {
|
||||
} from 'nx/src/plugins/js/utils/register';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
export { TargetProjectLocator } from 'nx/src/plugins/js/project-graph/build-dependencies/target-project-locator';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
export { findProjectsNpmDependencies } from 'nx/src/plugins/js/package-json/create-package-json';
|
||||
|
||||
@ -123,8 +123,7 @@ export function getHelperDependenciesFromProjectGraph(
|
||||
// we check if a dependency is part of the workspace and if it's a library
|
||||
// because we wouldn't want to include external dependencies (npm packages)
|
||||
if (
|
||||
!dependency.target.startsWith('npm:') &&
|
||||
!!projectGraph.nodes[dependency.target] &&
|
||||
projectGraph.nodes[dependency.target] &&
|
||||
projectGraph.nodes[dependency.target].type === 'lib'
|
||||
) {
|
||||
const targetData = projectGraph.nodes[dependency.target].data;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ESLint, Linter as LinterType } from 'eslint';
|
||||
import { Linter as LinterType } from 'eslint';
|
||||
|
||||
/**
|
||||
* This configuration is intended to apply to all TypeScript source files.
|
||||
@ -28,6 +28,20 @@ export const globalJavaScriptOverrides = {
|
||||
rules: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* This configuration is intended to apply to all JSON source files.
|
||||
* See the eslint-plugin package for what is in the referenced shareable config.
|
||||
*/
|
||||
export const globalJsonOverrides = {
|
||||
files: ['*.json'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
/**
|
||||
* Having an empty rules object present makes it more obvious to the user where they would
|
||||
* extend things from if they needed to
|
||||
*/
|
||||
rules: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* This configuration is intended to apply to all "source code" (but not
|
||||
* markup like HTML, or other custom file types like GraphQL)
|
||||
|
||||
@ -40,40 +40,15 @@ export function createPackageJson(
|
||||
} = {},
|
||||
fileMap: ProjectFileMap = null
|
||||
): PackageJson {
|
||||
if (fileMap == null) {
|
||||
fileMap = readProjectFileMapCache()?.projectFileMap || {};
|
||||
}
|
||||
|
||||
const projectNode = graph.nodes[projectName];
|
||||
const isLibrary = projectNode.type === 'lib';
|
||||
|
||||
const { selfInputs, dependencyInputs } = options.target
|
||||
? getTargetInputs(readNxJson(), projectNode, options.target)
|
||||
: { selfInputs: [], dependencyInputs: [] };
|
||||
|
||||
const npmDeps: NpmDeps = {
|
||||
dependencies: {},
|
||||
peerDependencies: {},
|
||||
peerDependenciesMeta: {},
|
||||
};
|
||||
|
||||
const seen = new Set<string>();
|
||||
|
||||
options.helperDependencies?.forEach((dep) => {
|
||||
seen.add(dep);
|
||||
npmDeps.dependencies[graph.externalNodes[dep].data.packageName] =
|
||||
graph.externalNodes[dep].data.version;
|
||||
recursivelyCollectPeerDependencies(dep, graph, npmDeps, seen);
|
||||
});
|
||||
|
||||
findAllNpmDeps(
|
||||
fileMap,
|
||||
const npmDeps = findProjectsNpmDependencies(
|
||||
projectNode,
|
||||
graph,
|
||||
npmDeps,
|
||||
seen,
|
||||
dependencyInputs,
|
||||
selfInputs
|
||||
options.target,
|
||||
{ helperDependencies: options.helperDependencies },
|
||||
fileMap
|
||||
);
|
||||
|
||||
// default package.json if one does not exist
|
||||
@ -200,6 +175,51 @@ export function createPackageJson(
|
||||
return packageJson;
|
||||
}
|
||||
|
||||
export function findProjectsNpmDependencies(
|
||||
projectNode: ProjectGraphProjectNode,
|
||||
graph: ProjectGraph,
|
||||
target: string,
|
||||
options: {
|
||||
helperDependencies?: string[];
|
||||
},
|
||||
fileMap?: ProjectFileMap
|
||||
): NpmDeps {
|
||||
if (fileMap == null) {
|
||||
fileMap = readProjectFileMapCache()?.projectFileMap || {};
|
||||
}
|
||||
|
||||
const { selfInputs, dependencyInputs } = target
|
||||
? getTargetInputs(readNxJson(), projectNode, target)
|
||||
: { selfInputs: [], dependencyInputs: [] };
|
||||
|
||||
const npmDeps: NpmDeps = {
|
||||
dependencies: {},
|
||||
peerDependencies: {},
|
||||
peerDependenciesMeta: {},
|
||||
};
|
||||
|
||||
const seen = new Set<string>();
|
||||
|
||||
options.helperDependencies?.forEach((dep) => {
|
||||
seen.add(dep);
|
||||
npmDeps.dependencies[graph.externalNodes[dep].data.packageName] =
|
||||
graph.externalNodes[dep].data.version;
|
||||
recursivelyCollectPeerDependencies(dep, graph, npmDeps, seen);
|
||||
});
|
||||
|
||||
findAllNpmDeps(
|
||||
fileMap,
|
||||
projectNode,
|
||||
graph,
|
||||
npmDeps,
|
||||
seen,
|
||||
dependencyInputs,
|
||||
selfInputs
|
||||
);
|
||||
|
||||
return npmDeps;
|
||||
}
|
||||
|
||||
function findAllNpmDeps(
|
||||
projectFileMap: ProjectFileMap,
|
||||
projectNode: ProjectGraphProjectNode,
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
formatFiles,
|
||||
joinPathFragments,
|
||||
logger,
|
||||
@ -16,9 +15,8 @@ import {
|
||||
|
||||
import type { Linter as ESLint } from 'eslint';
|
||||
|
||||
import { Schema as EsLintExecutorOptions } from '@nx/linter/src/executors/eslint/schema';
|
||||
import type { Schema as EsLintExecutorOptions } from '@nx/linter/src/executors/eslint/schema';
|
||||
|
||||
import { jsoncEslintParserVersion } from '../../utils/versions';
|
||||
import { PluginLintChecksGeneratorSchema } from './schema';
|
||||
import { NX_PREFIX } from 'nx/src/utils/logger';
|
||||
import { PackageJson, readNxMigrateConfig } from 'nx/src/utils/package-json';
|
||||
@ -57,17 +55,10 @@ export default async function pluginLintCheckGenerator(
|
||||
`${NX_PREFIX} plugin lint checks can only be added to plugins which use eslint for linting`
|
||||
);
|
||||
}
|
||||
const installTask = addDependenciesToPackageJson(
|
||||
host,
|
||||
{},
|
||||
{ 'jsonc-eslint-parser': jsoncEslintParserVersion }
|
||||
);
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(host);
|
||||
}
|
||||
|
||||
return () => installTask;
|
||||
}
|
||||
|
||||
export function addMigrationJsonChecks(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user