Skip to content

Commit 5d68116

Browse files
authored
feat(cli): Initialize a git repository and create an initial commit on scaffold (#1484)
## Summary Add a `--git` / `--no-git` flag to `vp create`. - In interactive mode, prompt the user with a confirm dialog (default: No) - In non-interactive mode, default to skipping git initialization - When enabled, run `git init` before installing dependencies, then `git add -A && git commit -m "Initial commit from Vite+"` after formatting completes ## Testing - Execute `snap-tests-global` - Launch locally with `bootstrap-cli` and confirm that it works Closes #1483
1 parent 023e700 commit 5d68116

12 files changed

Lines changed: 110 additions & 15 deletions

File tree

docs/guide/create.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ Run `vp create --list` to see the built-in templates and the common shorthand te
4545
- `--directory <dir>` writes the generated project into a specific target directory
4646
- `--agent <name>` creates agent instructions files during scaffolding
4747
- `--editor <name>` writes editor config files
48+
- `--git` initialize a git repository
49+
- `--no-git` skips git repository initialization
4850
- `--hooks` enables pre-commit hook setup
4951
- `--no-hooks` skips hook setup
5052
- `--no-interactive` runs without prompts

packages/cli/snap-tests-global/command-create-help/snap.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Options:
1717
--directory DIR Target directory for the generated project.
1818
--agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.
1919
--editor NAME Write editor config files for the specified editor.
20+
--git Initialize a git repository with an initial commit
21+
--no-git Skip git repository initialization
2022
--hooks Set up pre-commit hooks (default in non-interactive mode)
2123
--no-hooks Skip pre-commit hooks setup
2224
--package-manager NAME Use specified package manager (pnpm, npm, yarn, bun)
@@ -78,6 +80,8 @@ Options:
7880
--directory DIR Target directory for the generated project.
7981
--agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.
8082
--editor NAME Write editor config files for the specified editor.
83+
--git Initialize a git repository with an initial commit
84+
--no-git Skip git repository initialization
8185
--hooks Set up pre-commit hooks (default in non-interactive mode)
8286
--no-hooks Skip pre-commit hooks setup
8387
--package-manager NAME Use specified package manager (pnpm, npm, yarn, bun)
@@ -139,6 +143,8 @@ Options:
139143
--directory DIR Target directory for the generated project.
140144
--agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.
141145
--editor NAME Write editor config files for the specified editor.
146+
--git Initialize a git repository with an initial commit
147+
--no-git Skip git repository initialization
142148
--hooks Set up pre-commit hooks (default in non-interactive mode)
143149
--no-hooks Skip pre-commit hooks setup
144150
--package-manager NAME Use specified package manager (pnpm, npm, yarn, bun)

packages/cli/snap-tests-global/new-check/snap.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Options:
1717
--directory DIR Target directory for the generated project.
1818
--agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.
1919
--editor NAME Write editor config files for the specified editor.
20+
--git Initialize a git repository with an initial commit
21+
--no-git Skip git repository initialization
2022
--hooks Set up pre-commit hooks (default in non-interactive mode)
2123
--no-hooks Skip pre-commit hooks setup
2224
--package-manager NAME Use specified package manager (pnpm, npm, yarn, bun)

packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
> vp create vite:monorepo --no-interactive --package-manager bun # create monorepo with bun
1+
> vp create vite:monorepo --no-interactive --package-manager bun --git # create monorepo with bun
22
> ls vite-plus-monorepo | LC_ALL=C sort # check files created
33
AGENTS.md
44
README.md
@@ -48,7 +48,7 @@ No pnpm-workspace.yaml
4848
> test ! -f vite-plus-monorepo/.yarnrc.yml && echo 'No .yarnrc.yml' || echo 'ERROR: .yarnrc.yml exists' # verify no yarn config
4949
No .yarnrc.yml
5050

51-
> test -d vite-plus-monorepo/.git && echo 'Git initialized' || echo 'No git' # check git init
51+
> test -d vite-plus-monorepo/.git && echo 'Git initialized' # check git init
5252
Git initialized
5353

5454
> ls vite-plus-monorepo/apps # check apps directory

packages/cli/snap-tests-global/new-vite-monorepo-bun/steps.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
"ignoredPlatforms": ["win32"],
33
"commands": [
44
{
5-
"command": "vp create vite:monorepo --no-interactive --package-manager bun # create monorepo with bun",
5+
"command": "vp create vite:monorepo --no-interactive --package-manager bun --git # create monorepo with bun",
66
"ignoreOutput": true
77
},
88
"ls vite-plus-monorepo | LC_ALL=C sort # check files created",
99
"cat vite-plus-monorepo/package.json # check package.json with catalog",
1010
"test ! -f vite-plus-monorepo/pnpm-workspace.yaml && echo 'No pnpm-workspace.yaml' || echo 'ERROR: pnpm-workspace.yaml exists' # verify no pnpm config",
1111
"test ! -f vite-plus-monorepo/.yarnrc.yml && echo 'No .yarnrc.yml' || echo 'ERROR: .yarnrc.yml exists' # verify no yarn config",
12-
"test -d vite-plus-monorepo/.git && echo 'Git initialized' || echo 'No git' # check git init",
12+
"test -d vite-plus-monorepo/.git && echo 'Git initialized' # check git init",
1313
"ls vite-plus-monorepo/apps # check apps directory",
1414
"cat vite-plus-monorepo/apps/website/package.json # check website uses catalog:"
1515
]

packages/cli/snap-tests-global/new-vite-monorepo/snap.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
> vp create vite:monorepo --no-interactive # create monorepo with default values
1+
> vp create vite:monorepo --no-interactive --git # create monorepo with default values
22
> ls vite-plus-monorepo | LC_ALL=C sort # check files created
33
AGENTS.md
44
README.md
@@ -74,7 +74,7 @@ peerDependencyRules:
7474
> test ! -f vite-plus-monorepo/.yarnrc.yml && echo 'No .yarnrc.yml' || echo 'ERROR: .yarnrc.yml exists' # verify no yarn config for pnpm
7575
No .yarnrc.yml
7676

77-
> test -d vite-plus-monorepo/.git && echo 'Git initialized' || echo 'No git' # check git init
77+
> test -d vite-plus-monorepo/.git && echo 'Git initialized' # check git init
7878
Git initialized
7979

8080
> ls vite-plus-monorepo/apps # check apps directory created

packages/cli/snap-tests-global/new-vite-monorepo/steps.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"ignoredPlatforms": ["win32"],
33
"commands": [
44
{
5-
"command": "vp create vite:monorepo --no-interactive # create monorepo with default values",
5+
"command": "vp create vite:monorepo --no-interactive --git # create monorepo with default values",
66
"ignoreOutput": true
77
},
88
"ls vite-plus-monorepo | LC_ALL=C sort # check files created",
@@ -11,7 +11,7 @@
1111
"cat vite-plus-monorepo/pnpm-workspace.yaml # check workspace config",
1212
"test -f vite-plus-monorepo/.gitignore && echo '.gitignore exists' || echo 'ERROR: .gitignore missing' # verify gitignore renamed from _gitignore",
1313
"test ! -f vite-plus-monorepo/.yarnrc.yml && echo 'No .yarnrc.yml' || echo 'ERROR: .yarnrc.yml exists' # verify no yarn config for pnpm",
14-
"test -d vite-plus-monorepo/.git && echo 'Git initialized' || echo 'No git' # check git init",
14+
"test -d vite-plus-monorepo/.git && echo 'Git initialized' # check git init",
1515
"ls vite-plus-monorepo/apps # check apps directory created",
1616
"ls vite-plus-monorepo/apps/website/package.json # check website package.json",
1717
{

packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
> 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
1+
> 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
22
◇ Scaffolded my-mono
33
• Node <semver> pnpm <semver>
44
→ Next: cd my-mono && vp run
@@ -35,7 +35,7 @@ peerDependencyRules:
3535
vite: "*"
3636
vitest: "*"
3737

38-
> test -d my-mono/.git && echo 'Git initialized' || echo 'No git' # git-init prompt covers bundled monorepo path
38+
> test -d my-mono/.git && echo 'Git initialized' # git-init prompt covers bundled monorepo path
3939
Git initialized
4040

4141
> cat my-mono/.gitignore # node_modules excluded even though tarball shipped no .gitignore
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"commands": [
3-
"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",
3+
"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",
44
"cat my-mono/vite.config.ts # create.defaultTemplate auto-set to @your-org",
55
"cat my-mono/pnpm-workspace.yaml # workspace markers preserved",
6-
"test -d my-mono/.git && echo 'Git initialized' || echo 'No git' # git-init prompt covers bundled monorepo path",
6+
"test -d my-mono/.git && echo 'Git initialized' # git-init prompt covers bundled monorepo path",
77
"cat my-mono/.gitignore # node_modules excluded even though tarball shipped no .gitignore"
88
]
99
}

packages/cli/src/create/bin.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ import {
2929
writeAgentInstructions,
3030
} from '../utils/agent.ts';
3131
import { detectExistingEditors, selectEditors, writeEditorConfigs } from '../utils/editor.ts';
32+
import { createInitialCommit, initGitRepository } from '../utils/git.ts';
3233
import { renderCliDoc } from '../utils/help.ts';
3334
import { displayRelative } from '../utils/path.ts';
3435
import {
3536
type CommandRunSummary,
3637
defaultInteractive,
3738
downloadPackageManager,
3839
promptGitHooks,
40+
promptGitInit,
3941
runViteFmt,
4042
runViteInstall,
4143
selectPackageManager,
@@ -106,6 +108,8 @@ const helpMessage = renderCliDoc({
106108
label: '--editor NAME',
107109
description: 'Write editor config files for the specified editor.',
108110
},
111+
{ label: '--git', description: 'Initialize a git repository with an initial commit' },
112+
{ label: '--no-git', description: 'Skip git repository initialization' },
109113
{
110114
label: '--hooks',
111115
description: 'Set up pre-commit hooks (default in non-interactive mode)',
@@ -235,11 +239,12 @@ function parseArgs() {
235239
verbose?: boolean;
236240
agent?: string | string[] | false;
237241
editor?: string;
242+
git?: boolean;
238243
hooks?: boolean;
239244
'package-manager'?: string;
240245
}>(viteArgs, {
241246
alias: { h: 'help' },
242-
boolean: ['help', 'list', 'all', 'interactive', 'hooks', 'verbose'],
247+
boolean: ['help', 'list', 'all', 'interactive', 'hooks', 'verbose', 'git'],
243248
string: ['directory', 'agent', 'editor', 'package-manager'],
244249
default: { interactive: defaultInteractive() },
245250
});
@@ -256,6 +261,7 @@ function parseArgs() {
256261
verbose: parsed.verbose || false,
257262
agent: parsed.agent,
258263
editor: parsed.editor,
264+
git: parsed.git,
259265
hooks: parsed.hooks,
260266
packageManager: parsed['package-manager'],
261267
} as Options,
@@ -747,6 +753,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
747753
onCancel: () => cancelAndExit(),
748754
}));
749755

756+
const shouldSetupGit = await promptGitInit(options);
750757
if (!isMonorepo) {
751758
shouldSetupHooks = await promptGitHooks(options);
752759
}
@@ -827,7 +834,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
827834
// #region Handle monorepo template
828835
if (templateInfo.command === BuiltinTemplate.monorepo || isBundledMonorepo) {
829836
// Ask up-front so the prompt isn't buried under scaffold output.
830-
let shouldInitGit = true;
837+
let shouldInitGit = shouldSetupGit;
831838
if (options.interactive && !compactOutput) {
832839
pauseCreateProgress();
833840
const selected = await prompts.confirm({
@@ -841,7 +848,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
841848
} else {
842849
shouldInitGit = selected;
843850
}
844-
} else if (!compactOutput) {
851+
} else if (shouldInitGit && !compactOutput) {
845852
prompts.log.info('Initializing git repository (default: yes)');
846853
}
847854

@@ -902,6 +909,10 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
902909
workspaceInfo.rootDir = fullPath;
903910
updateCreateProgress('Integrating monorepo');
904911
rewriteMonorepo(workspaceInfo, undefined, compactOutput);
912+
if (shouldSetupGit) {
913+
updateCreateProgress('Initializing git repository');
914+
await initGitRepository(fullPath);
915+
}
905916
if (bundled?.monorepo) {
906917
// Wire `create.defaultTemplate: '<scope>'` into the new workspace's
907918
// 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
922933
});
923934
updateCreateProgress('Formatting code');
924935
await runViteFmt(fullPath, options.interactive, undefined, { silent: compactOutput });
936+
if (shouldSetupGit) {
937+
updateCreateProgress('Creating initial commit');
938+
const committed = await createInitialCommit(fullPath);
939+
if (!committed) {
940+
prompts.log.warn('Initial commit failed. Check your git user.name/user.email config');
941+
}
942+
}
925943
clearCreateProgress();
926944
showCreateSummary({
927945
description: describeScaffold(selectedTemplateName, selectedTemplateArgs),
@@ -1137,6 +1155,11 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
11371155
await runViteFmt(workspaceInfo.rootDir, options.interactive, [projectDir], {
11381156
silent: compactOutput,
11391157
});
1158+
if (shouldSetupGit) {
1159+
updateCreateProgress('Creating initial commit');
1160+
await initGitRepository(workspaceInfo.rootDir);
1161+
await createInitialCommit(workspaceInfo.rootDir);
1162+
}
11401163
} else {
11411164
if (shouldMigrateLintFmtTools) {
11421165
await installAndMigrate(fullPath);
@@ -1148,6 +1171,10 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
11481171
addFrameworkShim(fullPath, framework);
11491172
}
11501173
}
1174+
if (shouldSetupGit) {
1175+
updateCreateProgress('Initializing git repository');
1176+
await initGitRepository(fullPath);
1177+
}
11511178
if (shouldSetupHooks) {
11521179
installGitHooks(fullPath, compactOutput);
11531180
}
@@ -1159,6 +1186,13 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
11591186
});
11601187
updateCreateProgress('Formatting code');
11611188
await runViteFmt(fullPath, options.interactive, undefined, { silent: compactOutput });
1189+
if (shouldSetupGit) {
1190+
updateCreateProgress('Creating initial commit');
1191+
const committed = await createInitialCommit(fullPath);
1192+
if (!committed) {
1193+
prompts.log.warn('Initial commit failed. Check your git user.name/user.email config');
1194+
}
1195+
}
11621196
}
11631197

11641198
clearCreateProgress();

0 commit comments

Comments
 (0)