Skip to content

Commit

Permalink
feat(linter): add option for @nx/dependency-checks to update workspac…
Browse files Browse the repository at this point in the history
…e dependencies using local file paths
  • Loading branch information
jaysoo committed Nov 9, 2023
1 parent 40f54d5 commit b99b1c9
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 11 deletions.
7 changes: 7 additions & 0 deletions packages/eslint-plugin/src/rules/dependency-checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type Options = [
ignoredDependencies?: string[];
ignoredFiles?: string[];
includeTransitiveDependencies?: boolean;
useLocalPathsForWorkspaceDependencies?: boolean;
}
];

Expand Down Expand Up @@ -53,6 +54,7 @@ export default ESLintUtils.RuleCreator(() => ``)<Options, MessageIds>({
checkObsoleteDependencies: { type: 'boolean' },
checkVersionMismatches: { type: 'boolean' },
includeTransitiveDependencies: { type: 'boolean' },
useLocalPathsForWorkspaceDependencies: { type: 'boolean' },
},
additionalProperties: false,
},
Expand All @@ -73,6 +75,7 @@ export default ESLintUtils.RuleCreator(() => ``)<Options, MessageIds>({
ignoredDependencies: [],
ignoredFiles: [],
includeTransitiveDependencies: false,
useLocalPathsForWorkspaceDependencies: false,
},
],
create(
Expand All @@ -86,6 +89,7 @@ export default ESLintUtils.RuleCreator(() => ``)<Options, MessageIds>({
checkObsoleteDependencies,
checkVersionMismatches,
includeTransitiveDependencies,
useLocalPathsForWorkspaceDependencies,
},
]
) {
Expand Down Expand Up @@ -136,6 +140,7 @@ export default ESLintUtils.RuleCreator(() => ``)<Options, MessageIds>({
{
includeTransitiveDependencies,
ignoredFiles,
useLocalPathsForWorkspaceDependencies,
}
);
const expectedDependencyNames = Object.keys(npmDependencies);
Expand Down Expand Up @@ -203,6 +208,8 @@ export default ESLintUtils.RuleCreator(() => ``)<Options, MessageIds>({
return;
}
if (
npmDependencies[packageName].startsWith('file:') ||
packageRange.startsWith('file:') ||
npmDependencies[packageName] === '*' ||
packageRange === '*' ||
satisfies(npmDependencies[packageName], packageRange, {
Expand Down
70 changes: 68 additions & 2 deletions packages/js/src/utils/find-npm-dependencies.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,12 +389,78 @@ describe('findNpmDependencies', () => {
expect(
findNpmDependencies('/root', lib1, projectGraph, projectFileMap, 'build')
).toEqual({
'@acme/lib3': '*',
'@acme/lib3': '0.0.1',
});
expect(
findNpmDependencies('/root', lib2, projectGraph, projectFileMap, 'build')
).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',
});
});

Expand Down
36 changes: 27 additions & 9 deletions packages/js/src/utils/find-npm-dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { join } from 'path';
import { join, relative } from 'path';
import { readNxJson } from 'nx/src/config/configuration';
import {
getTargetInputs,
filterUsingGlobPatterns,
getTargetInputs,
} from 'nx/src/hasher/task-hasher';
import {
joinPathFragments,
normalizePath,
type ProjectFileMap,
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';
Expand All @@ -28,6 +28,7 @@ export function findNpmDependencies(
options: {
includeTransitiveDependencies?: boolean;
ignoredFiles?: string[];
useLocalPathsForWorkspaceDependencies?: boolean;
} = {}
): Record<string, string> {
let seen: null | Set<string> = null;
Expand All @@ -51,6 +52,7 @@ export function findNpmDependencies(
projectFileMap,
buildTarget,
options.ignoredFiles,
options.useLocalPathsForWorkspaceDependencies,
collectedDeps
);

Expand Down Expand Up @@ -86,6 +88,7 @@ function collectDependenciesFromFileMap(
projectFileMap: ProjectFileMap,
buildTarget: string,
ignoredFiles: string[],
useLocalPathsForWorkspaceDependencies: boolean,
npmDeps: Record<string, string>
): void {
const rawFiles = projectFileMap[sourceProject.name];
Expand Down Expand Up @@ -140,12 +143,26 @@ function collectDependenciesFromFileMap(
// Make sure package.json exists and has a valid 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.
// ASSUMPTION: Most users will use '*' for workspace lib versions. Otherwise, they can manually update it.
npmDeps[packageJson.name] = '*';
let version: string;
if (useLocalPathsForWorkspaceDependencies) {
// 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] = {
name: packageJson.name,
version: '*',
version,
};
}
}
Expand All @@ -158,6 +175,7 @@ function readPackageJson(
workspaceRoot: string
): null | {
name: string;
version?: string;
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
peerDependencies?: Record<string, string>;
Expand Down

0 comments on commit b99b1c9

Please sign in to comment.