@tanstack/cta-cli 0.37.1 → 0.40.0
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/command-line.js +8 -0
- package/dist/options.js +12 -1
- package/dist/types/utils.d.ts +4 -0
- package/dist/ui-prompts.js +13 -1
- package/dist/utils.js +10 -0
- package/package.json +5 -3
- package/src/command-line.ts +9 -0
- package/src/options.ts +11 -1
- package/src/ui-prompts.ts +16 -0
- package/src/utils.ts +14 -0
- package/tests/ui-prompts.test.ts +7 -1
package/dist/command-line.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
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
4
|
export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns, opts) {
|
|
4
5
|
const projectName = (cliOptions.projectName ?? '').trim();
|
|
5
6
|
if (!projectName && !opts?.disableNameCheck) {
|
|
6
7
|
return undefined;
|
|
7
8
|
}
|
|
9
|
+
if (projectName) {
|
|
10
|
+
const { valid, error } = validateProjectName(projectName);
|
|
11
|
+
if (!valid) {
|
|
12
|
+
console.error(error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
8
16
|
let tailwind = !!cliOptions.tailwind;
|
|
9
17
|
let mode = forcedMode ||
|
|
10
18
|
(cliOptions.template === 'file-router' ? 'file-router' : 'code-router');
|
package/dist/options.js
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
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
5
|
export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], forcedMode, showDeploymentOptions = false, }) {
|
|
5
6
|
const options = {};
|
|
6
7
|
options.framework = getFrameworkById(cliOptions.framework || 'react-cra');
|
|
7
|
-
|
|
8
|
+
if (cliOptions.projectName) {
|
|
9
|
+
const { valid, error } = validateProjectName(cliOptions.projectName);
|
|
10
|
+
if (!valid) {
|
|
11
|
+
console.error(error);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
options.projectName = cliOptions.projectName;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
options.projectName = await getProjectName();
|
|
18
|
+
}
|
|
8
19
|
// Router type selection
|
|
9
20
|
if (forcedMode) {
|
|
10
21
|
options.mode = forcedMode;
|
package/dist/types/utils.d.ts
CHANGED
package/dist/ui-prompts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { cancel, confirm, isCancel, multiselect, select, text, } from '@clack/prompts';
|
|
1
|
+
import { cancel, confirm, isCancel, multiselect, note, select, text, } from '@clack/prompts';
|
|
2
2
|
import { DEFAULT_PACKAGE_MANAGER, SUPPORTED_PACKAGE_MANAGERS, getAllAddOns, } from '@tanstack/cta-engine';
|
|
3
|
+
import { validateProjectName } from './utils.js';
|
|
3
4
|
export async function getProjectName() {
|
|
4
5
|
const value = await text({
|
|
5
6
|
message: 'What would you like to name your project?',
|
|
@@ -8,6 +9,10 @@ export async function getProjectName() {
|
|
|
8
9
|
if (!value) {
|
|
9
10
|
return 'Please enter a name';
|
|
10
11
|
}
|
|
12
|
+
const { valid, error } = validateProjectName(value);
|
|
13
|
+
if (!valid) {
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
11
16
|
},
|
|
12
17
|
});
|
|
13
18
|
if (isCancel(value)) {
|
|
@@ -74,12 +79,19 @@ export async function selectPackageManager() {
|
|
|
74
79
|
}
|
|
75
80
|
return packageManager;
|
|
76
81
|
}
|
|
82
|
+
// Track if we've shown the multiselect help text
|
|
83
|
+
let hasShownMultiselectHelp = false;
|
|
77
84
|
export async function selectAddOns(framework, mode, type, message, forcedAddOns = []) {
|
|
78
85
|
const allAddOns = await getAllAddOns(framework, mode);
|
|
79
86
|
const addOns = allAddOns.filter((addOn) => addOn.type === type);
|
|
80
87
|
if (addOns.length === 0) {
|
|
81
88
|
return [];
|
|
82
89
|
}
|
|
90
|
+
// Show help text only once
|
|
91
|
+
if (!hasShownMultiselectHelp) {
|
|
92
|
+
note('Use ↑/↓ to navigate • Space to select/deselect • Enter to confirm', 'Keyboard Shortcuts');
|
|
93
|
+
hasShownMultiselectHelp = true;
|
|
94
|
+
}
|
|
83
95
|
const value = await multiselect({
|
|
84
96
|
message,
|
|
85
97
|
options: addOns
|
package/dist/utils.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import validatePackageName from 'validate-npm-package-name';
|
|
1
2
|
export function convertTemplateToMode(template) {
|
|
2
3
|
if (template === 'typescript' || template === 'javascript') {
|
|
3
4
|
return 'code-router';
|
|
4
5
|
}
|
|
5
6
|
return 'file-router';
|
|
6
7
|
}
|
|
8
|
+
export function validateProjectName(name) {
|
|
9
|
+
const { validForNewPackages, validForOldPackages, errors, warnings } = validatePackageName(name);
|
|
10
|
+
const error = errors?.[0] || warnings?.[0];
|
|
11
|
+
return {
|
|
12
|
+
valid: validForNewPackages && validForOldPackages,
|
|
13
|
+
error: error?.replace(/name/g, 'Project name') ||
|
|
14
|
+
'Project name does not meet npm package naming requirements',
|
|
15
|
+
};
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/cta-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "Tanstack Application Builder CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,15 +29,17 @@
|
|
|
29
29
|
"commander": "^13.1.0",
|
|
30
30
|
"express": "^4.21.2",
|
|
31
31
|
"semver": "^7.7.2",
|
|
32
|
+
"validate-npm-package-name": "^7.0.0",
|
|
32
33
|
"zod": "^3.24.2",
|
|
33
|
-
"@tanstack/cta-engine": "0.
|
|
34
|
-
"@tanstack/cta-ui": "0.
|
|
34
|
+
"@tanstack/cta-engine": "0.40.0",
|
|
35
|
+
"@tanstack/cta-ui": "0.40.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@tanstack/config": "^0.16.2",
|
|
38
39
|
"@types/express": "^5.0.1",
|
|
39
40
|
"@types/node": "^22.13.4",
|
|
40
41
|
"@types/semver": "^7.7.0",
|
|
42
|
+
"@types/validate-npm-package-name": "^4.0.2",
|
|
41
43
|
"@vitest/coverage-v8": "3.1.1",
|
|
42
44
|
"eslint": "^9.20.0",
|
|
43
45
|
"typescript": "^5.6.3",
|
package/src/command-line.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
populateAddOnOptionsDefaults,
|
|
10
10
|
} from '@tanstack/cta-engine'
|
|
11
11
|
|
|
12
|
+
import { validateProjectName } from './utils.js'
|
|
12
13
|
import type { Options } from '@tanstack/cta-engine'
|
|
13
14
|
|
|
14
15
|
import type { CliOptions } from './types.js'
|
|
@@ -27,6 +28,14 @@ export async function normalizeOptions(
|
|
|
27
28
|
return undefined
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
if (projectName) {
|
|
32
|
+
const { valid, error } = validateProjectName(projectName)
|
|
33
|
+
if (!valid) {
|
|
34
|
+
console.error(error)
|
|
35
|
+
process.exit(1)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
let tailwind = !!cliOptions.tailwind
|
|
31
40
|
|
|
32
41
|
let mode: string =
|
package/src/options.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
selectTypescript,
|
|
22
22
|
} from './ui-prompts.js'
|
|
23
23
|
|
|
24
|
+
import { validateProjectName } from './utils.js'
|
|
24
25
|
import type { Options } from '@tanstack/cta-engine'
|
|
25
26
|
|
|
26
27
|
import type { CliOptions } from './types.js'
|
|
@@ -41,7 +42,16 @@ export async function promptForCreateOptions(
|
|
|
41
42
|
|
|
42
43
|
options.framework = getFrameworkById(cliOptions.framework || 'react-cra')!
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
if (cliOptions.projectName) {
|
|
46
|
+
const { valid, error } = validateProjectName(cliOptions.projectName)
|
|
47
|
+
if (!valid) {
|
|
48
|
+
console.error(error)
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
options.projectName = cliOptions.projectName
|
|
52
|
+
} else {
|
|
53
|
+
options.projectName = await getProjectName()
|
|
54
|
+
}
|
|
45
55
|
|
|
46
56
|
// Router type selection
|
|
47
57
|
if (forcedMode) {
|
package/src/ui-prompts.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
confirm,
|
|
4
4
|
isCancel,
|
|
5
5
|
multiselect,
|
|
6
|
+
note,
|
|
6
7
|
select,
|
|
7
8
|
text,
|
|
8
9
|
} from '@clack/prompts'
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
getAllAddOns,
|
|
14
15
|
} from '@tanstack/cta-engine'
|
|
15
16
|
|
|
17
|
+
import { validateProjectName } from './utils.js'
|
|
16
18
|
import type { AddOn, PackageManager } from '@tanstack/cta-engine'
|
|
17
19
|
|
|
18
20
|
import type { Framework } from '@tanstack/cta-engine/dist/types/types.js'
|
|
@@ -26,6 +28,11 @@ export async function getProjectName(): Promise<string> {
|
|
|
26
28
|
if (!value) {
|
|
27
29
|
return 'Please enter a name'
|
|
28
30
|
}
|
|
31
|
+
|
|
32
|
+
const { valid, error } = validateProjectName(value)
|
|
33
|
+
if (!valid) {
|
|
34
|
+
return error
|
|
35
|
+
}
|
|
29
36
|
},
|
|
30
37
|
})
|
|
31
38
|
|
|
@@ -103,6 +110,9 @@ export async function selectPackageManager(): Promise<PackageManager> {
|
|
|
103
110
|
return packageManager
|
|
104
111
|
}
|
|
105
112
|
|
|
113
|
+
// Track if we've shown the multiselect help text
|
|
114
|
+
let hasShownMultiselectHelp = false
|
|
115
|
+
|
|
106
116
|
export async function selectAddOns(
|
|
107
117
|
framework: Framework,
|
|
108
118
|
mode: string,
|
|
@@ -116,6 +126,12 @@ export async function selectAddOns(
|
|
|
116
126
|
return []
|
|
117
127
|
}
|
|
118
128
|
|
|
129
|
+
// Show help text only once
|
|
130
|
+
if (!hasShownMultiselectHelp) {
|
|
131
|
+
note('Use ↑/↓ to navigate • Space to select/deselect • Enter to confirm', 'Keyboard Shortcuts')
|
|
132
|
+
hasShownMultiselectHelp = true
|
|
133
|
+
}
|
|
134
|
+
|
|
119
135
|
const value = await multiselect({
|
|
120
136
|
message,
|
|
121
137
|
options: addOns
|
package/src/utils.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import validatePackageName from 'validate-npm-package-name'
|
|
1
2
|
import type { TemplateOptions } from './types.js'
|
|
2
3
|
|
|
3
4
|
export function convertTemplateToMode(template: TemplateOptions): string {
|
|
@@ -6,3 +7,16 @@ export function convertTemplateToMode(template: TemplateOptions): string {
|
|
|
6
7
|
}
|
|
7
8
|
return 'file-router'
|
|
8
9
|
}
|
|
10
|
+
|
|
11
|
+
export function validateProjectName(name: string) {
|
|
12
|
+
const { validForNewPackages, validForOldPackages, errors, warnings } =
|
|
13
|
+
validatePackageName(name)
|
|
14
|
+
const error = errors?.[0] || warnings?.[0]
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
valid: validForNewPackages && validForOldPackages,
|
|
18
|
+
error:
|
|
19
|
+
error?.replace(/name/g, 'Project name') ||
|
|
20
|
+
'Project name does not meet npm package naming requirements',
|
|
21
|
+
}
|
|
22
|
+
}
|
package/tests/ui-prompts.test.ts
CHANGED
|
@@ -93,7 +93,8 @@ describe('selectPackageManager', () => {
|
|
|
93
93
|
})
|
|
94
94
|
|
|
95
95
|
describe('selectAddOns', () => {
|
|
96
|
-
it('should select
|
|
96
|
+
it('should show keyboard shortcuts help and select add-ons', async () => {
|
|
97
|
+
const noteSpy = vi.spyOn(clack, 'note').mockImplementation(() => {})
|
|
97
98
|
vi.spyOn(clack, 'multiselect').mockImplementation(async () => ['add-on-1'])
|
|
98
99
|
vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
|
|
99
100
|
|
|
@@ -114,7 +115,12 @@ describe('selectAddOns', () => {
|
|
|
114
115
|
'add-on',
|
|
115
116
|
'Select add-ons',
|
|
116
117
|
)
|
|
118
|
+
|
|
117
119
|
expect(packageManager).toEqual(['add-on-1'])
|
|
120
|
+
expect(noteSpy).toHaveBeenCalledWith(
|
|
121
|
+
'Use ↑/↓ to navigate • Space to select/deselect • Enter to confirm',
|
|
122
|
+
'Keyboard Shortcuts',
|
|
123
|
+
)
|
|
118
124
|
})
|
|
119
125
|
|
|
120
126
|
it('should exit on cancel', async () => {
|