Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,25 @@ vp = "global"
steps = [
{ argv = ["vp", "create", "vite:application", "--no-interactive"], comment = "verify next command suggests vp run", continue-on-failure = true },
]

[[case]]
name = "create_git_command_application"
vp = "global"
steps = [
{ argv = ["vp", "create", "vite:application", "--no-interactive", "--git"], comment = "standalone create: Git command must not change the current directory", continue-on-failure = true },
{ argv = ["vpt", "stat-file", "vite-plus-application/.git", "--assert", "dir"], comment = "Git repository initialized", continue-on-failure = true },
]

[[case]]
name = "create_git_command_application_with_special_directories"
vp = "global"
steps = [
{ argv = ["vp", "create", "vite:application", "--no-interactive", "--git", "--directory", "examples with spaces/my-app"], comment = "quote a nested target directory that contains spaces", continue-on-failure = true },
{ argv = ["vpt", "stat-file", "examples with spaces/my-app/.git", "--assert", "dir"], comment = "Git repository initialized in the target directory with spaces", continue-on-failure = true },
{ argv = ["vp", "create", "vite:application", "--no-interactive", "--git", "--directory", "examples;tools/my-app"], comment = "quote a nested target directory that contains a shell metacharacter", continue-on-failure = true },
{ argv = ["vpt", "stat-file", "examples;tools/my-app/.git", "--assert", "dir"], comment = "Git repository initialized in the target directory with a shell metacharacter", continue-on-failure = true },
{ argv = ["vp", "create", "vite:application", "--no-interactive", "--git", "--directory", "示例/my-app"], comment = "quote a nested target directory that contains Chinese characters", continue-on-failure = true },
{ argv = ["vpt", "stat-file", "示例/my-app/.git", "--assert", "dir"], comment = "Git repository initialized in the target directory with Chinese characters", continue-on-failure = true },
{ argv = ["vp", "create", "vite:application", "--no-interactive", "--git", "--directory", "サンプル/my-app"], comment = "quote a nested target directory that contains Japanese characters", continue-on-failure = true },
{ argv = ["vpt", "stat-file", "サンプル/my-app/.git", "--assert", "dir"], comment = "Git repository initialized in the target directory with Japanese characters", continue-on-failure = true },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# create_git_command_application

## `vp create vite:application --no-interactive --git`

standalone create: Git command must not change the current directory

```

Using default package name: vite-plus-application
◇ Scaffolded vite-plus-application with Vite application
• Node <version> pnpm <version>
→ Git (optional): git -C vite-plus-application add -A && git -C vite-plus-application commit -m "chore: initial commit"
→ Next: cd vite-plus-application && vp run
```

## `vpt stat-file vite-plus-application/.git --assert dir`

Git repository initialized

```
vite-plus-application/.git: dir
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# create_git_command_application_with_special_directories

## `vp create vite:application --no-interactive --git --directory 'examples with spaces/my-app'`

quote a nested target directory that contains spaces

```
◇ Scaffolded examples with spaces/my-app with Vite application
• Node <version> pnpm <version>
→ Git (optional): git -C "examples with spaces/my-app" add -A && git -C "examples with spaces/my-app" commit -m "chore: initial commit"
→ Next: cd "examples with spaces/my-app" && vp run
```

## `vpt stat-file 'examples with spaces/my-app/.git' --assert dir`

Git repository initialized in the target directory with spaces

```
examples with spaces/my-app/.git: dir
```

## `vp create vite:application --no-interactive --git --directory examples;tools/my-app`

quote a nested target directory that contains a shell metacharacter

```
◇ Scaffolded examples;tools/my-app with Vite application
• Node <version> pnpm <version>
→ Git (optional): git -C "examples;tools/my-app" add -A && git -C "examples;tools/my-app" commit -m "chore: initial commit"
→ Next: cd "examples;tools/my-app" && vp run
```

## `vpt stat-file examples;tools/my-app/.git --assert dir`

Git repository initialized in the target directory with a shell metacharacter

```
examples;tools/my-app/.git: dir
```

## `vp create vite:application --no-interactive --git --directory 示例/my-app`

quote a nested target directory that contains Chinese characters

```
◇ Scaffolded 示例/my-app with Vite application
• Node <version> pnpm <version>
→ Git (optional): git -C "示例/my-app" add -A && git -C "示例/my-app" commit -m "chore: initial commit"
→ Next: cd "示例/my-app" && vp run
```

## `vpt stat-file 示例/my-app/.git --assert dir`

Git repository initialized in the target directory with Chinese characters

```
示例/my-app/.git: dir
```

## `vp create vite:application --no-interactive --git --directory サンプル/my-app`

quote a nested target directory that contains Japanese characters

```
◇ Scaffolded サンプル/my-app with Vite application
• Node <version> pnpm <version>
→ Git (optional): git -C "サンプル/my-app" add -A && git -C "サンプル/my-app" commit -m "chore: initial commit"
→ Next: cd "サンプル/my-app" && vp run
```

## `vpt stat-file サンプル/my-app/.git --assert dir`

Git repository initialized in the target directory with Japanese characters

```
サンプル/my-app/.git: dir
```
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ bundled monorepo: extract tarball, scaffold, inject create.defaultTemplate
```
◇ Scaffolded my-mono
• Node <version> pnpm <version>
→ Git (optional): git -C my-mono add -A && git -C my-mono commit -m "chore: initial commit"
→ Next: cd my-mono && vp run
```

Expand Down
11 changes: 11 additions & 0 deletions packages/cli/src/create/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ describe('formatTargetDir', () => {
expect(formatTargetDir('my-package@').error).matchSnapshot();
expect(formatTargetDir('my-package@1.0.0').error).matchSnapshot();
});

it('keeps valid package names under parent directories that need shell quoting', () => {
expect(formatTargetDir('examples with spaces/my-app')).toEqual({
directory: 'examples with spaces/my-app',
packageName: 'my-app',
});
expect(formatTargetDir('examples;tools/my-app')).toEqual({
directory: 'examples;tools/my-app',
packageName: 'my-app',
});
});
});

describe('deriveDefaultPackageName', () => {
Expand Down
24 changes: 21 additions & 3 deletions packages/cli/src/create/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,12 @@ function getNextCommand(projectDir: string, command: string) {
if (!projectDir || projectDir === '.') {
return command;
}
return `cd ${projectDir} && ${command}`;
return `cd ${formatProjectDirArgument(projectDir)} && ${command}`;
}

function formatProjectDirArgument(projectDir: string) {
const argument = projectDir.startsWith('-') ? `./${projectDir}` : projectDir;
return /^[A-Za-z0-9_@./\\-]+$/.test(argument) ? argument : JSON.stringify(argument);
}

function getCopilotSetupRoot(projectRoot: string, isExistingMonorepo: boolean) {
Expand All @@ -254,6 +259,7 @@ function getCopilotSetupRoot(projectRoot: string, isExistingMonorepo: boolean) {

function showCreateSummary(options: {
description?: string;
gitInitialized: boolean;
installSummary?: CommandRunSummary;
nextCommand: string;
packageManager: string;
Expand All @@ -262,6 +268,7 @@ function showCreateSummary(options: {
}) {
const {
description,
gitInitialized,
installSummary,
nextCommand,
packageManager,
Expand All @@ -284,6 +291,12 @@ function showCreateSummary(options: {
)}`,
);
}
if (gitInitialized) {
const git =
!projectDir || projectDir === '.' ? 'git' : `git -C ${formatProjectDirArgument(projectDir)}`;
const gitCommand = `${git} add -A && ${git} commit -m "chore: initial commit"`;
log(`${styleText('blue', '→')} Git (optional): ${accent(gitCommand)}`);
}
log(`${styleText('blue', '→')} Next: ${accent(nextCommand)}`);
}

Expand Down Expand Up @@ -346,6 +359,7 @@ async function main() {
let selectedEditors: Awaited<ReturnType<typeof selectEditors>>;
let selectedParentDir: string | undefined;
let remoteTargetDir: string | undefined;
let gitInitialized = false;
let shouldSetupHooks = false;
let bundled: Extract<OrgResolution, { kind: 'bundled' }> | undefined;
let skipShorthandExpansion = false;
Expand Down Expand Up @@ -883,7 +897,8 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
// matching the standalone path below.
if (shouldSetupGit) {
updateCreateProgress('Initializing git repository');
if (await initGitRepository(fullPath)) {
gitInitialized = await initGitRepository(fullPath);
if (gitInitialized) {
ensureDefaultGitignoreEntries(fullPath);
}
}
Expand Down Expand Up @@ -953,6 +968,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
clearCreateProgress();
showCreateSummary({
description: describeScaffold(selectedTemplateName, selectedTemplateArgs),
gitInitialized,
installSummary,
nextCommand: getNextCommand(projectDir, 'vp run'),
packageManager: workspaceInfo.packageManager,
Expand Down Expand Up @@ -1256,7 +1272,8 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
// for a subdirectory project and its staged workflow is left half-set-up.
if (shouldSetupGit) {
updateCreateProgress('Initializing git repository');
if (await initGitRepository(fullPath)) {
gitInitialized = await initGitRepository(fullPath);
if (gitInitialized) {
ensureDefaultGitignoreEntries(fullPath);
}
}
Expand Down Expand Up @@ -1293,6 +1310,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
clearCreateProgress();
showCreateSummary({
description: describeScaffold(selectedTemplateName, selectedTemplateArgs),
gitInitialized,
installSummary,
nextCommand: getNextCommand(projectDir, 'vp run'),
packageManager: workspaceInfo.packageManager,
Expand Down
Loading