From 5d56e211631dc5b6589d1f0988596bd4eb65ccdd Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Wed, 12 Jun 2024 11:18:29 -0400 Subject: [PATCH] fix(core): read project name from package json if not set in project json (#26386) ## Current Behavior If `project.json` exists without a name, we infer one based on the root path. This can be confusing when there exists a `package.json` alongside it that contains a name which doesn't match our inferred name. ## Expected Behavior If `project.json` and `package.json` both exist, the name from `package.json` will be used if `project.json` contains no name. ## Related Issue(s) Fixes #26347 --- packages/nx/migrations.json | 6 ++ .../update-19-2-4/set-project-name.spec.ts | 100 ++++++++++++++++++ .../update-19-2-4/set-project-name.ts | 43 ++++++++ .../project-json/build-nodes/project-json.ts | 20 +++- packages/nx/src/utils/package-json.ts | 1 + 5 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 packages/nx/src/migrations/update-19-2-4/set-project-name.spec.ts create mode 100644 packages/nx/src/migrations/update-19-2-4/set-project-name.ts diff --git a/packages/nx/migrations.json b/packages/nx/migrations.json index 08cf044c79dfe..1a495445cee32 100644 --- a/packages/nx/migrations.json +++ b/packages/nx/migrations.json @@ -83,6 +83,12 @@ "version": "19.2.2-beta.0", "description": "Updates the nx wrapper.", "implementation": "./src/migrations/update-17-3-0/update-nxw" + }, + "19-2-4-set-project-name": { + "version": "19.2.4-beta.0", + "description": "Set project name in nx.json explicitly", + "implementation": "./src/migrations/update-19-2-4/set-project-name", + "x-repair-skip": true } } } diff --git a/packages/nx/src/migrations/update-19-2-4/set-project-name.spec.ts b/packages/nx/src/migrations/update-19-2-4/set-project-name.spec.ts new file mode 100644 index 0000000000000..cce687cb31c54 --- /dev/null +++ b/packages/nx/src/migrations/update-19-2-4/set-project-name.spec.ts @@ -0,0 +1,100 @@ +import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace'; +import { readJson } from '../../generators/utils/json'; +import migrate from './set-project-name'; + +describe('set project name', () => { + it('should not update packageJson projects', async () => { + const tree = createTreeWithEmptyWorkspace(); + tree.write( + 'libs/proj/package.json', + JSON.stringify({ + name: '@scoped/package', + }) + ); + await migrate(tree); + expect(tree.exists('libs/proj/project.json')).toBe(false); + }); + + it('should not update projectJson if name specified', async () => { + const tree = createTreeWithEmptyWorkspace(); + tree.write( + 'libs/proj/project.json', + JSON.stringify({ + name: 'foo', + }) + ); + await migrate(tree); + const project = readJson(tree, 'libs/proj/project.json'); + expect(project.name).toBe('foo'); + }); + + it('should not update projectJson if name is not specified but no sibling package json', async () => { + const tree = createTreeWithEmptyWorkspace(); + tree.write('libs/proj/project.json', JSON.stringify({})); + await migrate(tree); + const project = readJson(tree, 'libs/proj/project.json'); + expect(project.name).not.toBeDefined(); + }); + + it('should not update projectJson if name is identical to package name', async () => { + const tree = createTreeWithEmptyWorkspace(); + tree.write( + 'libs/proj/package.json', + JSON.stringify({ + name: 'proj', + }) + ); + tree.write('libs/proj/project.json', JSON.stringify({})); + await migrate(tree); + const project = readJson(tree, 'libs/proj/project.json'); + expect(project.name).not.toBeDefined(); + }); + + it('should not update projectJson if name is identical to name in nx field', async () => { + const tree = createTreeWithEmptyWorkspace(); + tree.write( + 'libs/proj/package.json', + JSON.stringify({ + name: '@scoped/proj', + nx: { + name: 'proj', + }, + }) + ); + tree.write('libs/proj/project.json', JSON.stringify({})); + await migrate(tree); + const project = readJson(tree, 'libs/proj/project.json'); + expect(project.name).not.toBeDefined(); + }); + + it('should update projectJson if name is not specified and package name is different', async () => { + const tree = createTreeWithEmptyWorkspace(); + tree.write( + 'libs/proj/package.json', + JSON.stringify({ + name: '@scoped/proj', + }) + ); + tree.write('libs/proj/project.json', JSON.stringify({})); + await migrate(tree); + const project = readJson(tree, 'libs/proj/project.json'); + expect(project.name).toBe('proj'); + }); + + it('should update projectJson if name is not specified and name in nx field is different', async () => { + const tree = createTreeWithEmptyWorkspace(); + tree.write( + 'libs/foo/package.json', + JSON.stringify({ + name: '@scoped/proj', + nx: { + name: 'proj', + }, + }) + ); + tree.write('libs/foo/project.json', JSON.stringify({})); + await migrate(tree); + const project = readJson(tree, 'libs/foo/project.json'); + expect(project.name).toBe('foo'); + }); +}); diff --git a/packages/nx/src/migrations/update-19-2-4/set-project-name.ts b/packages/nx/src/migrations/update-19-2-4/set-project-name.ts new file mode 100644 index 0000000000000..d88fb79eee918 --- /dev/null +++ b/packages/nx/src/migrations/update-19-2-4/set-project-name.ts @@ -0,0 +1,43 @@ +import { toProjectName } from '../../config/to-project-name'; +import { ProjectConfiguration } from '../../config/workspace-json-project-json'; +import { Tree } from '../../generators/tree'; +import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available'; +import { readJson, writeJson } from '../../generators/utils/json'; +import { getProjects } from '../../generators/utils/project-configuration'; +import type { PackageJson } from '../../utils/package-json'; + +export default async function setProjectName(tree: Tree) { + // We are explicitly looking for project.json files here, so getProjects is fine. + const projects = getProjects(tree); + + for (const { root } of projects.values()) { + const projectJsonPath = `${root}/project.json`; + const packageJsonPath = `${root}/package.json`; + + // If either of these files doesn't exist, theres no behavioral difference + if (!tree.exists(projectJsonPath) || !tree.exists(packageJsonPath)) { + continue; + } + + const projectJson: ProjectConfiguration = readJson(tree, projectJsonPath); + + // In Nx 19.1+, the way the project name is inferred is different. + // For existing projects, if the name is not set, we can inline it + // based on the existing logic. This makes sure folks aren't caught + // off guard by the new behavior. + if (!projectJson.name) { + const siblingPackageJson = readJson(tree, packageJsonPath); + + const newName = siblingPackageJson.nx?.name ?? siblingPackageJson.name; + + const oldName = toProjectName(projectJsonPath); + + if (newName && oldName !== newName) { + projectJson.name = oldName; + writeJson(tree, projectJsonPath, projectJson); + } + } + } + + await formatChangedFilesWithPrettierIfAvailable(tree); +} diff --git a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts index 21198e136bc5b..e99530e2416b6 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts @@ -4,6 +4,7 @@ import { ProjectConfiguration } from '../../../config/workspace-json-project-jso import { toProjectName } from '../../../config/to-project-name'; import { readJsonFile } from '../../../utils/fileutils'; import { NxPluginV2 } from '../../../project-graph/plugins'; +import { PackageJson } from '../../../utils/package-json'; export const ProjectJsonProjectsPlugin: NxPluginV2 = { name: 'nx/core/project-json', @@ -15,6 +16,7 @@ export const ProjectJsonProjectsPlugin: NxPluginV2 = { ); const project = buildProjectFromProjectJson(json, file); + return { projects: { [project.root]: project, @@ -30,9 +32,21 @@ export function buildProjectFromProjectJson( json: Partial, path: string ): ProjectConfiguration { + const packageJsonPath = join(dirname(path), 'package.json'); + const { name, root, ...rest } = json; return { - name: toProjectName(path), - root: dirname(path), - ...json, + name: + name ?? readNameFromPackageJson(packageJsonPath) ?? toProjectName(path), + root: root ?? dirname(path), + ...rest, }; } + +export function readNameFromPackageJson(path: string): string { + try { + const json = readJsonFile(path); + return json.nx?.name ?? json.name; + } catch { + return undefined; + } +} diff --git a/packages/nx/src/utils/package-json.ts b/packages/nx/src/utils/package-json.ts index 4d9a80314e809..d6996dfd11f82 100644 --- a/packages/nx/src/utils/package-json.ts +++ b/packages/nx/src/utils/package-json.ts @@ -14,6 +14,7 @@ import { } from './package-manager'; export interface NxProjectPackageJsonConfiguration { + name?: string; implicitDependencies?: string[]; tags?: string[]; namedInputs?: { [inputName: string]: (string | InputDefinition)[] };