diff --git a/docs/guide/ide-integration.md b/docs/guide/ide-integration.md index e2e1c64924..e6b32e98bb 100644 --- a/docs/guide/ide-integration.md +++ b/docs/guide/ide-integration.md @@ -9,7 +9,9 @@ For the best VS Code experience with Vite+, install the [Vite Plus Extension Pac - `Oxc` for formatting and linting via `vp check` - `Vitest` for test runs via `vp test` -When you create or migrate a project, Vite+ prompts whether you want editor config written for VS Code. You can also manually set up the VS Code config: +When you create or migrate a project, Vite+ prompts whether you want editor config written for VS Code. `vp create` additionally sets `npm.scriptRunner` to `vp` so the VS Code NPM Scripts panel runs scripts through the Vite+ task runner. For migrated or existing projects, you can add this setting manually (see below). + +You can also manually set up the VS Code config: `.vscode/extensions.json` @@ -34,3 +36,13 @@ When you create or migrate a project, Vite+ prompts whether you want editor conf ``` This gives the project a shared default formatter and enables Oxc-powered fix actions on save. Setting `oxc.fmt.configPath` to `./vite.config.ts` keeps editor format-on-save aligned with the `fmt` block in your Vite+ config. Vite+ uses `formatOnSaveMode: "file"` because Oxfmt does not support partial formatting. + +To let the VS Code NPM Scripts panel run scripts through `vp`, add the following to your `.vscode/settings.json`: + +```json +{ + "npm.scriptRunner": "vp" +} +``` + +This is included automatically by `vp create` but not by `vp migrate`, since existing projects may have team members who do not have `vp` installed locally. diff --git a/packages/cli/src/create/bin.ts b/packages/cli/src/create/bin.ts index 028ea7c3b4..574e0c0c7c 100644 --- a/packages/cli/src/create/bin.ts +++ b/packages/cli/src/create/bin.ts @@ -796,6 +796,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h editorId: selectedEditor, interactive: options.interactive, silent: compactOutput, + extraVsCodeSettings: { 'npm.scriptRunner': 'vp' }, }); resumeCreateProgress(); workspaceInfo.rootDir = fullPath; @@ -885,6 +886,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h editorId: selectedEditor, interactive: options.interactive, silent: compactOutput, + extraVsCodeSettings: { 'npm.scriptRunner': 'vp' }, }); resumeCreateProgress(); diff --git a/packages/cli/src/utils/__tests__/editor.spec.ts b/packages/cli/src/utils/__tests__/editor.spec.ts index acd8bfec61..e1e82d65db 100644 --- a/packages/cli/src/utils/__tests__/editor.spec.ts +++ b/packages/cli/src/utils/__tests__/editor.spec.ts @@ -38,6 +38,26 @@ describe('writeEditorConfigs', () => { expect(settings['editor.defaultFormatter']).toBe('oxc.oxc-vscode'); expect(settings['oxc.fmt.configPath']).toBe('./vite.config.ts'); expect(settings['editor.formatOnSave']).toBe(true); + expect(settings['npm.scriptRunner']).toBeUndefined(); + }); + + it('includes additionalSettings in vscode settings.json when provided', async () => { + const projectRoot = createTempDir(); + + await writeEditorConfigs({ + projectRoot, + editorId: 'vscode', + interactive: false, + silent: true, + extraVsCodeSettings: { 'npm.scriptRunner': 'vp' }, + }); + + const settings = JSON.parse( + fs.readFileSync(path.join(projectRoot, '.vscode', 'settings.json'), 'utf8'), + ) as Record; + + expect(settings['npm.scriptRunner']).toBe('vp'); + expect(settings['editor.defaultFormatter']).toBe('oxc.oxc-vscode'); }); it('merges existing vscode JSONC settings (comments, trailing commas)', async () => { @@ -64,6 +84,7 @@ describe('writeEditorConfigs', () => { editorId: 'vscode', interactive: false, silent: true, + extraVsCodeSettings: { 'npm.scriptRunner': 'vp' }, }); const settings = JSON.parse( @@ -76,12 +97,58 @@ describe('writeEditorConfigs', () => { // New keys are added expect(settings['editor.defaultFormatter']).toBe('oxc.oxc-vscode'); expect(settings['oxc.fmt.configPath']).toBe('./vite.config.ts'); + expect(settings['npm.scriptRunner']).toBe('vp'); const codeActions = settings['editor.codeActionsOnSave'] as Record; expect(codeActions['source.organizeImports']).toBe('explicit'); expect(codeActions['source.fixAll.oxc']).toBe('explicit'); }); + it('does not apply extraVsCodeSettings to zed editor', async () => { + const projectRoot = createTempDir(); + + await writeEditorConfigs({ + projectRoot, + editorId: 'zed', + interactive: false, + silent: true, + extraVsCodeSettings: { 'npm.scriptRunner': 'vp' }, + }); + + const settings = JSON.parse( + fs.readFileSync(path.join(projectRoot, '.zed', 'settings.json'), 'utf8'), + ) as Record; + + expect(settings['npm.scriptRunner']).toBeUndefined(); + }); + + it('preserves existing npm.scriptRunner during merge with extraVsCodeSettings', async () => { + const projectRoot = createTempDir(); + + const vscodeDir = path.join(projectRoot, '.vscode'); + fs.mkdirSync(vscodeDir, { recursive: true }); + fs.writeFileSync( + path.join(vscodeDir, 'settings.json'), + JSON.stringify({ 'npm.scriptRunner': 'npm' }), + 'utf8', + ); + + await writeEditorConfigs({ + projectRoot, + editorId: 'vscode', + interactive: false, + silent: true, + extraVsCodeSettings: { 'npm.scriptRunner': 'vp' }, + }); + + const settings = JSON.parse( + fs.readFileSync(path.join(projectRoot, '.vscode', 'settings.json'), 'utf8'), + ) as Record; + + // deepMerge preserves existing keys — 'npm' is not overwritten by 'vp' + expect(settings['npm.scriptRunner']).toBe('npm'); + }); + it('writes zed settings that align formatter config with vite.config.ts', async () => { const projectRoot = createTempDir(); diff --git a/packages/cli/src/utils/editor.ts b/packages/cli/src/utils/editor.ts index 8caee360c8..baed2f8579 100644 --- a/packages/cli/src/utils/editor.ts +++ b/packages/cli/src/utils/editor.ts @@ -267,12 +267,14 @@ export async function writeEditorConfigs({ interactive, conflictDecisions, silent = false, + extraVsCodeSettings, }: { projectRoot: string; editorId: EditorId | undefined; interactive: boolean; conflictDecisions?: Map; silent?: boolean; + extraVsCodeSettings?: Record; }) { if (!editorId) { return; @@ -286,7 +288,11 @@ export async function writeEditorConfigs({ const targetDir = path.join(projectRoot, editorConfig.targetDir); await fsPromises.mkdir(targetDir, { recursive: true }); - for (const [fileName, incoming] of Object.entries(editorConfig.files)) { + for (const [fileName, baseIncoming] of Object.entries(editorConfig.files)) { + const incoming = + editorId === 'vscode' && fileName === 'settings.json' && extraVsCodeSettings + ? { ...extraVsCodeSettings, ...baseIncoming } + : baseIncoming; const filePath = path.join(targetDir, fileName); if (fs.existsSync(filePath)) {