Skip to content

Commit aba0eb2

Browse files
jaysooclaude
andauthored
fix(core): cnw sends correct selectedRepositoryName; prints instructions when user Ctrl+C (#33699)
This PR adds `SIGINT` handling when user kills the process via `Ctrl+C` during CNW. This only prints when the workspace setup is complete, and we also print the Cloud onboarding URL if it has been set up. Also fixes an issue where `selectedRepositoryName` is never sent during CNW. --------- Co-authored-by: Claude <[email protected]>
1 parent ba2c982 commit aba0eb2

5 files changed

Lines changed: 71 additions & 23 deletions

File tree

packages/create-nx-workspace/bin/create-nx-workspace.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
CreateWorkspaceOptions,
77
supportedAgents,
88
} from '../src/create-workspace-options';
9-
import { createWorkspace } from '../src/create-workspace';
9+
import {
10+
createWorkspace,
11+
getInterruptedWorkspaceState,
12+
} from '../src/create-workspace';
1013
import { isKnownPreset, Preset } from '../src/utils/preset/preset';
1114
import { CLIErrorMessageConfig, output } from '../src/utils/output';
1215
import { nxVersion } from '../src/utils/nx/nx-version';
@@ -271,6 +274,29 @@ process.on('uncaughtException', (error: unknown) => {
271274
throw error;
272275
});
273276

277+
// Handle Ctrl+C gracefully - show helpful message if workspace was already created
278+
process.on('SIGINT', () => {
279+
const { directory, connectUrl } = getInterruptedWorkspaceState();
280+
281+
if (directory) {
282+
console.log(''); // New line after ^C
283+
output.log({
284+
title: 'Workspace creation interrupted',
285+
bodyLines: [
286+
`Your workspace was created at: ${directory}`,
287+
'',
288+
'To complete the setup:',
289+
' 1. Ensure your repo is pushed (e.g. https://github.com/new)',
290+
connectUrl
291+
? ` 2. Connect to Nx Cloud: ${connectUrl}`
292+
: ' 2. Connect to Nx Cloud: Run "nx connect"',
293+
],
294+
});
295+
}
296+
297+
process.exit(130); // Standard exit code for SIGINT
298+
});
299+
274300
let rawArgs: Arguments;
275301
async function main(parsedArgs: yargs.Arguments<Arguments>) {
276302
output.log({

packages/create-nx-workspace/src/create-workspace.ts

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ import { Preset } from './utils/preset/preset';
2323
import { cloneTemplate } from './utils/template/clone-template';
2424
import { execAndWait } from './utils/child-process-utils';
2525

26+
// State for SIGINT handler - only set after workspace is fully installed
27+
let workspaceDirectory: string | undefined;
28+
let cloudConnectUrl: string | undefined;
29+
30+
export function getInterruptedWorkspaceState(): {
31+
directory: string | undefined;
32+
connectUrl: string | undefined;
33+
} {
34+
return { directory: workspaceDirectory, connectUrl: cloudConnectUrl };
35+
}
36+
2637
export async function createWorkspace<T extends CreateWorkspaceOptions>(
2738
preset: string,
2839
options: T,
@@ -67,6 +78,9 @@ export async function createWorkspace<T extends CreateWorkspaceOptions>(
6778
// Install dependencies (template flow always uses npm)
6879
await execAndWait('npm install --silent --ignore-scripts', directory);
6980

81+
// Mark workspace as ready for SIGINT handler
82+
workspaceDirectory = directory;
83+
7084
workspaceSetupSpinner.succeed(
7185
`Successfully created the workspace: ${directory}`
7286
);
@@ -95,6 +109,9 @@ export async function createWorkspace<T extends CreateWorkspaceOptions>(
95109
workspaceGlobs,
96110
});
97111

112+
// Mark workspace as ready for SIGINT handler
113+
workspaceDirectory = directory;
114+
98115
// If the preset is a third-party preset, we need to call createPreset to install it
99116
// For first-party presets, it will be created by createEmptyWorkspace instead.
100117
// In createEmptyWorkspace, it will call `nx new` -> `@nx/workspace newGenerator` -> `@nx/workspace generatePreset`.
@@ -111,29 +128,16 @@ export async function createWorkspace<T extends CreateWorkspaceOptions>(
111128

112129
const isTemplate = !!options.template;
113130

114-
let connectUrl: string | undefined;
115-
let nxCloudInfo: string | undefined;
116-
if (nxCloud !== 'skip') {
117-
const token = readNxCloudToken(directory) as string;
118-
119-
// Only generate CI for preset flow (not template)
120-
if (!isTemplate && nxCloud !== 'yes') {
121-
await setupCI(directory, nxCloud, packageManager);
122-
}
123-
124-
connectUrl = await createNxCloudOnboardingUrl(
125-
nxCloud,
126-
token,
127-
directory,
128-
useGitHub
129-
);
131+
// Only generate CI for preset flow (not template)
132+
if (nxCloud !== 'skip' && !isTemplate && nxCloud !== 'yes') {
133+
await setupCI(directory, nxCloud, packageManager);
130134
}
131135

132136
let pushedToVcs = VcsPushStatus.SkippedGit;
133137

134138
if (!skipGit) {
135139
try {
136-
await initializeGitRepo(directory, { defaultBase, commit, connectUrl });
140+
await initializeGitRepo(directory, { defaultBase, commit });
137141

138142
// Push to GitHub if commit was made, GitHub push is not skipped, and:
139143
// - CI provider is GitHub (preset flow), OR
@@ -162,7 +166,22 @@ export async function createWorkspace<T extends CreateWorkspaceOptions>(
162166
}
163167
}
164168

165-
if (connectUrl) {
169+
// Create onboarding URL AFTER git operations so getVcsRemoteInfo() can detect the repo
170+
let connectUrl: string | undefined;
171+
let nxCloudInfo: string | undefined;
172+
if (nxCloud !== 'skip') {
173+
const token = readNxCloudToken(directory) as string;
174+
175+
connectUrl = await createNxCloudOnboardingUrl(
176+
nxCloud,
177+
token,
178+
directory,
179+
useGitHub
180+
);
181+
182+
// Store for SIGINT handler
183+
cloudConnectUrl = connectUrl;
184+
166185
nxCloudInfo = await getNxCloudInfo(
167186
connectUrl,
168187
pushedToVcs,

packages/create-nx-workspace/src/utils/nx/nx-cloud.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ export async function createNxCloudOnboardingUrl(
109109
meta,
110110
false,
111111
useGitHub ??
112-
(nxCloud === 'yes' || nxCloud === 'github' || nxCloud === 'circleci')
112+
(nxCloud === 'yes' || nxCloud === 'github' || nxCloud === 'circleci'),
113+
directory
113114
);
114115
}
115116

packages/nx/src/nx-cloud/utilities/url-shorten.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ export async function createNxCloudOnboardingURL(
1010
accessToken?: string,
1111
meta?: string,
1212
forceManual = false,
13-
forceGithub = false
13+
forceGithub = false,
14+
directory?: string
1415
) {
15-
const remoteInfo = getVcsRemoteInfo();
16+
const remoteInfo = getVcsRemoteInfo(directory);
1617
const apiUrl = getCloudUrl();
1718

1819
const installationSupportsGitHub = await getInstallationSupportsGitHub(

packages/nx/src/utils/git-utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,12 @@ export function parseVcsRemoteUrl(url: string): VcsRemoteInfo | null {
289289
return null;
290290
}
291291

292-
export function getVcsRemoteInfo(): VcsRemoteInfo | null {
292+
export function getVcsRemoteInfo(directory?: string): VcsRemoteInfo | null {
293293
try {
294294
const gitRemote = execSync('git remote -v', {
295295
stdio: 'pipe',
296296
windowsHide: false,
297+
cwd: directory,
297298
})
298299
.toString()
299300
.trim();

0 commit comments

Comments
 (0)