Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(create-vite): improve project name inference from path #16490

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 15 additions & 1 deletion docs/guide/index.md
Copy link
Member

@bluwy bluwy Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for elaborating the feature, but I think we should keep it like before since the project name infer is QoL feature and shouldn't take much space in the docs.

Expand Up @@ -69,7 +69,7 @@ $ bun create vite

Then follow the prompts!

You can also directly specify the project name and the template you want to use via additional command line options. For example, to scaffold a Vite + Vue project, run:
You can also directly specify the project name as the first parameter, and then template you want to use via additional command line options. For example, to scaffold a Vite + Vue project, run:

```bash
# npm 7+, extra double-dash is needed:
Expand All @@ -85,6 +85,20 @@ pnpm create vite my-vue-app --template vue
bun create vite my-vue-app --template vue
```

The project name parameter also doubles as the target folder for your new Vite project. So the command:

```bash
npm create vite@latest my-vue-app -- --template vue
```

Will create your new project in the folder ./my-vue-app as a sub-folder of the folder from which you invoked the command, as well as setting its project name to "my-vue-app". If your project name/folder parameter contains a path then the project name will be taken from the deepest level folder of that path. So the the command:

```bash
npm create vite@latest ./my-sub-folder/my-vue-app -- --template vue
```

will create the project in that target folder, but set its project name to just "my-vue-app" again.

See [create-vite](https://github.com/vitejs/vite/tree/main/packages/create-vite) for more details on each supported template: `vanilla`, `vanilla-ts`, `vue`, `vue-ts`, `react`, `react-ts`, `react-swc`, `react-swc-ts`, `preact`, `preact-ts`, `lit`, `lit-ts`, `svelte`, `svelte-ts`, `solid`, `solid-ts`, `qwik`, `qwik-ts`.

## Community Templates
Expand Down
50 changes: 44 additions & 6 deletions packages/create-vite/__tests__/cli.spec.ts
Expand Up @@ -8,21 +8,24 @@ const CLI_PATH = join(__dirname, '..')

const projectName = 'test-app'
const genPath = join(__dirname, projectName)
const genPathWithSubfolder = join(__dirname, 'subfolder', projectName)

const run = (
args: string[],
options: SyncOptions = {},
): ExecaSyncReturnValue => {
return execaCommandSync(`node ${CLI_PATH} ${args.join(' ')}`, options)
const commandToExecute = `node ${CLI_PATH} ${args.join(' ')}`
return execaCommandSync(commandToExecute, options)
}

// Helper to create a non-empty directory
const createNonEmptyDir = () => {
const createNonEmptyDir = (overrideFolder?: string) => {
// Create the temporary directory
fs.mkdirpSync(genPath)
const newNonEmptyFolder = overrideFolder || genPath
fs.mkdirpSync(newNonEmptyFolder)

// Create a package.json file
const pkgJson = join(genPath, 'package.json')
const pkgJson = join(newNonEmptyFolder, 'package.json')
fs.writeFileSync(pkgJson, '{ "foo": "bar" }')
}

Expand All @@ -33,8 +36,24 @@ const templateFiles = fs
.map((filePath) => (filePath === '_gitignore' ? '.gitignore' : filePath))
.sort()

beforeAll(() => fs.remove(genPath))
afterEach(() => fs.remove(genPath))
// React starter template
const templateFilesReact = fs
.readdirSync(join(CLI_PATH, 'template-react'))
// _gitignore is renamed to .gitignore
.map((filePath) => (filePath === '_gitignore' ? '.gitignore' : filePath))
.sort()

const clearAnyPreviousFolders = () => {
if (fs.existsSync(genPath)) {
fs.removeSync(genPath)
}
if (fs.existsSync(genPathWithSubfolder)) {
fs.removeSync(genPathWithSubfolder)
}
}

beforeAll(() => clearAnyPreviousFolders())
afterEach(() => clearAnyPreviousFolders())

test('prompts for the project name if none supplied', () => {
const { stdout } = run([])
Expand Down Expand Up @@ -70,6 +89,14 @@ test('asks to overwrite non-empty target directory', () => {
expect(stdout).toContain(`Target directory "${projectName}" is not empty.`)
})

test('asks to overwrite non-empty target directory with subfolder', () => {
createNonEmptyDir(genPathWithSubfolder)
const { stdout } = run([`subfolder/${projectName}`], { cwd: __dirname })
expect(stdout).toContain(
`Target directory "subfolder/${projectName}" is not empty.`,
)
})

test('asks to overwrite non-empty current directory', () => {
createNonEmptyDir()
const { stdout } = run(['.'], { cwd: genPath })
Expand All @@ -87,6 +114,17 @@ test('successfully scaffolds a project based on vue starter template', () => {
expect(templateFiles).toEqual(generatedFiles)
})

test('successfully scaffolds a project with subfolder based on react starter template', () => {
const { stdout } = run([`subfolder/${projectName}`, '--template', 'react'], {
cwd: __dirname,
})
const generatedFiles = fs.readdirSync(genPathWithSubfolder).sort()

// Assertions
expect(stdout).toContain(`Scaffolding project in ${genPathWithSubfolder}`)
expect(templateFilesReact).toEqual(generatedFiles)
})

test('works with the -t alias', () => {
const { stdout } = run([projectName, '-t', 'vue'], {
cwd: __dirname,
Expand Down
3 changes: 1 addition & 2 deletions packages/create-vite/src/index.ts
Expand Up @@ -252,8 +252,7 @@ async function init() {
const argTemplate = argv.template || argv.t

let targetDir = argTargetDir || defaultTargetDir
const getProjectName = () =>
targetDir === '.' ? path.basename(path.resolve()) : targetDir
const getProjectName = () => path.basename(path.resolve(targetDir))

let result: prompts.Answers<
'projectName' | 'overwrite' | 'packageName' | 'framework' | 'variant'
Expand Down