From 353203880d5ac26d28980180f7f51b4ec802f33e Mon Sep 17 00:00:00 2001 From: KTrain <69028025+KTrain5169@users.noreply.github.com> Date: Fri, 17 Jul 2026 08:41:28 +0000 Subject: [PATCH 1/5] feat(create): implement support for JetBrains editors --- .../cli/src/utils/__tests__/editor.spec.ts | 40 ++++++++++ packages/cli/src/utils/editor.ts | 80 +++++++++++++++++-- 2 files changed, 112 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/utils/__tests__/editor.spec.ts b/packages/cli/src/utils/__tests__/editor.spec.ts index 12eeabd64c..9a329ed433 100644 --- a/packages/cli/src/utils/__tests__/editor.spec.ts +++ b/packages/cli/src/utils/__tests__/editor.spec.ts @@ -91,6 +91,14 @@ describe('detectExistingEditors', () => { it('returns undefined when no editor config files exist', () => { expect(detectExistingEditors(createTempDir())).toBeUndefined(); }); + + it('detects existing jetbrains editor config files', () => { + const projectRoot = createTempDir(); + fs.mkdirSync(path.join(projectRoot, '.idea'), { recursive: true }); + fs.writeFileSync(path.join(projectRoot, '.idea', 'externalDependencies.xml'), ''); + + expect(detectExistingEditors(projectRoot)).toEqual(['jetbrains']); + }); }); describe('writeEditorConfigs', () => { @@ -539,4 +547,36 @@ describe('writeEditorConfigs', () => { expect(zedSettings['npm.scriptRunner']).toBeUndefined(); expect(zedSettings.lsp).toBeDefined(); }); + + it('writes jetbrains config as XML based on file extension', async () => { + const projectRoot = createTempDir(); + + await writeEditorConfigs({ + projectRoot, + editorId: 'jetbrains', + interactive: false, + silent: true, + }); + + const xml = fs.readFileSync(path.join(projectRoot, '.idea', 'externalDependencies.xml'), 'utf8'); + expect(xml).toContain(''); + expect(xml).toContain(''); + }); + + it('does not overwrite existing non-JSON editor config in non-interactive mode', async () => { + const projectRoot = createTempDir(); + const xmlPath = path.join(projectRoot, '.idea', 'externalDependencies.xml'); + fs.mkdirSync(path.dirname(xmlPath), { recursive: true }); + fs.writeFileSync(xmlPath, '', 'utf8'); + + await writeEditorConfigs({ + projectRoot, + editorId: 'jetbrains', + interactive: false, + silent: true, + }); + + const xml = fs.readFileSync(xmlPath, 'utf8'); + expect(xml).toBe(''); + }); }); diff --git a/packages/cli/src/utils/editor.ts b/packages/cli/src/utils/editor.ts index 9688b36397..e210841ed0 100644 --- a/packages/cli/src/utils/editor.ts +++ b/packages/cli/src/utils/editor.ts @@ -150,6 +150,17 @@ const ZED_SETTINGS = { }, } as const; +const JETBRAINS_EXTERNAL_DEPENDENCIES = ` + + + + + + +`; + +type EditorConfigValue = Record | string; + export const EDITORS = [ { id: 'vscode', @@ -168,6 +179,14 @@ export const EDITORS = [ 'settings.json': ZED_SETTINGS as Record, }, }, + { + id: 'jetbrains', + label: 'JetBrains (IntelliJ, WebStorm, etc)', + targetDir: '.idea', + files: { + 'externalDependencies.xml': JETBRAINS_EXTERNAL_DEPENDENCIES, + }, + }, ] as const; export type EditorId = (typeof EDITORS)[number]['id']; @@ -386,11 +405,12 @@ async function writeEditorConfig({ await fsPromises.mkdir(targetDir, { recursive: true }); for (const [fileName, baseIncoming] of Object.entries(editorConfig.files)) { - const incoming = + const incoming: EditorConfigValue = editorId === 'vscode' && fileName === 'settings.json' && extraVsCodeSettings ? { ...extraVsCodeSettings, ...baseIncoming } : baseIncoming; const filePath = path.join(targetDir, fileName); + const jsonFormat = isJsonLikeFile(fileName); if (fs.existsSync(filePath)) { const displayPath = `${editorConfig.targetDir}/${fileName}`; @@ -406,13 +426,17 @@ async function writeEditorConfig({ `${displayPath} already exists.\n ` + styleText( 'gray', - `Vite+ adds ${editorConfig.label} settings for the built-in linter and formatter. Merge adds new keys without overwriting existing ones.`, + jsonFormat + ? `Vite+ adds ${editorConfig.label} settings for the built-in linter and formatter. Merge adds new keys without overwriting existing ones.` + : `Vite+ adds ${editorConfig.label} settings for the built-in linter and formatter. Overwrite replaces the existing file with the generated config.`, ), options: [ { - label: 'Merge', + label: jsonFormat ? 'Merge' : 'Overwrite', value: 'merge', - hint: 'Merge new settings into existing file', + hint: jsonFormat + ? 'Merge new settings into existing file' + : 'Replace existing file with generated config', }, { label: 'Skip', @@ -424,12 +448,19 @@ async function writeEditorConfig({ }); conflictAction = prompts.isCancel(action) || action === 'skip' ? 'skip' : 'merge'; } else { - // Non-interactive: always merge (safe because existing keys are never overwritten) - conflictAction = 'merge'; + // Non-interactive: merge JSON safely, skip non-JSON to avoid destructive overwrite. + conflictAction = jsonFormat ? 'merge' : 'skip'; } if (conflictAction === 'merge') { - mergeAndWriteEditorConfig(filePath, incoming, fileName, displayPath, silent); + if (jsonFormat) { + if (!isPlainObject(incoming)) { + throw new Error(`Cannot merge editor config: ${displayPath} incoming value is not JSON`); + } + mergeAndWriteEditorConfig(filePath, incoming, fileName, displayPath, silent); + } else { + writeTextEditorConfig(filePath, incoming, displayPath, silent); + } } else { if (!silent) { prompts.log.info(`Skipped writing ${displayPath}`); @@ -438,13 +469,46 @@ async function writeEditorConfig({ continue; } - writeJsonFile(filePath, incoming); + if (jsonFormat) { + if (!isPlainObject(incoming)) { + throw new Error(`Cannot write editor config: ${editorConfig.targetDir}/${fileName} must be JSON`); + } + writeJsonFile(filePath, incoming); + } else { + writeTextEditorConfig(filePath, incoming, `${editorConfig.targetDir}/${fileName}`, silent); + } if (!silent) { prompts.log.success(`Wrote editor config to ${editorConfig.targetDir}/${fileName}`); } } } +function isJsonLikeFile(fileName: string): boolean { + const ext = path.extname(fileName).toLowerCase(); + return ext === '.json' || ext === '.jsonc'; +} + +function writeTextEditorConfig( + filePath: string, + incoming: EditorConfigValue, + displayPath: string, + silent = false, +) { + if (typeof incoming !== 'string') { + throw new Error(`Cannot write editor config: ${displayPath} must be text content`); + } + + const existingText = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : undefined; + if (existingText === incoming) { + if (!silent) { + prompts.log.info(`No changes needed for ${displayPath}`); + } + return; + } + + fs.writeFileSync(filePath, incoming, 'utf-8'); +} + function normalizeEditorSelection(editorId: EditorSelection): EditorId[] { if (!editorId) { return []; From c56386fa7c7c0125078790bc487ba2b672bab334 Mon Sep 17 00:00:00 2001 From: KTrain <69028025+KTrain5169@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:50:27 +0000 Subject: [PATCH 2/5] WIP docs on JetBrains --- docs/guide/ide-integration.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/guide/ide-integration.md b/docs/guide/ide-integration.md index 554da52e4f..0d8b23d8fd 100644 --- a/docs/guide/ide-integration.md +++ b/docs/guide/ide-integration.md @@ -99,3 +99,15 @@ You can also manually set up the Zed config: ``` Setting `oxfmt.fmt.configPath` to `./vite.config.ts` keeps editor format-on-save aligned with the `fmt` block in your Vite+ config. The full generated config covers additional languages (CSS, HTML, JSON, Markdown, etc.) — run `vp create` or `vp migrate` to get the complete file written automatically. + +## JetBrains (IntelliJ, WebStorm, etc...) + +For the best Vite+ experience with JetBrains IDEs such as IntelliJ & WebStorm, install the [Oxc](https://plugins.jetbrains.com/plugin/27061-oxc) plugin from the JetBrains marketplace. + +When you create or migrate a project, Vite+ prompts you to choose whether you want the editor config written for Zed. + +You can also manually set up the IDE configuration to utilise Oxc: + +```json + +``` From 07bdba133bae3a8a1a2c2efaaeef6bc25b176e7b Mon Sep 17 00:00:00 2001 From: KTrain <69028025+KTrain5169@users.noreply.github.com> Date: Sun, 26 Jul 2026 05:55:30 +0000 Subject: [PATCH 3/5] some todos --- packages/cli/src/utils/editor.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/utils/editor.ts b/packages/cli/src/utils/editor.ts index 9ab3598e1b..c23b222518 100644 --- a/packages/cli/src/utils/editor.ts +++ b/packages/cli/src/utils/editor.ts @@ -150,6 +150,8 @@ const ZED_SETTINGS = { }, } as const; +// TODO Replace this raw XML template with a JSON-object definition +// once the XML parser/merge support below is in place. const JETBRAINS_EXTERNAL_DEPENDENCIES = ` @@ -159,6 +161,8 @@ const JETBRAINS_EXTERNAL_DEPENDENCIES = ` `; +// TODO: Extend this to allow XML files to be authored as JSON objects too +// e.g. `Record | string` -> add a dedicated XML-object type once an XML parser/differ is written type EditorConfigValue = Record | string; export const EDITORS = [ @@ -189,6 +193,15 @@ export const EDITORS = [ }, ] as const; +// Since some file types may not be obvious (e.g. `.editorconfig`), it may be beneficial to add a property for declaring file type overrides for both read and write +// The only problem is that now we have to maintain a list of file types and their extensions, which is not trivial. For now, we can just use the file extension to determine the file type and just deal with corner-case scenarios as we go. +// TODO: Replace values with custom parser classes/objects implemented specifically for each file type +export const FILE_TYPE_MAPPINGS = { + '.json': 'jsonc', + '.jsonc': 'jsonc', + '.xml': 'xml', +} + export type EditorId = (typeof EDITORS)[number]['id']; type EditorSelection = EditorId | readonly EditorId[] | undefined; @@ -451,7 +464,9 @@ async function writeEditorConfig({ if (conflictAction === 'merge') { if (jsonFormat) { if (!isPlainObject(incoming)) { - throw new Error(`Cannot merge editor config: ${displayPath} incoming value is not JSON`); + throw new Error( + `Cannot merge editor config: ${displayPath} incoming value is not JSON`, + ); } mergeAndWriteEditorConfig(filePath, incoming, fileName, displayPath, silent); } else { @@ -467,7 +482,9 @@ async function writeEditorConfig({ if (jsonFormat) { if (!isPlainObject(incoming)) { - throw new Error(`Cannot write editor config: ${editorConfig.targetDir}/${fileName} must be JSON`); + throw new Error( + `Cannot write editor config: ${editorConfig.targetDir}/${fileName} must be JSON`, + ); } writeJsonFile(filePath, incoming); } else { @@ -484,6 +501,9 @@ function isJsonLikeFile(fileName: string): boolean { return ext === '.json' || ext === '.jsonc'; } +// TODO: Add an `isXmlFile()` extension check (`.xml`) alongside `isJsonLikeFile()`, +// and dispatch `.xml` files to write/merge instead of write/skip-on-conflict +// See `writeEditorConfig()`'s `jsonFormat` branching for the call sites that need a parallel `xmlFormat` branch (conflict prompt copy, merge dispatch, initial write). function writeTextEditorConfig( filePath: string, incoming: EditorConfigValue, @@ -502,6 +522,8 @@ function writeTextEditorConfig( return; } + // TODO: Once XML merge support exists, this plain overwrite-or-skip behavior should be replaced with a merge that preserves comments and formatting, similar to `mergeAndWriteEditorConfig()`. + // Likely will need to be extended for other file types too, so consider a generic `mergeAndWriteFile()` that dispatches to JSON/XML/text merge strategies based on file extension. fs.writeFileSync(filePath, incoming, 'utf-8'); } From 8b9cc42c2ca6b38df3f6a8559db95fb9e2444702 Mon Sep 17 00:00:00 2001 From: KTrain <69028025+KTrain5169@users.noreply.github.com> Date: Sun, 26 Jul 2026 06:34:38 +0000 Subject: [PATCH 4/5] Finish IDE integration docs page --- docs/guide/ide-integration.md | 49 ++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/docs/guide/ide-integration.md b/docs/guide/ide-integration.md index 0d8b23d8fd..0704db4266 100644 --- a/docs/guide/ide-integration.md +++ b/docs/guide/ide-integration.md @@ -104,10 +104,53 @@ Setting `oxfmt.fmt.configPath` to `./vite.config.ts` keeps editor format-on-save For the best Vite+ experience with JetBrains IDEs such as IntelliJ & WebStorm, install the [Oxc](https://plugins.jetbrains.com/plugin/27061-oxc) plugin from the JetBrains marketplace. -When you create or migrate a project, Vite+ prompts you to choose whether you want the editor config written for Zed. +When you create or migrate a project, Vite+ prompts you to choose whether you want the editor config written for JetBrains IDEs. + +::: tip Vite+ does not merge with existing config files +Due to some complexities with merging XML files, Vite+ currently does not merge your current files if the files already exist. +You'll be given the opportunity to replace your files, instead of merging. +::: + +You can also manually set up the IDE configuration to match your Vite+ setup: + +```xml [.idea/externalDependencies.xml] + + + + + + + +``` -You can also manually set up the IDE configuration to utilise Oxc: +```xml [.idea/workspace.xml] + + + + + + + +``` -```json +```xml [.idea/OxfmtSettings.xml] + + + + + +``` +```gitignore [.idea/.gitignore] +!externalDependencies.xml ``` From 81a098fcb57027f88de183c45301ceb04e04a3dc Mon Sep 17 00:00:00 2001 From: KTrain <69028025+KTrain5169@users.noreply.github.com> Date: Sun, 26 Jul 2026 06:36:34 +0000 Subject: [PATCH 5/5] oops --- docs/guide/ide-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/ide-integration.md b/docs/guide/ide-integration.md index 0704db4266..71c92cab27 100644 --- a/docs/guide/ide-integration.md +++ b/docs/guide/ide-integration.md @@ -134,7 +134,7 @@ You can also manually set up the IDE configuration to match your Vite+ setup: "javascript.nodejs.core.library.configured.version": "24.18.0", // Replace with your selected Node.js version "javascript.nodejs.core.library.typings.version": "24.13.3", // Replace with the version of @types/node that corresponds to your runtime (or omit if you don't want it) "javascript.preferred.runtime.type.id": "node", - "nodejs_interpreter_path": "~/.vite-plus/bin/node", + "nodejs_interpreter_path": "~/.vite-plus/bin/node", // Replace ~ with the path to your home directory, IntelliJ/WebStorm don't understand ~ "nodejs_package_manager_path": "pnpm", // Replace with your package manager of choice } }]]>