Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/cli/snap-tests-global/new-vite-monorepo/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,27 @@ website
> ls vite-plus-monorepo/apps/vite-plus-application/package.json # check vite-plus-application package.json
vite-plus-monorepo/apps/vite-plus-application/package.json

> test ! -e vite-plus-monorepo/apps/vite-plus-application/.vscode && echo 'No app editor config' || (echo 'ERROR: app editor config exists' && exit 1) # monorepo package create without --editor should not write VS Code config
No app editor config

> cd vite-plus-monorepo && vp create --no-interactive vite:application --directory apps/no-editor --no-editor # create application with explicit editor opt-out inside monorepo
> test ! -e vite-plus-monorepo/apps/no-editor/.vscode && echo 'No opt-out app editor config' || (echo 'ERROR: opt-out app editor config exists' && exit 1) # --no-editor should not write VS Code config
No opt-out app editor config

> cd vite-plus-monorepo && vp create --no-interactive vite:application --directory apps/editor-opt-in --editor vscode # create application with explicit editor opt-in inside monorepo
> test -f vite-plus-monorepo/apps/editor-opt-in/.vscode/settings.json && echo 'Opt-in app VS Code settings exist' # explicit --editor should write VS Code settings
Opt-in app VS Code settings exist

> test -f vite-plus-monorepo/apps/editor-opt-in/.vscode/extensions.json && echo 'Opt-in app VS Code extensions exist' # explicit --editor should write VS Code extensions
Opt-in app VS Code extensions exist

> cd vite-plus-monorepo && vp create --no-interactive vite:library # create library in non-interactive mode
> ls vite-plus-monorepo/packages/vite-plus-library/package.json # check vite-plus-library package.json
vite-plus-monorepo/packages/vite-plus-library/package.json

> test ! -e vite-plus-monorepo/packages/vite-plus-library/.vscode && echo 'No library editor config' || (echo 'ERROR: library editor config exists' && exit 1) # monorepo package create without --editor should not write VS Code config
No library editor config

> cd vite-plus-monorepo && vp create --no-interactive vite:generator # create generator in non-interactive mode
> ls vite-plus-monorepo/tools # check tools directory created
vite-plus-generator
Expand Down
13 changes: 13 additions & 0 deletions packages/cli/snap-tests-global/new-vite-monorepo/steps.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,24 @@
},
"ls vite-plus-monorepo/apps # check apps directory created",
"ls vite-plus-monorepo/apps/vite-plus-application/package.json # check vite-plus-application package.json",
"test ! -e vite-plus-monorepo/apps/vite-plus-application/.vscode && echo 'No app editor config' || (echo 'ERROR: app editor config exists' && exit 1) # monorepo package create without --editor should not write VS Code config",
{
"command": "cd vite-plus-monorepo && vp create --no-interactive vite:application --directory apps/no-editor --no-editor # create application with explicit editor opt-out inside monorepo",
"ignoreOutput": true
},
"test ! -e vite-plus-monorepo/apps/no-editor/.vscode && echo 'No opt-out app editor config' || (echo 'ERROR: opt-out app editor config exists' && exit 1) # --no-editor should not write VS Code config",
{
"command": "cd vite-plus-monorepo && vp create --no-interactive vite:application --directory apps/editor-opt-in --editor vscode # create application with explicit editor opt-in inside monorepo",
"ignoreOutput": true
},
"test -f vite-plus-monorepo/apps/editor-opt-in/.vscode/settings.json && echo 'Opt-in app VS Code settings exist' # explicit --editor should write VS Code settings",
"test -f vite-plus-monorepo/apps/editor-opt-in/.vscode/extensions.json && echo 'Opt-in app VS Code extensions exist' # explicit --editor should write VS Code extensions",
{
"command": "cd vite-plus-monorepo && vp create --no-interactive vite:library # create library in non-interactive mode",
"ignoreOutput": true
},
"ls vite-plus-monorepo/packages/vite-plus-library/package.json # check vite-plus-library package.json",
"test ! -e vite-plus-monorepo/packages/vite-plus-library/.vscode && echo 'No library editor config' || (echo 'ERROR: library editor config exists' && exit 1) # monorepo package create without --editor should not write VS Code config",
{
"command": "cd vite-plus-monorepo && vp create --no-interactive vite:generator # create generator in non-interactive mode",
"ignoreOutput": true
Expand Down
29 changes: 29 additions & 0 deletions packages/cli/src/create/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
ensureGitignoreVsCodeEditorConfigs,
formatTargetDir,
getProjectDirFromPackageName,
normalizeEditorOption,
renameFiles,
shouldConfigureEditorsForCreate,
} from '../utils.js';

describe('getProjectDirFromPackageName', () => {
Expand All @@ -20,6 +22,33 @@ describe('getProjectDirFromPackageName', () => {
});
});

describe('editor configuration policy', () => {
it('normalizes repeated editor options to a single editor value', () => {
expect(normalizeEditorOption('vscode')).toBe('vscode');
expect(normalizeEditorOption(['vscode', 'zed'])).toBe('zed');
expect(normalizeEditorOption(['vscode', false])).toBe(false);
expect(normalizeEditorOption([undefined, 'vscode'])).toBe('vscode');
});

it('allows automatic editor configuration outside existing monorepos', () => {
expect(shouldConfigureEditorsForCreate({ isMonorepo: false, editor: undefined })).toBe(true);
});

it('skips automatic editor configuration inside existing monorepos', () => {
expect(shouldConfigureEditorsForCreate({ isMonorepo: true, editor: undefined })).toBe(false);
});

it('allows explicit editor opt-in inside existing monorepos', () => {
expect(shouldConfigureEditorsForCreate({ isMonorepo: true, editor: 'vscode' })).toBe(true);
expect(shouldConfigureEditorsForCreate({ isMonorepo: true, editor: ' ' })).toBe(false);
});

it('keeps --no-editor as an explicit opt-out in every workspace mode', () => {
expect(shouldConfigureEditorsForCreate({ isMonorepo: false, editor: false })).toBe(false);
expect(shouldConfigureEditorsForCreate({ isMonorepo: true, editor: false })).toBe(false);
});
});

describe('formatTargetDir', () => {
it('should format "." as current directory with empty package name', () => {
expect(formatTargetDir('.')).toEqual({
Expand Down
62 changes: 36 additions & 26 deletions packages/cli/src/create/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ import {
ensureGitignoreNodeModules,
ensureGitignoreVsCodeEditorConfigs,
formatTargetDir,
normalizeEditorOption,
shouldConfigureEditorsForCreate,
} from './utils.ts';

const helpMessage = renderCliDoc({
Expand Down Expand Up @@ -222,7 +224,7 @@ export interface Options {
help: boolean;
verbose: boolean;
agent?: string | string[] | false;
editor?: string;
editor?: string | false;
hooks?: boolean;
packageManager?: string;
}
Expand Down Expand Up @@ -257,7 +259,7 @@ function parseArgs() {
help?: boolean;
verbose?: boolean;
agent?: ParsedAgentOption;
editor?: string;
editor?: string | false | Array<string | false>;
git?: boolean;
hooks?: boolean;
'package-manager'?: string;
Expand All @@ -279,7 +281,7 @@ function parseArgs() {
help: parsed.help || false,
verbose: parsed.verbose || false,
agent: normalizeAgentOption(parsed.agent),
editor: parsed.editor,
editor: normalizeEditorOption(parsed.editor),
git: parsed.git,
hooks: parsed.hooks,
packageManager: parsed['package-manager'],
Expand Down Expand Up @@ -787,17 +789,23 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
);
}

const existingEditors =
options.editor || !options.interactive
? undefined
: detectExistingEditors(workspaceInfoOptional.rootDir);
selectedEditors =
existingEditors ??
(await selectEditors({
interactive: options.interactive,
editor: options.editor,
onCancel: () => cancelAndExit(),
}));
const shouldConfigureEditors = shouldConfigureEditorsForCreate({
editor: options.editor,
isMonorepo,
});
if (shouldConfigureEditors) {
const existingEditors =
options.editor || !options.interactive
? undefined
: detectExistingEditors(workspaceInfoOptional.rootDir);
selectedEditors =
existingEditors ??
(await selectEditors({
interactive: options.interactive,
editor: options.editor,
onCancel: () => cancelAndExit(),
}));
}

const shouldSetupGit = await promptGitInit(options);
if (!isMonorepo) {
Expand Down Expand Up @@ -1076,19 +1084,21 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
});
}
resumeCreateProgress();
updateCreateProgress('Writing editor configs');
pauseCreateProgress();
await writeEditorConfigs({
projectRoot: fullPath,
editorId: selectedEditors,
interactive: options.interactive,
silent: compactOutput,
extraVsCodeSettings: { 'npm.scriptRunner': 'vp' },
});
if (selectedEditors?.includes('vscode')) {
ensureGitignoreVsCodeEditorConfigs(fullPath);
if (shouldConfigureEditors) {
updateCreateProgress('Writing editor configs');
pauseCreateProgress();
await writeEditorConfigs({
projectRoot: fullPath,
editorId: selectedEditors,
interactive: options.interactive,
silent: compactOutput,
extraVsCodeSettings: { 'npm.scriptRunner': 'vp' },
});
if (selectedEditors?.includes('vscode')) {
ensureGitignoreVsCodeEditorConfigs(fullPath);
}
resumeCreateProgress();
}
resumeCreateProgress();

// The migrate-before-rewrite reorder is only needed when the template
// actually ships ESLint or Prettier (e.g. `create-vite --template
Expand Down
33 changes: 33 additions & 0 deletions packages/cli/src/create/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,39 @@ import validateNpmPackageName from 'validate-npm-package-name';
import { editJsonFile } from '../utils/json.ts';
import { getRandomProjectName } from './random-name.ts';

export type CreateEditorOption = string | false | undefined;
type ParsedCreateEditorOption = CreateEditorOption | CreateEditorOption[];

function hasExplicitEditorOptIn(editor: CreateEditorOption): boolean {
return typeof editor === 'string' && editor.trim() !== '';
}

export function normalizeEditorOption(editor: ParsedCreateEditorOption): CreateEditorOption {
if (!Array.isArray(editor)) {
return editor;
}
if (editor.includes(false)) {
return false;
}
return editor.findLast((value): value is string => typeof value === 'string');
}

export function shouldConfigureEditorsForCreate({
editor,
isMonorepo,
}: {
editor: CreateEditorOption;
isMonorepo: boolean;
}): boolean {
if (editor === false) {
return false;
}
if (!isMonorepo) {
return true;
}
return hasExplicitEditorOptIn(editor);
}

// Helper functions for file operations
export function copy(src: string, dest: string) {
const stat = fs.statSync(src);
Expand Down
Loading