From e773bdbb794c44448a83e5b5148a1432caaa5623 Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Fri, 3 Mar 2023 16:56:48 -0500 Subject: [PATCH] fix(core): add warning if running on an outdated global installation (#15408) --- docs/generated/manifests/menus.json | 16 ++++ docs/generated/manifests/nx.json | 20 +++++ docs/map.json | 5 ++ docs/shared/guides/global-nx.md | 122 ++++++++++++++++++++++++++++ packages/nx/bin/nx.ts | 65 ++++++++++++++- 5 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 docs/shared/guides/global-nx.md diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index adba7a12102b8..adfd6d5118244 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -1153,6 +1153,14 @@ "isExternal": false, "children": [], "disableCollapsible": false + }, + { + "name": "Managing your Global Nx Installation", + "path": "/more-concepts/global-nx", + "id": "global-nx", + "isExternal": false, + "children": [], + "disableCollapsible": false } ], "disableCollapsible": false @@ -1317,6 +1325,14 @@ "children": [], "disableCollapsible": false }, + { + "name": "Managing your Global Nx Installation", + "path": "/more-concepts/global-nx", + "id": "global-nx", + "isExternal": false, + "children": [], + "disableCollapsible": false + }, { "name": "All Recipes ยป", "path": "/recipes", diff --git a/docs/generated/manifests/nx.json b/docs/generated/manifests/nx.json index 64cf490b0025d..b2b0060bbe191 100644 --- a/docs/generated/manifests/nx.json +++ b/docs/generated/manifests/nx.json @@ -1434,6 +1434,16 @@ "isExternal": false, "path": "/more-concepts/encapsulated-nx-and-the-wrapper", "tags": [] + }, + { + "id": "global-nx", + "name": "Managing your Global Nx Installation", + "description": "", + "file": "shared/guides/global-nx", + "itemList": [], + "isExternal": false, + "path": "/more-concepts/global-nx", + "tags": [] } ], "isExternal": false, @@ -1640,6 +1650,16 @@ "path": "/more-concepts/encapsulated-nx-and-the-wrapper", "tags": [] }, + "/more-concepts/global-nx": { + "id": "global-nx", + "name": "Managing your Global Nx Installation", + "description": "", + "file": "shared/guides/global-nx", + "itemList": [], + "isExternal": false, + "path": "/more-concepts/global-nx", + "tags": [] + }, "/recipes": { "id": "all", "name": "All Recipes ยป", diff --git a/docs/map.json b/docs/map.json index e1cee6ad289de..f07cabf49b5cf 100644 --- a/docs/map.json +++ b/docs/map.json @@ -489,6 +489,11 @@ "name": "Encapsulated Nx and the Nx Wrapper", "id": "encapsulated-nx-and-the-wrapper", "file": "shared/guides/encapsulated-nx-and-the-wrapper" + }, + { + "name": "Managing your Global Nx Installation", + "id": "global-nx", + "file": "shared/guides/global-nx" } ] }, diff --git a/docs/shared/guides/global-nx.md b/docs/shared/guides/global-nx.md new file mode 100644 index 0000000000000..eb2b3c17c860f --- /dev/null +++ b/docs/shared/guides/global-nx.md @@ -0,0 +1,122 @@ +# Managing your Global Nx Installation + +Nx can be ran in a total of 3 ways: + +- Through your package manager (e.g. `npx nx`, `yarn nx`, or `pnpm exec nx`) +- Through an [encapsulated install](/more-concepts/encapsulated-nx-and-the-wrapper) (e.g. `./nx` or `./nx.bat`) +- Through a global Nx installation (e.g. `nx`) + +With a global Nx installation, Nx looks for the local copy of Nx in your repo and hands off the process execution to it. This means that whichever version of Nx is installed locally in your repo is still the version of Nx that runs your code. For the most part, this can eliminate any issues that may arise from the global install being outdated. + +However, there are still cases where an issue could arise. If the structure of your Nx workspace no longer matches up with what the globally installed copy of Nx expects, it may fail to hand off to your local installation properly and instead error. This commonly results in errors such as: + +- Could not find Nx modules in this workspace. +- The current directory isn't part of an Nx workspace. + +If you find yourself in this position, you will need to update your global install of Nx. + +## Updating your global Nx installation + +Exactly how you do this will depend on which package manager you originally installed Nx with. + +{% tabs %} +{% tab label="npm" %} + +```shell +npm install --global nx@latest +``` + +{% /tab %} +{% tab label="yarn" %} + +```shell +yarn global add nx@latest +``` + +{% /tab %} +{% tab label="pnpm" %} + +```shell +pnpm install --global nx@latest +``` + +{% /tab %} +{% /tabs %} + +If you cannot remember which package manager you installed Nx globally with or are still encountering issues, you can locate other installs of Nx with these commands: + +{% tabs %} +{% tab label="npm" %} + +```shell +npm list --global nx +``` + +{% /tab %} +{% tab label="yarn" %} + +```shell +yarn global list nx +``` + +{% /tab %} +{% tab label="pnpm" %} + +```shell +pnpm list --global nx +``` + +{% /tab %} +{% /tabs %} + +You can then remove the extra global installations by running the following commands for the duplicate installations: + +{% tabs %} +{% tab label="npm" %} + +```shell +npm rm --global nx +``` + +{% /tab %} +{% tab label="yarn" %} + +```shell +yarn global remove nx +``` + +{% /tab %} +{% tab label="pnpm" %} + +```shell +pnpm rm --global nx +``` + +{% /tab %} +{% /tabs %} + +Finally, to complete your global installation update, simply reinstall it with the package manager of your choosing: + +{% tabs %} +{% tab label="npm" %} + +```shell +npm install --global nx@latest +``` + +{% /tab %} +{% tab label="yarn" %} + +```shell +yarn global add nx@latest +``` + +{% /tab %} +{% tab label="pnpm" %} + +```shell +pnpm install --global nx@latest +``` + +{% /tab %} +{% /tabs %} diff --git a/packages/nx/bin/nx.ts b/packages/nx/bin/nx.ts index 31dbd91601f04..7e9c6c469b520 100644 --- a/packages/nx/bin/nx.ts +++ b/packages/nx/bin/nx.ts @@ -6,7 +6,13 @@ import { import * as chalk from 'chalk'; import { initLocal } from './init-local'; import { output } from '../src/utils/output'; -import { getNxInstallationPath } from 'nx/src/utils/installation-directory'; +import { + getNxInstallationPath, + getNxRequirePaths, +} from '../src/utils/installation-directory'; +import { major } from 'semver'; +import { readJsonFile } from '../src/utils/fileutils'; +import { execSync } from 'child_process'; const workspace = findWorkspaceRoot(process.cwd()); // new is a special case because there is no local workspace to load @@ -53,6 +59,8 @@ if ( try { localNx = resolveNx(workspace); } catch { + // If we can't resolve a local copy of Nx, we must be global. + warnIfUsingOutdatedGlobalInstall(); output.error({ title: `Could not find Nx modules in this workspace.`, bodyLines: [`Have you run ${chalk.bold.white(`npm/yarn install`)}?`], @@ -69,6 +77,7 @@ if ( initLocal(workspace); } else { // Nx is being run from globally installed CLI - hand off to the local + warnIfUsingOutdatedGlobalInstall(getLocalNxVersion(workspace)); require(localNx); } } @@ -93,3 +102,57 @@ function resolveNx(workspace: WorkspaceTypeAndRoot | null) { }); } } + +/** + * Assumes currently running Nx is global install. + * Warns if out of date by 1 major version or more. + */ +function warnIfUsingOutdatedGlobalInstall(localNxVersion?: string) { + const globalVersion = require('../package.json').version; + const isOutdatedGlobalInstall = + globalVersion && + ((localNxVersion && major(globalVersion) < major(localNxVersion)) || + (getLatestVersionOfNx() && + major(globalVersion) < major(getLatestVersionOfNx()))); + + // Using a global Nx Install + if (isOutdatedGlobalInstall) { + const bodyLines = localNxVersion + ? ['Its time to update Nx ๐ŸŽ‰'] + : [ + 'Its possible that this is causing Nx to not pick up your workspace properly.', + ]; + + bodyLines.push( + 'For more information, see https://nx.dev/more-concepts/global-nx' + ); + output.warn({ + title: `Its time to update Nx ๐ŸŽ‰`, + bodyLines, + }); + } +} + +function getLocalNxVersion(workspace: WorkspaceTypeAndRoot): string { + return readJsonFile( + require.resolve('nx/package.json', { + paths: getNxRequirePaths(workspace.dir), + }) + ).version; +} + +let _latest = null; +function getLatestVersionOfNx(): string { + if (!_latest) { + try { + _latest = execSync('npm view nx@latest version').toString().trim(); + } catch { + try { + _latest = execSync('pnpm view nx@latest version').toString().trim(); + } catch { + _latest = null; + } + } + } + return _latest; +}