Skip to content
Open
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
56 changes: 56 additions & 0 deletions docs/guide/ide-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,59 @@ 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 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 any existing files, instead of merging.
:::

You can also manually set up the IDE configuration to match your Vite+ setup:

```xml [.idea/externalDependencies.xml]
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalDependencies">
<plugin id="com.github.oxc.project.oxcintellijplugin" />
</component>
</project>
```

```xml [.idea/workspace.xml]
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<!-- other settings... -->
<component name="PropertiesComponent">
<![CDATA[{
"keyToString": {
// other settings
"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", // Replace ~ with the path to your home directory, IntelliJ/WebStorm doesn't understand ~
"nodejs_package_manager_path": "pnpm", // Replace with your package manager of choice
}
}]]>
</component>
</project>
```

```xml [.idea/OxfmtSettings.xml]
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="OxfmtSettings">
<option name="preferOxfmtCodeStyleSettings" value="true" />
</component>
</project>
```

Often, `.idea` folders are gitignored in a project, even the `externalDependencies.xml` file. Adding the `.idea/.gitignore` file with the following content will help ensure that it is present:

```gitignore [.idea/.gitignore]
!externalDependencies.xml
```
2 changes: 2 additions & 0 deletions packages/cli/src/create/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
interactive: options.interactive,
silent: compactOutput,
extraVsCodeSettings: { 'npm.scriptRunner': 'vp' },
packageManager,
});
if (selectedEditors?.includes('vscode')) {
ensureGitignoreVsCodeEditorConfigs(fullPath);
Expand Down Expand Up @@ -1269,6 +1270,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
interactive: options.interactive,
silent: compactOutput,
extraVsCodeSettings: { 'npm.scriptRunner': 'vp' },
packageManager,
});
if (selectedEditors?.includes('vscode')) {
ensureGitignoreVsCodeEditorConfigs(fullPath);
Expand Down
38 changes: 38 additions & 0 deletions packages/cli/src/migration/__tests__/migrator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8593,3 +8593,41 @@ describe('collectMigrationSetupPlan ESLint gating', () => {
},
);
});

describe('collectMigrationSetupPlan non-interactive editor conflicts', () => {
let tmpDir: string;

beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vp-test-setup-plan-editor-'));
writePkgAt(tmpDir, { name: 'x' });
});

afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});

it('skips (never overwrites) existing non-JSON jetbrains files, only merges JSON-like ones', async () => {
fs.mkdirSync(path.join(tmpDir, '.idea'), { recursive: true });
fs.writeFileSync(
path.join(tmpDir, '.idea', 'externalDependencies.xml'),
'<project version="4"><component name="Custom"/></project>',
);
fs.writeFileSync(path.join(tmpDir, '.idea', 'workspace.xml'), '<project version="4"/>');

const plan = await collectMigrationSetupPlan(
tmpDir,
PackageManager.pnpm,
{
interactive: false,
hooks: false,
agent: false as const,
editor: 'jetbrains',
},
undefined,
false,
);

expect(plan.editorConflictDecisions.get('externalDependencies.xml')).toBe('skip');
expect(plan.editorConflictDecisions.get('workspace.xml')).toBe('skip');
});
});
2 changes: 2 additions & 0 deletions packages/cli/src/migration/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,7 @@ async function executeMigrationPlan(
interactive,
conflictDecisions: plan.editorConflictDecisions,
silent: true,
packageManager: plan.packageManager,
});

// 11. Add framework shims if requested
Expand Down Expand Up @@ -1472,6 +1473,7 @@ async function main() {
interactive: options.interactive,
conflictDecisions: plan.editorConflictDecisions,
silent: true,
packageManager,
});
didMigrate = true;
}
Expand Down
13 changes: 11 additions & 2 deletions packages/cli/src/migration/setup-plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import {
detectExistingAgentTargetPaths,
selectAgentTargetPaths,
} from '../utils/agent.ts';
import { detectEditorConflicts, type EditorId, selectEditor } from '../utils/editor.ts';
import {
detectEditorConflicts,
type EditorId,
isJsonLikeFile,
selectEditor,
} from '../utils/editor.ts';
import { cancelAndExit, promptGitHooks } from '../utils/prompts.ts';
import {
confirmEslintMigration,
Expand Down Expand Up @@ -141,7 +146,11 @@ async function collectEditorConfigPlan(
}
editorConflictDecisions.set(conflict.fileName, action);
} else {
editorConflictDecisions.set(conflict.fileName, 'merge');
// Non-JSON files (e.g. JetBrains XML) can't be merged, so only skip them here.
editorConflictDecisions.set(
conflict.fileName,
isJsonLikeFile(conflict.fileName) ? 'merge' : 'skip',
);
}
}

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

expect(detectExistingEditors(projectRoot)).toEqual(['jetbrains']);
});
});

describe('writeEditorConfigs', () => {
Expand Down Expand Up @@ -539,4 +547,89 @@ describe('writeEditorConfigs', () => {
expect(zedSettings['npm.scriptRunner']).toBeUndefined();
expect(zedSettings.lsp).toBeDefined();
});

it('writes all jetbrains editor config files', async () => {
const projectRoot = createTempDir();

await writeEditorConfigs({
projectRoot,
editorId: 'jetbrains',
interactive: false,
silent: true,
});

const externalDependenciesXml = fs.readFileSync(
path.join(projectRoot, '.idea', 'externalDependencies.xml'),
'utf8',
);
expect(externalDependenciesXml).toContain('<?xml version="1.0" encoding="UTF-8"?>');
expect(externalDependenciesXml).toContain(
'<plugin id="com.github.oxc.project.oxcintellijplugin" />',
);

const workspaceXml = fs.readFileSync(path.join(projectRoot, '.idea', 'workspace.xml'), 'utf8');
expect(workspaceXml).toContain('<?xml version="1.0" encoding="UTF-8"?>');
expect(workspaceXml).toContain('"javascript.preferred.runtime.type.id": "node"');
expect(workspaceXml).toContain(
`"nodejs_interpreter_path": "${os.homedir()}/.vite-plus/bin/node"`,
);
expect(workspaceXml).toContain('"nodejs_package_manager_path": "pnpm"');

const oxfmtSettingsXml = fs.readFileSync(
path.join(projectRoot, '.idea', 'OxfmtSettings.xml'),
'utf8',
);
expect(oxfmtSettingsXml).toContain('<?xml version="1.0" encoding="UTF-8"?>');
expect(oxfmtSettingsXml).toContain('<component name="OxfmtSettings">');
expect(oxfmtSettingsXml).toContain(
'<option name="preferOxfmtCodeStyleSettings" value="true" />',
);

const gitignore = fs.readFileSync(path.join(projectRoot, '.idea', '.gitignore'), 'utf8');
expect(gitignore).toBe('**\n!externalDependencies.xml\n');
});

it('writes workspace.xml with the resolved package manager', async () => {
const projectRoot = createTempDir();

await writeEditorConfigs({
projectRoot,
editorId: 'jetbrains',
interactive: false,
silent: true,
packageManager: 'yarn',
});

const workspaceXml = fs.readFileSync(path.join(projectRoot, '.idea', 'workspace.xml'), 'utf8');
expect(workspaceXml).toContain('"nodejs_package_manager_path": "yarn"');
});

it('does not overwrite existing non-JSON jetbrains file 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, '<project version="4"><component name="Custom"/></project>', 'utf8');

await writeEditorConfigs({
projectRoot,
editorId: 'jetbrains',
interactive: false,
silent: true,
});

const xml = fs.readFileSync(xmlPath, 'utf8');
expect(xml).toBe('<project version="4"><component name="Custom"/></project>');

const workspaceXml = fs.readFileSync(path.join(projectRoot, '.idea', 'workspace.xml'), 'utf8');
expect(workspaceXml).toContain('<?xml version="1.0" encoding="UTF-8"?>');

const oxfmtSettingsXml = fs.readFileSync(
path.join(projectRoot, '.idea', 'OxfmtSettings.xml'),
'utf8',
);
expect(oxfmtSettingsXml).toContain('<component name="OxfmtSettings">');

const gitignore = fs.readFileSync(path.join(projectRoot, '.idea', '.gitignore'), 'utf8');
expect(gitignore).toBe('**\n!externalDependencies.xml\n');
});
});
Loading