From 39cfd004e937802148a453594e829672edbac122 Mon Sep 17 00:00:00 2001 From: FrozenPandaz Date: Tue, 30 Jul 2024 15:00:49 -0400 Subject: [PATCH] feat(misc): add onboarding a/b testing --- .../create-nx-plugin/bin/create-nx-plugin.ts | 3 +- .../bin/create-nx-workspace.ts | 3 +- .../src/create-workspace.ts | 34 ++-- .../src/utils/ci/setup-ci.ts | 12 +- .../src/utils/nx/messages.ts | 185 ++++++++++++++++++ .../src/utils/nx/nx-cloud.ts | 89 +++++---- .../create-nx-workspace/src/utils/output.ts | 21 +- .../connect/connect-to-nx-cloud.ts | 55 +++++- .../nx/src/command-line/connect/view-logs.ts | 19 +- .../init/implementation/add-nx-to-monorepo.ts | 2 +- .../init/implementation/add-nx-to-nest.ts | 2 +- .../init/implementation/add-nx-to-npm-repo.ts | 2 +- .../init/implementation/angular/index.ts | 2 +- .../angular/legacy-angular-versions.ts | 2 +- .../command-line/init/implementation/utils.ts | 19 +- packages/nx/src/command-line/init/init-v2.ts | 9 +- .../connect-to-nx-cloud.ts | 92 +++------ .../nx/src/nx-cloud/utilities/url-shorten.ts | 16 +- 18 files changed, 380 insertions(+), 187 deletions(-) create mode 100644 packages/create-nx-workspace/src/utils/nx/messages.ts diff --git a/packages/create-nx-plugin/bin/create-nx-plugin.ts b/packages/create-nx-plugin/bin/create-nx-plugin.ts index ee2c29a34e21b..5aced381f477b 100644 --- a/packages/create-nx-plugin/bin/create-nx-plugin.ts +++ b/packages/create-nx-plugin/bin/create-nx-plugin.ts @@ -20,7 +20,6 @@ import { output } from 'create-nx-workspace/src/utils/output'; import { NxCloud } from 'create-nx-workspace/src/utils/nx/nx-cloud'; import type { PackageManager } from 'create-nx-workspace/src/utils/package-manager'; import { showNxWarning } from 'create-nx-workspace/src/utils/nx/show-nx-warning'; -import { printNxCloudSuccessMessage } from 'create-nx-workspace/src/utils/nx/nx-cloud'; import { messages, recordStat, @@ -164,7 +163,7 @@ async function main(parsedArgs: yargs.Arguments) { }); if (parsedArgs.nxCloud && workspaceInfo.nxCloudInfo) { - printNxCloudSuccessMessage(workspaceInfo.nxCloudInfo); + console.log(workspaceInfo.nxCloudInfo); } } diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index bc541f23699a7..66d30694b38dd 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -26,7 +26,6 @@ import { withPackageManager, } from '../src/internal-utils/yargs-options'; import { showNxWarning } from '../src/utils/nx/show-nx-warning'; -import { printNxCloudSuccessMessage } from '../src/utils/nx/nx-cloud'; import { messages, recordStat } from '../src/utils/nx/ab-testing'; import { mapErrorToBodyLines } from '../src/utils/error-utils'; import { existsSync } from 'fs'; @@ -233,7 +232,7 @@ async function main(parsedArgs: yargs.Arguments) { }); if (parsedArgs.nxCloud && workspaceInfo.nxCloudInfo) { - printNxCloudSuccessMessage(workspaceInfo.nxCloudInfo); + console.log(workspaceInfo.nxCloudInfo); } if (isKnownPreset(parsedArgs.preset)) { diff --git a/packages/create-nx-workspace/src/create-workspace.ts b/packages/create-nx-workspace/src/create-workspace.ts index d776aeb189df7..c1bc203daddd3 100644 --- a/packages/create-nx-workspace/src/create-workspace.ts +++ b/packages/create-nx-workspace/src/create-workspace.ts @@ -1,6 +1,6 @@ import { CreateWorkspaceOptions } from './create-workspace-options'; import { output } from './utils/output'; -import { setupNxCloud } from './utils/nx/nx-cloud'; +import { getOnboardingInfo, setupNxCloud } from './utils/nx/nx-cloud'; import { createSandbox } from './create-sandbox'; import { createEmptyWorkspace } from './create-empty-workspace'; import { createPreset } from './create-preset'; @@ -51,31 +51,27 @@ export async function createWorkspace( ); } - let nxCloudInstallRes; + let connectUrl: string | undefined; + let nxCloudInfo: string | undefined; if (nxCloud !== 'skip') { - nxCloudInstallRes = await setupNxCloud( - directory, - packageManager, - nxCloud, - useGitHub - ); + const token = await setupNxCloud(directory, nxCloud, useGitHub); if (nxCloud !== 'yes') { - await setupCI( - directory, - nxCloud, - packageManager, - nxCloudInstallRes?.code === 0 - ); + await setupCI(directory, nxCloud, packageManager); } + + const { connectCloudUrl, output } = await getOnboardingInfo( + nxCloud, + token, + directory, + useGitHub + ); + connectUrl = connectCloudUrl; + nxCloudInfo = output; } if (!skipGit) { try { - let connectUrl; - if (nxCloudInstallRes?.code === 0) { - connectUrl = extractConnectUrl(nxCloudInstallRes?.stdout); - } await initializeGitRepo(directory, { defaultBase, commit, connectUrl }); } catch (e) { if (e instanceof Error) { @@ -90,7 +86,7 @@ export async function createWorkspace( } return { - nxCloudInfo: nxCloudInstallRes?.stdout, + nxCloudInfo, directory, }; } diff --git a/packages/create-nx-workspace/src/utils/ci/setup-ci.ts b/packages/create-nx-workspace/src/utils/ci/setup-ci.ts index fd375d2ff5e69..a3aa977dd6006 100644 --- a/packages/create-nx-workspace/src/utils/ci/setup-ci.ts +++ b/packages/create-nx-workspace/src/utils/ci/setup-ci.ts @@ -8,18 +8,8 @@ import { getPackageManagerCommand, PackageManager } from '../package-manager'; export async function setupCI( directory: string, ci: string, - packageManager: PackageManager, - nxCloudSuccessfullyInstalled: boolean + packageManager: PackageManager ) { - if (!nxCloudSuccessfullyInstalled) { - output.error({ - title: `CI workflow generation skipped`, - bodyLines: [ - `Nx Cloud was not installed`, - `The autogenerated CI workflow requires Nx Cloud to be set-up.`, - ], - }); - } const ciSpinner = ora(`Generating CI workflow`).start(); try { const pmc = getPackageManagerCommand(packageManager); diff --git a/packages/create-nx-workspace/src/utils/nx/messages.ts b/packages/create-nx-workspace/src/utils/nx/messages.ts new file mode 100644 index 0000000000000..588f03608d451 --- /dev/null +++ b/packages/create-nx-workspace/src/utils/nx/messages.ts @@ -0,0 +1,185 @@ +const outputMessages = { + 'create-nx-workspace-success-ci-setup': [ + { + code: 'nx-cloud-workspace-push-goto', + createMessage: (url: string) => ({ + title: `Your Nx Cloud workspace is ready.`, + type: 'success', + bodyLines: [ + `To claim it, connect it to your Nx Cloud account:`, + `- Push your repository to your git hosting provider.`, + `- Go to the following URL to connect your workspace to Nx Cloud:`, + '', + `${url}`, + ], + }), + }, + { + code: 'nx-cloud-powered-ci-setup-visit', + createMessage: (url: string) => ({ + title: `Your CI setup powered by Nx Cloud is almost complete.`, + type: 'success', + bodyLines: [`Finish it by visiting: ${url}`], + }), + }, + { + code: 'nx-cloud-powered-ci-setup-connect', + createMessage: (url: string) => ({ + title: `Your CI setup powered by Nx Cloud is almost complete.`, + type: 'success', + bodyLines: [`Connect your repository: ${url}`], + }), + }, + { + code: 'ci-setup-visit', + createMessage: (url: string) => ({ + title: `Your CI setup is almost complete.`, + type: 'success', + bodyLines: [`Finish it by visiting: ${url}`], + }), + }, + { + code: 'ci-setup-connect', + createMessage: (url: string) => ({ + title: `Your CI setup is almost complete.`, + type: 'success', + bodyLines: [`Connect your repository: ${url}`], + }), + }, + { + code: 'nx-cloud-powered-ci-setup-visit-warn', + createMessage: (url: string) => ({ + title: `Your CI setup powered by Nx Cloud is almost complete.`, + type: 'warning', + bodyLines: [`Finish it by visiting: ${url}`], + }), + }, + { + code: 'nx-cloud-powered-ci-setup-connect-warn', + createMessage: (url: string) => ({ + title: `Your CI setup powered by Nx Cloud is almost complete.`, + type: 'warning', + bodyLines: [`Connect your repository: ${url}`], + }), + }, + { + code: 'ci-setup-visit-warn', + createMessage: (url: string) => ({ + title: `Your CI setup is almost complete.`, + type: 'warning', + bodyLines: [`Finish it by visiting: ${url}`], + }), + }, + { + code: 'ci-setup-connect-warn', + createMessage: (url: string) => ({ + title: `Your CI setup is almost complete.`, + type: 'warning', + bodyLines: [`Connect your repository: ${url}`], + }), + }, + ], + 'create-nx-workspace-success-cache-setup': [ + { + code: 'nx-cloud-workspace-push-goto', + createMessage: (url: string) => ({ + title: `Your Nx Cloud workspace is ready.`, + type: 'success', + bodyLines: [ + `To claim it, connect it to your Nx Cloud account:`, + `- Push your repository to your git hosting provider.`, + `- Go to the following URL to connect your workspace to Nx Cloud:`, + '', + `${url}`, + ], + }), + }, + { + code: 'nx-cloud-remote-cache-setup-finish', + createMessage: (url: string) => ({ + title: `Your Nx Cloud remote cache setup is almost complete.`, + type: 'success', + bodyLines: [`Finish it by visiting: ${url}`], + }), + }, + { + code: 'nx-cloud-remote-cache-setup-connect', + createMessage: (url: string) => ({ + title: `Your Nx Cloud remote cache setup is almost complete.`, + type: 'success', + bodyLines: [`Connect your repository: ${url}`], + }), + }, + { + code: 'remote-cache-visit', + createMessage: (url: string) => ({ + title: `Your remote cache setup is almost complete.`, + type: 'success', + bodyLines: [`Finish it by visiting: ${url}`], + }), + }, + { + code: 'remote-cache-connect', + createMessage: (url: string) => ({ + title: `Your remote cache setup is almost complete.`, + type: 'success', + bodyLines: [`Connect your repository: ${url}`], + }), + }, + { + code: 'nx-cloud-remote-cache-setup-visit-warn', + createMessage: (url: string) => ({ + title: `Your Nx Cloud remote cache setup is almost complete.`, + type: 'warning', + bodyLines: [`Finish it by visiting: ${url}`], + }), + }, + { + code: 'nx-cloud-remote-cache-setup-connect-warn', + createMessage: (url: string) => ({ + title: `Your Nx Cloud remote cache setup is almost complete.`, + type: 'warning', + bodyLines: [`Connect your repository: ${url}`], + }), + }, + { + code: 'remote-cache-visit-warn', + createMessage: (url: string) => ({ + title: `Your remote cache setup is almost complete.`, + type: 'warning', + bodyLines: [`Finish it by visiting: ${url}`], + }), + }, + { + code: 'remote-cache-connect-warn', + createMessage: (url: string) => ({ + title: `Your remote cache setup is almost complete.`, + type: 'warning', + bodyLines: [`Connect your repository: ${url}`], + }), + }, + ], +} as const; +type OutputMessageKey = keyof typeof outputMessages; + +class ABTestingMessages { + private selectedMessages: Record = {}; + getMessageFactory(key: OutputMessageKey) { + if (this.selectedMessages[key] === undefined) { + if (process.env.NX_GENERATE_DOCS_PROCESS === 'true') { + this.selectedMessages[key] = 0; + } else { + this.selectedMessages[key] = Math.floor( + Math.random() * outputMessages[key].length + ); + } + } + return outputMessages[key][this.selectedMessages[key]!]; + } +} + +const messages = new ABTestingMessages(); + +export function getMessageFactory(key: OutputMessageKey) { + return messages.getMessageFactory(key); +} diff --git a/packages/create-nx-workspace/src/utils/nx/nx-cloud.ts b/packages/create-nx-workspace/src/utils/nx/nx-cloud.ts index c9a1f3b31252c..96833e668a80d 100644 --- a/packages/create-nx-workspace/src/utils/nx/nx-cloud.ts +++ b/packages/create-nx-workspace/src/utils/nx/nx-cloud.ts @@ -1,37 +1,34 @@ import * as ora from 'ora'; -import { execAndWait } from '../child-process-utils'; -import { output } from '../output'; -import { getPackageManagerCommand, PackageManager } from '../package-manager'; +import { CLIOutput, output } from '../output'; import { mapErrorToBodyLines } from '../error-utils'; +import { getMessageFactory } from './messages'; export type NxCloud = 'yes' | 'github' | 'circleci' | 'skip'; export async function setupNxCloud( directory: string, - packageManager: PackageManager, nxCloud: NxCloud, useGitHub?: boolean ) { const nxCloudSpinner = ora(`Setting up Nx Cloud`).start(); try { - const pmc = getPackageManagerCommand(packageManager); - const res = await execAndWait( - `${ - pmc.exec - } nx g nx:connect-to-nx-cloud --installationSource=create-nx-workspace --directory=${directory} ${ - useGitHub ? '--github' : '' - } --no-interactive`, - directory - ); + // nx-ignore-next-line + const { connectWorkspaceToCloud } = require(require.resolve( + 'nx/src/command-line/connect/connect-to-nx-cloud', + { + paths: [directory], + } + // nx-ignore-next-line + )) as typeof import('nx/src/command-line/connect/connect-to-nx-cloud'); - if (nxCloud !== 'yes') { - nxCloudSpinner.succeed( - 'CI workflow with Nx Cloud has been generated successfully' - ); - } else { - nxCloudSpinner.succeed('Nx Cloud has been set up successfully'); - } - return res; + const accessToken = await connectWorkspaceToCloud({ + installationSource: 'create-nx-workspace', + directory, + github: useGitHub, + }); + + nxCloudSpinner.succeed('Nx Cloud has been set up successfully'); + return accessToken; } catch (e) { nxCloudSpinner.fail(); @@ -50,19 +47,39 @@ export async function setupNxCloud( } } -export function printNxCloudSuccessMessage(nxCloudOut: string) { - // remove leading Nx carret and any new lines - const logContent = nxCloudOut.split('NX ')[1]; - const indexOfTitleEnd = logContent.indexOf('\n'); - const title = logContent.slice(0, logContent.indexOf('\n')).trim(); - const bodyLines = logContent - .slice(indexOfTitleEnd) - .replace(/^\n*/, '') // remove leading new lines - .replace(/\n*$/, '') // remove trailing new lines - .split('\n') - .map((r) => r.trim()); - output.warn({ - title, - bodyLines, - }); +export async function getOnboardingInfo( + nxCloud: NxCloud, + token: string, + directory: string, + useGithub?: boolean +) { + // nx-ignore-next-line + const { createNxCloudOnboardingURL } = require(require.resolve( + 'nx/src/nx-cloud/utilities/url-shorten', + { + paths: [directory], + } + // nx-ignore-next-line + )) as typeof import('nx/src/nx-cloud/utilities/url-shorten'); + + const source = + nxCloud === 'yes' + ? 'create-nx-workspace-success-cache-setup' + : 'create-nx-workspace-success-ci-setup'; + const { code, createMessage } = getMessageFactory(source); + const connectCloudUrl = await createNxCloudOnboardingURL( + source, + token, + useGithub ?? + (nxCloud === 'yes' || nxCloud === 'github' || nxCloud === 'circleci'), + code + ); + const out = new CLIOutput(false); + const message = createMessage(connectCloudUrl); + if (message.type === 'success') { + out.success(message); + } else { + out.warn(message); + } + return { output: out.getOutput(), connectCloudUrl }; } diff --git a/packages/create-nx-workspace/src/utils/output.ts b/packages/create-nx-workspace/src/utils/output.ts index fb712aa1621bd..d6e4a01f42382 100644 --- a/packages/create-nx-workspace/src/utils/output.ts +++ b/packages/create-nx-workspace/src/utils/output.ts @@ -34,7 +34,9 @@ if (isCI()) { (chalk as any).level = 0; } -class CLIOutput { +export class CLIOutput { + private outstream = this.real ? process.stdout : new FakeStdout(); + constructor(private real = true) {} /** * Longer dash character which forms more of a continuous line when place side to side * with itself, unlike the standard dash character @@ -64,7 +66,7 @@ class CLIOutput { dim = chalk.dim; private writeToStdOut(str: string) { - process.stdout.write(str); + this.outstream.write(str); } private writeOutputTitle({ @@ -192,6 +194,21 @@ class CLIOutput { this.addNewline(); } + + getOutput() { + return this.outstream.toString(); + } } export const output = new CLIOutput(); + +class FakeStdout { + private content = ''; + write(str: string) { + this.content += str; + } + + toString() { + return this.content; + } +} diff --git a/packages/nx/src/command-line/connect/connect-to-nx-cloud.ts b/packages/nx/src/command-line/connect/connect-to-nx-cloud.ts index c16d4a8420e56..60506869cfbb7 100644 --- a/packages/nx/src/command-line/connect/connect-to-nx-cloud.ts +++ b/packages/nx/src/command-line/connect/connect-to-nx-cloud.ts @@ -1,8 +1,11 @@ import { output } from '../../utils/output'; import { readNxJson } from '../../config/configuration'; import { FsTree, flushChanges } from '../../generators/tree'; -import { connectToNxCloud } from '../../nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud'; -import { shortenedCloudUrl } from '../../nx-cloud/utilities/url-shorten'; +import { + connectToNxCloud, + ConnectToNxCloudOptions, +} from '../../nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud'; +import { createNxCloudOnboardingURL } from '../../nx-cloud/utilities/url-shorten'; import { isNxCloudUsed } from '../../utils/nx-cloud-utils'; import { runNxSync } from '../../utils/child-process'; import { NxJsonConfiguration } from '../../config/nx-json'; @@ -16,6 +19,8 @@ import { import { nxVersion } from '../../utils/versions'; import { workspaceRoot } from '../../utils/workspace-root'; import chalk = require('chalk'); +import * as ora from 'ora'; +import * as open from 'open'; export function onlyDefaultRunnerIsUsed(nxJson: NxJsonConfiguration) { const defaultRunner = nxJson.tasksRunnerOptions?.default?.runner; @@ -50,6 +55,16 @@ export async function connectToNxCloudIfExplicitlyAsked( } } +export async function connectWorkspaceToCloud( + options: ConnectToNxCloudOptions +) { + const tree = new FsTree(workspaceRoot, false, 'connect-to-nx-cloud'); + const accessToken = await connectToNxCloud(tree, options); + tree.lock(); + flushChanges(workspaceRoot, tree.listChanges()); + return accessToken; +} + export async function connectToNxCloudCommand( command?: string ): Promise { @@ -63,7 +78,10 @@ export async function connectToNxCloudCommand( `Unable to authenticate. Either define accessToken in nx.json or set the NX_CLOUD_ACCESS_TOKEN env variable.` ); } - const connectCloudUrl = await shortenedCloudUrl('nx-connect', token); + const connectCloudUrl = await createNxCloudOnboardingURL( + 'nx-connect', + token + ); output.log({ title: '✔ This workspace already has Nx Cloud set up', bodyLines: [ @@ -76,18 +94,37 @@ export async function connectToNxCloudCommand( return false; } - - const tree = new FsTree(workspaceRoot, false, 'connect-to-nx-cloud'); - const callback = await connectToNxCloud(tree, { + const token = await connectWorkspaceToCloud({ installationSource: command ?? 'nx-connect', }); - tree.lock(); - flushChanges(workspaceRoot, tree.listChanges()); - await callback(); + + const connectCloudUrl = await createNxCloudOnboardingURL('nx-connect', token); + try { + const cloudConnectSpinner = ora( + `Opening Nx Cloud ${connectCloudUrl} in your browser to connect your workspace.` + ).start(); + await sleep(2000); + await open(connectCloudUrl); + cloudConnectSpinner.succeed(); + } catch (e) { + output.note({ + title: `Your Nx Cloud workspace is ready.`, + bodyLines: [ + `To claim it, connect it to your Nx Cloud account:`, + `- Go to the following URL to connect your workspace to Nx Cloud:`, + '', + `${connectCloudUrl}`, + ], + }); + } return true; } +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + export async function connectExistingRepoToNxCloudPrompt( command = 'init', key: MessageKey = 'setupNxCloud' diff --git a/packages/nx/src/command-line/connect/view-logs.ts b/packages/nx/src/command-line/connect/view-logs.ts index aa77ec5390232..97cbc236e73c2 100644 --- a/packages/nx/src/command-line/connect/view-logs.ts +++ b/packages/nx/src/command-line/connect/view-logs.ts @@ -2,9 +2,13 @@ import { getPackageManagerCommand } from '../../utils/package-manager'; import { execSync } from 'child_process'; import { isNxCloudUsed } from '../../utils/nx-cloud-utils'; import { output } from '../../utils/output'; -import { runNxSync } from '../../utils/child-process'; import { readNxJson } from '../../config/nx-json'; -import { connectExistingRepoToNxCloudPrompt } from './connect-to-nx-cloud'; +import { + connectExistingRepoToNxCloudPrompt, + connectWorkspaceToCloud, +} from './connect-to-nx-cloud'; +import { printSuccessMessage } from '../../nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud'; +import { repoUsesGithub } from '../../nx-cloud/utilities/url-shorten'; export async function viewLogs(): Promise { const cloudUsed = isNxCloudUsed(readNxJson()); @@ -30,12 +34,11 @@ export async function viewLogs(): Promise { output.log({ title: 'Connecting to Nx Cloud', }); - runNxSync( - `g nx:connect-to-nx-cloud --installation-source=view-logs --quiet --no-interactive`, - { - stdio: 'ignore', - } - ); + const token = await connectWorkspaceToCloud({ + installationSource: 'view-logs', + }); + + await printSuccessMessage(token, 'view-logs', await repoUsesGithub()); } catch (e) { output.log({ title: 'Failed to connect to Nx Cloud', diff --git a/packages/nx/src/command-line/init/implementation/add-nx-to-monorepo.ts b/packages/nx/src/command-line/init/implementation/add-nx-to-monorepo.ts index 2ec476dac5af5..9ef59d2c3d240 100644 --- a/packages/nx/src/command-line/init/implementation/add-nx-to-monorepo.ts +++ b/packages/nx/src/command-line/init/implementation/add-nx-to-monorepo.ts @@ -108,7 +108,7 @@ export async function addNxToMonorepo(options: Options) { if (useNxCloud) { output.log({ title: '🛠️ Setting up Nx Cloud' }); - initCloud(repoRoot, 'nx-init-monorepo'); + await initCloud('nx-init-monorepo'); } } diff --git a/packages/nx/src/command-line/init/implementation/add-nx-to-nest.ts b/packages/nx/src/command-line/init/implementation/add-nx-to-nest.ts index d94ec2c978e28..a3d7e72536ff1 100644 --- a/packages/nx/src/command-line/init/implementation/add-nx-to-nest.ts +++ b/packages/nx/src/command-line/init/implementation/add-nx-to-nest.ts @@ -140,7 +140,7 @@ export async function addNxToNest(options: Options, packageJson: PackageJson) { if (useNxCloud) { output.log({ title: '🛠️ Setting up Nx Cloud' }); - initCloud(repoRoot, 'nx-init-nest'); + await initCloud('nx-init-nest'); } } diff --git a/packages/nx/src/command-line/init/implementation/add-nx-to-npm-repo.ts b/packages/nx/src/command-line/init/implementation/add-nx-to-npm-repo.ts index ebfa87521a906..d5197b4b1ea54 100644 --- a/packages/nx/src/command-line/init/implementation/add-nx-to-npm-repo.ts +++ b/packages/nx/src/command-line/init/implementation/add-nx-to-npm-repo.ts @@ -97,6 +97,6 @@ export async function addNxToNpmRepo(options: Options) { if (useNxCloud) { output.log({ title: '🛠️ Setting up Nx Cloud' }); - initCloud(repoRoot, 'nx-init-npm-repo'); + await initCloud('nx-init-npm-repo'); } } diff --git a/packages/nx/src/command-line/init/implementation/angular/index.ts b/packages/nx/src/command-line/init/implementation/angular/index.ts index 2ac238d5ecc54..bda42feae399a 100644 --- a/packages/nx/src/command-line/init/implementation/angular/index.ts +++ b/packages/nx/src/command-line/init/implementation/angular/index.ts @@ -62,7 +62,7 @@ export async function addNxToAngularCliRepo(options: Options) { if (useNxCloud) { output.log({ title: '🛠️ Setting up Nx Cloud' }); - initCloud(repoRoot, 'nx-init-angular'); + await initCloud('nx-init-angular'); } } diff --git a/packages/nx/src/command-line/init/implementation/angular/legacy-angular-versions.ts b/packages/nx/src/command-line/init/implementation/angular/legacy-angular-versions.ts index 6cbde226d733f..6b9177d948eb8 100644 --- a/packages/nx/src/command-line/init/implementation/angular/legacy-angular-versions.ts +++ b/packages/nx/src/command-line/init/implementation/angular/legacy-angular-versions.ts @@ -110,7 +110,7 @@ export async function getLegacyMigrationFunctionIfApplicable( if (useNxCloud) { output.log({ title: '🛠️ Setting up Nx Cloud' }); - initCloud(repoRoot, 'nx-init-angular'); + await initCloud('nx-init-angular'); } }; } diff --git a/packages/nx/src/command-line/init/implementation/utils.ts b/packages/nx/src/command-line/init/implementation/utils.ts index 1a4d842ae1b77..cd2e1d44148cd 100644 --- a/packages/nx/src/command-line/init/implementation/utils.ts +++ b/packages/nx/src/command-line/init/implementation/utils.ts @@ -2,7 +2,6 @@ import { execSync } from 'child_process'; import { join } from 'path'; import { NxJsonConfiguration } from '../../../config/nx-json'; -import { runNxSync } from '../../../utils/child-process'; import { fileExists, readJsonFile, @@ -17,6 +16,9 @@ import { import { joinPathFragments } from '../../../utils/path'; import { nxVersion } from '../../../utils/versions'; import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { printSuccessMessage } from '../../../nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud'; +import { repoUsesGithub } from '../../../nx-cloud/utilities/url-shorten'; +import { connectWorkspaceToCloud } from '../../connect/connect-to-nx-cloud'; export function createNxJsonFile( repoRoot: string, @@ -141,22 +143,19 @@ export function runInstall( execSync(pmc.install, { stdio: [0, 1, 2], cwd: repoRoot }); } -export function initCloud( - repoRoot: string, +export async function initCloud( installationSource: + | 'nx-init' | 'nx-init-angular' | 'nx-init-cra' | 'nx-init-monorepo' | 'nx-init-nest' | 'nx-init-npm-repo' ) { - runNxSync( - `g nx:connect-to-nx-cloud --installationSource=${installationSource} --quiet --no-interactive`, - { - stdio: [0, 1, 2], - cwd: repoRoot, - } - ); + const token = await connectWorkspaceToCloud({ + installationSource, + }); + await printSuccessMessage(token, installationSource, await repoUsesGithub()); } export function addVsCodeRecommendedExtensions( diff --git a/packages/nx/src/command-line/init/init-v2.ts b/packages/nx/src/command-line/init/init-v2.ts index 725a49c4b075e..8085bbdfca2d3 100644 --- a/packages/nx/src/command-line/init/init-v2.ts +++ b/packages/nx/src/command-line/init/init-v2.ts @@ -10,6 +10,7 @@ import { nxVersion } from '../../utils/versions'; import { addDepsToPackageJson, createNxJsonFile, + initCloud, isMonorepo, printFinalMessage, runInstall, @@ -126,13 +127,7 @@ export async function initHandler(options: InitArgs): Promise { if (useNxCloud) { output.log({ title: '🛠️ Setting up Nx Cloud' }); - execSync( - `${pmc.exec} nx g nx:connect-to-nx-cloud --installationSource=nx-init --quiet --hideFormatLogs --no-interactive`, - { - stdio: [0, 1, 2], - cwd: repoRoot, - } - ); + await initCloud('nx-init'); } printFinalMessage({ diff --git a/packages/nx/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.ts b/packages/nx/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.ts index 9ccb66511559a..92e79918f211c 100644 --- a/packages/nx/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.ts +++ b/packages/nx/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.ts @@ -5,10 +5,11 @@ import { readJson } from '../../../generators/utils/json'; import { NxJsonConfiguration } from '../../../config/nx-json'; import { readNxJson, updateNxJson } from '../../../generators/utils/nx-json'; import { formatChangedFilesWithPrettierIfAvailable } from '../../../generators/internal-utils/format-changed-files-with-prettier-if-available'; -import { repoUsesGithub, shortenedCloudUrl } from '../../utilities/url-shorten'; +import { + repoUsesGithub, + createNxCloudOnboardingURL, +} from '../../utilities/url-shorten'; import { getCloudUrl } from '../../utilities/get-cloud-options'; -import * as ora from 'ora'; -import * as open from 'open'; function printCloudConnectionDisabledMessage() { output.error({ @@ -68,65 +69,31 @@ async function createNxCloudWorkspace( return response.data; } -async function printSuccessMessage( +export async function printSuccessMessage( token: string | undefined, installationSource: string, usesGithub: boolean ) { - const connectCloudUrl = await shortenedCloudUrl( + const connectCloudUrl = await createNxCloudOnboardingURL( installationSource, token, usesGithub ); - - if (installationSource === 'nx-connect' && usesGithub) { - try { - const cloudConnectSpinner = ora( - `Opening Nx Cloud ${connectCloudUrl} in your browser to connect your workspace.` - ).start(); - await sleep(2000); - open(connectCloudUrl); - cloudConnectSpinner.succeed(); - } catch (e) { - output.note({ - title: `Your Nx Cloud workspace is ready.`, - bodyLines: [ - `To claim it, connect it to your Nx Cloud account:`, - `- Go to the following URL to connect your workspace to Nx Cloud:`, - '', - `${connectCloudUrl}`, - ], - }); - } - } else { - if (installationSource === 'create-nx-workspace') { - output.note({ - title: `Your Nx Cloud workspace is ready.`, - bodyLines: [ - `To claim it, connect it to your Nx Cloud account:`, - `- Push your repository to your git hosting provider.`, - `- Go to the following URL to connect your workspace to Nx Cloud:`, - '', - `${connectCloudUrl}`, - ], - }); - } else { - output.note({ - title: `Your Nx Cloud workspace is ready.`, - bodyLines: [ - `To claim it, connect it to your Nx Cloud account:`, - `- Commit and push your changes.`, - `- Create a pull request for the changes.`, - `- Go to the following URL to connect your workspace to Nx Cloud:`, - '', - `${connectCloudUrl}`, - ], - }); - } - } + output.note({ + title: `Your Nx Cloud workspace is ready.`, + bodyLines: [ + `To claim it, connect it to your Nx Cloud account:`, + `- Commit and push your changes.`, + `- Create a pull request for the changes.`, + `- Go to the following URL to connect your workspace to Nx Cloud:`, + '', + `${connectCloudUrl}`, + ], + }); + return connectCloudUrl; } -interface ConnectToNxCloudOptions { +export interface ConnectToNxCloudOptions { analytics?: boolean; installationSource?: string; hideFormatLogs?: boolean; @@ -153,7 +120,7 @@ function addNxCloudOptionsToNxJson( export async function connectToNxCloud( tree: Tree, schema: ConnectToNxCloudOptions -) { +): Promise { schema.installationSource ??= 'user'; const nxJson = readNxJson(tree) as @@ -161,16 +128,14 @@ export async function connectToNxCloud( | (NxJsonConfiguration & { neverConnectToCloud: boolean }); if (nxJson?.neverConnectToCloud) { - return () => { - printCloudConnectionDisabledMessage(); - }; + printCloudConnectionDisabledMessage(); + return null; } else { - const usesGithub = await repoUsesGithub(schema.github); + const usesGithub = schema.github ?? (await repoUsesGithub(schema.github)); let responseFromCreateNxCloudWorkspace: | { token: string; - url: string; } | undefined; @@ -192,18 +157,9 @@ export async function connectToNxCloud( await formatChangedFilesWithPrettierIfAvailable(tree, { silent: schema.hideFormatLogs, }); + return responseFromCreateNxCloudWorkspace.token; } - return async () => - await printSuccessMessage( - responseFromCreateNxCloudWorkspace?.token, - schema.installationSource, - usesGithub - ); } } -function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - export default connectToNxCloud; diff --git a/packages/nx/src/nx-cloud/utilities/url-shorten.ts b/packages/nx/src/nx-cloud/utilities/url-shorten.ts index 122f5d9854f58..5a28ae708212d 100644 --- a/packages/nx/src/nx-cloud/utilities/url-shorten.ts +++ b/packages/nx/src/nx-cloud/utilities/url-shorten.ts @@ -2,10 +2,11 @@ import { logger } from '../../devkit-exports'; import { getGithubSlugOrNull } from '../../utils/git-utils'; import { getCloudUrl } from './get-cloud-options'; -export async function shortenedCloudUrl( - installationSource: string, +export async function createNxCloudOnboardingURL( + onboardingSource: string, accessToken?: string, - usesGithub?: boolean + usesGithub?: boolean, + meta?: string ) { const githubSlug = getGithubSlugOrNull(); @@ -29,7 +30,7 @@ export async function shortenedCloudUrl( return apiUrl; } - const source = getSource(installationSource); + const source = getSource(onboardingSource); try { const response = await require('axios').post( @@ -39,6 +40,7 @@ export async function shortenedCloudUrl( source, accessToken: usesGithub ? null : accessToken, selectedRepositoryName: githubSlug === 'github' ? null : githubSlug, + meta, } ); @@ -87,15 +89,13 @@ export async function repoUsesGithub( function getSource( installationSource: string -): 'nx-init' | 'nx-connect' | 'create-nx-workspace' | 'other' { +): 'nx-init' | 'nx-connect' | string { if (installationSource.includes('nx-init')) { return 'nx-init'; } else if (installationSource.includes('nx-connect')) { return 'nx-connect'; - } else if (installationSource.includes('create-nx-workspace')) { - return 'create-nx-workspace'; } else { - return 'other'; + return installationSource; } }