feat(linter): add option for @nx/dependency-checks to update workspace dependencies using local file paths (#20157)
Co-authored-by: Miroslav Jonaš <missing.manual@gmail.com>
This commit is contained in:
parent
c38440e85f
commit
f6d2dccd62
@ -22,6 +22,7 @@ export type Options = [
|
|||||||
ignoredDependencies?: string[];
|
ignoredDependencies?: string[];
|
||||||
ignoredFiles?: string[];
|
ignoredFiles?: string[];
|
||||||
includeTransitiveDependencies?: boolean;
|
includeTransitiveDependencies?: boolean;
|
||||||
|
useLocalPathsForWorkspaceDependencies?: boolean;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ export default ESLintUtils.RuleCreator(
|
|||||||
checkObsoleteDependencies: { type: 'boolean' },
|
checkObsoleteDependencies: { type: 'boolean' },
|
||||||
checkVersionMismatches: { type: 'boolean' },
|
checkVersionMismatches: { type: 'boolean' },
|
||||||
includeTransitiveDependencies: { type: 'boolean' },
|
includeTransitiveDependencies: { type: 'boolean' },
|
||||||
|
useLocalPathsForWorkspaceDependencies: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
@ -76,6 +78,7 @@ export default ESLintUtils.RuleCreator(
|
|||||||
ignoredDependencies: [],
|
ignoredDependencies: [],
|
||||||
ignoredFiles: [],
|
ignoredFiles: [],
|
||||||
includeTransitiveDependencies: false,
|
includeTransitiveDependencies: false,
|
||||||
|
useLocalPathsForWorkspaceDependencies: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
create(
|
create(
|
||||||
@ -89,6 +92,7 @@ export default ESLintUtils.RuleCreator(
|
|||||||
checkObsoleteDependencies,
|
checkObsoleteDependencies,
|
||||||
checkVersionMismatches,
|
checkVersionMismatches,
|
||||||
includeTransitiveDependencies,
|
includeTransitiveDependencies,
|
||||||
|
useLocalPathsForWorkspaceDependencies,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
) {
|
) {
|
||||||
@ -139,6 +143,7 @@ export default ESLintUtils.RuleCreator(
|
|||||||
{
|
{
|
||||||
includeTransitiveDependencies,
|
includeTransitiveDependencies,
|
||||||
ignoredFiles,
|
ignoredFiles,
|
||||||
|
useLocalPathsForWorkspaceDependencies,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const expectedDependencyNames = Object.keys(npmDependencies);
|
const expectedDependencyNames = Object.keys(npmDependencies);
|
||||||
@ -206,6 +211,8 @@ export default ESLintUtils.RuleCreator(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
npmDependencies[packageName].startsWith('file:') ||
|
||||||
|
packageRange.startsWith('file:') ||
|
||||||
npmDependencies[packageName] === '*' ||
|
npmDependencies[packageName] === '*' ||
|
||||||
packageRange === '*' ||
|
packageRange === '*' ||
|
||||||
satisfies(npmDependencies[packageName], packageRange, {
|
satisfies(npmDependencies[packageName], packageRange, {
|
||||||
|
|||||||
@ -389,12 +389,78 @@ describe('findNpmDependencies', () => {
|
|||||||
expect(
|
expect(
|
||||||
findNpmDependencies('/root', lib1, projectGraph, projectFileMap, 'build')
|
findNpmDependencies('/root', lib1, projectGraph, projectFileMap, 'build')
|
||||||
).toEqual({
|
).toEqual({
|
||||||
'@acme/lib3': '*',
|
'@acme/lib3': '0.0.1',
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
findNpmDependencies('/root', lib2, projectGraph, projectFileMap, 'build')
|
findNpmDependencies('/root', lib2, projectGraph, projectFileMap, 'build')
|
||||||
).toEqual({
|
).toEqual({
|
||||||
'@acme/lib3': '*',
|
'@acme/lib3': '0.0.1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support local path for workspace dependencies', () => {
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'./libs/c/package.json': JSON.stringify({
|
||||||
|
name: '@acme/c',
|
||||||
|
version: '0.0.1',
|
||||||
|
}),
|
||||||
|
'./nx.json': JSON.stringify(nxJson),
|
||||||
|
},
|
||||||
|
'/root'
|
||||||
|
);
|
||||||
|
const a = {
|
||||||
|
name: 'a',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/a',
|
||||||
|
targets: { build: {} },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const b = {
|
||||||
|
name: 'b',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/b',
|
||||||
|
targets: { build: {} },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const c = {
|
||||||
|
name: 'c',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/c',
|
||||||
|
targets: { build: {} },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const projectGraph = {
|
||||||
|
nodes: {
|
||||||
|
a: a,
|
||||||
|
b: b,
|
||||||
|
c: c,
|
||||||
|
},
|
||||||
|
externalNodes: {},
|
||||||
|
dependencies: {},
|
||||||
|
};
|
||||||
|
const projectFileMap = {
|
||||||
|
a: [{ file: 'libs/a/index.ts', hash: '123', deps: ['c'] }],
|
||||||
|
b: [{ file: 'libs/a/index.ts', hash: '123', deps: ['c'] }],
|
||||||
|
c: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
findNpmDependencies('/root', a, projectGraph, projectFileMap, 'build', {
|
||||||
|
useLocalPathsForWorkspaceDependencies: true,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
'@acme/c': 'file:../c',
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
findNpmDependencies('/root', b, projectGraph, projectFileMap, 'build', {
|
||||||
|
useLocalPathsForWorkspaceDependencies: true,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
'@acme/c': 'file:../c',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { join } from 'path';
|
import { join, relative } from 'path';
|
||||||
import { readNxJson } from 'nx/src/config/configuration';
|
import { readNxJson } from 'nx/src/config/configuration';
|
||||||
import {
|
import {
|
||||||
|
joinPathFragments,
|
||||||
|
normalizePath,
|
||||||
|
type ProjectFileMap,
|
||||||
type ProjectGraph,
|
type ProjectGraph,
|
||||||
type ProjectGraphProjectNode,
|
type ProjectGraphProjectNode,
|
||||||
type ProjectFileMap,
|
|
||||||
readJsonFile,
|
readJsonFile,
|
||||||
FileData,
|
|
||||||
joinPathFragments,
|
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { fileExists } from 'nx/src/utils/fileutils';
|
import { fileExists } from 'nx/src/utils/fileutils';
|
||||||
import { fileDataDepTarget } from 'nx/src/config/project-graph';
|
import { fileDataDepTarget } from 'nx/src/config/project-graph';
|
||||||
@ -28,6 +28,7 @@ export function findNpmDependencies(
|
|||||||
options: {
|
options: {
|
||||||
includeTransitiveDependencies?: boolean;
|
includeTransitiveDependencies?: boolean;
|
||||||
ignoredFiles?: string[];
|
ignoredFiles?: string[];
|
||||||
|
useLocalPathsForWorkspaceDependencies?: boolean;
|
||||||
} = {}
|
} = {}
|
||||||
): Record<string, string> {
|
): Record<string, string> {
|
||||||
let seen: null | Set<string> = null;
|
let seen: null | Set<string> = null;
|
||||||
@ -51,6 +52,7 @@ export function findNpmDependencies(
|
|||||||
projectFileMap,
|
projectFileMap,
|
||||||
buildTarget,
|
buildTarget,
|
||||||
options.ignoredFiles,
|
options.ignoredFiles,
|
||||||
|
options.useLocalPathsForWorkspaceDependencies,
|
||||||
collectedDeps
|
collectedDeps
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -86,6 +88,7 @@ function collectDependenciesFromFileMap(
|
|||||||
projectFileMap: ProjectFileMap,
|
projectFileMap: ProjectFileMap,
|
||||||
buildTarget: string,
|
buildTarget: string,
|
||||||
ignoredFiles: string[],
|
ignoredFiles: string[],
|
||||||
|
useLocalPathsForWorkspaceDependencies: boolean,
|
||||||
npmDeps: Record<string, string>
|
npmDeps: Record<string, string>
|
||||||
): void {
|
): void {
|
||||||
const rawFiles = projectFileMap[sourceProject.name];
|
const rawFiles = projectFileMap[sourceProject.name];
|
||||||
@ -140,12 +143,26 @@ function collectDependenciesFromFileMap(
|
|||||||
// Make sure package.json exists and has a valid name.
|
// Make sure package.json exists and has a valid name.
|
||||||
packageJson?.name
|
packageJson?.name
|
||||||
) {
|
) {
|
||||||
// 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.
|
let version: string;
|
||||||
// ASSUMPTION: Most users will use '*' for workspace lib versions. Otherwise, they can manually update it.
|
if (useLocalPathsForWorkspaceDependencies) {
|
||||||
npmDeps[packageJson.name] = '*';
|
// Find the relative `file:...` path and use that as the version value.
|
||||||
|
// This is useful for monorepos like Nx where the release will handle setting the correct version in dist.
|
||||||
|
const depRoot = join(workspaceRoot, workspaceDep.data.root);
|
||||||
|
const ownRoot = join(workspaceRoot, sourceProject.data.root);
|
||||||
|
const relativePath = relative(ownRoot, depRoot);
|
||||||
|
const filePath = normalizePath(relativePath); // normalize slashes for windows
|
||||||
|
version = `file:${filePath}`;
|
||||||
|
} else {
|
||||||
|
// Otherwise, read the version from the dependencies `package.json` file.
|
||||||
|
// This is useful for monorepos that commit release versions.
|
||||||
|
// Users can also set version as "*" in source `package.json` files, which will be the value set here.
|
||||||
|
// This is useful if they use custom scripts to update them in dist.
|
||||||
|
version = packageJson.version ?? '*'; // fallback in case version is missing
|
||||||
|
}
|
||||||
|
npmDeps[packageJson.name] = version;
|
||||||
seenWorkspaceDeps[workspaceDep.name] = {
|
seenWorkspaceDeps[workspaceDep.name] = {
|
||||||
name: packageJson.name,
|
name: packageJson.name,
|
||||||
version: '*',
|
version,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,6 +175,7 @@ function readPackageJson(
|
|||||||
workspaceRoot: string
|
workspaceRoot: string
|
||||||
): null | {
|
): null | {
|
||||||
name: string;
|
name: string;
|
||||||
|
version?: string;
|
||||||
dependencies?: Record<string, string>;
|
dependencies?: Record<string, string>;
|
||||||
optionalDependencies?: Record<string, string>;
|
optionalDependencies?: Record<string, string>;
|
||||||
peerDependencies?: Record<string, string>;
|
peerDependencies?: Record<string, string>;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user