@tanstack/cta-cli 0.46.2 → 0.48.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/CHANGELOG.md +27 -0
- package/dist/cli.js +67 -7
- package/dist/command-line.js +57 -1
- package/dist/dev-watch.js +290 -0
- package/dist/file-syncer.js +148 -0
- package/dist/options.js +39 -10
- package/dist/types/cli.d.ts +3 -1
- package/dist/types/command-line.d.ts +4 -0
- package/dist/types/dev-watch.d.ts +27 -0
- package/dist/types/file-syncer.d.ts +18 -0
- package/dist/types/types.d.ts +4 -1
- package/dist/types/ui-prompts.d.ts +1 -1
- package/dist/ui-prompts.js +3 -0
- package/package.json +8 -3
- package/src/cli.ts +104 -17
- package/src/command-line.ts +69 -1
- package/src/dev-watch.ts +430 -0
- package/src/file-syncer.ts +205 -0
- package/src/options.ts +45 -10
- package/src/types.ts +4 -1
- package/src/ui-prompts.ts +5 -2
- package/tests/command-line.test.ts +6 -2
- package/tests/options.test.ts +5 -0
package/dist/options.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { cancel, confirm, intro, isCancel } from '@clack/prompts';
|
|
2
3
|
import { finalizeAddOns, getFrameworkById, getPackageManager, populateAddOnOptionsDefaults, readConfigFile, } from '@tanstack/cta-engine';
|
|
3
|
-
import { getProjectName, promptForAddOnOptions, selectAddOns,
|
|
4
|
+
import { getProjectName, promptForAddOnOptions, selectAddOns, selectDeployment, selectGit, selectPackageManager, selectRouterType, selectTailwind, selectToolchain, selectTypescript, } from './ui-prompts.js';
|
|
4
5
|
import { getCurrentDirectoryName, sanitizePackageName, validateProjectName, } from './utils.js';
|
|
5
6
|
export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], forcedMode, showDeploymentOptions = false, }) {
|
|
6
7
|
const options = {};
|
|
7
8
|
options.framework = getFrameworkById(cliOptions.framework || 'react-cra');
|
|
9
|
+
// Validate project name
|
|
8
10
|
if (cliOptions.projectName) {
|
|
9
11
|
// Handle "." as project name - use sanitized current directory name
|
|
10
12
|
if (cliOptions.projectName === '.') {
|
|
@@ -22,6 +24,19 @@ export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], fo
|
|
|
22
24
|
else {
|
|
23
25
|
options.projectName = await getProjectName();
|
|
24
26
|
}
|
|
27
|
+
// Check if target directory is empty
|
|
28
|
+
if (!cliOptions.force &&
|
|
29
|
+
fs.existsSync(options.projectName) &&
|
|
30
|
+
fs.readdirSync(options.projectName).length > 0) {
|
|
31
|
+
const shouldContinue = await confirm({
|
|
32
|
+
message: `Target directory ${options.projectName} is not empty. Do you want to continue?`,
|
|
33
|
+
initialValue: true,
|
|
34
|
+
});
|
|
35
|
+
if (isCancel(shouldContinue) || !shouldContinue) {
|
|
36
|
+
cancel('Operation cancelled.');
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
25
40
|
// Router type selection
|
|
26
41
|
if (forcedMode) {
|
|
27
42
|
options.mode = forcedMode;
|
|
@@ -44,13 +59,6 @@ export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], fo
|
|
|
44
59
|
if (!options.typescript && options.mode === 'code-router') {
|
|
45
60
|
options.typescript = await selectTypescript();
|
|
46
61
|
}
|
|
47
|
-
// Tailwind selection
|
|
48
|
-
if (!cliOptions.tailwind && options.framework.id === 'react-cra') {
|
|
49
|
-
options.tailwind = await selectTailwind();
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
options.tailwind = true;
|
|
53
|
-
}
|
|
54
62
|
// Package manager selection
|
|
55
63
|
if (cliOptions.packageManager) {
|
|
56
64
|
options.packageManager = cliOptions.packageManager;
|
|
@@ -92,9 +100,27 @@ export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], fo
|
|
|
92
100
|
}
|
|
93
101
|
options.chosenAddOns = Array.from(await finalizeAddOns(options.framework, options.mode, Array.from(addOns)));
|
|
94
102
|
if (options.chosenAddOns.length) {
|
|
95
|
-
options.tailwind = true;
|
|
96
103
|
options.typescript = true;
|
|
97
104
|
}
|
|
105
|
+
// Tailwind selection
|
|
106
|
+
// Only treat add-ons as requiring tailwind if they explicitly have "tailwind": true
|
|
107
|
+
const addOnsRequireTailwind = options.chosenAddOns.some((addOn) => addOn.tailwind === true);
|
|
108
|
+
if (addOnsRequireTailwind) {
|
|
109
|
+
// If any add-on explicitly requires tailwind, enable it automatically
|
|
110
|
+
options.tailwind = true;
|
|
111
|
+
}
|
|
112
|
+
else if (cliOptions.tailwind !== undefined) {
|
|
113
|
+
// User explicitly provided a CLI flag, respect it
|
|
114
|
+
options.tailwind = !!cliOptions.tailwind;
|
|
115
|
+
}
|
|
116
|
+
else if (options.framework.id === 'react-cra') {
|
|
117
|
+
// Only show prompt for react-cra when no CLI flag and no add-ons require it
|
|
118
|
+
options.tailwind = await selectTailwind();
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// For other frameworks (like solid), default to true
|
|
122
|
+
options.tailwind = true;
|
|
123
|
+
}
|
|
98
124
|
// Prompt for add-on options in interactive mode
|
|
99
125
|
if (Array.isArray(cliOptions.addOns)) {
|
|
100
126
|
// Non-interactive mode: use defaults
|
|
@@ -108,6 +134,9 @@ export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], fo
|
|
|
108
134
|
options.addOnOptions = { ...defaultOptions, ...userOptions };
|
|
109
135
|
}
|
|
110
136
|
options.git = cliOptions.git || (await selectGit());
|
|
137
|
+
if (cliOptions.install === false) {
|
|
138
|
+
options.install = false;
|
|
139
|
+
}
|
|
111
140
|
return options;
|
|
112
141
|
}
|
|
113
142
|
export async function promptForAddOns() {
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TemplateOptions } from './types.js';
|
|
2
|
-
|
|
2
|
+
import type { FrameworkDefinition } from '@tanstack/cta-engine';
|
|
3
|
+
export declare function cli({ name, appName, forcedMode, forcedAddOns, defaultTemplate, forcedDeployment, defaultFramework, craCompatible, webBase, frameworkDefinitionInitializers, showDeploymentOptions, }: {
|
|
3
4
|
name: string;
|
|
4
5
|
appName: string;
|
|
5
6
|
forcedMode?: string;
|
|
@@ -9,5 +10,6 @@ export declare function cli({ name, appName, forcedMode, forcedAddOns, defaultTe
|
|
|
9
10
|
defaultFramework?: string;
|
|
10
11
|
craCompatible?: boolean;
|
|
11
12
|
webBase?: string;
|
|
13
|
+
frameworkDefinitionInitializers?: Array<() => FrameworkDefinition>;
|
|
12
14
|
showDeploymentOptions?: boolean;
|
|
13
15
|
}): void;
|
|
@@ -4,3 +4,7 @@ export declare function normalizeOptions(cliOptions: CliOptions, forcedMode?: st
|
|
|
4
4
|
disableNameCheck?: boolean;
|
|
5
5
|
forcedDeployment?: string;
|
|
6
6
|
}): Promise<Options | undefined>;
|
|
7
|
+
export declare function validateDevWatchOptions(cliOptions: CliOptions): {
|
|
8
|
+
valid: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Environment, Framework, FrameworkDefinition, Options } from '@tanstack/cta-engine';
|
|
2
|
+
export interface DevWatchOptions {
|
|
3
|
+
watchPath: string;
|
|
4
|
+
targetDir: string;
|
|
5
|
+
framework: Framework;
|
|
6
|
+
cliOptions: Options;
|
|
7
|
+
packageManager: string;
|
|
8
|
+
environment: Environment;
|
|
9
|
+
frameworkDefinitionInitializers?: Array<() => FrameworkDefinition>;
|
|
10
|
+
}
|
|
11
|
+
export declare class DevWatchManager {
|
|
12
|
+
private options;
|
|
13
|
+
private watcher;
|
|
14
|
+
private debounceQueue;
|
|
15
|
+
private syncer;
|
|
16
|
+
private tempDir;
|
|
17
|
+
private isBuilding;
|
|
18
|
+
private buildCount;
|
|
19
|
+
constructor(options: DevWatchOptions);
|
|
20
|
+
start(): Promise<void>;
|
|
21
|
+
stop(): Promise<void>;
|
|
22
|
+
private startWatcher;
|
|
23
|
+
private handleChange;
|
|
24
|
+
private rebuild;
|
|
25
|
+
private cleanup;
|
|
26
|
+
private log;
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface FileUpdate {
|
|
2
|
+
path: string;
|
|
3
|
+
diff?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SyncResult {
|
|
6
|
+
updated: Array<FileUpdate>;
|
|
7
|
+
skipped: Array<string>;
|
|
8
|
+
created: Array<string>;
|
|
9
|
+
errors: Array<string>;
|
|
10
|
+
}
|
|
11
|
+
export declare class FileSyncer {
|
|
12
|
+
sync(sourceDir: string, targetDir: string): Promise<SyncResult>;
|
|
13
|
+
private syncDirectory;
|
|
14
|
+
private shouldUpdateFile;
|
|
15
|
+
private calculateHash;
|
|
16
|
+
private shouldSkipDirectory;
|
|
17
|
+
private shouldSkipFile;
|
|
18
|
+
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface CliOptions {
|
|
|
5
5
|
framework?: string;
|
|
6
6
|
tailwind?: boolean;
|
|
7
7
|
packageManager?: PackageManager;
|
|
8
|
-
toolchain?: string;
|
|
8
|
+
toolchain?: string | false;
|
|
9
9
|
deployment?: string;
|
|
10
10
|
projectName?: string;
|
|
11
11
|
git?: boolean;
|
|
@@ -18,5 +18,8 @@ export interface CliOptions {
|
|
|
18
18
|
targetDir?: string;
|
|
19
19
|
interactive?: boolean;
|
|
20
20
|
ui?: boolean;
|
|
21
|
+
devWatch?: string;
|
|
22
|
+
install?: boolean;
|
|
21
23
|
addOnConfig?: string;
|
|
24
|
+
force?: boolean;
|
|
22
25
|
}
|
|
@@ -7,6 +7,6 @@ export declare function selectTailwind(): Promise<boolean>;
|
|
|
7
7
|
export declare function selectPackageManager(): Promise<PackageManager>;
|
|
8
8
|
export declare function selectAddOns(framework: Framework, mode: string, type: string, message: string, forcedAddOns?: Array<string>, allowMultiple?: boolean): Promise<Array<string>>;
|
|
9
9
|
export declare function selectGit(): Promise<boolean>;
|
|
10
|
-
export declare function selectToolchain(framework: Framework, toolchain?: string): Promise<string | undefined>;
|
|
10
|
+
export declare function selectToolchain(framework: Framework, toolchain?: string | false): Promise<string | undefined>;
|
|
11
11
|
export declare function promptForAddOnOptions(addOnIds: Array<string>, framework: Framework): Promise<Record<string, Record<string, any>>>;
|
|
12
12
|
export declare function selectDeployment(framework: Framework, deployment?: string): Promise<string | undefined>;
|
package/dist/ui-prompts.js
CHANGED
|
@@ -147,6 +147,9 @@ export async function selectGit() {
|
|
|
147
147
|
return git;
|
|
148
148
|
}
|
|
149
149
|
export async function selectToolchain(framework, toolchain) {
|
|
150
|
+
if (toolchain === false) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
150
153
|
const toolchains = new Set();
|
|
151
154
|
for (const addOn of framework.getAddOns()) {
|
|
152
155
|
if (addOn.type === 'toolchain') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/cta-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "Tanstack Application Builder CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -27,15 +27,20 @@
|
|
|
27
27
|
"@clack/prompts": "^0.10.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.6.0",
|
|
29
29
|
"chalk": "^5.4.1",
|
|
30
|
+
"chokidar": "^3.6.0",
|
|
30
31
|
"commander": "^13.1.0",
|
|
32
|
+
"diff": "^7.0.0",
|
|
31
33
|
"express": "^4.21.2",
|
|
32
34
|
"semver": "^7.7.2",
|
|
35
|
+
"tempy": "^3.1.0",
|
|
33
36
|
"validate-npm-package-name": "^7.0.0",
|
|
34
37
|
"zod": "^3.24.2",
|
|
35
|
-
"@tanstack/cta-engine": "0.
|
|
36
|
-
"@tanstack/cta-ui": "0.
|
|
38
|
+
"@tanstack/cta-engine": "0.48.0",
|
|
39
|
+
"@tanstack/cta-ui": "0.48.0"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
42
|
+
"@tanstack/config": "^0.16.2",
|
|
43
|
+
"@types/diff": "^5.2.0",
|
|
39
44
|
"@types/express": "^5.0.1",
|
|
40
45
|
"@types/node": "^22.13.4",
|
|
41
46
|
"@types/semver": "^7.7.0",
|
package/src/cli.ts
CHANGED
|
@@ -24,13 +24,18 @@ import { launchUI } from '@tanstack/cta-ui'
|
|
|
24
24
|
import { runMCPServer } from './mcp.js'
|
|
25
25
|
|
|
26
26
|
import { promptForAddOns, promptForCreateOptions } from './options.js'
|
|
27
|
-
import { normalizeOptions } from './command-line.js'
|
|
27
|
+
import { normalizeOptions, validateDevWatchOptions } from './command-line.js'
|
|
28
28
|
|
|
29
29
|
import { createUIEnvironment } from './ui-environment.js'
|
|
30
30
|
import { convertTemplateToMode } from './utils.js'
|
|
31
|
+
import { DevWatchManager } from './dev-watch.js'
|
|
31
32
|
|
|
32
33
|
import type { CliOptions, TemplateOptions } from './types.js'
|
|
33
|
-
import type {
|
|
34
|
+
import type {
|
|
35
|
+
FrameworkDefinition,
|
|
36
|
+
Options,
|
|
37
|
+
PackageManager,
|
|
38
|
+
} from '@tanstack/cta-engine'
|
|
34
39
|
|
|
35
40
|
// This CLI assumes that all of the registered frameworks have the same set of toolchains, deployments, modes, etc.
|
|
36
41
|
|
|
@@ -44,6 +49,7 @@ export function cli({
|
|
|
44
49
|
defaultFramework,
|
|
45
50
|
craCompatible = false,
|
|
46
51
|
webBase,
|
|
52
|
+
frameworkDefinitionInitializers,
|
|
47
53
|
showDeploymentOptions = false,
|
|
48
54
|
}: {
|
|
49
55
|
name: string
|
|
@@ -55,6 +61,7 @@ export function cli({
|
|
|
55
61
|
defaultFramework?: string
|
|
56
62
|
craCompatible?: boolean
|
|
57
63
|
webBase?: string
|
|
64
|
+
frameworkDefinitionInitializers?: Array<() => FrameworkDefinition>
|
|
58
65
|
showDeploymentOptions?: boolean
|
|
59
66
|
}) {
|
|
60
67
|
const environment = createUIEnvironment(appName, false)
|
|
@@ -293,6 +300,7 @@ Remove your node_modules directory and package lock file and re-install.`,
|
|
|
293
300
|
'initialize this project from a starter URL',
|
|
294
301
|
false,
|
|
295
302
|
)
|
|
303
|
+
.option('--no-install', 'skip installing dependencies')
|
|
296
304
|
.option<PackageManager>(
|
|
297
305
|
`--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`,
|
|
298
306
|
`Explicitly tell the CLI to use this package manager`,
|
|
@@ -307,6 +315,10 @@ Remove your node_modules directory and package lock file and re-install.`,
|
|
|
307
315
|
return value as PackageManager
|
|
308
316
|
},
|
|
309
317
|
)
|
|
318
|
+
.option(
|
|
319
|
+
'--dev-watch <path>',
|
|
320
|
+
'Watch a framework directory for changes and auto-rebuild',
|
|
321
|
+
)
|
|
310
322
|
|
|
311
323
|
if (deployments.size > 0) {
|
|
312
324
|
program.option<string>(
|
|
@@ -326,25 +338,28 @@ Remove your node_modules directory and package lock file and re-install.`,
|
|
|
326
338
|
}
|
|
327
339
|
|
|
328
340
|
if (toolchains.size > 0) {
|
|
329
|
-
program
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
program
|
|
342
|
+
.option<string>(
|
|
343
|
+
`--toolchain <${Array.from(toolchains).join('|')}>`,
|
|
344
|
+
`Explicitly tell the CLI to use this toolchain`,
|
|
345
|
+
(value) => {
|
|
346
|
+
if (!toolchains.has(value)) {
|
|
347
|
+
throw new InvalidArgumentError(
|
|
348
|
+
`Invalid toolchain: ${value}. The following are allowed: ${Array.from(
|
|
349
|
+
toolchains,
|
|
350
|
+
).join(', ')}`,
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
return value
|
|
354
|
+
},
|
|
355
|
+
)
|
|
356
|
+
.option('--no-toolchain', 'skip toolchain selection')
|
|
343
357
|
}
|
|
344
358
|
|
|
345
359
|
program
|
|
346
360
|
.option('--interactive', 'interactive mode', false)
|
|
347
|
-
.option('--tailwind', 'add Tailwind CSS'
|
|
361
|
+
.option('--tailwind', 'add Tailwind CSS')
|
|
362
|
+
.option('--no-tailwind', 'skip Tailwind CSS')
|
|
348
363
|
.option<Array<string> | boolean>(
|
|
349
364
|
'--add-ons [...add-ons]',
|
|
350
365
|
'pick from a list of available add-ons (comma separated list)',
|
|
@@ -373,6 +388,11 @@ Remove your node_modules directory and package lock file and re-install.`,
|
|
|
373
388
|
'--add-on-config <config>',
|
|
374
389
|
'JSON string with add-on configuration options',
|
|
375
390
|
)
|
|
391
|
+
.option(
|
|
392
|
+
'-f, --force',
|
|
393
|
+
'force project creation even if the target directory is not empty',
|
|
394
|
+
false,
|
|
395
|
+
)
|
|
376
396
|
|
|
377
397
|
program.action(async (projectName: string, options: CliOptions) => {
|
|
378
398
|
if (options.listAddOns) {
|
|
@@ -462,6 +482,73 @@ Remove your node_modules directory and package lock file and re-install.`,
|
|
|
462
482
|
forcedAddOns,
|
|
463
483
|
appName,
|
|
464
484
|
})
|
|
485
|
+
} else if (options.devWatch) {
|
|
486
|
+
// Validate dev watch options
|
|
487
|
+
const validation = validateDevWatchOptions({ ...options, projectName })
|
|
488
|
+
if (!validation.valid) {
|
|
489
|
+
console.error(validation.error)
|
|
490
|
+
process.exit(1)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Enter dev watch mode
|
|
494
|
+
if (!projectName && !options.targetDir) {
|
|
495
|
+
console.error(
|
|
496
|
+
'Project name/target directory is required for dev watch mode',
|
|
497
|
+
)
|
|
498
|
+
process.exit(1)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (!options.framework) {
|
|
502
|
+
console.error('Failed to detect framework')
|
|
503
|
+
process.exit(1)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const framework = getFrameworkByName(options.framework)
|
|
507
|
+
if (!framework) {
|
|
508
|
+
console.error('Failed to detect framework')
|
|
509
|
+
process.exit(1)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// First, create the app normally using the standard flow
|
|
513
|
+
const normalizedOpts = await normalizeOptions(
|
|
514
|
+
{
|
|
515
|
+
...options,
|
|
516
|
+
projectName,
|
|
517
|
+
framework: framework.id,
|
|
518
|
+
},
|
|
519
|
+
defaultMode,
|
|
520
|
+
forcedAddOns,
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
if (!normalizedOpts) {
|
|
524
|
+
throw new Error('Failed to normalize options')
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
normalizedOpts.targetDir =
|
|
528
|
+
options.targetDir || resolve(process.cwd(), projectName)
|
|
529
|
+
|
|
530
|
+
// Create the initial app with minimal output for dev watch mode
|
|
531
|
+
console.log(chalk.bold('\ndev-watch'))
|
|
532
|
+
console.log(chalk.gray('├─') + ' ' + `creating initial ${appName} app...`)
|
|
533
|
+
if (normalizedOpts.install !== false) {
|
|
534
|
+
console.log(chalk.gray('├─') + ' ' + chalk.yellow('⟳') + ' installing packages...')
|
|
535
|
+
}
|
|
536
|
+
const silentEnvironment = createUIEnvironment(appName, true)
|
|
537
|
+
await createApp(silentEnvironment, normalizedOpts)
|
|
538
|
+
console.log(chalk.gray('└─') + ' ' + chalk.green('✓') + ` app created`)
|
|
539
|
+
|
|
540
|
+
// Now start the dev watch mode
|
|
541
|
+
const manager = new DevWatchManager({
|
|
542
|
+
watchPath: options.devWatch,
|
|
543
|
+
targetDir: normalizedOpts.targetDir,
|
|
544
|
+
framework,
|
|
545
|
+
cliOptions: normalizedOpts,
|
|
546
|
+
packageManager: normalizedOpts.packageManager,
|
|
547
|
+
environment,
|
|
548
|
+
frameworkDefinitionInitializers,
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
await manager.start()
|
|
465
552
|
} else {
|
|
466
553
|
try {
|
|
467
554
|
const cliOptions = {
|
package/src/command-line.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
|
+
import fs from 'node:fs'
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
DEFAULT_PACKAGE_MANAGER,
|
|
@@ -131,8 +132,25 @@ export async function normalizeOptions(
|
|
|
131
132
|
const chosenAddOns = await selectAddOns()
|
|
132
133
|
|
|
133
134
|
if (chosenAddOns.length) {
|
|
134
|
-
tailwind = true
|
|
135
135
|
typescript = true
|
|
136
|
+
|
|
137
|
+
// Check if any add-on explicitly requires tailwind
|
|
138
|
+
const addOnsRequireTailwind = chosenAddOns.some(
|
|
139
|
+
(addOn) => addOn.tailwind === true,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
// Only set tailwind to true if:
|
|
143
|
+
// 1. An add-on explicitly requires it, OR
|
|
144
|
+
// 2. User explicitly set it via CLI
|
|
145
|
+
if (addOnsRequireTailwind) {
|
|
146
|
+
tailwind = true
|
|
147
|
+
} else if (cliOptions.tailwind === true) {
|
|
148
|
+
tailwind = true
|
|
149
|
+
} else if (cliOptions.tailwind === false) {
|
|
150
|
+
tailwind = false
|
|
151
|
+
}
|
|
152
|
+
// If cliOptions.tailwind is undefined and no add-ons require it,
|
|
153
|
+
// leave tailwind as is (will be prompted in interactive mode)
|
|
136
154
|
}
|
|
137
155
|
|
|
138
156
|
// Handle add-on configuration option
|
|
@@ -158,6 +176,7 @@ export async function normalizeOptions(
|
|
|
158
176
|
getPackageManager() ||
|
|
159
177
|
DEFAULT_PACKAGE_MANAGER,
|
|
160
178
|
git: !!cliOptions.git,
|
|
179
|
+
install: cliOptions.install,
|
|
161
180
|
chosenAddOns,
|
|
162
181
|
addOnOptions: {
|
|
163
182
|
...populateAddOnOptionsDefaults(chosenAddOns),
|
|
@@ -166,3 +185,52 @@ export async function normalizeOptions(
|
|
|
166
185
|
starter: starter,
|
|
167
186
|
}
|
|
168
187
|
}
|
|
188
|
+
|
|
189
|
+
export function validateDevWatchOptions(cliOptions: CliOptions): {
|
|
190
|
+
valid: boolean
|
|
191
|
+
error?: string
|
|
192
|
+
} {
|
|
193
|
+
if (!cliOptions.devWatch) {
|
|
194
|
+
return { valid: true }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Validate watch path exists
|
|
198
|
+
const watchPath = resolve(process.cwd(), cliOptions.devWatch)
|
|
199
|
+
if (!fs.existsSync(watchPath)) {
|
|
200
|
+
return {
|
|
201
|
+
valid: false,
|
|
202
|
+
error: `Watch path does not exist: ${watchPath}`,
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Validate it's a directory
|
|
207
|
+
const stats = fs.statSync(watchPath)
|
|
208
|
+
if (!stats.isDirectory()) {
|
|
209
|
+
return {
|
|
210
|
+
valid: false,
|
|
211
|
+
error: `Watch path is not a directory: ${watchPath}`,
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Ensure target directory is specified
|
|
216
|
+
if (!cliOptions.projectName && !cliOptions.targetDir) {
|
|
217
|
+
return {
|
|
218
|
+
valid: false,
|
|
219
|
+
error: 'Project name or target directory is required for dev watch mode',
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check for framework structure
|
|
224
|
+
const hasAddOns = fs.existsSync(resolve(watchPath, 'add-ons'))
|
|
225
|
+
const hasAssets = fs.existsSync(resolve(watchPath, 'assets'))
|
|
226
|
+
const hasFrameworkJson = fs.existsSync(resolve(watchPath, 'framework.json'))
|
|
227
|
+
|
|
228
|
+
if (!hasAddOns && !hasAssets && !hasFrameworkJson) {
|
|
229
|
+
return {
|
|
230
|
+
valid: false,
|
|
231
|
+
error: `Watch path does not appear to be a valid framework directory: ${watchPath}`,
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return { valid: true }
|
|
236
|
+
}
|