@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 +17 -2
- package/dist/command-line.js +12 -3
- package/dist/options.js +9 -3
- package/dist/types/utils.d.ts +2 -0
- package/dist/utils.js +14 -0
- package/package.json +13 -6
- package/src/cli.ts +14 -2
- package/src/command-line.ts +17 -3
- package/src/options.ts +12 -3
- package/src/utils.ts +16 -0
- package/tests/command-line.test.ts +51 -0
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
|
-
|
|
362
|
-
|
|
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) {
|
package/dist/command-line.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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();
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -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.
|
|
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-
|
|
35
|
-
"@tanstack/cta-
|
|
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
|
-
|
|
532
|
-
|
|
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) {
|
package/src/command-line.ts
CHANGED
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
populateAddOnOptionsDefaults,
|
|
10
10
|
} from '@tanstack/cta-engine'
|
|
11
11
|
|
|
12
|
-
import {
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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',
|