Skip to content

Commit e22fc93

Browse files
authored
feat(create): set npm.scriptRunner to vp in vp create (#1346)
## Summary - Add an optional `extraVsCodeSettings` parameter to `writeEditorConfigs()` that gets merged into `.vscode/settings.json` (only for the `vscode` editor). Existing keys in the user's settings file win during merge. - `vp create` passes `{ 'npm.scriptRunner': 'vp' }` through this parameter so newly created projects have VS Code's NPM Scripts panel run scripts through the Vite+ task runner. - `vp migrate` intentionally does **not** set this — existing projects may have teammates without `vp` installed locally. - Update IDE integration docs to explain the new behavior and show how to add the setting manually for migrated/existing projects. - Add editor config tests covering: the setting is written on `vp create`, absent by default, not applied to `zed`, and existing `npm.scriptRunner` values are preserved on merge. Closes #1337
1 parent 8300a50 commit e22fc93

File tree

4 files changed

+89
-2
lines changed

4 files changed

+89
-2
lines changed

docs/guide/ide-integration.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ For the best VS Code experience with Vite+, install the [Vite Plus Extension Pac
99
- `Oxc` for formatting and linting via `vp check`
1010
- `Vitest` for test runs via `vp test`
1111

12-
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:
12+
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).
13+
14+
You can also manually set up the VS Code config:
1315

1416
`.vscode/extensions.json`
1517

@@ -34,3 +36,13 @@ When you create or migrate a project, Vite+ prompts whether you want editor conf
3436
```
3537

3638
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.
39+
40+
To let the VS Code NPM Scripts panel run scripts through `vp`, add the following to your `.vscode/settings.json`:
41+
42+
```json
43+
{
44+
"npm.scriptRunner": "vp"
45+
}
46+
```
47+
48+
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.

packages/cli/src/create/bin.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
796796
editorId: selectedEditor,
797797
interactive: options.interactive,
798798
silent: compactOutput,
799+
extraVsCodeSettings: { 'npm.scriptRunner': 'vp' },
799800
});
800801
resumeCreateProgress();
801802
workspaceInfo.rootDir = fullPath;
@@ -885,6 +886,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
885886
editorId: selectedEditor,
886887
interactive: options.interactive,
887888
silent: compactOutput,
889+
extraVsCodeSettings: { 'npm.scriptRunner': 'vp' },
888890
});
889891
resumeCreateProgress();
890892

packages/cli/src/utils/__tests__/editor.spec.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,26 @@ describe('writeEditorConfigs', () => {
3838
expect(settings['editor.defaultFormatter']).toBe('oxc.oxc-vscode');
3939
expect(settings['oxc.fmt.configPath']).toBe('./vite.config.ts');
4040
expect(settings['editor.formatOnSave']).toBe(true);
41+
expect(settings['npm.scriptRunner']).toBeUndefined();
42+
});
43+
44+
it('includes additionalSettings in vscode settings.json when provided', async () => {
45+
const projectRoot = createTempDir();
46+
47+
await writeEditorConfigs({
48+
projectRoot,
49+
editorId: 'vscode',
50+
interactive: false,
51+
silent: true,
52+
extraVsCodeSettings: { 'npm.scriptRunner': 'vp' },
53+
});
54+
55+
const settings = JSON.parse(
56+
fs.readFileSync(path.join(projectRoot, '.vscode', 'settings.json'), 'utf8'),
57+
) as Record<string, unknown>;
58+
59+
expect(settings['npm.scriptRunner']).toBe('vp');
60+
expect(settings['editor.defaultFormatter']).toBe('oxc.oxc-vscode');
4161
});
4262

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

6990
const settings = JSON.parse(
@@ -76,12 +97,58 @@ describe('writeEditorConfigs', () => {
7697
// New keys are added
7798
expect(settings['editor.defaultFormatter']).toBe('oxc.oxc-vscode');
7899
expect(settings['oxc.fmt.configPath']).toBe('./vite.config.ts');
100+
expect(settings['npm.scriptRunner']).toBe('vp');
79101

80102
const codeActions = settings['editor.codeActionsOnSave'] as Record<string, unknown>;
81103
expect(codeActions['source.organizeImports']).toBe('explicit');
82104
expect(codeActions['source.fixAll.oxc']).toBe('explicit');
83105
});
84106

107+
it('does not apply extraVsCodeSettings to zed editor', async () => {
108+
const projectRoot = createTempDir();
109+
110+
await writeEditorConfigs({
111+
projectRoot,
112+
editorId: 'zed',
113+
interactive: false,
114+
silent: true,
115+
extraVsCodeSettings: { 'npm.scriptRunner': 'vp' },
116+
});
117+
118+
const settings = JSON.parse(
119+
fs.readFileSync(path.join(projectRoot, '.zed', 'settings.json'), 'utf8'),
120+
) as Record<string, unknown>;
121+
122+
expect(settings['npm.scriptRunner']).toBeUndefined();
123+
});
124+
125+
it('preserves existing npm.scriptRunner during merge with extraVsCodeSettings', async () => {
126+
const projectRoot = createTempDir();
127+
128+
const vscodeDir = path.join(projectRoot, '.vscode');
129+
fs.mkdirSync(vscodeDir, { recursive: true });
130+
fs.writeFileSync(
131+
path.join(vscodeDir, 'settings.json'),
132+
JSON.stringify({ 'npm.scriptRunner': 'npm' }),
133+
'utf8',
134+
);
135+
136+
await writeEditorConfigs({
137+
projectRoot,
138+
editorId: 'vscode',
139+
interactive: false,
140+
silent: true,
141+
extraVsCodeSettings: { 'npm.scriptRunner': 'vp' },
142+
});
143+
144+
const settings = JSON.parse(
145+
fs.readFileSync(path.join(projectRoot, '.vscode', 'settings.json'), 'utf8'),
146+
) as Record<string, unknown>;
147+
148+
// deepMerge preserves existing keys — 'npm' is not overwritten by 'vp'
149+
expect(settings['npm.scriptRunner']).toBe('npm');
150+
});
151+
85152
it('writes zed settings that align formatter config with vite.config.ts', async () => {
86153
const projectRoot = createTempDir();
87154

packages/cli/src/utils/editor.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,12 +267,14 @@ export async function writeEditorConfigs({
267267
interactive,
268268
conflictDecisions,
269269
silent = false,
270+
extraVsCodeSettings,
270271
}: {
271272
projectRoot: string;
272273
editorId: EditorId | undefined;
273274
interactive: boolean;
274275
conflictDecisions?: Map<string, 'merge' | 'skip'>;
275276
silent?: boolean;
277+
extraVsCodeSettings?: Record<string, string>;
276278
}) {
277279
if (!editorId) {
278280
return;
@@ -286,7 +288,11 @@ export async function writeEditorConfigs({
286288
const targetDir = path.join(projectRoot, editorConfig.targetDir);
287289
await fsPromises.mkdir(targetDir, { recursive: true });
288290

289-
for (const [fileName, incoming] of Object.entries(editorConfig.files)) {
291+
for (const [fileName, baseIncoming] of Object.entries(editorConfig.files)) {
292+
const incoming =
293+
editorId === 'vscode' && fileName === 'settings.json' && extraVsCodeSettings
294+
? { ...extraVsCodeSettings, ...baseIncoming }
295+
: baseIncoming;
290296
const filePath = path.join(targetDir, fileName);
291297

292298
if (fs.existsSync(filePath)) {

0 commit comments

Comments
 (0)