From e2d9048d6d035fd7dd19d21407319d5f3740929d Mon Sep 17 00:00:00 2001 From: hakshu25 Date: Thu, 2 Apr 2026 16:18:51 +0900 Subject: [PATCH 1/6] feat(migrate): add explanations to migration prompts Add prompts.log.info() calls before interactive prompts in the migration flow to explain why each option is being presented to the user. - Agent selection: explain what agent instruction files are - Agent conflict (append/skip): explain template contents - Editor selection: explain what editor configs will be written - Editor conflict (merge/skip): explain merge behavior - ESLint migration: explain what Oxlint is and what the migration does - Prettier migration: explain what Oxfmt is and what the migration does Closes #1030 Co-Authored-By: Claude Sonnet 4.6 --- packages/cli/src/migration/bin.ts | 20 ++++++++++++++++++++ packages/cli/src/utils/agent.ts | 12 ++++++++++++ packages/cli/src/utils/editor.ts | 12 ++++++++++++ 3 files changed, 44 insertions(+) diff --git a/packages/cli/src/migration/bin.ts b/packages/cli/src/migration/bin.ts index a20400f774..2187bf0d60 100644 --- a/packages/cli/src/migration/bin.ts +++ b/packages/cli/src/migration/bin.ts @@ -71,6 +71,10 @@ function warnLegacyEslintConfig(legacyConfigFile: string) { async function confirmEslintMigration(interactive: boolean): Promise { if (interactive) { + prompts.log.info( + "Oxlint is Vite+'s built-in linter — significantly faster than ESLint with compatible " + + 'rule support. @oxlint/migrate will convert your existing rules automatically.', + ); const confirmed = await prompts.confirm({ message: 'Migrate ESLint rules to Oxlint using @oxlint/migrate?', initialValue: true, @@ -125,6 +129,10 @@ function warnPackageLevelPrettier() { async function confirmPrettierMigration(interactive: boolean): Promise { if (interactive) { + prompts.log.info( + "Oxfmt is Vite+'s built-in formatter that replaces Prettier with faster performance. " + + 'Your Prettier configuration will be converted automatically.', + ); const confirmed = await prompts.confirm({ message: 'Migrate Prettier to Oxfmt?', initialValue: true, @@ -361,6 +369,12 @@ async function collectMigrationPlan( targetPaths: selectedAgentTargetPaths, }); const agentConflictDecisions = new Map(); + if (agentConflicts.length > 0 && options.interactive) { + prompts.log.info( + 'The Vite+ agent instructions template includes guidance on `vp` commands, ' + + 'the build pipeline, and project conventions.', + ); + } for (const conflict of agentConflicts) { if (options.interactive) { const action = await prompts.select({ @@ -393,6 +407,12 @@ async function collectMigrationPlan( editorId: selectedEditor, }); const editorConflictDecisions = new Map(); + if (editorConflicts.length > 0 && options.interactive) { + prompts.log.info( + 'Vite+ adds editor settings for the built-in linter and formatter. ' + + 'Merge adds new keys without overwriting your existing settings.', + ); + } for (const conflict of editorConflicts) { if (options.interactive) { const action = await prompts.select({ diff --git a/packages/cli/src/utils/agent.ts b/packages/cli/src/utils/agent.ts index bcb1d26179..7c0a253802 100644 --- a/packages/cli/src/utils/agent.ts +++ b/packages/cli/src/utils/agent.ts @@ -209,6 +209,10 @@ export async function selectAgentTargetPaths({ } if (interactive && !agent) { + prompts.log.info( + 'Vite+ can write AI agent instruction files (e.g. CLAUDE.md, AGENTS.md) ' + + 'to help coding assistants understand `vp` commands and the project workflow.', + ); const selectedAgents = await prompts.multiselect({ message: 'Which agents are you using?', options: AGENTS.map((option) => ({ @@ -470,6 +474,7 @@ export async function writeAgentInstructions({ const seenDestinationPaths = new Set(); const seenRealPaths = new Set(); const incomingContent = await fsPromises.readFile(sourcePath, 'utf-8'); + let shownConflictInfo = false; const shouldLinkToAgents = paths.includes(AGENT_STANDARD_PATH); const orderedPaths = shouldLinkToAgents ? [AGENT_STANDARD_PATH, ...paths.filter((p) => p !== AGENT_STANDARD_PATH)] @@ -527,6 +532,13 @@ export async function writeAgentInstructions({ if (preResolved) { conflictAction = preResolved; } else if (interactive) { + if (!shownConflictInfo) { + prompts.log.info( + 'The Vite+ agent instructions template includes guidance on `vp` commands, ' + + 'the build pipeline, and project conventions.', + ); + shownConflictInfo = true; + } const action = await prompts.select({ message: `Agent instructions already exist at ${targetPathToWrite}.`, options: [ diff --git a/packages/cli/src/utils/editor.ts b/packages/cli/src/utils/editor.ts index 8da5c5b10e..cfb6757222 100644 --- a/packages/cli/src/utils/editor.ts +++ b/packages/cli/src/utils/editor.ts @@ -172,6 +172,10 @@ export async function selectEditor({ } if (interactive && !editor) { + prompts.log.info( + 'Vite+ can write editor configuration files to enable recommended extensions ' + + 'and Oxlint/Oxfmt integrations.', + ); const editorOptions = EDITORS.map((option) => ({ label: option.label, value: option.id, @@ -282,6 +286,7 @@ export async function writeEditorConfigs({ const targetDir = path.join(projectRoot, editorConfig.targetDir); await fsPromises.mkdir(targetDir, { recursive: true }); + let shownConflictInfo = false; for (const [fileName, incoming] of Object.entries(editorConfig.files)) { const filePath = path.join(targetDir, fileName); @@ -294,6 +299,13 @@ export async function writeEditorConfigs({ if (preResolved) { conflictAction = preResolved; } else if (interactive) { + if (!shownConflictInfo) { + prompts.log.info( + `Vite+ adds ${editorConfig.label} settings for the built-in linter and formatter. ` + + 'Merge adds new keys without overwriting your existing settings.', + ); + shownConflictInfo = true; + } const action = await prompts.select({ message: `${displayPath} already exists.`, options: [ From 58f8d91061d253c4a30b15337e838b1cd9f0cec4 Mon Sep 17 00:00:00 2001 From: hakshu25 Date: Thu, 2 Apr 2026 16:48:22 +0900 Subject: [PATCH 2/6] refactor(migrate): embed prompt explanations inline as gray subtext Replace standalone prompts.log.info() calls with inline gray subtext embedded in the prompt message itself via styleText('gray', ...). This keeps the explanation visually tied to its associated prompt instead of appearing as a detached message before it. Co-Authored-By: Claude Sonnet 4.6 --- packages/cli/src/migration/bin.ts | 48 +++++++++++++++---------------- packages/cli/src/utils/agent.ts | 27 +++++++++-------- packages/cli/src/utils/editor.ts | 24 +++++++--------- 3 files changed, 47 insertions(+), 52 deletions(-) diff --git a/packages/cli/src/migration/bin.ts b/packages/cli/src/migration/bin.ts index 2187bf0d60..934cdcd8c8 100644 --- a/packages/cli/src/migration/bin.ts +++ b/packages/cli/src/migration/bin.ts @@ -71,12 +71,13 @@ function warnLegacyEslintConfig(legacyConfigFile: string) { async function confirmEslintMigration(interactive: boolean): Promise { if (interactive) { - prompts.log.info( - "Oxlint is Vite+'s built-in linter — significantly faster than ESLint with compatible " + - 'rule support. @oxlint/migrate will convert your existing rules automatically.', - ); const confirmed = await prompts.confirm({ - message: 'Migrate ESLint rules to Oxlint using @oxlint/migrate?', + message: + 'Migrate ESLint rules to Oxlint using @oxlint/migrate?\n ' + + styleText( + 'gray', + "Oxlint is Vite+'s built-in linter — significantly faster than ESLint with compatible rule support. @oxlint/migrate converts your existing rules automatically.", + ), initialValue: true, }); if (prompts.isCancel(confirmed)) { @@ -129,12 +130,13 @@ function warnPackageLevelPrettier() { async function confirmPrettierMigration(interactive: boolean): Promise { if (interactive) { - prompts.log.info( - "Oxfmt is Vite+'s built-in formatter that replaces Prettier with faster performance. " + - 'Your Prettier configuration will be converted automatically.', - ); const confirmed = await prompts.confirm({ - message: 'Migrate Prettier to Oxfmt?', + message: + 'Migrate Prettier to Oxfmt?\n ' + + styleText( + 'gray', + "Oxfmt is Vite+'s built-in formatter that replaces Prettier with faster performance. Your configuration will be converted automatically.", + ), initialValue: true, }); if (prompts.isCancel(confirmed)) { @@ -369,16 +371,15 @@ async function collectMigrationPlan( targetPaths: selectedAgentTargetPaths, }); const agentConflictDecisions = new Map(); - if (agentConflicts.length > 0 && options.interactive) { - prompts.log.info( - 'The Vite+ agent instructions template includes guidance on `vp` commands, ' + - 'the build pipeline, and project conventions.', - ); - } for (const conflict of agentConflicts) { if (options.interactive) { const action = await prompts.select({ - message: `Agent instructions already exist at ${conflict.targetPath}.`, + message: + `Agent instructions already exist at ${conflict.targetPath}.\n ` + + styleText( + 'gray', + 'The Vite+ template includes guidance on `vp` commands, the build pipeline, and project conventions.', + ), options: [ { label: 'Append', value: 'append' as const, hint: 'Add template content to the end' }, { label: 'Skip', value: 'skip' as const, hint: 'Leave existing file unchanged' }, @@ -407,16 +408,15 @@ async function collectMigrationPlan( editorId: selectedEditor, }); const editorConflictDecisions = new Map(); - if (editorConflicts.length > 0 && options.interactive) { - prompts.log.info( - 'Vite+ adds editor settings for the built-in linter and formatter. ' + - 'Merge adds new keys without overwriting your existing settings.', - ); - } for (const conflict of editorConflicts) { if (options.interactive) { const action = await prompts.select({ - message: `${conflict.displayPath} already exists.`, + message: + `${conflict.displayPath} already exists.\n ` + + styleText( + 'gray', + 'Vite+ adds editor settings for the built-in linter and formatter. Merge adds new keys without overwriting existing ones.', + ), options: [ { label: 'Merge', diff --git a/packages/cli/src/utils/agent.ts b/packages/cli/src/utils/agent.ts index 7c0a253802..ab1af07fc8 100644 --- a/packages/cli/src/utils/agent.ts +++ b/packages/cli/src/utils/agent.ts @@ -1,6 +1,7 @@ import fs from 'node:fs'; import fsPromises from 'node:fs/promises'; import path from 'node:path'; +import { styleText } from 'node:util'; import * as prompts from '@voidzero-dev/vite-plus-prompts'; @@ -209,12 +210,13 @@ export async function selectAgentTargetPaths({ } if (interactive && !agent) { - prompts.log.info( - 'Vite+ can write AI agent instruction files (e.g. CLAUDE.md, AGENTS.md) ' + - 'to help coding assistants understand `vp` commands and the project workflow.', - ); const selectedAgents = await prompts.multiselect({ - message: 'Which agents are you using?', + message: + 'Which agents are you using?\n ' + + styleText( + 'gray', + 'Writes AI agent instruction files (CLAUDE.md, AGENTS.md) to help coding assistants understand `vp` commands.', + ), options: AGENTS.map((option) => ({ label: option.label, value: option.id, @@ -474,7 +476,6 @@ export async function writeAgentInstructions({ const seenDestinationPaths = new Set(); const seenRealPaths = new Set(); const incomingContent = await fsPromises.readFile(sourcePath, 'utf-8'); - let shownConflictInfo = false; const shouldLinkToAgents = paths.includes(AGENT_STANDARD_PATH); const orderedPaths = shouldLinkToAgents ? [AGENT_STANDARD_PATH, ...paths.filter((p) => p !== AGENT_STANDARD_PATH)] @@ -532,15 +533,13 @@ export async function writeAgentInstructions({ if (preResolved) { conflictAction = preResolved; } else if (interactive) { - if (!shownConflictInfo) { - prompts.log.info( - 'The Vite+ agent instructions template includes guidance on `vp` commands, ' + - 'the build pipeline, and project conventions.', - ); - shownConflictInfo = true; - } const action = await prompts.select({ - message: `Agent instructions already exist at ${targetPathToWrite}.`, + message: + `Agent instructions already exist at ${targetPathToWrite}.\n ` + + styleText( + 'gray', + 'The Vite+ template includes guidance on `vp` commands, the build pipeline, and project conventions.', + ), options: [ { label: 'Append', diff --git a/packages/cli/src/utils/editor.ts b/packages/cli/src/utils/editor.ts index cfb6757222..3713b79ffe 100644 --- a/packages/cli/src/utils/editor.ts +++ b/packages/cli/src/utils/editor.ts @@ -1,6 +1,7 @@ import fs from 'node:fs'; import fsPromises from 'node:fs/promises'; import path from 'node:path'; +import { styleText } from 'node:util'; import * as prompts from '@voidzero-dev/vite-plus-prompts'; @@ -172,10 +173,6 @@ export async function selectEditor({ } if (interactive && !editor) { - prompts.log.info( - 'Vite+ can write editor configuration files to enable recommended extensions ' + - 'and Oxlint/Oxfmt integrations.', - ); const editorOptions = EDITORS.map((option) => ({ label: option.label, value: option.id, @@ -187,7 +184,9 @@ export async function selectEditor({ hint: 'Skip writing editor configs', }; const selectedEditor = await prompts.select({ - message: 'Which editor are you using?', + message: + 'Which editor are you using?\n ' + + styleText('gray', 'Writes editor config files to enable recommended extensions and Oxlint/Oxfmt integrations.'), options: [...editorOptions, noneOption], initialValue: 'vscode', }); @@ -286,7 +285,6 @@ export async function writeEditorConfigs({ const targetDir = path.join(projectRoot, editorConfig.targetDir); await fsPromises.mkdir(targetDir, { recursive: true }); - let shownConflictInfo = false; for (const [fileName, incoming] of Object.entries(editorConfig.files)) { const filePath = path.join(targetDir, fileName); @@ -299,15 +297,13 @@ export async function writeEditorConfigs({ if (preResolved) { conflictAction = preResolved; } else if (interactive) { - if (!shownConflictInfo) { - prompts.log.info( - `Vite+ adds ${editorConfig.label} settings for the built-in linter and formatter. ` + - 'Merge adds new keys without overwriting your existing settings.', - ); - shownConflictInfo = true; - } const action = await prompts.select({ - message: `${displayPath} already exists.`, + message: + `${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.`, + ), options: [ { label: 'Merge', From 1e48beb479ed5ab8d4018c88bc7a2aae31fd971c Mon Sep 17 00:00:00 2001 From: hakshu25 Date: Thu, 2 Apr 2026 16:51:26 +0900 Subject: [PATCH 3/6] fix(migrate): generalize agent selection prompt description The previous message only mentioned CLAUDE.md and AGENTS.md, but each agent has its own file (GEMINI.md, etc.). Use a generic description that covers all agents. Co-Authored-By: Claude Sonnet 4.6 --- packages/cli/src/utils/agent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/utils/agent.ts b/packages/cli/src/utils/agent.ts index ab1af07fc8..4e8b00e3eb 100644 --- a/packages/cli/src/utils/agent.ts +++ b/packages/cli/src/utils/agent.ts @@ -215,7 +215,7 @@ export async function selectAgentTargetPaths({ 'Which agents are you using?\n ' + styleText( 'gray', - 'Writes AI agent instruction files (CLAUDE.md, AGENTS.md) to help coding assistants understand `vp` commands.', + 'Writes an instruction file for each selected agent to help it understand `vp` commands and the project workflow.', ), options: AGENTS.map((option) => ({ label: option.label, From 565e5711acbe51b2c712411f9d863d6c48ab14a4 Mon Sep 17 00:00:00 2001 From: hakshu25 Date: Thu, 2 Apr 2026 17:08:34 +0900 Subject: [PATCH 4/6] style(migrate): fix formatting in editor.ts Co-Authored-By: Claude Sonnet 4.6 --- packages/cli/src/utils/editor.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/utils/editor.ts b/packages/cli/src/utils/editor.ts index 3713b79ffe..b2eccbe4ea 100644 --- a/packages/cli/src/utils/editor.ts +++ b/packages/cli/src/utils/editor.ts @@ -186,7 +186,10 @@ export async function selectEditor({ const selectedEditor = await prompts.select({ message: 'Which editor are you using?\n ' + - styleText('gray', 'Writes editor config files to enable recommended extensions and Oxlint/Oxfmt integrations.'), + styleText( + 'gray', + 'Writes editor config files to enable recommended extensions and Oxlint/Oxfmt integrations.', + ), options: [...editorOptions, noneOption], initialValue: 'vscode', }); From 63aa46c0bcc9089e0aee6dc6fa7dd7d912a7d3a7 Mon Sep 17 00:00:00 2001 From: hakshu25 Date: Thu, 2 Apr 2026 21:18:14 +0900 Subject: [PATCH 5/6] fix(migrate): rename editor 'None' option to 'Other' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some editors don't have recommendations — 'Other' is more accurate than 'None' for this case. Co-Authored-By: Claude Sonnet 4.6 --- packages/cli/src/utils/editor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/utils/editor.ts b/packages/cli/src/utils/editor.ts index b2eccbe4ea..a09cab7645 100644 --- a/packages/cli/src/utils/editor.ts +++ b/packages/cli/src/utils/editor.ts @@ -179,7 +179,7 @@ export async function selectEditor({ hint: option.targetDir, })); const noneOption = { - label: 'None', + label: 'Other', value: null, hint: 'Skip writing editor configs', }; From 3cd4dad23dae89e083a88cae43f6c8033d664792 Mon Sep 17 00:00:00 2001 From: hakshu25 Date: Thu, 2 Apr 2026 21:24:10 +0900 Subject: [PATCH 6/6] refactor(migrate): rename noneOption to otherOption in selectEditor Co-Authored-By: Claude Sonnet 4.6 --- packages/cli/src/utils/editor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/utils/editor.ts b/packages/cli/src/utils/editor.ts index a09cab7645..52f6dad422 100644 --- a/packages/cli/src/utils/editor.ts +++ b/packages/cli/src/utils/editor.ts @@ -178,7 +178,7 @@ export async function selectEditor({ value: option.id, hint: option.targetDir, })); - const noneOption = { + const otherOption = { label: 'Other', value: null, hint: 'Skip writing editor configs', @@ -190,7 +190,7 @@ export async function selectEditor({ 'gray', 'Writes editor config files to enable recommended extensions and Oxlint/Oxfmt integrations.', ), - options: [...editorOptions, noneOption], + options: [...editorOptions, otherOption], initialValue: 'vscode', });