@tanstack/cta-cli 0.41.2 → 0.43.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -358,8 +358,23 @@ Remove your node_modules directory and package lock file and re-install.`);
358
358
  if (!finalOptions) {
359
359
  throw new Error('No options were provided');
360
360
  }
361
- finalOptions.targetDir =
362
- options.targetDir || resolve(process.cwd(), finalOptions.projectName);
361
+ // Determine target directory:
362
+ // 1. Use --target-dir if provided
363
+ // 2. Use targetDir from normalizeOptions if set (handles "." case)
364
+ // 3. If original projectName was ".", use current directory
365
+ // 4. Otherwise, use project name as subdirectory
366
+ if (options.targetDir) {
367
+ finalOptions.targetDir = options.targetDir;
368
+ }
369
+ else if (finalOptions.targetDir) {
370
+ // Keep the targetDir from normalizeOptions (handles "." case)
371
+ }
372
+ else if (projectName === '.') {
373
+ finalOptions.targetDir = resolve(process.cwd());
374
+ }
375
+ else {
376
+ finalOptions.targetDir = resolve(process.cwd(), finalOptions.projectName);
377
+ }
363
378
  await createApp(environment, finalOptions);
364
379
  }
365
380
  catch (error) {
@@ -1,8 +1,17 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { DEFAULT_PACKAGE_MANAGER, finalizeAddOns, getFrameworkById, getPackageManager, loadStarter, populateAddOnOptionsDefaults, } from '@tanstack/cta-engine';
3
- import { validateProjectName } from './utils.js';
3
+ import { getCurrentDirectoryName, sanitizePackageName, validateProjectName, } from './utils.js';
4
4
  export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns, opts) {
5
- const projectName = (cliOptions.projectName ?? '').trim();
5
+ let projectName = (cliOptions.projectName ?? '').trim();
6
+ let targetDir;
7
+ // Handle "." as project name - use current directory
8
+ if (projectName === '.') {
9
+ projectName = sanitizePackageName(getCurrentDirectoryName());
10
+ targetDir = resolve(process.cwd());
11
+ }
12
+ else {
13
+ targetDir = resolve(process.cwd(), projectName);
14
+ }
6
15
  if (!projectName && !opts?.disableNameCheck) {
7
16
  return undefined;
8
17
  }
@@ -90,7 +99,7 @@ export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns, opt
90
99
  }
91
100
  return {
92
101
  projectName: projectName,
93
- targetDir: resolve(process.cwd(), projectName),
102
+ targetDir,
94
103
  framework,
95
104
  mode,
96
105
  typescript,
package/dist/options.js CHANGED
@@ -1,17 +1,23 @@
1
1
  import { intro } from '@clack/prompts';
2
2
  import { finalizeAddOns, getFrameworkById, getPackageManager, populateAddOnOptionsDefaults, readConfigFile, } from '@tanstack/cta-engine';
3
3
  import { getProjectName, promptForAddOnOptions, selectAddOns, selectGit, selectDeployment, selectPackageManager, selectRouterType, selectTailwind, selectToolchain, selectTypescript, } from './ui-prompts.js';
4
- import { validateProjectName } from './utils.js';
4
+ import { getCurrentDirectoryName, sanitizePackageName, validateProjectName, } from './utils.js';
5
5
  export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], forcedMode, showDeploymentOptions = false, }) {
6
6
  const options = {};
7
7
  options.framework = getFrameworkById(cliOptions.framework || 'react-cra');
8
8
  if (cliOptions.projectName) {
9
- const { valid, error } = validateProjectName(cliOptions.projectName);
9
+ // Handle "." as project name - use sanitized current directory name
10
+ if (cliOptions.projectName === '.') {
11
+ options.projectName = sanitizePackageName(getCurrentDirectoryName());
12
+ }
13
+ else {
14
+ options.projectName = cliOptions.projectName;
15
+ }
16
+ const { valid, error } = validateProjectName(options.projectName);
10
17
  if (!valid) {
11
18
  console.error(error);
12
19
  process.exit(1);
13
20
  }
14
- options.projectName = cliOptions.projectName;
15
21
  }
16
22
  else {
17
23
  options.projectName = await getProjectName();
@@ -1,5 +1,7 @@
1
1
  import type { TemplateOptions } from './types.js';
2
2
  export declare function convertTemplateToMode(template: TemplateOptions): string;
3
+ export declare function sanitizePackageName(name: string): string;
4
+ export declare function getCurrentDirectoryName(): string;
3
5
  export declare function validateProjectName(name: string): {
4
6
  valid: boolean;
5
7
  error: string;
package/dist/utils.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { basename } from 'node:path';
1
2
  import validatePackageName from 'validate-npm-package-name';
2
3
  export function convertTemplateToMode(template) {
3
4
  if (template === 'typescript' || template === 'javascript') {
@@ -5,6 +6,19 @@ export function convertTemplateToMode(template) {
5
6
  }
6
7
  return 'file-router';
7
8
  }
9
+ export function sanitizePackageName(name) {
10
+ return name
11
+ .toLowerCase()
12
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
13
+ .replace(/_/g, '-') // Replace underscores with hyphens
14
+ .replace(/[^a-z0-9-]/g, '') // Remove invalid characters
15
+ .replace(/^[^a-z]+/, '') // Ensure it starts with a letter
16
+ .replace(/-+/g, '-') // Collapse multiple hyphens
17
+ .replace(/-$/, ''); // Remove trailing hyphen
18
+ }
19
+ export function getCurrentDirectoryName() {
20
+ return basename(process.cwd());
21
+ }
8
22
  export function validateProjectName(name) {
9
23
  const { validForNewPackages, validForOldPackages, errors, warnings } = validatePackageName(name);
10
24
  const error = errors?.[0] || warnings?.[0];
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@tanstack/cta-cli",
3
- "version": "0.41.2",
3
+ "version": "0.43.1",
4
4
  "description": "Tanstack Application Builder CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/types/index.d.ts",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/TanStack/create-tsrouter-app.git"
10
+ "url": "git+https://github.com/TanStack/create-tsrouter-app.git",
11
+ "directory": "packages/cta-cli"
11
12
  },
12
13
  "homepage": "https://tanstack.com/router",
13
14
  "funding": {
@@ -31,11 +32,10 @@
31
32
  "semver": "^7.7.2",
32
33
  "validate-npm-package-name": "^7.0.0",
33
34
  "zod": "^3.24.2",
34
- "@tanstack/cta-ui": "0.41.2",
35
- "@tanstack/cta-engine": "0.41.2"
35
+ "@tanstack/cta-engine": "0.43.1",
36
+ "@tanstack/cta-ui": "0.43.1"
36
37
  },
37
38
  "devDependencies": {
38
- "@tanstack/config": "^0.16.2",
39
39
  "@types/express": "^5.0.1",
40
40
  "@types/node": "^22.13.4",
41
41
  "@types/semver": "^7.7.0",
@@ -46,5 +46,12 @@
46
46
  "vitest": "^3.1.1",
47
47
  "vitest-fetch-mock": "^0.4.5"
48
48
  },
49
- "scripts": {}
49
+ "scripts": {
50
+ "build": "tsc",
51
+ "dev": "tsc --watch",
52
+ "test:lint": "eslint ./src",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "test:coverage": "vitest run --coverage"
56
+ }
50
57
  }
package/src/cli.ts CHANGED
@@ -528,8 +528,20 @@ Remove your node_modules directory and package lock file and re-install.`,
528
528
  throw new Error('No options were provided')
529
529
  }
530
530
 
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
+ }
533
545
 
534
546
  await createApp(environment, finalOptions)
535
547
  } catch (error) {
@@ -9,7 +9,11 @@ import {
9
9
  populateAddOnOptionsDefaults,
10
10
  } from '@tanstack/cta-engine'
11
11
 
12
- import { validateProjectName } from './utils.js'
12
+ import {
13
+ getCurrentDirectoryName,
14
+ sanitizePackageName,
15
+ validateProjectName,
16
+ } from './utils.js'
13
17
  import type { Options } from '@tanstack/cta-engine'
14
18
 
15
19
  import type { CliOptions } from './types.js'
@@ -23,7 +27,17 @@ export async function normalizeOptions(
23
27
  forcedDeployment?: string
24
28
  },
25
29
  ): 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
+
27
41
  if (!projectName && !opts?.disableNameCheck) {
28
42
  return undefined
29
43
  }
@@ -134,7 +148,7 @@ export async function normalizeOptions(
134
148
 
135
149
  return {
136
150
  projectName: projectName,
137
- targetDir: resolve(process.cwd(), projectName),
151
+ targetDir,
138
152
  framework,
139
153
  mode,
140
154
  typescript,
package/src/options.ts CHANGED
@@ -21,7 +21,11 @@ import {
21
21
  selectTypescript,
22
22
  } from './ui-prompts.js'
23
23
 
24
- import { validateProjectName } from './utils.js'
24
+ import {
25
+ getCurrentDirectoryName,
26
+ sanitizePackageName,
27
+ validateProjectName,
28
+ } from './utils.js'
25
29
  import type { Options } from '@tanstack/cta-engine'
26
30
 
27
31
  import type { CliOptions } from './types.js'
@@ -43,12 +47,17 @@ export async function promptForCreateOptions(
43
47
  options.framework = getFrameworkById(cliOptions.framework || 'react-cra')!
44
48
 
45
49
  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)
47
57
  if (!valid) {
48
58
  console.error(error)
49
59
  process.exit(1)
50
60
  }
51
- options.projectName = cliOptions.projectName
52
61
  } else {
53
62
  options.projectName = await getProjectName()
54
63
  }
package/src/utils.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { basename } from 'node:path'
1
2
  import validatePackageName from 'validate-npm-package-name'
2
3
  import type { TemplateOptions } from './types.js'
3
4
 
@@ -8,6 +9,21 @@ export function convertTemplateToMode(template: TemplateOptions): string {
8
9
  return 'file-router'
9
10
  }
10
11
 
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
+
11
27
  export function validateProjectName(name: string) {
12
28
  const { validForNewPackages, validForOldPackages, errors, warnings } =
13
29
  validatePackageName(name)
@@ -1,6 +1,11 @@
1
+ import { basename, resolve } from 'node:path'
1
2
  import { beforeEach, describe, expect, it } from 'vitest'
2
3
 
3
4
  import { normalizeOptions } from '../src/command-line.js'
5
+ import {
6
+ sanitizePackageName,
7
+ getCurrentDirectoryName,
8
+ } from '../src/utils.js'
4
9
  import {
5
10
  __testRegisterFramework,
6
11
  __testClearFrameworks,
@@ -10,12 +15,58 @@ beforeEach(() => {
10
15
  __testClearFrameworks()
11
16
  })
12
17
 
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
+
13
55
  describe('normalizeOptions', () => {
14
56
  it('should return undefined if project name is not provided', async () => {
15
57
  const options = await normalizeOptions({})
16
58
  expect(options).toBeUndefined()
17
59
  })
18
60
 
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
+
19
70
  it('should return enable typescript based on the framework', async () => {
20
71
  const jsOptions = await normalizeOptions({
21
72
  projectName: 'test',