Skip to content

Commit 5ff8f5a

Browse files
committed
feat(create): improve UX when running vp create from monorepo subdirectories
When a user runs `vp create` from a subdirectory of a monorepo, the command auto-detects the workspace root and creates packages relative to that root. This can be confusing since the user expects location-awareness. Changes: - Show "Detected monorepo root at <path>" info message when in a subdir - In interactive mode, add a "current directory" option to the parent dir selector when the user's cwd is not under a standard workspace pattern dir, and default to it - In non-interactive mode, show a hint about using --directory - Add snap test covering all edge cases: workspace subdir, workspace parent dir, non-workspace dir, and --directory from a subdir
1 parent e27b3f1 commit 5ff8f5a

7 files changed

Lines changed: 119 additions & 18 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "website",
3+
"version": "0.0.0"
4+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "test-monorepo",
3+
"version": "0.0.0",
4+
"private": true,
5+
"packageManager": "pnpm@10.12.1"
6+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
packages:
2+
- apps/*
3+
- packages/*
4+
- tools/*
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "helper",
3+
"version": "0.0.0"
4+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
> cd apps/website && vp create --no-interactive vite:generator # from workspace subdir
2+
> test -f tools/vite-plus-generator/package.json && echo 'Created at tools/vite-plus-generator' || echo 'NOT at tools/'
3+
Created at tools/vite-plus-generator
4+
5+
> test ! -f apps/website/tools/vite-plus-generator/package.json && echo 'Not in apps/website/' || echo 'BUG: in apps/website/'
6+
Not in apps/website/
7+
8+
> cd apps && vp create --no-interactive vite:application # from workspace parent dir
9+
> test -f apps/vite-plus-application/package.json && echo 'Created at apps/vite-plus-application' || echo 'NOT at apps/'
10+
Created at apps/vite-plus-application
11+
12+
> cd scripts/helper && vp create --no-interactive vite:library # from non-workspace dir
13+
> test -f packages/vite-plus-library/package.json && echo 'Created at packages/vite-plus-library' || echo 'NOT at packages/'
14+
Created at packages/vite-plus-library
15+
16+
> test ! -f scripts/helper/packages/vite-plus-library/package.json && echo 'Not in scripts/helper/' || echo 'BUG: in scripts/helper/'
17+
Not in scripts/helper/
18+
19+
> cd scripts/helper && vp create --no-interactive vite:application --directory apps/custom-app # --directory from non-workspace dir
20+
> test -f apps/custom-app/package.json && echo 'Created at apps/custom-app with --directory' || echo 'NOT at apps/custom-app'
21+
Created at apps/custom-app with --directory
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"commands": [
3+
{
4+
"command": "cd apps/website && vp create --no-interactive vite:generator # from workspace subdir",
5+
"ignoreOutput": true
6+
},
7+
"test -f tools/vite-plus-generator/package.json && echo 'Created at tools/vite-plus-generator' || echo 'NOT at tools/'",
8+
"test ! -f apps/website/tools/vite-plus-generator/package.json && echo 'Not in apps/website/' || echo 'BUG: in apps/website/'",
9+
10+
{
11+
"command": "cd apps && vp create --no-interactive vite:application # from workspace parent dir",
12+
"ignoreOutput": true
13+
},
14+
"test -f apps/vite-plus-application/package.json && echo 'Created at apps/vite-plus-application' || echo 'NOT at apps/'",
15+
16+
{
17+
"command": "cd scripts/helper && vp create --no-interactive vite:library # from non-workspace dir",
18+
"ignoreOutput": true
19+
},
20+
"test -f packages/vite-plus-library/package.json && echo 'Created at packages/vite-plus-library' || echo 'NOT at packages/'",
21+
"test ! -f scripts/helper/packages/vite-plus-library/package.json && echo 'Not in scripts/helper/' || echo 'BUG: in scripts/helper/'",
22+
23+
{
24+
"command": "cd scripts/helper && vp create --no-interactive vite:application --directory apps/custom-app # --directory from non-workspace dir",
25+
"ignoreOutput": true
26+
},
27+
"test -f apps/custom-app/package.json && echo 'Created at apps/custom-app with --directory' || echo 'NOT at apps/custom-app'"
28+
]
29+
}

packages/cli/src/create/bin.ts

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
selectAgentTargetPath,
1616
writeAgentInstructions,
1717
} from '../utils/agent.js';
18+
import { displayRelative } from '../utils/path.js';
1819
import {
1920
defaultInteractive,
2021
downloadPackageManager,
@@ -184,8 +185,20 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
184185
packageName = formatted.packageName;
185186
}
186187

187-
const workspaceInfoOptional = await detectWorkspace(process.cwd());
188+
const cwd = process.cwd();
189+
const workspaceInfoOptional = await detectWorkspace(cwd);
188190
const isMonorepo = workspaceInfoOptional.isMonorepo;
191+
const cwdRelativeToRoot =
192+
isMonorepo && workspaceInfoOptional.rootDir !== cwd
193+
? displayRelative(cwd, workspaceInfoOptional.rootDir)
194+
: '';
195+
const isInSubdirectory = cwdRelativeToRoot !== '';
196+
const cwdUnderParentDir = isInSubdirectory
197+
? workspaceInfoOptional.parentDirs.some(
198+
(dir) => cwdRelativeToRoot === dir || cwdRelativeToRoot.startsWith(`${dir}/`),
199+
)
200+
: true;
201+
const shouldOfferCwdOption = isInSubdirectory && !cwdUnderParentDir;
189202

190203
// Interactive mode: prompt for template if not provided
191204
let selectedTemplateName = templateName as string;
@@ -287,27 +300,44 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
287300
cancelAndExit('Cannot create a monorepo inside an existing monorepo', 1);
288301
}
289302

303+
if (isInSubdirectory) {
304+
prompts.log.info(`Detected monorepo root at ${accent(workspaceInfoOptional.rootDir)}`);
305+
}
306+
290307
if (isMonorepo && options.interactive && !targetDir) {
291308
let parentDir: string | undefined;
292-
if (workspaceInfoOptional.parentDirs.length > 0) {
293-
const defaultParentDir =
294-
inferParentDir(selectedTemplateName, workspaceInfoOptional) ??
295-
workspaceInfoOptional.parentDirs[0];
309+
const hasParentDirs = workspaceInfoOptional.parentDirs.length > 0;
310+
311+
if (hasParentDirs || isInSubdirectory) {
312+
const dirOptions: { label: string; value: string; hint: string }[] =
313+
workspaceInfoOptional.parentDirs.map((dir) => ({
314+
label: `${dir}/`,
315+
value: dir,
316+
hint: '',
317+
}));
318+
319+
if (shouldOfferCwdOption) {
320+
dirOptions.push({
321+
label: `${cwdRelativeToRoot}/ (current directory)`,
322+
value: cwdRelativeToRoot,
323+
hint: '',
324+
});
325+
}
326+
327+
dirOptions.push({
328+
label: 'other',
329+
value: 'other',
330+
hint: 'Enter a custom target directory',
331+
});
332+
333+
const defaultParentDir = shouldOfferCwdOption
334+
? cwdRelativeToRoot
335+
: (inferParentDir(selectedTemplateName, workspaceInfoOptional) ??
336+
workspaceInfoOptional.parentDirs[0]);
337+
296338
const selected = await prompts.select({
297339
message: 'Where should the new package be added to the monorepo:',
298-
options: workspaceInfoOptional.parentDirs
299-
.map((dir) => ({
300-
label: `${dir}/`,
301-
value: dir,
302-
hint: ``,
303-
}))
304-
.concat([
305-
{
306-
label: 'other',
307-
value: 'other',
308-
hint: 'Enter a custom target directory',
309-
},
310-
]),
340+
options: dirOptions,
311341
initialValue: defaultParentDir,
312342
});
313343

@@ -339,6 +369,9 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
339369
selectedParentDir = parentDir;
340370
}
341371
if (isMonorepo && !options.interactive && !targetDir) {
372+
if (isInSubdirectory) {
373+
prompts.log.info(`Use ${accent('--directory')} to specify a different target location.`);
374+
}
342375
const inferredParentDir =
343376
inferParentDir(selectedTemplateName, workspaceInfoOptional) ??
344377
workspaceInfoOptional.parentDirs[0];

0 commit comments

Comments
 (0)