Skip to content

Commit

Permalink
feat: make normalize-package-dependencies work with new project.names
Browse files Browse the repository at this point in the history
  • Loading branch information
Hotell committed Jul 2, 2024
1 parent f3e4fdb commit 5ee689c
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import generator from './index';
import { PackageJson } from '../../types';
import { disableChalk } from '../../utils-testing';

const graphMock: ProjectGraph = {
let graphMock: ProjectGraph;
const getBlankGraphMock = () => ({
dependencies: {},
nodes: {},
externalNodes: {},
};
});

jest.mock('@nx/devkit', () => {
async function createProjectGraphAsyncMock(): Promise<ProjectGraph> {
Expand All @@ -43,6 +44,9 @@ describe('normalize-package-dependencies generator', () => {
const infoLogSpy = jest.spyOn(console, 'info').mockImplementation(noop);

beforeEach(() => {
graphMock = {
...getBlankGraphMock(),
};
tree = createTreeWithEmptyWorkspace();
tree = createProject(tree, {
projectName: 'react-one',
Expand Down Expand Up @@ -161,13 +165,13 @@ describe('normalize-package-dependencies generator', () => {

expect(logLogSpy.mock.calls.flat()).toMatchInlineSnapshot(`
Array [
"@proj/react-one has following dependency version issues:",
"[react-one] project has following dependency version issues:",
" - @proj/build-tool@^1.0.0",
"",
"@proj/react-two has following dependency version issues:",
"[react-two] project has following dependency version issues:",
" - @proj/build-tool@^1.0.0",
"",
"@proj/react-app has following dependency version issues:",
"[react-app] project has following dependency version issues:",
" - @proj/build-tool@^1.0.0",
" - @proj/react-one@^1.0.0",
" - @proj/react-two@^1.0.0",
Expand Down Expand Up @@ -198,7 +202,7 @@ describe('normalize-package-dependencies generator', () => {

expect(logLogSpy.mock.calls.flat()).toMatchInlineSnapshot(`
Array [
"@proj/react-app has following dependency version issues:",
"[react-app] project has following dependency version issues:",
" - @proj/react-four@>=1.0.0-alpha",
"",
]
Expand All @@ -209,7 +213,7 @@ describe('normalize-package-dependencies generator', () => {
describe(`application`, () => {
it(`should update workspace devDependencies,dependencies,peerDependencies versions to "*"`, async () => {
// this tests that dependency which has different major version (thus is installed from npm) will not be updated to '*'
addNonWorkspaceDependency(tree, '@proj/react-app', 'prod', {
addNonWorkspaceDependency(tree, 'react-app', 'prod', {
pkgName: '@proj/react-three',
pkgVersion: '^0.1.0',
});
Expand Down Expand Up @@ -288,11 +292,11 @@ describe('normalize-package-dependencies generator', () => {
describe(`library`, () => {
it(`should update workspace only devDependencies versions to "*"`, async () => {
// this tests that dependency which has different major version (thus is installed from npm) will not be updated to '*'
addNonWorkspaceDependency(tree, '@proj/react-three', 'prod', {
addNonWorkspaceDependency(tree, 'react-three', 'prod', {
pkgName: '@proj/react-one',
pkgVersion: '^0.1.0',
});
addNonWorkspaceDependency(tree, '@proj/react-three', 'dev', {
addNonWorkspaceDependency(tree, 'react-three', 'dev', {
pkgName: '@proj/build-tool',
pkgVersion: '^0.1.0',
});
Expand Down Expand Up @@ -325,10 +329,10 @@ describe('normalize-package-dependencies generator', () => {
function getPackageJsonForAllProjects(tree: Tree) {
const projects = getProjects(tree);

const reactOne = projects.get('@proj/react-one');
const reactTwo = projects.get('@proj/react-two');
const reactThree = projects.get('@proj/react-three');
const reactApp = projects.get('@proj/react-app');
const reactOne = projects.get('react-one');
const reactTwo = projects.get('react-two');
const reactThree = projects.get('react-three');
const reactApp = projects.get('react-app');

return {
reactOne: readJson<PackageJson>(tree, joinPathFragments(reactOne!.root, 'package.json')),
Expand All @@ -347,8 +351,8 @@ function updateProject(
},
) {
const { projectName, version, dependencies } = options;
const packageName = `@proj/${projectName}`;
const project = readProjectConfiguration(tree, packageName);

const project = readProjectConfiguration(tree, projectName);
updateJson<PackageJson>(tree, joinPathFragments(project.root, 'package.json'), json => {
if (version) {
json.version = version;
Expand Down Expand Up @@ -388,21 +392,25 @@ function createProject(
peerDependencies: { ...deps.peer },
});

addProjectConfiguration(tree, packageName, {
addProjectConfiguration(tree, projectName, {
root: rootPath,
projectType,
...(tags ? { tags } : null),
});

const depKeys = [...Object.keys(deps.prod), ...Object.keys(deps.dev), ...Object.keys(deps.peer)];

graphMock.dependencies[packageName] = depKeys.map(value => {
return { source: packageName, target: value, type: 'static' };
graphMock.dependencies[projectName] = depKeys.map(value => {
return {
source: projectName,
target: value.startsWith('@proj/') ? value.replace('@proj/', '') : `npm:${value}`,
type: 'static',
};
});
graphMock.nodes[packageName] = {
name: packageName,
graphMock.nodes[projectName] = {
name: projectName,
type: projectType === 'library' ? 'lib' : 'app',
data: { name: packageName, root: rootPath },
data: { name: projectName, root: rootPath },
};

return tree;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import semver from 'semver';

import { NormalizePackageDependenciesGeneratorSchema } from './schema';
import { PackageJson } from '../../types';
import { getNpmScope } from '../../utils';

type ProjectIssues = { [projectName: string]: { [depName: string]: string } };

Expand All @@ -27,12 +28,18 @@ export default async function (tree: Tree, schema: NormalizePackageDependenciesG

const graph = await createProjectGraphAsync();

const npmScope = getNpmScope(tree);
const projects = getProjects(tree);
const issues: ProjectIssues = {};

projects.forEach(projectConfig => {
if (normalizedOptions.verify) {
const foundIssues = getPackageJsonDependenciesIssues(tree, { allProjects: projects, projectConfig, graph });
const foundIssues = getPackageJsonDependenciesIssues(tree, {
allProjects: projects,
projectConfig,
graph,
npmScope,
});

if (foundIssues) {
issues[projectConfig.name!] = foundIssues;
Expand All @@ -41,7 +48,7 @@ export default async function (tree: Tree, schema: NormalizePackageDependenciesG
return;
}

normalizePackageJsonDependencies(tree, { allProjects: projects, projectConfig, graph });
normalizePackageJsonDependencies(tree, { allProjects: projects, projectConfig, graph, npmScope });
});

reportPackageJsonDependenciesIssues(issues);
Expand All @@ -55,9 +62,10 @@ function normalizePackageJsonDependencies(
allProjects: ReturnType<typeof getProjects>;
projectConfig: ProjectConfiguration;
graph: ProjectGraph;
npmScope: string;
},
) {
const { allProjects, graph, projectConfig } = options;
const { allProjects, graph, projectConfig, npmScope } = options;
const projectDependencies = getProjectDependenciesFromGraph(projectConfig.name!, graph);
const packageJsonPath = joinPathFragments(projectConfig.root, 'package.json');

Expand All @@ -74,18 +82,19 @@ function normalizePackageJsonDependencies(

return tree;

function updateDepType(json: PackageJson, depType: 'dependencies' | 'devDependencies' | 'peerDependencies') {
const deps = json[depType];
if (!deps) {
return;
}

for (const packageName in deps) {
if (isProjectDependencyAnWorkspaceProject(graph, packageName, projectDependencies)) {
const { updated } = getVersion(tree, { allProjects, deps, packageName });
deps[packageName] = updated;
function updateDepType(json: PackageJson, depType: DepType) {
processDepType(json, depType, npmScope, (deps, npmPackageName, projectName) => {
if (isProjectDependencyAnWorkspaceProject(graph, projectName, projectDependencies)) {
const { updated } = getVersion(tree, {
allProjects,
deps,
npmPackageName,
projectName,
});

deps[npmPackageName] = updated;
}
}
});
}
}

Expand All @@ -97,7 +106,7 @@ function reportPackageJsonDependenciesIssues(issues: ProjectIssues) {
}

issueEntries.forEach(([projectName, dependencyIssues]) => {
logger.log(chalk.bold(chalk.red(`${projectName} has following dependency version issues:`)));
logger.log(chalk.red(`[${chalk.bold(projectName)}] project has following dependency version issues:`));
// eslint-disable-next-line guard-for-in
for (const dep in dependencyIssues) {
logger.log(chalk.red(` - ${dep}@${dependencyIssues[dep]}`));
Expand All @@ -117,12 +126,22 @@ function getVersion(
tree: Tree,
options: {
allProjects: ReturnType<typeof getProjects>;
/**
* dependencies from package.json -> including npm scope
*/
deps: Record<string, string>;
packageName: string;
/**
* name from package.json - must contain npm scope
*/
npmPackageName: string;
/**
* project name - doesn't contain npm scope
*/
projectName: string;
},
) {
const { allProjects, deps, packageName } = options;
const current = deps[packageName];
const { allProjects, deps, npmPackageName, projectName } = options;
const current = deps[npmPackageName];
const updated = getUpdatedVersion(current);

const match = current === updated;
Expand All @@ -135,9 +154,9 @@ function getVersion(
}

if (NORMALIZED_PRERELEASE_RANGE_VERSION_REGEXP.test(currentVersion)) {
const prereleasePkg = allProjects.get(packageName);
const prereleasePkg = allProjects.get(projectName);
if (!prereleasePkg) {
throw new Error(`Package ${packageName} not found in the workspace`);
throw new Error(`Package ${projectName} not found in the workspace`);
}
const prereleasePkgJson = readJson<PackageJson>(tree, joinPathFragments(prereleasePkg.root, 'package.json'));
const isPrerelease = semver.prerelease(prereleasePkgJson.version) !== null;
Expand All @@ -161,13 +180,15 @@ function getPackageJsonDependenciesIssues(
allProjects: ReturnType<typeof getProjects>;
projectConfig: ProjectConfiguration;
graph: ProjectGraph;
npmScope: string;
},
): Record<string, string> | null {
const { allProjects, projectConfig, graph } = options;
const { allProjects, projectConfig, graph, npmScope } = options;
const projectDependencies = getProjectDependenciesFromGraph(projectConfig.name!, graph);
const packageJson = readJson<PackageJson>(tree, joinPathFragments(projectConfig.root, 'package.json'));

let issues: Record<string, string> | null = null;

checkDepType(packageJson, 'devDependencies');

if (projectConfig.projectType === 'application') {
Expand All @@ -177,23 +198,51 @@ function getPackageJsonDependenciesIssues(

return issues;

function checkDepType(json: PackageJson, depType: 'dependencies' | 'devDependencies' | 'peerDependencies') {
const deps = json[depType];
if (!deps) {
return null;
}

// eslint-disable-next-line guard-for-in
for (const packageName in deps) {
const { match } = getVersion(tree, { allProjects, deps, packageName });
function checkDepType(json: PackageJson, depType: DepType) {
processDepType(json, depType, npmScope, (deps, npmPackageName, projectName) => {
const { match } = getVersion(tree, {
allProjects,
deps,
npmPackageName,
projectName,
});

if (isProjectDependencyAnWorkspaceProject(graph, packageName, projectDependencies) && !match) {
if (isProjectDependencyAnWorkspaceProject(graph, projectName, projectDependencies) && !match) {
issues = issues ?? {};
issues[packageName] = deps[packageName];
issues[npmPackageName] = deps[npmPackageName];
}
});
}
}

type DepType = 'dependencies' | 'devDependencies' | 'peerDependencies';
type Callback = (
/** package.json dependencies - names include npm scope */
deps: Record<string, string>,
/** npm valid package name - can include npm scope */
npmPackageName: string,
/** workspace project name - wont include npm scope */
projectName: string,
) => void;

function processDepType(json: PackageJson, depType: DepType, npmScope: string, callback: Callback) {
const deps = json[depType];

if (!deps) {
return;
}

const npmScopeToCheck = `@${npmScope}/`;

// eslint-disable-next-line guard-for-in
for (const npmPackageName in deps) {
if (!npmPackageName.startsWith(npmScopeToCheck)) {
continue;
}

return issues;
const projectName = npmPackageName.replace(npmScopeToCheck, '');

callback(deps, npmPackageName, projectName);
}
}

Expand Down
22 changes: 15 additions & 7 deletions tools/workspace-plugin/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,26 @@ export function getWorkspaceConfig(tree: Tree): NxJsonConfiguration & { npmScope
throw new Error('nx.json doesnt exist at root of monorepo');
}

const npmScope = getNpmScope(tree);
return {
npmScope,
...nxConfig,
};
}

export function getNpmScope(tree: Tree) {
const packageJSON = readJson<PackageJson>(tree, '/package.json');
const matchedName = NPM_SCOPE_REGEX.exec(packageJSON.name);

if (!matchedName) {
throw new Error('root package.json doesnt provide valid monorepo name');
throw new Error(`root package.json doesn't provide valid monorepo name`);
}
if (!matchedName[1]) {
throw new Error(
'unable to obtain monorepo npmScope. Please make sure that root package.json#name includes npmScope',
);
}

const [, npmScope] = matchedName;
return {
npmScope,
...nxConfig,
};
return matchedName[1];
}

export function getProjectNameWithoutScope(projectName: string) {
Expand Down

0 comments on commit 5ee689c

Please sign in to comment.