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/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 2897ec7fe0..d9cb4d274b 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 { createInitialCommit, 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); } @@ -827,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({ @@ -841,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)'); } @@ -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 @@ -922,6 +933,13 @@ 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'); + const committed = await createInitialCommit(fullPath); + if (!committed) { + prompts.log.warn('Initial commit failed. Check your git user.name/user.email config'); + } + } clearCreateProgress(); showCreateSummary({ description: describeScaffold(selectedTemplateName, selectedTemplateArgs), @@ -1137,6 +1155,11 @@ 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) { + updateCreateProgress('Creating initial commit'); + await initGitRepository(workspaceInfo.rootDir); + await createInitialCommit(workspaceInfo.rootDir); + } } else { if (shouldMigrateLintFmtTools) { await installAndMigrate(fullPath); @@ -1148,6 +1171,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); } @@ -1159,6 +1186,13 @@ 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'); + const committed = await createInitialCommit(fullPath); + if (!committed) { + prompts.log.warn('Initial commit failed. Check your git user.name/user.email config'); + } + } } clearCreateProgress(); diff --git a/packages/cli/src/utils/git.ts b/packages/cli/src/utils/git.ts new file mode 100644 index 0000000000..d58de4c090 --- /dev/null +++ b/packages/cli/src/utils/git.ts @@ -0,0 +1,27 @@ +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; +} + +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; +} 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;