diff --git a/packages/nx/bin/nx-cloud.ts b/packages/nx/bin/nx-cloud.ts index 054776d752277..85b6e5154364b 100644 --- a/packages/nx/bin/nx-cloud.ts +++ b/packages/nx/bin/nx-cloud.ts @@ -44,6 +44,11 @@ async function invokeCommandWithNxCloudClient(options: CloudTaskRunnerOptions) { const body = ['Cannot run commands from the `nx-cloud` CLI.']; if (e instanceof NxCloudEnterpriseOutdatedError) { + try { + // TODO: Remove this when all enterprise customers have updated. + // Try requiring the bin from the `nx-cloud` package. + return require('nx-cloud/bin/nx-cloud'); + } catch {} body.push( 'If you are an Nx Enterprise customer, please reach out to your assigned Developer Productivity Engineer.', 'If you are NOT an Nx Enterprise customer but are seeing this message, please reach out to cloud-support@nrwl.io.' diff --git a/packages/nx/src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options.spec.ts b/packages/nx/src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options.spec.ts index fdd4ab9a36e6a..7944360127968 100644 --- a/packages/nx/src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options.spec.ts +++ b/packages/nx/src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options.spec.ts @@ -2,6 +2,12 @@ import { NxJsonConfiguration } from '../../config/nx-json'; import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace'; import { readJson, writeJson } from '../../generators/utils/json'; import { Tree } from '../../generators/tree'; + +const verifyOrUpdateNxCloudClient = jest.fn(); +jest.mock('../../nx-cloud/update-manager', () => ({ + ...jest.requireActual('../../nx-cloud/update-manager'), + verifyOrUpdateNxCloudClient, +})); import migrate from './use-minimal-config-for-tasks-runner-options'; describe('use-minimal-config-for-tasks-runner-options migration', () => { @@ -128,6 +134,35 @@ describe('use-minimal-config-for-tasks-runner-options migration', () => { expect(nxJson.tasksRunnerOptions.default.runner).not.toBeDefined(); }); + it('should add useLightClient false for outdated enterprise customers', async () => { + writeJson(tree, 'nx.json', { + tasksRunnerOptions: { + default: { + runner: 'nx-cloud', + options: { + accessToken: 'abc123', + url: 'https://nx-cloud.example.com', + }, + }, + }, + }); + verifyOrUpdateNxCloudClient.mockImplementation(() => { + throw new Error(); + }); + + await migrate(tree); + + const nxJson = readJson(tree, 'nx.json'); + expect(nxJson.nxCloudAccessToken).toEqual('abc123'); + expect(nxJson.nxCloudUrl).toEqual('https://nx-cloud.example.com'); + expect(nxJson.tasksRunnerOptions.default.options).toMatchInlineSnapshot(` + { + "useLightClient": false, + } + `); + expect(nxJson.tasksRunnerOptions.default.runner).not.toBeDefined(); + }); + it('should not update accessToken if runner is not nx-cloud', async () => { writeJson(tree, 'nx.json', { tasksRunnerOptions: { diff --git a/packages/nx/src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options.ts b/packages/nx/src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options.ts index d2d9ffa222b5a..7fc2d7b334023 100644 --- a/packages/nx/src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options.ts +++ b/packages/nx/src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options.ts @@ -3,17 +3,28 @@ import { Tree } from '../../generators/tree'; import { NxJsonConfiguration } from '../../config/nx-json'; import { PackageJson } from '../../utils/package-json'; import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available'; +import { readNxJson } from '../../generators/utils/nx-json'; +import { + NxCloudEnterpriseOutdatedError, + verifyOrUpdateNxCloudClient, +} from '../../nx-cloud/update-manager'; +import { getRunnerOptions } from '../../tasks-runner/run-command'; +import { output } from '../../utils/output'; export default async function migrate(tree: Tree) { if (!tree.exists('nx.json')) { return; } - updateJson(tree, 'nx.json', (nxJson) => { - // Already migrated - if (!nxJson.tasksRunnerOptions?.default) { - return nxJson; - } + const nxJson = readNxJson(tree); + + // Already migrated + if (!nxJson.tasksRunnerOptions?.default) { + return; + } + + const nxCloudClientSupported = await isNxCloudClientSupported(nxJson); + updateJson(tree, 'nx.json', (nxJson) => { const { runner, options } = nxJson.tasksRunnerOptions.default; // This property shouldn't ever be part of tasks runner options. @@ -35,19 +46,12 @@ export default async function migrate(tree: Tree) { if (options.url) { nxJson.nxCloudUrl = options.url; delete options.url; + } - if ( - [ - 'https://nx.app', - 'https://cloud.nx.app', - 'https://staging.nx.app', - 'https://snapshot.nx.app', - ].includes(nxJson.nxCloudUrl) - ) { - removeNxCloudDependency(tree); - } - } else { + if (nxCloudClientSupported) { removeNxCloudDependency(tree); + } else { + options.useLightClient = false; } if (options.encryptionKey) { nxJson.nxCloudEncryptionKey = options.encryptionKey; @@ -88,6 +92,46 @@ export default async function migrate(tree: Tree) { await formatChangedFilesWithPrettierIfAvailable(tree); } +async function isNxCloudClientSupported(nxJson: NxJsonConfiguration) { + const nxCloudOptions = getRunnerOptions('default', nxJson, {}, true); + + // Non enterprise workspaces support the Nx Cloud Client + if (!isNxCloudEnterpriseWorkspace(nxJson)) { + return true; + } + + // If we can get the nx cloud client, it's supported + try { + await verifyOrUpdateNxCloudClient(nxCloudOptions); + return true; + } catch (e) { + if (e instanceof NxCloudEnterpriseOutdatedError) { + output.warn({ + title: 'Nx Cloud Instance is outdated.', + bodyLines: [ + 'If you are an Nx Enterprise customer, please reach out to your assigned Developer Productivity Engineer.', + 'If you are NOT an Nx Enterprise customer but are seeing this message, please reach out to cloud-support@nrwl.io.', + ], + }); + } + return false; + } +} + +function isNxCloudEnterpriseWorkspace(nxJson: NxJsonConfiguration) { + const { runner, options } = nxJson.tasksRunnerOptions.default; + return ( + (runner === 'nx-cloud' || runner === '@nrwl/nx-cloud') && + options.url && + ![ + 'https://nx.app', + 'https://cloud.nx.app', + 'https://staging.nx.app', + 'https://snapshot.nx.app', + ].includes(options.url) + ); +} + function removeNxCloudDependency(tree: Tree) { if (tree.exists('package.json')) { updateJson(tree, 'package.json', (packageJson) => {