Skip to content

Commit 0989996

Browse files
committed
feat: supporting . as the package name
1 parent 2f0b63f commit 0989996

File tree

7 files changed

+124
-12
lines changed

7 files changed

+124
-12
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
// This file is auto-generated. Do not edit manually.
22
// Generated from add-ons, examples, hosts, project, and toolchains directories
3-
export const contentChecksum = 'b722b3f8235cbf4618c508da1b499a8a6a583d8107eebeb9e2ffe19716ddc7e9'
3+
export const contentChecksum = '0b4c2f7b4e91fb80d16db47f572d9547629aac841a00f154cb18869936013618'

packages/cta-cli/src/cli.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,20 @@ Remove your node_modules directory and package lock file and re-install.`,
528528
throw new Error('No options were provided')
529529
}
530530

531-
finalOptions.targetDir =
532-
options.targetDir || resolve(process.cwd(), finalOptions.projectName)
531+
// Determine target directory:
532+
// 1. Use --target-dir if provided
533+
// 2. Use targetDir from normalizeOptions if set (handles "." case)
534+
// 3. If original projectName was ".", use current directory
535+
// 4. Otherwise, use project name as subdirectory
536+
if (options.targetDir) {
537+
finalOptions.targetDir = options.targetDir
538+
} else if (finalOptions.targetDir) {
539+
// Keep the targetDir from normalizeOptions (handles "." case)
540+
} else if (projectName === '.') {
541+
finalOptions.targetDir = resolve(process.cwd())
542+
} else {
543+
finalOptions.targetDir = resolve(process.cwd(), finalOptions.projectName)
544+
}
533545

534546
await createApp(environment, finalOptions)
535547
} catch (error) {

packages/cta-cli/src/command-line.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import {
99
populateAddOnOptionsDefaults,
1010
} from '@tanstack/cta-engine'
1111

12-
import { validateProjectName } from './utils.js'
12+
import {
13+
getCurrentDirectoryName,
14+
sanitizePackageName,
15+
validateProjectName,
16+
} from './utils.js'
1317
import type { Options } from '@tanstack/cta-engine'
1418

1519
import type { CliOptions } from './types.js'
@@ -23,7 +27,17 @@ export async function normalizeOptions(
2327
forcedDeployment?: string
2428
},
2529
): Promise<Options | undefined> {
26-
const projectName = (cliOptions.projectName ?? '').trim()
30+
let projectName = (cliOptions.projectName ?? '').trim()
31+
let targetDir: string
32+
33+
// Handle "." as project name - use current directory
34+
if (projectName === '.') {
35+
projectName = sanitizePackageName(getCurrentDirectoryName())
36+
targetDir = resolve(process.cwd())
37+
} else {
38+
targetDir = resolve(process.cwd(), projectName)
39+
}
40+
2741
if (!projectName && !opts?.disableNameCheck) {
2842
return undefined
2943
}
@@ -134,7 +148,7 @@ export async function normalizeOptions(
134148

135149
return {
136150
projectName: projectName,
137-
targetDir: resolve(process.cwd(), projectName),
151+
targetDir,
138152
framework,
139153
mode,
140154
typescript,

packages/cta-cli/src/options.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import {
2121
selectTypescript,
2222
} from './ui-prompts.js'
2323

24-
import { validateProjectName } from './utils.js'
24+
import {
25+
getCurrentDirectoryName,
26+
sanitizePackageName,
27+
validateProjectName,
28+
} from './utils.js'
2529
import type { Options } from '@tanstack/cta-engine'
2630

2731
import type { CliOptions } from './types.js'
@@ -43,12 +47,17 @@ export async function promptForCreateOptions(
4347
options.framework = getFrameworkById(cliOptions.framework || 'react-cra')!
4448

4549
if (cliOptions.projectName) {
46-
const { valid, error } = validateProjectName(cliOptions.projectName)
50+
// Handle "." as project name - use sanitized current directory name
51+
if (cliOptions.projectName === '.') {
52+
options.projectName = sanitizePackageName(getCurrentDirectoryName())
53+
} else {
54+
options.projectName = cliOptions.projectName
55+
}
56+
const { valid, error } = validateProjectName(options.projectName)
4757
if (!valid) {
4858
console.error(error)
4959
process.exit(1)
5060
}
51-
options.projectName = cliOptions.projectName
5261
} else {
5362
options.projectName = await getProjectName()
5463
}

packages/cta-cli/src/utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { basename } from 'node:path'
12
import validatePackageName from 'validate-npm-package-name'
23
import type { TemplateOptions } from './types.js'
34

@@ -8,6 +9,21 @@ export function convertTemplateToMode(template: TemplateOptions): string {
89
return 'file-router'
910
}
1011

12+
export function sanitizePackageName(name: string): string {
13+
return name
14+
.toLowerCase()
15+
.replace(/\s+/g, '-') // Replace spaces with hyphens
16+
.replace(/_/g, '-') // Replace underscores with hyphens
17+
.replace(/[^a-z0-9-]/g, '') // Remove invalid characters
18+
.replace(/^[^a-z]+/, '') // Ensure it starts with a letter
19+
.replace(/-+/g, '-') // Collapse multiple hyphens
20+
.replace(/-$/, '') // Remove trailing hyphen
21+
}
22+
23+
export function getCurrentDirectoryName(): string {
24+
return basename(process.cwd())
25+
}
26+
1127
export function validateProjectName(name: string) {
1228
const { validForNewPackages, validForOldPackages, errors, warnings } =
1329
validatePackageName(name)

packages/cta-cli/tests/command-line.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import { basename, resolve } from 'node:path'
12
import { beforeEach, describe, expect, it } from 'vitest'
23

34
import { normalizeOptions } from '../src/command-line.js'
5+
import {
6+
sanitizePackageName,
7+
getCurrentDirectoryName,
8+
} from '../src/utils.js'
49
import {
510
__testRegisterFramework,
611
__testClearFrameworks,
@@ -10,12 +15,58 @@ beforeEach(() => {
1015
__testClearFrameworks()
1116
})
1217

18+
describe('sanitizePackageName', () => {
19+
it('should convert to lowercase', () => {
20+
expect(sanitizePackageName('MyProject')).toBe('myproject')
21+
})
22+
23+
it('should replace spaces with hyphens', () => {
24+
expect(sanitizePackageName('my project')).toBe('my-project')
25+
})
26+
27+
it('should replace underscores with hyphens', () => {
28+
expect(sanitizePackageName('my_project')).toBe('my-project')
29+
})
30+
31+
it('should remove invalid characters', () => {
32+
expect(sanitizePackageName('my@project!')).toBe('myproject')
33+
})
34+
35+
it('should ensure it starts with a letter', () => {
36+
expect(sanitizePackageName('123project')).toBe('project')
37+
expect(sanitizePackageName('_myproject')).toBe('myproject')
38+
})
39+
40+
it('should collapse multiple hyphens', () => {
41+
expect(sanitizePackageName('my--project')).toBe('my-project')
42+
})
43+
44+
it('should remove trailing hyphen', () => {
45+
expect(sanitizePackageName('myproject-')).toBe('myproject')
46+
})
47+
})
48+
49+
describe('getCurrentDirectoryName', () => {
50+
it('should return the basename of the current working directory', () => {
51+
expect(getCurrentDirectoryName()).toBe(basename(process.cwd()))
52+
})
53+
})
54+
1355
describe('normalizeOptions', () => {
1456
it('should return undefined if project name is not provided', async () => {
1557
const options = await normalizeOptions({})
1658
expect(options).toBeUndefined()
1759
})
1860

61+
it('should handle "." as project name by using sanitized current directory name', async () => {
62+
const options = await normalizeOptions({
63+
projectName: '.',
64+
})
65+
const expectedName = sanitizePackageName(getCurrentDirectoryName())
66+
expect(options?.projectName).toBe(expectedName)
67+
expect(options?.targetDir).toBe(resolve(process.cwd()))
68+
})
69+
1970
it('should return enable typescript based on the framework', async () => {
2071
const jsOptions = await normalizeOptions({
2172
projectName: 'test',

packages/cta-engine/src/create-app.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,23 @@ Errors were encountered during the creation of your app:
241241
${environment.getErrors().join('\n')}`
242242
}
243243

244+
// Check if we created in current directory (user specified ".")
245+
const isCurrentDirectory =
246+
resolve(options.targetDir) === resolve(process.cwd())
247+
const locationMessage = isCurrentDirectory
248+
? `Your ${environment.appName} app is ready.`
249+
: `Your ${environment.appName} app is ready in '${basename(options.targetDir)}'.`
250+
const cdInstruction = isCurrentDirectory
251+
? ''
252+
: `% cd ${options.projectName}
253+
`
254+
244255
// Use the force luke! :)
245256
environment.outro(
246-
`Your ${environment.appName} app is ready in '${basename(options.targetDir)}'.
257+
`${locationMessage}
247258
248259
Use the following commands to start your app:
249-
% cd ${options.projectName}
250-
% ${formatCommand(
260+
${cdInstruction}% ${formatCommand(
251261
getPackageManagerScriptCommand(options.packageManager, ['dev']),
252262
)}
253263

0 commit comments

Comments
 (0)