diff --git a/packages/cli/snap-tests-global/create-from-monorepo-subdir/apps/website/package.json b/packages/cli/snap-tests-global/create-from-monorepo-subdir/apps/website/package.json new file mode 100644 index 0000000000..042a63fe8e --- /dev/null +++ b/packages/cli/snap-tests-global/create-from-monorepo-subdir/apps/website/package.json @@ -0,0 +1,4 @@ +{ + "name": "website", + "version": "0.0.0" +} diff --git a/packages/cli/snap-tests-global/create-from-monorepo-subdir/package.json b/packages/cli/snap-tests-global/create-from-monorepo-subdir/package.json new file mode 100644 index 0000000000..3e9925adba --- /dev/null +++ b/packages/cli/snap-tests-global/create-from-monorepo-subdir/package.json @@ -0,0 +1,6 @@ +{ + "name": "test-monorepo", + "version": "0.0.0", + "private": true, + "packageManager": "pnpm@10.12.1" +} diff --git a/packages/cli/snap-tests-global/create-from-monorepo-subdir/pnpm-workspace.yaml b/packages/cli/snap-tests-global/create-from-monorepo-subdir/pnpm-workspace.yaml new file mode 100644 index 0000000000..7a82ab00f0 --- /dev/null +++ b/packages/cli/snap-tests-global/create-from-monorepo-subdir/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +packages: + - apps/* + - packages/* + - tools/* diff --git a/packages/cli/snap-tests-global/create-from-monorepo-subdir/scripts/helper/package.json b/packages/cli/snap-tests-global/create-from-monorepo-subdir/scripts/helper/package.json new file mode 100644 index 0000000000..d74302b4c7 --- /dev/null +++ b/packages/cli/snap-tests-global/create-from-monorepo-subdir/scripts/helper/package.json @@ -0,0 +1,4 @@ +{ + "name": "helper", + "version": "0.0.0" +} diff --git a/packages/cli/snap-tests-global/create-from-monorepo-subdir/snap.txt b/packages/cli/snap-tests-global/create-from-monorepo-subdir/snap.txt new file mode 100644 index 0000000000..79e092d56e --- /dev/null +++ b/packages/cli/snap-tests-global/create-from-monorepo-subdir/snap.txt @@ -0,0 +1,21 @@ +> cd apps/website && vp create --no-interactive vite:generator # from workspace subdir +> test -f tools/vite-plus-generator/package.json && echo 'Created at tools/vite-plus-generator' || echo 'NOT at tools/' +Created at tools/vite-plus-generator + +> test ! -f apps/website/tools/vite-plus-generator/package.json && echo 'Not in apps/website/' || echo 'BUG: in apps/website/' +Not in apps/website/ + +> cd apps && vp create --no-interactive vite:application # from workspace parent dir +> test -f apps/vite-plus-application/package.json && echo 'Created at apps/vite-plus-application' || echo 'NOT at apps/' +Created at apps/vite-plus-application + +> cd scripts/helper && vp create --no-interactive vite:library # from non-workspace dir +> test -f packages/vite-plus-library/package.json && echo 'Created at packages/vite-plus-library' || echo 'NOT at packages/' +Created at packages/vite-plus-library + +> test ! -f scripts/helper/packages/vite-plus-library/package.json && echo 'Not in scripts/helper/' || echo 'BUG: in scripts/helper/' +Not in scripts/helper/ + +> cd scripts/helper && vp create --no-interactive vite:application --directory apps/custom-app # --directory from non-workspace dir +> test -f apps/custom-app/package.json && echo 'Created at apps/custom-app with --directory' || echo 'NOT at apps/custom-app' +Created at apps/custom-app with --directory diff --git a/packages/cli/snap-tests-global/create-from-monorepo-subdir/steps.json b/packages/cli/snap-tests-global/create-from-monorepo-subdir/steps.json new file mode 100644 index 0000000000..df59c30332 --- /dev/null +++ b/packages/cli/snap-tests-global/create-from-monorepo-subdir/steps.json @@ -0,0 +1,29 @@ +{ + "commands": [ + { + "command": "cd apps/website && vp create --no-interactive vite:generator # from workspace subdir", + "ignoreOutput": true + }, + "test -f tools/vite-plus-generator/package.json && echo 'Created at tools/vite-plus-generator' || echo 'NOT at tools/'", + "test ! -f apps/website/tools/vite-plus-generator/package.json && echo 'Not in apps/website/' || echo 'BUG: in apps/website/'", + + { + "command": "cd apps && vp create --no-interactive vite:application # from workspace parent dir", + "ignoreOutput": true + }, + "test -f apps/vite-plus-application/package.json && echo 'Created at apps/vite-plus-application' || echo 'NOT at apps/'", + + { + "command": "cd scripts/helper && vp create --no-interactive vite:library # from non-workspace dir", + "ignoreOutput": true + }, + "test -f packages/vite-plus-library/package.json && echo 'Created at packages/vite-plus-library' || echo 'NOT at packages/'", + "test ! -f scripts/helper/packages/vite-plus-library/package.json && echo 'Not in scripts/helper/' || echo 'BUG: in scripts/helper/'", + + { + "command": "cd scripts/helper && vp create --no-interactive vite:application --directory apps/custom-app # --directory from non-workspace dir", + "ignoreOutput": true + }, + "test -f apps/custom-app/package.json && echo 'Created at apps/custom-app with --directory' || echo 'NOT at apps/custom-app'" + ] +} diff --git a/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/package.json b/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/package.json new file mode 100644 index 0000000000..8b8be750e5 --- /dev/null +++ b/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/package.json @@ -0,0 +1,4 @@ +{ + "name": "parent-project", + "version": "0.0.0" +} diff --git a/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/scripts/.keep b/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/scripts/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/snap.txt b/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/snap.txt new file mode 100644 index 0000000000..2a1b2ea212 --- /dev/null +++ b/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/snap.txt @@ -0,0 +1,6 @@ +> cd scripts && vp create --no-interactive vite:application # from non-monorepo subdir +> test -f scripts/vite-plus-application/package.json && echo 'Created at scripts/vite-plus-application' || echo 'NOT at scripts/' +Created at scripts/vite-plus-application + +> test ! -f vite-plus-application/package.json && echo 'Not at parent root' || echo 'BUG: created at parent root' +Not at parent root diff --git a/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/steps.json b/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/steps.json new file mode 100644 index 0000000000..bbbd6abbd6 --- /dev/null +++ b/packages/cli/snap-tests-global/create-from-nonworkspace-subdir/steps.json @@ -0,0 +1,10 @@ +{ + "commands": [ + { + "command": "cd scripts && vp create --no-interactive vite:application # from non-monorepo subdir", + "ignoreOutput": true + }, + "test -f scripts/vite-plus-application/package.json && echo 'Created at scripts/vite-plus-application' || echo 'NOT at scripts/'", + "test ! -f vite-plus-application/package.json && echo 'Not at parent root' || echo 'BUG: created at parent root'" + ] +} diff --git a/packages/cli/src/create/bin.ts b/packages/cli/src/create/bin.ts index ed7a3a6976..6a1cd8c094 100644 --- a/packages/cli/src/create/bin.ts +++ b/packages/cli/src/create/bin.ts @@ -15,6 +15,7 @@ import { selectAgentTargetPath, writeAgentInstructions, } from '../utils/agent.js'; +import { displayRelative } from '../utils/path.js'; import { defaultInteractive, downloadPackageManager, @@ -184,9 +185,28 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h packageName = formatted.packageName; } - const workspaceInfoOptional = await detectWorkspace(process.cwd()); + const cwd = process.cwd(); + const workspaceInfoOptional = await detectWorkspace(cwd); const isMonorepo = workspaceInfoOptional.isMonorepo; + // For non-monorepo, always use cwd as rootDir. + // detectWorkspace walks up to find the nearest package.json, but for `vp create` + // in standalone mode, the project should be created relative to where the user is. + if (!isMonorepo) { + workspaceInfoOptional.rootDir = cwd; + } + const cwdRelativeToRoot = + isMonorepo && workspaceInfoOptional.rootDir !== cwd + ? displayRelative(cwd, workspaceInfoOptional.rootDir) + : ''; + const isInSubdirectory = cwdRelativeToRoot !== ''; + const cwdUnderParentDir = isInSubdirectory + ? workspaceInfoOptional.parentDirs.some( + (dir) => cwdRelativeToRoot === dir || cwdRelativeToRoot.startsWith(`${dir}/`), + ) + : true; + const shouldOfferCwdOption = isInSubdirectory && !cwdUnderParentDir; + // Interactive mode: prompt for template if not provided let selectedTemplateName = templateName as string; let selectedTemplateArgs = [...templateArgs]; @@ -287,27 +307,44 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h cancelAndExit('Cannot create a monorepo inside an existing monorepo', 1); } + if (isInSubdirectory) { + prompts.log.info(`Detected monorepo root at ${accent(workspaceInfoOptional.rootDir)}`); + } + if (isMonorepo && options.interactive && !targetDir) { let parentDir: string | undefined; - if (workspaceInfoOptional.parentDirs.length > 0) { - const defaultParentDir = - inferParentDir(selectedTemplateName, workspaceInfoOptional) ?? - workspaceInfoOptional.parentDirs[0]; + const hasParentDirs = workspaceInfoOptional.parentDirs.length > 0; + + if (hasParentDirs || isInSubdirectory) { + const dirOptions: { label: string; value: string; hint: string }[] = + workspaceInfoOptional.parentDirs.map((dir) => ({ + label: `${dir}/`, + value: dir, + hint: '', + })); + + if (shouldOfferCwdOption) { + dirOptions.push({ + label: `${cwdRelativeToRoot}/ (current directory)`, + value: cwdRelativeToRoot, + hint: '', + }); + } + + dirOptions.push({ + label: 'other directory', + value: 'other', + hint: 'Enter a custom target directory', + }); + + const defaultParentDir = shouldOfferCwdOption + ? cwdRelativeToRoot + : (inferParentDir(selectedTemplateName, workspaceInfoOptional) ?? + workspaceInfoOptional.parentDirs[0]); + const selected = await prompts.select({ message: 'Where should the new package be added to the monorepo:', - options: workspaceInfoOptional.parentDirs - .map((dir) => ({ - label: `${dir}/`, - value: dir, - hint: ``, - })) - .concat([ - { - label: 'other', - value: 'other', - hint: 'Enter a custom target directory', - }, - ]), + options: dirOptions, initialValue: defaultParentDir, }); @@ -339,6 +376,9 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h selectedParentDir = parentDir; } if (isMonorepo && !options.interactive && !targetDir) { + if (isInSubdirectory) { + prompts.log.info(`Use ${accent('--directory')} to specify a different target location.`); + } const inferredParentDir = inferParentDir(selectedTemplateName, workspaceInfoOptional) ?? workspaceInfoOptional.parentDirs[0]; diff --git a/packages/cli/src/create/prompts.ts b/packages/cli/src/create/prompts.ts index 6d170c9c6c..f270a7fded 100644 --- a/packages/cli/src/create/prompts.ts +++ b/packages/cli/src/create/prompts.ts @@ -20,7 +20,7 @@ export async function promptPackageNameAndTargetDir( placeholder: defaultPackageName, defaultValue: defaultPackageName, validate: (value) => { - if (value != null && value.length === 0) { + if (value == null || value.length === 0) { return; }