From 8a134368ef0132a9ace4bf393336126b9053cc42 Mon Sep 17 00:00:00 2001 From: ryohidaka <39184410+ryohidaka@users.noreply.github.com> Date: Mon, 27 Apr 2026 09:48:33 +0900 Subject: [PATCH 1/4] feat(cli): add flag to initialize a git repository --- docs/guide/create.md | 2 ++ .../command-create-help/snap.txt | 6 +++++ .../cli/snap-tests-global/new-check/snap.txt | 2 ++ packages/cli/src/create/bin.ts | 20 +++++++++++++++- packages/cli/src/utils/git.ts | 11 +++++++++ packages/cli/src/utils/prompts.ts | 24 +++++++++++++++++++ 6 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 packages/cli/src/utils/git.ts diff --git a/docs/guide/create.md b/docs/guide/create.md index 200be46aaa..73287a15ef 100644 --- a/docs/guide/create.md +++ b/docs/guide/create.md @@ -45,6 +45,8 @@ Run `vp create --list` to see the built-in templates and the common shorthand te - `--directory ` writes the generated project into a specific target directory - `--agent ` creates agent instructions files during scaffolding - `--editor ` writes editor config files +- `--git` initialize a git repository +- `--no-git` skips git repository initialization - `--hooks` enables pre-commit hook setup - `--no-hooks` skips hook setup - `--no-interactive` runs without prompts diff --git a/packages/cli/snap-tests-global/command-create-help/snap.txt b/packages/cli/snap-tests-global/command-create-help/snap.txt index ab69160242..5ff8f11dbb 100644 --- a/packages/cli/snap-tests-global/command-create-help/snap.txt +++ b/packages/cli/snap-tests-global/command-create-help/snap.txt @@ -17,6 +17,8 @@ Options: --directory DIR Target directory for the generated project. --agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc. --editor NAME Write editor config files for the specified editor. + --git Initialize a git repository with an initial commit + --no-git Skip git repository initialization --hooks Set up pre-commit hooks (default in non-interactive mode) --no-hooks Skip pre-commit hooks setup --package-manager NAME Use specified package manager (pnpm, npm, yarn, bun) @@ -78,6 +80,8 @@ Options: --directory DIR Target directory for the generated project. --agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc. --editor NAME Write editor config files for the specified editor. + --git Initialize a git repository with an initial commit + --no-git Skip git repository initialization --hooks Set up pre-commit hooks (default in non-interactive mode) --no-hooks Skip pre-commit hooks setup --package-manager NAME Use specified package manager (pnpm, npm, yarn, bun) @@ -139,6 +143,8 @@ Options: --directory DIR Target directory for the generated project. --agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc. --editor NAME Write editor config files for the specified editor. + --git Initialize a git repository with an initial commit + --no-git Skip git repository initialization --hooks Set up pre-commit hooks (default in non-interactive mode) --no-hooks Skip pre-commit hooks setup --package-manager NAME Use specified package manager (pnpm, npm, yarn, bun) diff --git a/packages/cli/snap-tests-global/new-check/snap.txt b/packages/cli/snap-tests-global/new-check/snap.txt index 4602d3ef40..8266887b4b 100644 --- a/packages/cli/snap-tests-global/new-check/snap.txt +++ b/packages/cli/snap-tests-global/new-check/snap.txt @@ -17,6 +17,8 @@ Options: --directory DIR Target directory for the generated project. --agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc. --editor NAME Write editor config files for the specified editor. + --git Initialize a git repository with an initial commit + --no-git Skip git repository initialization --hooks Set up pre-commit hooks (default in non-interactive mode) --no-hooks Skip pre-commit hooks setup --package-manager NAME Use specified package manager (pnpm, npm, yarn, bun) diff --git a/packages/cli/src/create/bin.ts b/packages/cli/src/create/bin.ts index 2897ec7fe0..00ad9e3050 100644 --- a/packages/cli/src/create/bin.ts +++ b/packages/cli/src/create/bin.ts @@ -29,6 +29,7 @@ import { writeAgentInstructions, } from '../utils/agent.ts'; import { detectExistingEditors, selectEditors, writeEditorConfigs } from '../utils/editor.ts'; +import { initGitRepository } from '../utils/git.ts'; import { renderCliDoc } from '../utils/help.ts'; import { displayRelative } from '../utils/path.ts'; import { @@ -36,6 +37,7 @@ import { defaultInteractive, downloadPackageManager, promptGitHooks, + promptGitInit, runViteFmt, runViteInstall, selectPackageManager, @@ -106,6 +108,8 @@ const helpMessage = renderCliDoc({ label: '--editor NAME', description: 'Write editor config files for the specified editor.', }, + { label: '--git', description: 'Initialize a git repository with an initial commit' }, + { label: '--no-git', description: 'Skip git repository initialization' }, { label: '--hooks', description: 'Set up pre-commit hooks (default in non-interactive mode)', @@ -235,11 +239,12 @@ function parseArgs() { verbose?: boolean; agent?: string | string[] | false; editor?: string; + git?: boolean; hooks?: boolean; 'package-manager'?: string; }>(viteArgs, { alias: { h: 'help' }, - boolean: ['help', 'list', 'all', 'interactive', 'hooks', 'verbose'], + boolean: ['help', 'list', 'all', 'interactive', 'hooks', 'verbose', 'git'], string: ['directory', 'agent', 'editor', 'package-manager'], default: { interactive: defaultInteractive() }, }); @@ -256,6 +261,7 @@ function parseArgs() { verbose: parsed.verbose || false, agent: parsed.agent, editor: parsed.editor, + git: parsed.git, hooks: parsed.hooks, packageManager: parsed['package-manager'], } as Options, @@ -747,6 +753,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h onCancel: () => cancelAndExit(), })); + const shouldSetupGit = await promptGitInit(options); if (!isMonorepo) { shouldSetupHooks = await promptGitHooks(options); } @@ -902,6 +909,10 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h workspaceInfo.rootDir = fullPath; updateCreateProgress('Integrating monorepo'); rewriteMonorepo(workspaceInfo, undefined, compactOutput); + if (shouldSetupGit) { + updateCreateProgress('Initializing git repository'); + await initGitRepository(fullPath); + } if (bundled?.monorepo) { // Wire `create.defaultTemplate: ''` into the new workspace's // vite.config.ts so a bare `vp create` from inside it opens the @@ -1137,6 +1148,9 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h await runViteFmt(workspaceInfo.rootDir, options.interactive, [projectDir], { silent: compactOutput, }); + if (shouldSetupGit) { + await initGitRepository(workspaceInfo.rootDir); + } } else { if (shouldMigrateLintFmtTools) { await installAndMigrate(fullPath); @@ -1148,6 +1162,10 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h addFrameworkShim(fullPath, framework); } } + if (shouldSetupGit) { + updateCreateProgress('Initializing git repository'); + await initGitRepository(fullPath); + } if (shouldSetupHooks) { installGitHooks(fullPath, compactOutput); } diff --git a/packages/cli/src/utils/git.ts b/packages/cli/src/utils/git.ts new file mode 100644 index 0000000000..fa16710397 --- /dev/null +++ b/packages/cli/src/utils/git.ts @@ -0,0 +1,11 @@ +import { runCommandSilently } from './command.ts'; + +export async function initGitRepository(cwd: string): Promise { + const result = await runCommandSilently({ + command: 'git', + args: ['init'], + cwd, + envs: process.env, + }); + return result.exitCode === 0; +} diff --git a/packages/cli/src/utils/prompts.ts b/packages/cli/src/utils/prompts.ts index 7f882b8ff8..9d14853d5a 100644 --- a/packages/cli/src/utils/prompts.ts +++ b/packages/cli/src/utils/prompts.ts @@ -215,6 +215,30 @@ export async function promptGitHooks(options: { return true; // non-interactive default } +export async function promptGitInit(options: { + git?: boolean; + interactive: boolean; +}): Promise { + if (options.git === false) { + return false; + } + if (options.git === true) { + return true; + } + if (options.interactive) { + const selected = await prompts.confirm({ + message: 'Initialize a git repository with an initial commit?', + initialValue: false, + }); + if (prompts.isCancel(selected)) { + cancelAndExit(); + return false; + } + return selected; + } + return false; // non-interactive default +} + export function defaultInteractive() { // If CI environment, use non-interactive mode by default return !process.env.CI && process.stdin.isTTY; From 6232ecf5f9e228d7a1fc79b882f47a5ba06176d7 Mon Sep 17 00:00:00 2001 From: ryohidaka <39184410+ryohidaka@users.noreply.github.com> Date: Mon, 27 Apr 2026 09:54:57 +0900 Subject: [PATCH 2/4] feat(cli): create initial commit after scaffold completes --- packages/cli/src/create/bin.ts | 12 +++++++++++- packages/cli/src/utils/git.ts | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/create/bin.ts b/packages/cli/src/create/bin.ts index 00ad9e3050..bacf244639 100644 --- a/packages/cli/src/create/bin.ts +++ b/packages/cli/src/create/bin.ts @@ -29,7 +29,7 @@ import { writeAgentInstructions, } from '../utils/agent.ts'; import { detectExistingEditors, selectEditors, writeEditorConfigs } from '../utils/editor.ts'; -import { initGitRepository } from '../utils/git.ts'; +import { createInitialCommit, initGitRepository } from '../utils/git.ts'; import { renderCliDoc } from '../utils/help.ts'; import { displayRelative } from '../utils/path.ts'; import { @@ -933,6 +933,10 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h }); updateCreateProgress('Formatting code'); await runViteFmt(fullPath, options.interactive, undefined, { silent: compactOutput }); + if (shouldSetupGit) { + updateCreateProgress('Creating initial commit'); + await createInitialCommit(fullPath); + } clearCreateProgress(); showCreateSummary({ description: describeScaffold(selectedTemplateName, selectedTemplateArgs), @@ -1149,7 +1153,9 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h silent: compactOutput, }); if (shouldSetupGit) { + updateCreateProgress('Creating initial commit'); await initGitRepository(workspaceInfo.rootDir); + await createInitialCommit(workspaceInfo.rootDir); } } else { if (shouldMigrateLintFmtTools) { @@ -1177,6 +1183,10 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h }); updateCreateProgress('Formatting code'); await runViteFmt(fullPath, options.interactive, undefined, { silent: compactOutput }); + if (shouldSetupGit) { + updateCreateProgress('Creating initial commit'); + await createInitialCommit(fullPath); + } } clearCreateProgress(); diff --git a/packages/cli/src/utils/git.ts b/packages/cli/src/utils/git.ts index fa16710397..d58de4c090 100644 --- a/packages/cli/src/utils/git.ts +++ b/packages/cli/src/utils/git.ts @@ -9,3 +9,19 @@ export async function initGitRepository(cwd: string): Promise { }); return result.exitCode === 0; } + +export async function createInitialCommit(cwd: string): Promise { + await runCommandSilently({ + command: 'git', + args: ['add', '-A'], + cwd, + envs: process.env, + }); + const result = await runCommandSilently({ + command: 'git', + args: ['commit', '-m', 'Initial commit from Vite+'], + cwd, + envs: process.env, + }); + return result.exitCode === 0; +} From bd2a84e374f25e1bfd9bb77be69bc5e550fb70d1 Mon Sep 17 00:00:00 2001 From: ryohidaka <39184410+ryohidaka@users.noreply.github.com> Date: Thu, 14 May 2026 21:35:09 +0900 Subject: [PATCH 3/4] fix(cli): respect --no-git flag when scaffolding monorepo * https://github.com/voidzero-dev/vite-plus/pull/1484#discussion_r3206608796 --- packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt | 4 ++-- .../cli/snap-tests-global/new-vite-monorepo-bun/steps.json | 4 ++-- packages/cli/snap-tests-global/new-vite-monorepo/snap.txt | 4 ++-- packages/cli/snap-tests-global/new-vite-monorepo/steps.json | 4 ++-- packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt | 4 ++-- .../cli/snap-tests/create-org-bundled-monorepo/steps.json | 4 ++-- packages/cli/src/create/bin.ts | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt b/packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt index 7ec03af6f9..0cd639acdc 100644 --- a/packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt +++ b/packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt @@ -1,4 +1,4 @@ -> vp create vite:monorepo --no-interactive --package-manager bun # create monorepo with bun +> vp create vite:monorepo --no-interactive --package-manager bun --git # create monorepo with bun > ls vite-plus-monorepo | LC_ALL=C sort # check files created AGENTS.md README.md @@ -48,7 +48,7 @@ No pnpm-workspace.yaml > test ! -f vite-plus-monorepo/.yarnrc.yml && echo 'No .yarnrc.yml' || echo 'ERROR: .yarnrc.yml exists' # verify no yarn config No .yarnrc.yml -> test -d vite-plus-monorepo/.git && echo 'Git initialized' || echo 'No git' # check git init +> test -d vite-plus-monorepo/.git && echo 'Git initialized' # check git init Git initialized > ls vite-plus-monorepo/apps # check apps directory diff --git a/packages/cli/snap-tests-global/new-vite-monorepo-bun/steps.json b/packages/cli/snap-tests-global/new-vite-monorepo-bun/steps.json index 2a8f9e1280..699e751ba0 100644 --- a/packages/cli/snap-tests-global/new-vite-monorepo-bun/steps.json +++ b/packages/cli/snap-tests-global/new-vite-monorepo-bun/steps.json @@ -2,14 +2,14 @@ "ignoredPlatforms": ["win32"], "commands": [ { - "command": "vp create vite:monorepo --no-interactive --package-manager bun # create monorepo with bun", + "command": "vp create vite:monorepo --no-interactive --package-manager bun --git # create monorepo with bun", "ignoreOutput": true }, "ls vite-plus-monorepo | LC_ALL=C sort # check files created", "cat vite-plus-monorepo/package.json # check package.json with catalog", "test ! -f vite-plus-monorepo/pnpm-workspace.yaml && echo 'No pnpm-workspace.yaml' || echo 'ERROR: pnpm-workspace.yaml exists' # verify no pnpm config", "test ! -f vite-plus-monorepo/.yarnrc.yml && echo 'No .yarnrc.yml' || echo 'ERROR: .yarnrc.yml exists' # verify no yarn config", - "test -d vite-plus-monorepo/.git && echo 'Git initialized' || echo 'No git' # check git init", + "test -d vite-plus-monorepo/.git && echo 'Git initialized' # check git init", "ls vite-plus-monorepo/apps # check apps directory", "cat vite-plus-monorepo/apps/website/package.json # check website uses catalog:" ] diff --git a/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt b/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt index e0fb6cd576..b0c66b0574 100644 --- a/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt +++ b/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt @@ -1,4 +1,4 @@ -> vp create vite:monorepo --no-interactive # create monorepo with default values +> vp create vite:monorepo --no-interactive --git # create monorepo with default values > ls vite-plus-monorepo | LC_ALL=C sort # check files created AGENTS.md README.md @@ -74,7 +74,7 @@ peerDependencyRules: > test ! -f vite-plus-monorepo/.yarnrc.yml && echo 'No .yarnrc.yml' || echo 'ERROR: .yarnrc.yml exists' # verify no yarn config for pnpm No .yarnrc.yml -> test -d vite-plus-monorepo/.git && echo 'Git initialized' || echo 'No git' # check git init +> test -d vite-plus-monorepo/.git && echo 'Git initialized' # check git init Git initialized > ls vite-plus-monorepo/apps # check apps directory created diff --git a/packages/cli/snap-tests-global/new-vite-monorepo/steps.json b/packages/cli/snap-tests-global/new-vite-monorepo/steps.json index 4212e84c7d..d6fbf85850 100644 --- a/packages/cli/snap-tests-global/new-vite-monorepo/steps.json +++ b/packages/cli/snap-tests-global/new-vite-monorepo/steps.json @@ -2,7 +2,7 @@ "ignoredPlatforms": ["win32"], "commands": [ { - "command": "vp create vite:monorepo --no-interactive # create monorepo with default values", + "command": "vp create vite:monorepo --no-interactive --git # create monorepo with default values", "ignoreOutput": true }, "ls vite-plus-monorepo | LC_ALL=C sort # check files created", @@ -11,7 +11,7 @@ "cat vite-plus-monorepo/pnpm-workspace.yaml # check workspace config", "test -f vite-plus-monorepo/.gitignore && echo '.gitignore exists' || echo 'ERROR: .gitignore missing' # verify gitignore renamed from _gitignore", "test ! -f vite-plus-monorepo/.yarnrc.yml && echo 'No .yarnrc.yml' || echo 'ERROR: .yarnrc.yml exists' # verify no yarn config for pnpm", - "test -d vite-plus-monorepo/.git && echo 'Git initialized' || echo 'No git' # check git init", + "test -d vite-plus-monorepo/.git && echo 'Git initialized' # check git init", "ls vite-plus-monorepo/apps # check apps directory created", "ls vite-plus-monorepo/apps/website/package.json # check website package.json", { diff --git a/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt b/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt index 7c4209e2bd..2659dbb1b4 100644 --- a/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt +++ b/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt @@ -1,4 +1,4 @@ -> node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org:workspace --no-interactive --directory my-mono # bundled monorepo: extract tarball, scaffold, inject create.defaultTemplate +> node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org:workspace --no-interactive --directory my-mono --git # bundled monorepo: extract tarball, scaffold, inject create.defaultTemplate ◇ Scaffolded my-mono • Node pnpm → Next: cd my-mono && vp run @@ -35,7 +35,7 @@ peerDependencyRules: vite: "*" vitest: "*" -> test -d my-mono/.git && echo 'Git initialized' || echo 'No git' # git-init prompt covers bundled monorepo path +> test -d my-mono/.git && echo 'Git initialized' # git-init prompt covers bundled monorepo path Git initialized > cat my-mono/.gitignore # node_modules excluded even though tarball shipped no .gitignore diff --git a/packages/cli/snap-tests/create-org-bundled-monorepo/steps.json b/packages/cli/snap-tests/create-org-bundled-monorepo/steps.json index c299dc0d00..03d4ad4cc0 100644 --- a/packages/cli/snap-tests/create-org-bundled-monorepo/steps.json +++ b/packages/cli/snap-tests/create-org-bundled-monorepo/steps.json @@ -1,9 +1,9 @@ { "commands": [ - "node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org:workspace --no-interactive --directory my-mono # bundled monorepo: extract tarball, scaffold, inject create.defaultTemplate", + "node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org:workspace --no-interactive --directory my-mono --git # bundled monorepo: extract tarball, scaffold, inject create.defaultTemplate", "cat my-mono/vite.config.ts # create.defaultTemplate auto-set to @your-org", "cat my-mono/pnpm-workspace.yaml # workspace markers preserved", - "test -d my-mono/.git && echo 'Git initialized' || echo 'No git' # git-init prompt covers bundled monorepo path", + "test -d my-mono/.git && echo 'Git initialized' # git-init prompt covers bundled monorepo path", "cat my-mono/.gitignore # node_modules excluded even though tarball shipped no .gitignore" ] } diff --git a/packages/cli/src/create/bin.ts b/packages/cli/src/create/bin.ts index bacf244639..db4d2c3cc7 100644 --- a/packages/cli/src/create/bin.ts +++ b/packages/cli/src/create/bin.ts @@ -834,7 +834,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h // #region Handle monorepo template if (templateInfo.command === BuiltinTemplate.monorepo || isBundledMonorepo) { // Ask up-front so the prompt isn't buried under scaffold output. - let shouldInitGit = true; + let shouldInitGit = shouldSetupGit; if (options.interactive && !compactOutput) { pauseCreateProgress(); const selected = await prompts.confirm({ @@ -848,7 +848,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h } else { shouldInitGit = selected; } - } else if (!compactOutput) { + } else if (shouldInitGit && !compactOutput) { prompts.log.info('Initializing git repository (default: yes)'); } From 9ef2832c8eb0f207befd125e948aed7b22658c2a Mon Sep 17 00:00:00 2001 From: ryohidaka <39184410+ryohidaka@users.noreply.github.com> Date: Fri, 15 May 2026 22:31:34 +0900 Subject: [PATCH 4/4] fix(cli): warn when initial commit fails * https://github.com/voidzero-dev/vite-plus/pull/1484#discussion_r3206608811 --- packages/cli/src/create/bin.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/create/bin.ts b/packages/cli/src/create/bin.ts index db4d2c3cc7..d9cb4d274b 100644 --- a/packages/cli/src/create/bin.ts +++ b/packages/cli/src/create/bin.ts @@ -935,7 +935,10 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h await runViteFmt(fullPath, options.interactive, undefined, { silent: compactOutput }); if (shouldSetupGit) { updateCreateProgress('Creating initial commit'); - await createInitialCommit(fullPath); + const committed = await createInitialCommit(fullPath); + if (!committed) { + prompts.log.warn('Initial commit failed. Check your git user.name/user.email config'); + } } clearCreateProgress(); showCreateSummary({ @@ -1185,7 +1188,10 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h await runViteFmt(fullPath, options.interactive, undefined, { silent: compactOutput }); if (shouldSetupGit) { updateCreateProgress('Creating initial commit'); - await createInitialCommit(fullPath); + const committed = await createInitialCommit(fullPath); + if (!committed) { + prompts.log.warn('Initial commit failed. Check your git user.name/user.email config'); + } } }