fix(linter): skip verifying deps of deps by default in package.json (#18058)
This commit is contained in:
parent
da2674ded6
commit
ae773d547e
@ -452,6 +452,12 @@ describe('Linter', () => {
|
|||||||
];
|
];
|
||||||
return json;
|
return json;
|
||||||
});
|
});
|
||||||
|
// Set this to false for now until the `@nx/js:lib` generator is updated to include ts/swc helpers by default.
|
||||||
|
// TODO(jack): Remove this once the above is addressed in another PR.
|
||||||
|
updateJson(`libs/${mylib}/tsconfig.lib.json`, (json) => {
|
||||||
|
json.compilerOptions.importHelpers = false;
|
||||||
|
return json;
|
||||||
|
});
|
||||||
updateJson(`libs/${mylib}/project.json`, (json) => {
|
updateJson(`libs/${mylib}/project.json`, (json) => {
|
||||||
json.targets.lint.options.lintFilePatterns = [
|
json.targets.lint.options.lintFilePatterns = [
|
||||||
`libs/${mylib}/**/*.ts`,
|
`libs/${mylib}/**/*.ts`,
|
||||||
@ -465,8 +471,7 @@ describe('Linter', () => {
|
|||||||
it('should report dependency check issues', () => {
|
it('should report dependency check issues', () => {
|
||||||
const rootPackageJson = readJson('package.json');
|
const rootPackageJson = readJson('package.json');
|
||||||
const nxVersion = rootPackageJson.devDependencies.nx;
|
const nxVersion = rootPackageJson.devDependencies.nx;
|
||||||
const swcCoreVersion = rootPackageJson.devDependencies['@swc/core'];
|
const tslibVersion = rootPackageJson.devDependencies['tslib'];
|
||||||
const swcHelpersVersion = rootPackageJson.dependencies['@swc/helpers'];
|
|
||||||
|
|
||||||
let out = runCLI(`lint ${mylib}`, { silenceError: true });
|
let out = runCLI(`lint ${mylib}`, { silenceError: true });
|
||||||
expect(out).toContain('All files pass linting');
|
expect(out).toContain('All files pass linting');
|
||||||
@ -495,9 +500,6 @@ describe('Linter', () => {
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nx/devkit": "${nxVersion}",
|
"@nx/devkit": "${nxVersion}",
|
||||||
"@swc/core": "${swcCoreVersion}",
|
|
||||||
"@swc/helpers": "${swcHelpersVersion}",
|
|
||||||
"nx": "${nxVersion}",
|
|
||||||
},
|
},
|
||||||
"name": "@proj/${mylib}",
|
"name": "@proj/${mylib}",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
|
|||||||
@ -104,7 +104,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -134,6 +134,74 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
expect(failures.length).toEqual(0);
|
expect(failures.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should exclude files not matching input of the build target', () => {
|
||||||
|
const packageJson = {
|
||||||
|
name: '@mycompany/liba',
|
||||||
|
dependencies: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileSys = {
|
||||||
|
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
|
||||||
|
'./libs/liba/src/index.ts': '',
|
||||||
|
'./libs/liba/project.json': JSON.stringify(
|
||||||
|
{
|
||||||
|
name: 'liba',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
command: 'tsc -p tsconfig.lib.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
'./nx.json': JSON.stringify({
|
||||||
|
targetDefaults: {
|
||||||
|
build: {
|
||||||
|
inputs: [
|
||||||
|
'{projectRoot}/**/*',
|
||||||
|
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'./package.json': JSON.stringify(rootPackageJson, null, 2),
|
||||||
|
};
|
||||||
|
vol.fromJSON(fileSys, '/root');
|
||||||
|
|
||||||
|
const failures = runRule(
|
||||||
|
{},
|
||||||
|
`/root/libs/liba/package.json`,
|
||||||
|
JSON.stringify(packageJson, null, 2),
|
||||||
|
{
|
||||||
|
nodes: {
|
||||||
|
liba: {
|
||||||
|
name: 'liba',
|
||||||
|
type: 'lib',
|
||||||
|
data: {
|
||||||
|
root: 'libs/liba',
|
||||||
|
targets: {
|
||||||
|
build: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
externalNodes,
|
||||||
|
dependencies: {
|
||||||
|
liba: [{ source: 'liba', target: 'npm:external1', type: 'static' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
liba: [
|
||||||
|
createFile(`libs/liba/src/main.ts`, []),
|
||||||
|
createFile(`libs/liba/src/main.spec.ts`, ['npm:external1']),
|
||||||
|
createFile(`libs/liba/package.json`, []),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(failures.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('should report missing dependencies section and fix it', () => {
|
it('should report missing dependencies section and fix it', () => {
|
||||||
const packageJson = {
|
const packageJson = {
|
||||||
name: '@mycompany/liba',
|
name: '@mycompany/liba',
|
||||||
@ -148,7 +216,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -191,7 +259,6 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
"{
|
"{
|
||||||
"name": "@mycompany/liba",
|
"name": "@mycompany/liba",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"external1": "~16.1.2"
|
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`);
|
`);
|
||||||
@ -211,7 +278,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{ ignoredDependencies: ['external1'] },
|
{ ignoredDependencies: ['external1'] },
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -255,7 +322,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{ ignoredDependencies: ['external1'] },
|
{ ignoredDependencies: ['external1'] },
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -299,7 +366,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{ ignoredDependencies: ['external1'] },
|
{ ignoredDependencies: ['external1'] },
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -344,7 +411,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -416,7 +483,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -473,7 +540,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -534,7 +601,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{ buildTargets: ['notbuild'] },
|
{ buildTargets: ['notbuild'] },
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -590,7 +657,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{ checkMissingDependencies: false },
|
{ checkMissingDependencies: false },
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -646,7 +713,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{ ignoredDependencies: ['external2'] },
|
{ ignoredDependencies: ['external2'] },
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -706,7 +773,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -778,7 +845,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -857,7 +924,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -936,7 +1003,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -1015,7 +1082,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{ checkObsoleteDependencies: false },
|
{ checkObsoleteDependencies: false },
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -1068,7 +1135,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{ ignoredDependencies: ['unneeded'] },
|
{ ignoredDependencies: ['unneeded'] },
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -1119,7 +1186,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -1204,7 +1271,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{ checkVersionMismatches: false },
|
{ checkVersionMismatches: false },
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -1261,7 +1328,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{ ignoredDependencies: ['external1'] },
|
{ ignoredDependencies: ['external1'] },
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -1330,7 +1397,7 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
include: ['**/*.ts'],
|
include: ['**/*.ts'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const tsConfiogBaseJson = {
|
const tsConfigBaseJson = {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
target: 'es2015',
|
target: 'es2015',
|
||||||
importHelpers: true,
|
importHelpers: true,
|
||||||
@ -1353,15 +1420,15 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
const fileSys = {
|
const fileSys = {
|
||||||
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
|
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
|
||||||
'./libs/liba/src/index.ts': '',
|
'./libs/liba/src/index.ts': '',
|
||||||
'./libs/libb/tsconfig.json': JSON.stringify(tsConfigJson, null, 2),
|
'./libs/liba/tsconfig.json': JSON.stringify(tsConfigJson, null, 2),
|
||||||
'./package.json': JSON.stringify(rootPackageJson, null, 2),
|
'./package.json': JSON.stringify(rootPackageJson, null, 2),
|
||||||
'./tsconfig.base.json': JSON.stringify(tsConfiogBaseJson, null, 2),
|
'./tsconfig.base.json': JSON.stringify(tsConfigBaseJson, null, 2),
|
||||||
};
|
};
|
||||||
vol.fromJSON(fileSys, '/root');
|
vol.fromJSON(fileSys, '/root');
|
||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -1370,21 +1437,11 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
type: 'lib',
|
type: 'lib',
|
||||||
data: {
|
data: {
|
||||||
root: 'libs/liba',
|
root: 'libs/liba',
|
||||||
targets: {
|
|
||||||
build: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
libb: {
|
|
||||||
name: 'libb',
|
|
||||||
type: 'lib',
|
|
||||||
data: {
|
|
||||||
root: 'libs/libb',
|
|
||||||
targets: {
|
targets: {
|
||||||
build: {
|
build: {
|
||||||
executor: '@nx/js:tsc',
|
executor: '@nx/js:tsc',
|
||||||
options: {
|
options: {
|
||||||
tsConfig: 'libs/libb/tsconfig.json',
|
tsConfig: 'libs/liba/tsconfig.json',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1393,26 +1450,20 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
},
|
},
|
||||||
externalNodes,
|
externalNodes,
|
||||||
dependencies: {
|
dependencies: {
|
||||||
liba: [
|
liba: [{ source: 'liba', target: 'npm:external1', type: 'static' }],
|
||||||
{ source: 'liba', target: 'npm:external1', type: 'static' },
|
|
||||||
{ source: 'liba', target: 'libb', type: 'static' },
|
|
||||||
],
|
|
||||||
libb: [{ source: 'libb', target: 'npm:external2', type: 'static' }],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
liba: [
|
liba: [
|
||||||
createFile(`libs/liba/src/main.ts`, ['npm:external1']),
|
createFile(`libs/liba/src/main.ts`, ['npm:external1']),
|
||||||
createFile(`libs/liba/package.json`, ['npm:external1']),
|
createFile(`libs/liba/package.json`, ['npm:external1']),
|
||||||
createFile(`libs/libb/src/main.ts`, ['npm:external2']),
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
expect(failures.length).toEqual(1);
|
expect(failures.length).toEqual(1);
|
||||||
expect(failures[0].message).toMatchInlineSnapshot(`
|
expect(failures[0].message).toMatchInlineSnapshot(`
|
||||||
"The "liba" uses the following packages, but they are missing from the "dependencies":
|
"The "liba" uses the following packages, but they are missing from the "dependencies":
|
||||||
- tslib
|
- tslib"
|
||||||
- external2"
|
|
||||||
`);
|
`);
|
||||||
expect(failures[0].line).toEqual(3);
|
expect(failures[0].line).toEqual(3);
|
||||||
});
|
});
|
||||||
@ -1435,14 +1486,14 @@ it('should require swc if @nx/js:swc executor', () => {
|
|||||||
const fileSys = {
|
const fileSys = {
|
||||||
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
|
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
|
||||||
'./libs/liba/src/index.ts': '',
|
'./libs/liba/src/index.ts': '',
|
||||||
'./libs/libb/.swcrc': JSON.stringify(swcrc, null, 2),
|
'./libs/liba/.swcrc': JSON.stringify(swcrc, null, 2),
|
||||||
'./package.json': JSON.stringify(rootPackageJson, null, 2),
|
'./package.json': JSON.stringify(rootPackageJson, null, 2),
|
||||||
};
|
};
|
||||||
vol.fromJSON(fileSys, '/root');
|
vol.fromJSON(fileSys, '/root');
|
||||||
|
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
`/root/libs/liba/package.json`,
|
||||||
JSON.stringify(packageJson, null, 2),
|
JSON.stringify(packageJson, null, 2),
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -1451,22 +1502,10 @@ it('should require swc if @nx/js:swc executor', () => {
|
|||||||
type: 'lib',
|
type: 'lib',
|
||||||
data: {
|
data: {
|
||||||
root: 'libs/liba',
|
root: 'libs/liba',
|
||||||
targets: {
|
|
||||||
build: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
libb: {
|
|
||||||
name: 'libb',
|
|
||||||
type: 'lib',
|
|
||||||
data: {
|
|
||||||
root: 'libs/libb',
|
|
||||||
targets: {
|
targets: {
|
||||||
build: {
|
build: {
|
||||||
executor: '@nx/js:swc',
|
executor: '@nx/js:swc',
|
||||||
options: {
|
options: {},
|
||||||
tsConfig: 'libs/libb/tsconfig.json',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1474,18 +1513,13 @@ it('should require swc if @nx/js:swc executor', () => {
|
|||||||
},
|
},
|
||||||
externalNodes,
|
externalNodes,
|
||||||
dependencies: {
|
dependencies: {
|
||||||
liba: [
|
liba: [{ source: 'liba', target: 'npm:external1', type: 'static' }],
|
||||||
{ source: 'liba', target: 'npm:external1', type: 'static' },
|
|
||||||
{ source: 'liba', target: 'libb', type: 'static' },
|
|
||||||
],
|
|
||||||
libb: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
liba: [
|
liba: [
|
||||||
createFile(`libs/liba/src/main.ts`, ['npm:external1']),
|
createFile(`libs/liba/src/main.ts`, ['npm:external1']),
|
||||||
createFile(`libs/liba/package.json`, ['npm:external1']),
|
createFile(`libs/liba/package.json`, ['npm:external1']),
|
||||||
createFile(`libs/libb/src/main.ts`),
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -1518,7 +1552,6 @@ function runRule(
|
|||||||
projectGraph: ProjectGraph,
|
projectGraph: ProjectGraph,
|
||||||
projectFileMap: ProjectFileMap
|
projectFileMap: ProjectFileMap
|
||||||
): Linter.LintMessage[] {
|
): Linter.LintMessage[] {
|
||||||
globalThis.projectPath = `${process.cwd()}/proj`;
|
|
||||||
globalThis.projectGraph = projectGraph;
|
globalThis.projectGraph = projectGraph;
|
||||||
globalThis.projectFileMap = projectFileMap;
|
globalThis.projectFileMap = projectFileMap;
|
||||||
globalThis.projectRootMappings = createProjectRootMappings(
|
globalThis.projectRootMappings = createProjectRootMappings(
|
||||||
|
|||||||
@ -1,18 +1,22 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
import { satisfies } from 'semver';
|
||||||
import { AST } from 'jsonc-eslint-parser';
|
import { AST } from 'jsonc-eslint-parser';
|
||||||
import { normalizePath, workspaceRoot } from '@nx/devkit';
|
import { type JSONLiteral } from 'jsonc-eslint-parser/lib/parser/ast';
|
||||||
|
import {
|
||||||
|
normalizePath,
|
||||||
|
ProjectGraphProjectNode,
|
||||||
|
FileData,
|
||||||
|
workspaceRoot,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { findNpmDependencies } from '@nx/js/src/utils/find-npm-dependencies';
|
||||||
|
|
||||||
import { createESLintRule } from '../utils/create-eslint-rule';
|
import { createESLintRule } from '../utils/create-eslint-rule';
|
||||||
import { readProjectGraph } from '../utils/project-graph-utils';
|
import { readProjectGraph } from '../utils/project-graph-utils';
|
||||||
import { findProject, getSourceFilePath } from '../utils/runtime-lint-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 {
|
import {
|
||||||
getAllDependencies,
|
getAllDependencies,
|
||||||
getPackageJson,
|
getPackageJson,
|
||||||
removePackageJsonFromFileMap,
|
|
||||||
} from '../utils/package-json-utils';
|
} from '../utils/package-json-utils';
|
||||||
import { JSONLiteral } from 'jsonc-eslint-parser/lib/parser/ast';
|
|
||||||
|
|
||||||
export type Options = [
|
export type Options = [
|
||||||
{
|
{
|
||||||
@ -22,6 +26,7 @@ export type Options = [
|
|||||||
checkVersionMismatches?: boolean;
|
checkVersionMismatches?: boolean;
|
||||||
checkMissingPackageJson?: boolean;
|
checkMissingPackageJson?: boolean;
|
||||||
ignoredDependencies?: string[];
|
ignoredDependencies?: string[];
|
||||||
|
includeTransitiveDependencies?: boolean;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -51,6 +56,7 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
checkMissingDependencies: { type: 'boolean' },
|
checkMissingDependencies: { type: 'boolean' },
|
||||||
checkObsoleteDependencies: { type: 'boolean' },
|
checkObsoleteDependencies: { type: 'boolean' },
|
||||||
checkVersionMismatches: { type: 'boolean' },
|
checkVersionMismatches: { type: 'boolean' },
|
||||||
|
includeTransitiveDependencies: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
@ -69,6 +75,7 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
checkObsoleteDependencies: true,
|
checkObsoleteDependencies: true,
|
||||||
checkVersionMismatches: true,
|
checkVersionMismatches: true,
|
||||||
ignoredDependencies: [],
|
ignoredDependencies: [],
|
||||||
|
includeTransitiveDependencies: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
create(
|
create(
|
||||||
@ -80,6 +87,7 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
checkMissingDependencies,
|
checkMissingDependencies,
|
||||||
checkObsoleteDependencies,
|
checkObsoleteDependencies,
|
||||||
checkVersionMismatches,
|
checkVersionMismatches,
|
||||||
|
includeTransitiveDependencies,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
) {
|
) {
|
||||||
@ -92,8 +100,7 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectPath = normalizePath(globalThis.projectPath || workspaceRoot);
|
const sourceFilePath = getSourceFilePath(fileName, workspaceRoot);
|
||||||
const sourceFilePath = getSourceFilePath(fileName, projectPath);
|
|
||||||
const { projectGraph, projectRootMappings, projectFileMap } =
|
const { projectGraph, projectRootMappings, projectFileMap } =
|
||||||
readProjectGraph(RULE_NAME);
|
readProjectGraph(RULE_NAME);
|
||||||
|
|
||||||
@ -120,32 +127,19 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// gather helper dependencies for @nx/js executors
|
|
||||||
const helperDependencies = getHelperDependenciesFromProjectGraph(
|
|
||||||
workspaceRoot,
|
|
||||||
sourceProject.name,
|
|
||||||
projectGraph
|
|
||||||
);
|
|
||||||
|
|
||||||
const rootPackageJson = getPackageJson(join(workspaceRoot, 'package.json'));
|
const rootPackageJson = getPackageJson(join(workspaceRoot, 'package.json'));
|
||||||
|
|
||||||
// find all dependencies for the project
|
const npmDependencies = findNpmDependencies(
|
||||||
const npmDeps = findProjectsNpmDependencies(
|
workspaceRoot,
|
||||||
sourceProject,
|
sourceProject,
|
||||||
projectGraph,
|
projectGraph,
|
||||||
buildTarget,
|
projectFileMap,
|
||||||
rootPackageJson,
|
buildTarget, // TODO: What if child library has a build target different from the parent?
|
||||||
{
|
{
|
||||||
helperDependencies: helperDependencies.map((dep) => dep.target),
|
includeTransitiveDependencies,
|
||||||
isProduction: true,
|
}
|
||||||
},
|
|
||||||
removePackageJsonFromFileMap(projectFileMap)
|
|
||||||
);
|
);
|
||||||
const projDependencies = {
|
const expectedDependencyNames = Object.keys(npmDependencies);
|
||||||
...npmDeps.dependencies,
|
|
||||||
...npmDeps.peerDependencies,
|
|
||||||
};
|
|
||||||
const expectedDependencyNames = Object.keys(projDependencies);
|
|
||||||
|
|
||||||
const projPackageJsonPath = join(
|
const projPackageJsonPath = join(
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
@ -180,7 +174,7 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
fix(fixer) {
|
fix(fixer) {
|
||||||
missingDeps.forEach((d) => {
|
missingDeps.forEach((d) => {
|
||||||
projPackageJsonDeps[d] =
|
projPackageJsonDeps[d] =
|
||||||
rootPackageJsonDeps[d] || projDependencies[d];
|
rootPackageJsonDeps[d] || npmDependencies[d];
|
||||||
});
|
});
|
||||||
|
|
||||||
const deps = (node.value as AST.JSONObjectExpression).properties;
|
const deps = (node.value as AST.JSONObjectExpression).properties;
|
||||||
@ -213,8 +207,9 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
projDependencies[packageName] === '*' ||
|
npmDependencies[packageName] === '*' ||
|
||||||
satisfies(projDependencies[packageName], packageRange)
|
packageRange === '*' ||
|
||||||
|
satisfies(npmDependencies[packageName], packageRange)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -224,13 +219,13 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
messageId: 'versionMismatch',
|
messageId: 'versionMismatch',
|
||||||
data: {
|
data: {
|
||||||
packageName: packageName,
|
packageName: packageName,
|
||||||
version: projDependencies[packageName],
|
version: npmDependencies[packageName],
|
||||||
},
|
},
|
||||||
fix: (fixer) =>
|
fix: (fixer) =>
|
||||||
fixer.replaceText(
|
fixer.replaceText(
|
||||||
node as any,
|
node as any,
|
||||||
`"${packageName}": "${
|
`"${packageName}": "${
|
||||||
rootPackageJsonDeps[packageName] || projDependencies[packageName]
|
rootPackageJsonDeps[packageName] || npmDependencies[packageName]
|
||||||
}"`
|
}"`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -308,15 +303,15 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
.join(),
|
.join(),
|
||||||
},
|
},
|
||||||
fix: (fixer) => {
|
fix: (fixer) => {
|
||||||
expectedDependencyNames.sort().reduce((acc, d) => {
|
|
||||||
acc[d] = rootPackageJsonDeps[d] || projDependencies[d];
|
|
||||||
return acc;
|
|
||||||
}, projPackageJsonDeps);
|
|
||||||
|
|
||||||
const dependencies = Object.keys(projPackageJsonDeps)
|
const dependencies = Object.keys(projPackageJsonDeps)
|
||||||
.map((d) => `\n "${d}": "${projPackageJsonDeps[d]}"`)
|
.map((d) => `\n "${d}": "${projPackageJsonDeps[d]}"`)
|
||||||
.join(',');
|
.join(',');
|
||||||
|
|
||||||
|
expectedDependencyNames.sort().reduce((acc, d) => {
|
||||||
|
acc[d] = rootPackageJsonDeps[d] || dependencies[d];
|
||||||
|
return acc;
|
||||||
|
}, projPackageJsonDeps);
|
||||||
|
|
||||||
if (!node.properties.length) {
|
if (!node.properties.length) {
|
||||||
return fixer.replaceText(
|
return fixer.replaceText(
|
||||||
node as any,
|
node as any,
|
||||||
@ -337,7 +332,7 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i]'](
|
['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i]'](
|
||||||
node: AST.JSONProperty
|
node: AST.JSONProperty
|
||||||
) {
|
) {
|
||||||
return validateMissingDependencies(node);
|
validateMissingDependencies(node);
|
||||||
},
|
},
|
||||||
['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i] > JSONObjectExpression > JSONProperty'](
|
['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i] > JSONObjectExpression > JSONProperty'](
|
||||||
node: AST.JSONProperty
|
node: AST.JSONProperty
|
||||||
@ -350,19 +345,15 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (expectedDependencyNames.includes(packageName)) {
|
if (expectedDependencyNames.includes(packageName)) {
|
||||||
return validateVersionMatchesInstalled(
|
validateVersionMatchesInstalled(node, packageName, packageRange);
|
||||||
node,
|
|
||||||
packageName,
|
|
||||||
packageRange
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return reportObsoleteDependency(node, packageName);
|
reportObsoleteDependency(node, packageName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
['JSONExpressionStatement > JSONObjectExpression'](
|
['JSONExpressionStatement > JSONObjectExpression'](
|
||||||
node: AST.JSONObjectExpression
|
node: AST.JSONObjectExpression
|
||||||
) {
|
) {
|
||||||
return validateDependenciesSectionExistance(node);
|
validateDependenciesSectionExistance(node);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export function getAllDependencies(
|
|||||||
...packageJson.dependencies,
|
...packageJson.dependencies,
|
||||||
...packageJson.devDependencies,
|
...packageJson.devDependencies,
|
||||||
...packageJson.peerDependencies,
|
...packageJson.peerDependencies,
|
||||||
|
...packageJson.optionalDependencies,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,15 +19,3 @@ export function getPackageJson(path: string): PackageJson {
|
|||||||
}
|
}
|
||||||
return {} as PackageJson;
|
return {} as PackageJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|||||||
400
packages/js/src/utils/find-npm-dependencies.spec.ts
Normal file
400
packages/js/src/utils/find-npm-dependencies.spec.ts
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
import 'nx/src/utils/testing/mock-fs';
|
||||||
|
import { vol } from 'memfs';
|
||||||
|
import { findNpmDependencies } from './find-npm-dependencies';
|
||||||
|
|
||||||
|
jest.mock('@nx/devkit', () => ({
|
||||||
|
...jest.requireActual<any>('@nx/devkit'),
|
||||||
|
workspaceRoot: '/root',
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('nx/src/utils/workspace-root', () => ({
|
||||||
|
workspaceRoot: '/root',
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('findNpmDependencies', () => {
|
||||||
|
const nxJson = {
|
||||||
|
targetDefaults: {
|
||||||
|
build: {
|
||||||
|
inputs: [
|
||||||
|
'{projectRoot}/**/*',
|
||||||
|
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vol.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pick up external npm dependencies and their versions', () => {
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'./nx.json': JSON.stringify(nxJson),
|
||||||
|
},
|
||||||
|
'/root'
|
||||||
|
);
|
||||||
|
const libWithExternalDeps = {
|
||||||
|
name: 'my-lib',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/my-lib',
|
||||||
|
targets: { build: {} },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const projectGraph = {
|
||||||
|
nodes: {
|
||||||
|
'my-lib': libWithExternalDeps,
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
'npm:foo': {
|
||||||
|
name: 'npm:foo' as const,
|
||||||
|
type: 'npm' as const,
|
||||||
|
data: {
|
||||||
|
packageName: 'foo',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {},
|
||||||
|
};
|
||||||
|
const projectFileMap = {
|
||||||
|
'my-lib': [
|
||||||
|
{
|
||||||
|
file: 'libs/my-lib/index.ts',
|
||||||
|
hash: '123',
|
||||||
|
deps: ['npm:foo'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = findNpmDependencies(
|
||||||
|
'/root',
|
||||||
|
libWithExternalDeps,
|
||||||
|
projectGraph,
|
||||||
|
projectFileMap,
|
||||||
|
'build'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(results).toEqual({
|
||||||
|
foo: '1.0.0',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pick up helper npm dependencies if required', () => {
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'./nx.json': JSON.stringify(nxJson),
|
||||||
|
'./libs/my-lib/tsconfig.json': JSON.stringify({
|
||||||
|
compilerOptions: {
|
||||||
|
importHelpers: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'./libs/my-lib/.swcrc': JSON.stringify({
|
||||||
|
jsc: {
|
||||||
|
externalHelpers: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
'/root'
|
||||||
|
);
|
||||||
|
const libWithHelpers = {
|
||||||
|
name: 'my-lib',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/my-lib',
|
||||||
|
targets: {
|
||||||
|
build1: {
|
||||||
|
executor: '@nx/js:tsc',
|
||||||
|
options: {
|
||||||
|
tsConfig: 'libs/my-lib/tsconfig.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build2: {
|
||||||
|
executor: '@nx/js:swc',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const projectGraph = {
|
||||||
|
nodes: {
|
||||||
|
'my-lib': libWithHelpers,
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
'npm:tslib': {
|
||||||
|
name: 'npm:tslib' as const,
|
||||||
|
type: 'npm' as const,
|
||||||
|
data: {
|
||||||
|
packageName: 'tslib',
|
||||||
|
version: '2.6.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'npm:@swc/helpers': {
|
||||||
|
name: 'npm:@swc/helpers' as const,
|
||||||
|
type: 'npm' as const,
|
||||||
|
data: {
|
||||||
|
packageName: '@swc/helpers',
|
||||||
|
version: '0.5.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {},
|
||||||
|
};
|
||||||
|
const projectFileMap = {
|
||||||
|
'my-lib': [],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
findNpmDependencies(
|
||||||
|
'/root',
|
||||||
|
libWithHelpers,
|
||||||
|
projectGraph,
|
||||||
|
projectFileMap,
|
||||||
|
'build1'
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
tslib: '2.6.0',
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
findNpmDependencies(
|
||||||
|
'/root',
|
||||||
|
libWithHelpers,
|
||||||
|
projectGraph,
|
||||||
|
projectFileMap,
|
||||||
|
'build2'
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
'@swc/helpers': '0.5.0',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not pick up helper npm dependencies if not required', () => {
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'./libs/my-lib/tsconfig.json': JSON.stringify({
|
||||||
|
compilerOptions: {
|
||||||
|
importHelpers: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'./libs/my-lib/.swcrc': JSON.stringify({
|
||||||
|
jsc: {
|
||||||
|
externalHelpers: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
'/root'
|
||||||
|
);
|
||||||
|
const libWithInlinedHelpers = {
|
||||||
|
name: 'my-lib',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/my-lib',
|
||||||
|
targets: {
|
||||||
|
build1: {
|
||||||
|
executor: '@nx/js:tsc',
|
||||||
|
options: {
|
||||||
|
tsConfig: 'libs/my-lib/tsconfig.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build2: {
|
||||||
|
executor: '@nx/js:swc',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const projectGraph = {
|
||||||
|
nodes: {
|
||||||
|
'my-lib': libWithInlinedHelpers,
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
'npm:tslib': {
|
||||||
|
name: 'npm:tslib' as const,
|
||||||
|
type: 'npm' as const,
|
||||||
|
data: {
|
||||||
|
packageName: 'tslib',
|
||||||
|
version: '2.6.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'npm:@swc/helpers': {
|
||||||
|
name: 'npm:@swc/helpers' as const,
|
||||||
|
type: 'npm' as const,
|
||||||
|
data: {
|
||||||
|
packageName: '@swc/helpers',
|
||||||
|
version: '0.5.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {},
|
||||||
|
};
|
||||||
|
const projectFileMap = {
|
||||||
|
'my-lib': [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = findNpmDependencies(
|
||||||
|
'/root',
|
||||||
|
libWithInlinedHelpers,
|
||||||
|
projectGraph,
|
||||||
|
projectFileMap,
|
||||||
|
'build'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(results).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support recursive collection of dependencies', () => {
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'./nx.json': JSON.stringify(nxJson),
|
||||||
|
},
|
||||||
|
'/root'
|
||||||
|
);
|
||||||
|
const parentLib = {
|
||||||
|
name: 'parent',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/parent',
|
||||||
|
targets: { build: {} },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const projectGraph = {
|
||||||
|
nodes: {
|
||||||
|
parent: parentLib,
|
||||||
|
child1: {
|
||||||
|
name: 'child1',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/child1',
|
||||||
|
targets: { build: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
child2: {
|
||||||
|
name: 'child2',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/child2',
|
||||||
|
targets: { build: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
'npm:foo': {
|
||||||
|
name: 'npm:foo' as const,
|
||||||
|
type: 'npm' as const,
|
||||||
|
data: {
|
||||||
|
packageName: 'foo',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
parent: [
|
||||||
|
{
|
||||||
|
type: 'static',
|
||||||
|
source: 'parent',
|
||||||
|
target: 'child1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
child1: [
|
||||||
|
{
|
||||||
|
type: 'static',
|
||||||
|
source: 'child1',
|
||||||
|
target: 'child2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
child2: [
|
||||||
|
{
|
||||||
|
type: 'static',
|
||||||
|
source: 'child2',
|
||||||
|
target: 'npm:foo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const projectFileMap = {
|
||||||
|
parent: [{ file: 'libs/parent/index.ts', hash: '123', deps: ['child1'] }],
|
||||||
|
child1: [{ file: 'libs/child1/index.ts', hash: '123', deps: ['child2'] }],
|
||||||
|
child2: [
|
||||||
|
{ file: 'libs/child2/index.ts', hash: '123', deps: ['npm:foo'] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = findNpmDependencies(
|
||||||
|
'/root',
|
||||||
|
parentLib,
|
||||||
|
projectGraph,
|
||||||
|
projectFileMap,
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
includeTransitiveDependencies: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(results).toEqual({
|
||||||
|
foo: '1.0.0',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find workspace dependencies', () => {
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'./libs/lib3/package.json': JSON.stringify({
|
||||||
|
name: '@acme/lib3',
|
||||||
|
version: '0.0.1',
|
||||||
|
}),
|
||||||
|
'./nx.json': JSON.stringify(nxJson),
|
||||||
|
},
|
||||||
|
'/root'
|
||||||
|
);
|
||||||
|
const lib1 = {
|
||||||
|
name: 'lib1',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/lib1',
|
||||||
|
targets: { build: {} },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const lib2 = {
|
||||||
|
name: 'lib2',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/lib2',
|
||||||
|
targets: { build: {} },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const lib3 = {
|
||||||
|
name: 'lib3',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/lib3',
|
||||||
|
targets: { build: {} },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const projectGraph = {
|
||||||
|
nodes: {
|
||||||
|
lib1: lib1,
|
||||||
|
lib2: lib2,
|
||||||
|
lib3: lib3,
|
||||||
|
},
|
||||||
|
externalNodes: {},
|
||||||
|
dependencies: {},
|
||||||
|
};
|
||||||
|
const projectFileMap = {
|
||||||
|
lib1: [{ file: 'libs/lib1/index.ts', hash: '123', deps: ['lib3'] }],
|
||||||
|
lib2: [{ file: 'libs/lib1/index.ts', hash: '123', deps: ['lib3'] }],
|
||||||
|
lib3: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
findNpmDependencies('/root', lib1, projectGraph, projectFileMap, 'build')
|
||||||
|
).toEqual({
|
||||||
|
'@acme/lib3': '*',
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
findNpmDependencies('/root', lib2, projectGraph, projectFileMap, 'build')
|
||||||
|
).toEqual({
|
||||||
|
'@acme/lib3': '*',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
191
packages/js/src/utils/find-npm-dependencies.ts
Normal file
191
packages/js/src/utils/find-npm-dependencies.ts
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
import { readNxJson } from 'nx/src/project-graph/file-utils';
|
||||||
|
import {
|
||||||
|
getTargetInputs,
|
||||||
|
filterUsingGlobPatterns,
|
||||||
|
} from 'nx/src/hasher/task-hasher';
|
||||||
|
import {
|
||||||
|
type ProjectGraph,
|
||||||
|
type ProjectGraphProjectNode,
|
||||||
|
type ProjectFileMap,
|
||||||
|
readJsonFile,
|
||||||
|
FileData,
|
||||||
|
joinPathFragments,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { fileExists } from 'nx/src/utils/fileutils';
|
||||||
|
import { fileDataDepTarget } from 'nx/src/config/project-graph';
|
||||||
|
import { readTsConfig } from './typescript/ts-config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all npm dependencies and their expected versions for a given project.
|
||||||
|
*/
|
||||||
|
export function findNpmDependencies(
|
||||||
|
workspaceRoot: string,
|
||||||
|
sourceProject: ProjectGraphProjectNode,
|
||||||
|
projectGraph: ProjectGraph,
|
||||||
|
projectFileMap: ProjectFileMap,
|
||||||
|
buildTarget: string,
|
||||||
|
options: {
|
||||||
|
includeTransitiveDependencies?: boolean;
|
||||||
|
} = {}
|
||||||
|
): Record<string, string> {
|
||||||
|
let seen: null | Set<string> = null;
|
||||||
|
if (options.includeTransitiveDependencies) {
|
||||||
|
seen = new Set<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: Record<string, string> = {};
|
||||||
|
|
||||||
|
function collectAll(
|
||||||
|
currentProject: ProjectGraphProjectNode,
|
||||||
|
collectedDeps: Record<string, string>
|
||||||
|
): void {
|
||||||
|
if (seen?.has(currentProject.name)) return;
|
||||||
|
|
||||||
|
collectDependenciesFromFileMap(
|
||||||
|
workspaceRoot,
|
||||||
|
currentProject,
|
||||||
|
projectGraph,
|
||||||
|
projectFileMap,
|
||||||
|
buildTarget,
|
||||||
|
collectedDeps
|
||||||
|
);
|
||||||
|
|
||||||
|
collectHelperDependencies(
|
||||||
|
workspaceRoot,
|
||||||
|
currentProject,
|
||||||
|
projectGraph,
|
||||||
|
buildTarget,
|
||||||
|
collectedDeps
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.includeTransitiveDependencies) {
|
||||||
|
const projectDeps = projectGraph.dependencies[currentProject.name];
|
||||||
|
for (const dep of projectDeps) {
|
||||||
|
const projectDep = projectGraph.nodes[dep.target];
|
||||||
|
if (projectDep) collectAll(projectDep, collectedDeps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collectAll(sourceProject, results);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep track of workspace libs we already read package.json for so we don't read from disk again.
|
||||||
|
const seenWorkspaceDeps: Record<string, { name: string; version: string }> = {};
|
||||||
|
|
||||||
|
function collectDependenciesFromFileMap(
|
||||||
|
workspaceRoot: string,
|
||||||
|
sourceProject: ProjectGraphProjectNode,
|
||||||
|
projectGraph: ProjectGraph,
|
||||||
|
projectFileMap: ProjectFileMap,
|
||||||
|
buildTarget: string,
|
||||||
|
npmDeps: Record<string, string>
|
||||||
|
): void {
|
||||||
|
const rawFiles = projectFileMap[sourceProject.name];
|
||||||
|
if (!rawFiles) return;
|
||||||
|
|
||||||
|
// Cannot read inputs if the target does not exist on the project.
|
||||||
|
if (!sourceProject.data.targets[buildTarget]) return;
|
||||||
|
|
||||||
|
const inputs = getTargetInputs(
|
||||||
|
readNxJson(),
|
||||||
|
sourceProject,
|
||||||
|
buildTarget
|
||||||
|
).selfInputs;
|
||||||
|
const files = filterUsingGlobPatterns(
|
||||||
|
sourceProject.data.root,
|
||||||
|
projectFileMap[sourceProject.name] || [],
|
||||||
|
inputs
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const fileData of files) {
|
||||||
|
if (
|
||||||
|
!fileData.deps ||
|
||||||
|
fileData.file ===
|
||||||
|
joinPathFragments(sourceProject.data.root, 'package.json')
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dep of fileData.deps) {
|
||||||
|
const target = fileDataDepTarget(dep);
|
||||||
|
|
||||||
|
// If the node is external, then read package info from `data`.
|
||||||
|
const externalDep = projectGraph.externalNodes[target];
|
||||||
|
if (externalDep?.type === 'npm') {
|
||||||
|
npmDeps[externalDep.data.packageName] = externalDep.data.version;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If node is internal, then try reading package info from `package.json` (which must exist for this to work).
|
||||||
|
const workspaceDep = projectGraph.nodes[target];
|
||||||
|
if (!workspaceDep) continue;
|
||||||
|
const cached = seenWorkspaceDeps[workspaceDep.name];
|
||||||
|
if (cached) {
|
||||||
|
npmDeps[cached.name] = cached.version;
|
||||||
|
} else {
|
||||||
|
const packageJson = readPackageJson(workspaceDep, workspaceRoot);
|
||||||
|
if (packageJson) {
|
||||||
|
// This is a workspace lib so we can't reliably read in a specific version since it depends on how the workspace is set up.
|
||||||
|
// ASSUMPTION: Most users will use '*' for workspace lib versions. Otherwise, they can manually update it.
|
||||||
|
npmDeps[packageJson.name] = '*';
|
||||||
|
seenWorkspaceDeps[workspaceDep.name] = {
|
||||||
|
name: packageJson.name,
|
||||||
|
version: '*',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPackageJson(
|
||||||
|
project: ProjectGraphProjectNode,
|
||||||
|
workspaceRoot: string
|
||||||
|
): null | {
|
||||||
|
name: string;
|
||||||
|
dependencies?: Record<string, string>;
|
||||||
|
optionalDependencies?: Record<string, string>;
|
||||||
|
peerDependencies?: Record<string, string>;
|
||||||
|
} {
|
||||||
|
const packageJsonPath = join(
|
||||||
|
workspaceRoot,
|
||||||
|
project.data.root,
|
||||||
|
'package.json'
|
||||||
|
);
|
||||||
|
if (fileExists(packageJsonPath)) return readJsonFile(packageJsonPath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectHelperDependencies(
|
||||||
|
workspaceRoot: string,
|
||||||
|
sourceProject: ProjectGraphProjectNode,
|
||||||
|
projectGraph: ProjectGraph,
|
||||||
|
buildTarget: string,
|
||||||
|
npmDeps: Record<string, string>
|
||||||
|
): void {
|
||||||
|
const target = sourceProject.data.targets[buildTarget];
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
if (target.executor === '@nx/js:tsc' && target.options?.tsConfig) {
|
||||||
|
const tsConfig = readTsConfig(join(workspaceRoot, target.options.tsConfig));
|
||||||
|
if (tsConfig?.options['importHelpers']) {
|
||||||
|
npmDeps['tslib'] = projectGraph.externalNodes['npm:tslib']?.data.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (target.executor === '@nx/js:swc') {
|
||||||
|
const swcConfigPath = target.options.swcrc
|
||||||
|
? join(workspaceRoot, target.options.swcrc)
|
||||||
|
: join(workspaceRoot, sourceProject.data.root, '.swcrc');
|
||||||
|
const swcConfig = fileExists(swcConfigPath)
|
||||||
|
? readJsonFile(swcConfigPath)
|
||||||
|
: {};
|
||||||
|
if (swcConfig?.jsc?.externalHelpers) {
|
||||||
|
npmDeps['@swc/helpers'] =
|
||||||
|
projectGraph.externalNodes['npm:@swc/helpers']?.data.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user