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
14 changes: 13 additions & 1 deletion docs/guide/ide-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand All @@ -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.
2 changes: 2 additions & 0 deletions packages/cli/src/create/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down
67 changes: 67 additions & 0 deletions packages/cli/src/utils/__tests__/editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>;

expect(settings['npm.scriptRunner']).toBe('vp');
expect(settings['editor.defaultFormatter']).toBe('oxc.oxc-vscode');
});

it('merges existing vscode JSONC settings (comments, trailing commas)', async () => {
Expand All @@ -64,6 +84,7 @@ describe('writeEditorConfigs', () => {
editorId: 'vscode',
interactive: false,
silent: true,
extraVsCodeSettings: { 'npm.scriptRunner': 'vp' },
});

const settings = JSON.parse(
Expand All @@ -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<string, unknown>;
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<string, unknown>;

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<string, unknown>;

// 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();

Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/utils/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,14 @@ export async function writeEditorConfigs({
interactive,
conflictDecisions,
silent = false,
extraVsCodeSettings,
}: {
projectRoot: string;
editorId: EditorId | undefined;
interactive: boolean;
conflictDecisions?: Map<string, 'merge' | 'skip'>;
silent?: boolean;
extraVsCodeSettings?: Record<string, string>;
}) {
if (!editorId) {
return;
Expand All @@ -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)) {
Expand Down
Loading