zephyr-cli 0.0.3 → 0.1.3-next.7

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.
Files changed (55) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc.json +22 -0
  3. package/LICENSE +40 -21
  4. package/README.md +90 -17
  5. package/dist/cli.d.ts +25 -0
  6. package/dist/cli.js +141 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/deploy.d.ts +12 -0
  9. package/dist/commands/deploy.js +60 -0
  10. package/dist/commands/deploy.js.map +1 -0
  11. package/dist/commands/run.d.ts +9 -0
  12. package/dist/commands/run.js +161 -0
  13. package/dist/commands/run.js.map +1 -0
  14. package/dist/index.js +48 -60
  15. package/dist/index.js.map +1 -0
  16. package/dist/lib/build-stats.d.ts +7 -0
  17. package/dist/lib/build-stats.js +12 -0
  18. package/dist/lib/build-stats.js.map +1 -0
  19. package/dist/lib/command-detector.d.ts +38 -0
  20. package/dist/lib/command-detector.js +453 -0
  21. package/dist/lib/command-detector.js.map +1 -0
  22. package/dist/lib/config-readers.d.ts +37 -0
  23. package/dist/lib/config-readers.js +239 -0
  24. package/dist/lib/config-readers.js.map +1 -0
  25. package/dist/lib/extract-assets.d.ts +6 -0
  26. package/dist/lib/extract-assets.js +95 -0
  27. package/dist/lib/extract-assets.js.map +1 -0
  28. package/dist/lib/shell-parser.d.ts +40 -0
  29. package/dist/lib/shell-parser.js +190 -0
  30. package/dist/lib/shell-parser.js.map +1 -0
  31. package/dist/lib/spawn-helper.d.ts +14 -0
  32. package/dist/lib/spawn-helper.js +36 -0
  33. package/dist/lib/spawn-helper.js.map +1 -0
  34. package/dist/lib/upload.d.ts +14 -0
  35. package/dist/lib/upload.js +32 -0
  36. package/dist/lib/upload.js.map +1 -0
  37. package/dist/package.json +48 -0
  38. package/dist/tsconfig.tsbuildinfo +1 -0
  39. package/jest.config.ts +10 -0
  40. package/package.json +38 -21
  41. package/project.json +34 -0
  42. package/src/cli.ts +150 -0
  43. package/src/commands/deploy.ts +74 -0
  44. package/src/commands/run.ts +196 -0
  45. package/src/index.ts +58 -0
  46. package/src/lib/build-stats.ts +13 -0
  47. package/src/lib/command-detector.ts +600 -0
  48. package/src/lib/config-readers.ts +269 -0
  49. package/src/lib/extract-assets.ts +111 -0
  50. package/src/lib/shell-parser.ts +229 -0
  51. package/src/lib/spawn-helper.ts +49 -0
  52. package/src/lib/upload.ts +39 -0
  53. package/tsconfig.json +22 -0
  54. package/tsconfig.lib.json +10 -0
  55. package/tsconfig.spec.json +14 -0
package/src/index.ts ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cwd } from 'node:process';
4
+ import { ZeErrors, ZephyrError } from 'zephyr-agent';
5
+ import { parseArgs } from './cli';
6
+ import { deployCommand } from './commands/deploy';
7
+ import { runCommand } from './commands/run';
8
+
9
+ async function main(): Promise<void> {
10
+ try {
11
+ // Parse command line arguments
12
+ const args = process.argv.slice(2);
13
+ const options = parseArgs(args);
14
+
15
+ // Get current working directory
16
+ const workingDir = cwd();
17
+
18
+ // Dispatch to the appropriate command
19
+ if (options.command === 'deploy') {
20
+ if (!options.directory) {
21
+ throw new ZephyrError(ZeErrors.ERR_UNKNOWN, {
22
+ message: 'Directory is required for deploy command',
23
+ });
24
+ }
25
+
26
+ await deployCommand({
27
+ directory: options.directory,
28
+ target: options.target,
29
+ verbose: options.verbose,
30
+ ssr: options.ssr,
31
+ cwd: workingDir,
32
+ });
33
+ } else if (options.command === 'run') {
34
+ if (!options.commandLine) {
35
+ throw new ZephyrError(ZeErrors.ERR_UNKNOWN, {
36
+ message: 'Command line is required for run command',
37
+ });
38
+ }
39
+
40
+ await runCommand({
41
+ commandLine: options.commandLine,
42
+ target: options.target,
43
+ verbose: options.verbose,
44
+ ssr: options.ssr,
45
+ cwd: workingDir,
46
+ });
47
+ }
48
+ } catch (error) {
49
+ console.error('[ze-cli] Error:', ZephyrError.format(error));
50
+ process.exit(1);
51
+ }
52
+ }
53
+
54
+ // Run the CLI
55
+ main().catch((error) => {
56
+ console.error('[ze-cli] Fatal error:', ZephyrError.format(error));
57
+ process.exit(1);
58
+ });
@@ -0,0 +1,13 @@
1
+ import type { ZephyrBuildStats } from 'zephyr-edge-contract';
2
+ import type { ZephyrEngine } from 'zephyr-agent';
3
+ import { zeBuildDashData } from 'zephyr-agent';
4
+
5
+ /**
6
+ * Generate build stats from ZephyrEngine. This is a simple wrapper around zeBuildDashData
7
+ * for consistency.
8
+ */
9
+ export async function getBuildStats(
10
+ zephyr_engine: ZephyrEngine
11
+ ): Promise<ZephyrBuildStats> {
12
+ return await zeBuildDashData(zephyr_engine);
13
+ }
@@ -0,0 +1,600 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join, resolve, relative, sep } from 'node:path';
3
+ import {
4
+ configFileExists,
5
+ isJavaScriptConfig,
6
+ readPackageJson,
7
+ readTsConfig,
8
+ loadWebpackConfig,
9
+ loadViteConfig,
10
+ loadRollupConfig,
11
+ loadSwcConfig,
12
+ } from './config-readers';
13
+ import type { ParsedCommand } from './shell-parser';
14
+ import { parseShellCommand, splitCommands } from './shell-parser';
15
+
16
+ export interface DetectedCommand {
17
+ /** The build tool detected (e.g., 'tsc', 'webpack', 'npm') */
18
+ tool: string;
19
+ /** The configuration file path, if detected */
20
+ configFile: string | null;
21
+ /** Whether the config file is JavaScript (dynamic config) */
22
+ isDynamicConfig: boolean;
23
+ /** The inferred output directory, if detectable */
24
+ outputDir: string | null;
25
+ /** Warnings to display to the user */
26
+ warnings: string[];
27
+ /**
28
+ * Sub-commands detected when a command expands to multiple commands (e.g., npm script
29
+ * with &&)
30
+ */
31
+ subCommands?: DetectedCommand[];
32
+ }
33
+
34
+ /**
35
+ * Find the value of a command-line argument, supporting both formats:
36
+ *
37
+ * - `--flag value` (space-separated)
38
+ * - `--flag=value` (equals-separated)
39
+ * - `-f value` (short flag with space)
40
+ * - `-f=value` (short flag with equals)
41
+ */
42
+ function findArgValue(args: string[], ...flags: string[]): string | null {
43
+ for (const flag of flags) {
44
+ // Look for space-separated format: --flag value
45
+ const index = args.indexOf(flag);
46
+ if (index !== -1 && index + 1 < args.length) {
47
+ return args[index + 1];
48
+ }
49
+
50
+ // Look for equals format: --flag=value
51
+ const prefix = `${flag}=`;
52
+ for (const arg of args) {
53
+ if (arg.startsWith(prefix)) {
54
+ return arg.substring(prefix.length);
55
+ }
56
+ }
57
+ }
58
+
59
+ return null;
60
+ }
61
+
62
+ /**
63
+ * Find the common ancestor directory of multiple paths, bounded by the project root.
64
+ *
65
+ * @param paths - Array of absolute paths
66
+ * @param projectRoot - The project root directory (boundary)
67
+ * @returns The common ancestor path, or null if paths are empty or no common ancestor
68
+ * within project root
69
+ */
70
+ export function findCommonAncestor(paths: string[], projectRoot: string): string | null {
71
+ if (paths.length === 0) {
72
+ return null;
73
+ }
74
+
75
+ if (paths.length === 1) {
76
+ return paths[0];
77
+ }
78
+
79
+ // Normalize all paths to absolute
80
+ const normalizedPaths = paths.map((p) => resolve(p));
81
+ const normalizedRoot = resolve(projectRoot);
82
+
83
+ // Split each path into segments
84
+ const pathSegments = normalizedPaths.map((p) => p.split(sep));
85
+ const rootSegments = normalizedRoot.split(sep);
86
+
87
+ // Find the common prefix across all paths
88
+ const commonSegments: string[] = [];
89
+ const minLength = Math.min(...pathSegments.map((p) => p.length));
90
+
91
+ for (let i = 0; i < minLength; i++) {
92
+ const segment = pathSegments[0][i];
93
+
94
+ // Check if all paths have the same segment at this position
95
+ if (pathSegments.every((p) => p[i] === segment)) {
96
+ commonSegments.push(segment);
97
+ } else {
98
+ break;
99
+ }
100
+ }
101
+
102
+ // Ensure the common ancestor is within or equal to the project root
103
+ const commonPath = commonSegments.join(sep) || sep;
104
+
105
+ // Check if common path is within project root
106
+ const relativeToRoot = relative(normalizedRoot, commonPath);
107
+
108
+ // If relative path starts with '..' or is absolute, it's outside project root
109
+ if (relativeToRoot.startsWith('..') || resolve(commonPath) !== commonPath) {
110
+ return normalizedRoot;
111
+ }
112
+
113
+ // If common path is shallower than project root, use project root
114
+ if (commonSegments.length < rootSegments.length) {
115
+ return normalizedRoot;
116
+ }
117
+
118
+ return commonPath;
119
+ }
120
+
121
+ /**
122
+ * Detect multiple commands from a command line that may contain shell operators (;, &&).
123
+ * Returns output directories from all detected commands.
124
+ */
125
+ export async function detectMultipleCommands(
126
+ commandLine: string,
127
+ cwd: string
128
+ ): Promise<{
129
+ commands: DetectedCommand[];
130
+ outputDirs: string[];
131
+ commonOutputDir: string | null;
132
+ }> {
133
+ const individualCommands = splitCommands(commandLine);
134
+ const detectedCommands: DetectedCommand[] = [];
135
+ const outputDirs: string[] = [];
136
+
137
+ for (const cmd of individualCommands) {
138
+ try {
139
+ const parsed = parseShellCommand(cmd);
140
+ const detected = await detectCommand(parsed, cwd);
141
+ detectedCommands.push(detected);
142
+
143
+ if (detected.outputDir) {
144
+ const absoluteOutputDir = resolve(cwd, detected.outputDir);
145
+ outputDirs.push(absoluteOutputDir);
146
+ }
147
+ } catch {
148
+ // Skip commands that fail to parse
149
+ console.error(`[ze-cli] Warning: Failed to parse command: ${cmd}`);
150
+ }
151
+ }
152
+
153
+ // Find common ancestor of all output directories
154
+ const commonOutputDir =
155
+ outputDirs.length > 0 ? findCommonAncestor(outputDirs, cwd) : null;
156
+
157
+ return {
158
+ commands: detectedCommands,
159
+ outputDirs,
160
+ commonOutputDir,
161
+ };
162
+ }
163
+
164
+ /** Detect the build tool and its configuration from a parsed command */
165
+ export async function detectCommand(
166
+ parsed: ParsedCommand,
167
+ cwd: string,
168
+ depth = 0
169
+ ): Promise<DetectedCommand> {
170
+ const { command, args } = parsed;
171
+ const warnings: string[] = [];
172
+
173
+ // Prevent infinite recursion
174
+ if (depth > 3) {
175
+ warnings.push('Max recursion depth reached while parsing scripts');
176
+ return {
177
+ tool: command,
178
+ configFile: null,
179
+ isDynamicConfig: false,
180
+ outputDir: null,
181
+ warnings,
182
+ };
183
+ }
184
+
185
+ // Detect npm/yarn/pnpm commands
186
+ if (['npm', 'yarn', 'pnpm'].includes(command)) {
187
+ return await detectPackageManagerCommand(command, args, cwd, warnings, depth);
188
+ }
189
+
190
+ // Detect tsc (TypeScript compiler)
191
+ if (command === 'tsc') {
192
+ return detectTscCommand(args, cwd, warnings);
193
+ }
194
+
195
+ // Detect webpack
196
+ if (command === 'webpack' || command === 'webpack-cli') {
197
+ return await detectWebpackCommand(cwd, warnings);
198
+ }
199
+
200
+ // Detect rollup
201
+ if (command === 'rollup') {
202
+ return await detectRollupCommand(cwd, warnings);
203
+ }
204
+
205
+ // Detect esbuild
206
+ if (command === 'esbuild') {
207
+ return detectEsbuildCommand(args, warnings);
208
+ }
209
+
210
+ // Detect vite
211
+ if (command === 'vite') {
212
+ return await detectViteCommand(cwd, warnings);
213
+ }
214
+
215
+ // Detect swc
216
+ if (command === 'swc') {
217
+ return await detectSwcCommand(cwd, warnings);
218
+ }
219
+
220
+ // Unknown command
221
+ return {
222
+ tool: command,
223
+ configFile: null,
224
+ isDynamicConfig: false,
225
+ outputDir: null,
226
+ warnings: [
227
+ `Unknown build tool: ${command}`,
228
+ 'Output directory detection may not work correctly.',
229
+ ],
230
+ };
231
+ }
232
+
233
+ async function detectPackageManagerCommand(
234
+ command: string,
235
+ args: string[],
236
+ cwd: string,
237
+ warnings: string[],
238
+ depth: number
239
+ ): Promise<DetectedCommand> {
240
+ const packageJson = readPackageJson(cwd);
241
+
242
+ if (!packageJson) {
243
+ warnings.push('package.json not found');
244
+ return {
245
+ tool: command,
246
+ configFile: null,
247
+ isDynamicConfig: false,
248
+ outputDir: null,
249
+ warnings,
250
+ };
251
+ }
252
+
253
+ // Find the script name
254
+ let scriptName: string | null = null;
255
+ if (command === 'npm' && args[0] === 'run' && args[1]) {
256
+ scriptName = args[1];
257
+ } else if ((command === 'yarn' || command === 'pnpm') && args[0]) {
258
+ scriptName = args[0];
259
+ }
260
+
261
+ if (!scriptName || !packageJson.scripts?.[scriptName]) {
262
+ warnings.push(`Script "${scriptName || 'unknown'}" not found in package.json`);
263
+ return {
264
+ tool: command,
265
+ configFile: join(cwd, 'package.json'),
266
+ isDynamicConfig: false,
267
+ outputDir: null,
268
+ warnings,
269
+ };
270
+ }
271
+
272
+ const script = packageJson.scripts[scriptName];
273
+
274
+ // Parse the script command - check if there are multiple commands with shell operators
275
+ try {
276
+ const individualCommands = splitCommands(script);
277
+
278
+ // If there are multiple commands in the script, detect each one
279
+ if (individualCommands.length > 1) {
280
+ const detectedCommands: DetectedCommand[] = [];
281
+ const allWarnings: string[] = [...warnings];
282
+ const outputDirs: string[] = [];
283
+
284
+ for (const cmd of individualCommands) {
285
+ try {
286
+ const parsedScript = parseShellCommand(cmd);
287
+ const detected = await detectCommand(parsedScript, cwd, depth + 1);
288
+ detectedCommands.push(detected);
289
+
290
+ if (detected.outputDir) {
291
+ outputDirs.push(resolve(cwd, detected.outputDir));
292
+ }
293
+
294
+ allWarnings.push(...detected.warnings);
295
+ } catch {
296
+ // Skip commands that fail to parse
297
+ allWarnings.push(`Failed to parse sub-command: ${cmd}`);
298
+ }
299
+ }
300
+
301
+ // Find the last command that produces output
302
+ let primaryDetected =
303
+ detectedCommands.find((d) => d.outputDir) ||
304
+ detectedCommands[detectedCommands.length - 1];
305
+
306
+ // If multiple output directories, use common ancestor
307
+ if (outputDirs.length > 1) {
308
+ const commonOutputDir = findCommonAncestor(outputDirs, cwd);
309
+ if (commonOutputDir) {
310
+ primaryDetected = {
311
+ ...primaryDetected,
312
+ outputDir: relative(cwd, commonOutputDir) || '.',
313
+ };
314
+ }
315
+ } else if (outputDirs.length === 1) {
316
+ primaryDetected = {
317
+ ...primaryDetected,
318
+ outputDir: relative(cwd, outputDirs[0]) || '.',
319
+ };
320
+ }
321
+
322
+ // Preserve the package.json reference
323
+ if (!primaryDetected.configFile) {
324
+ primaryDetected.configFile = join(cwd, 'package.json');
325
+ }
326
+
327
+ primaryDetected.warnings = allWarnings;
328
+ // Store sub-commands for logging purposes
329
+ primaryDetected.subCommands = detectedCommands;
330
+ return primaryDetected;
331
+ }
332
+
333
+ // Single command - parse normally
334
+ const parsedScript = parseShellCommand(script);
335
+
336
+ // Recursively detect the actual build tool
337
+ const detected = await detectCommand(parsedScript, cwd, depth + 1);
338
+
339
+ // Preserve the package.json reference
340
+ if (!detected.configFile) {
341
+ detected.configFile = join(cwd, 'package.json');
342
+ }
343
+
344
+ return detected;
345
+ } catch (error) {
346
+ warnings.push(`Failed to parse package.json script "${scriptName}": ${script}`);
347
+ warnings.push(`Parse error: ${(error as Error).message}`);
348
+
349
+ return {
350
+ tool: command,
351
+ configFile: join(cwd, 'package.json'),
352
+ isDynamicConfig: false,
353
+ outputDir: null,
354
+ warnings,
355
+ };
356
+ }
357
+ }
358
+
359
+ function detectTscCommand(
360
+ args: string[],
361
+ cwd: string,
362
+ warnings: string[]
363
+ ): DetectedCommand {
364
+ // Look for -p or --project flag
365
+ const configPath = findArgValue(args, '-p', '--project') || 'tsconfig.json';
366
+
367
+ const fullConfigPath = join(cwd, configPath);
368
+ const configExists = existsSync(fullConfigPath);
369
+
370
+ if (!configExists) {
371
+ warnings.push(`TypeScript config not found: ${configPath}`);
372
+ return {
373
+ tool: 'tsc',
374
+ configFile: null,
375
+ isDynamicConfig: false,
376
+ outputDir: null,
377
+ warnings,
378
+ };
379
+ }
380
+
381
+ // Try to read outDir from tsconfig
382
+ const tsConfig = readTsConfig(cwd, configPath);
383
+ const outDir = tsConfig?.compilerOptions?.outDir || null;
384
+
385
+ return {
386
+ tool: 'tsc',
387
+ configFile: fullConfigPath,
388
+ isDynamicConfig: false,
389
+ outputDir: outDir,
390
+ warnings,
391
+ };
392
+ }
393
+
394
+ async function detectWebpackCommand(
395
+ cwd: string,
396
+ warnings: string[]
397
+ ): Promise<DetectedCommand> {
398
+ const configFile = configFileExists(cwd, 'webpack.config');
399
+ const isDynamicConfig = isJavaScriptConfig(cwd, 'webpack.config');
400
+
401
+ // Try to load the config using cosmiconfig if it's a JS config
402
+ if (isDynamicConfig) {
403
+ try {
404
+ const loadedConfig = await loadWebpackConfig(cwd);
405
+
406
+ if (loadedConfig && loadedConfig.outputDir) {
407
+ // Successfully loaded and extracted output directory
408
+ return {
409
+ tool: 'webpack',
410
+ configFile: loadedConfig.filepath,
411
+ isDynamicConfig: true,
412
+ outputDir: loadedConfig.outputDir,
413
+ warnings,
414
+ };
415
+ }
416
+
417
+ // Config loaded but couldn't extract output directory
418
+ warnings.push(
419
+ 'Webpack configuration loaded but output directory could not be determined.',
420
+ 'Consider using @zephyrcloud/webpack-plugin or ze-cli deploy after building.'
421
+ );
422
+ } catch {
423
+ warnings.push(
424
+ 'Failed to load Webpack configuration.',
425
+ 'Consider using @zephyrcloud/webpack-plugin or ze-cli deploy after building.'
426
+ );
427
+ }
428
+ }
429
+
430
+ return {
431
+ tool: 'webpack',
432
+ configFile,
433
+ isDynamicConfig,
434
+ outputDir: isDynamicConfig ? null : 'dist',
435
+ warnings,
436
+ };
437
+ }
438
+
439
+ async function detectRollupCommand(
440
+ cwd: string,
441
+ warnings: string[]
442
+ ): Promise<DetectedCommand> {
443
+ const configFile = configFileExists(cwd, 'rollup.config');
444
+ const isDynamicConfig = isJavaScriptConfig(cwd, 'rollup.config');
445
+
446
+ // Try to load the config using cosmiconfig if it's a JS config
447
+ if (isDynamicConfig) {
448
+ try {
449
+ const loadedConfig = await loadRollupConfig(cwd);
450
+
451
+ if (loadedConfig && loadedConfig.outputDir) {
452
+ // Successfully loaded and extracted output directory
453
+ return {
454
+ tool: 'rollup',
455
+ configFile: loadedConfig.filepath,
456
+ isDynamicConfig: true,
457
+ outputDir: loadedConfig.outputDir,
458
+ warnings,
459
+ };
460
+ }
461
+
462
+ // Config loaded but couldn't extract output directory
463
+ warnings.push(
464
+ 'Rollup configuration loaded but output directory could not be determined.',
465
+ 'Consider using @zephyrcloud/rollup-plugin or ze-cli deploy after building.'
466
+ );
467
+ } catch {
468
+ warnings.push(
469
+ 'Failed to load Rollup configuration.',
470
+ 'Consider using @zephyrcloud/rollup-plugin or ze-cli deploy after building.'
471
+ );
472
+ }
473
+ }
474
+
475
+ return {
476
+ tool: 'rollup',
477
+ configFile,
478
+ isDynamicConfig,
479
+ outputDir: isDynamicConfig ? null : 'dist',
480
+ warnings,
481
+ };
482
+ }
483
+
484
+ function detectEsbuildCommand(args: string[], warnings: string[]): DetectedCommand {
485
+ // Look for --outdir or --outfile in args
486
+ let outputDir: string | null = null;
487
+
488
+ const outdir = findArgValue(args, '--outdir');
489
+ const outfile = findArgValue(args, '--outfile');
490
+
491
+ if (outdir) {
492
+ outputDir = outdir;
493
+ } else if (outfile) {
494
+ // Extract directory from outfile
495
+ const lastSlash = Math.max(outfile.lastIndexOf('/'), outfile.lastIndexOf('\\'));
496
+ outputDir = lastSlash !== -1 ? outfile.substring(0, lastSlash) : '.';
497
+ }
498
+
499
+ if (!outputDir) {
500
+ warnings.push('Could not detect esbuild output directory from arguments');
501
+ }
502
+
503
+ return {
504
+ tool: 'esbuild',
505
+ configFile: null,
506
+ isDynamicConfig: false,
507
+ outputDir,
508
+ warnings,
509
+ };
510
+ }
511
+
512
+ async function detectViteCommand(
513
+ cwd: string,
514
+ warnings: string[]
515
+ ): Promise<DetectedCommand> {
516
+ const configFile = configFileExists(cwd, 'vite.config');
517
+ const isDynamicConfig = isJavaScriptConfig(cwd, 'vite.config');
518
+
519
+ // Try to load the config using cosmiconfig if it's a JS config
520
+ if (isDynamicConfig) {
521
+ try {
522
+ const loadedConfig = await loadViteConfig(cwd);
523
+
524
+ if (loadedConfig && loadedConfig.outputDir) {
525
+ // Successfully loaded and extracted output directory
526
+ return {
527
+ tool: 'vite',
528
+ configFile: loadedConfig.filepath,
529
+ isDynamicConfig: true,
530
+ outputDir: loadedConfig.outputDir,
531
+ warnings,
532
+ };
533
+ }
534
+
535
+ // Config loaded but couldn't extract output directory (fallback to default)
536
+ if (loadedConfig) {
537
+ return {
538
+ tool: 'vite',
539
+ configFile: loadedConfig.filepath,
540
+ isDynamicConfig: true,
541
+ outputDir: 'dist', // Vite default
542
+ warnings,
543
+ };
544
+ }
545
+
546
+ warnings.push(
547
+ 'Failed to load Vite configuration.',
548
+ 'Consider using @zephyrcloud/vite-plugin or ze-cli deploy after building.'
549
+ );
550
+ } catch {
551
+ warnings.push(
552
+ 'Failed to load Vite configuration.',
553
+ 'Consider using @zephyrcloud/vite-plugin or ze-cli deploy after building.'
554
+ );
555
+ }
556
+ }
557
+
558
+ return {
559
+ tool: 'vite',
560
+ configFile,
561
+ isDynamicConfig,
562
+ outputDir: isDynamicConfig ? null : 'dist',
563
+ warnings,
564
+ };
565
+ }
566
+
567
+ async function detectSwcCommand(
568
+ cwd: string,
569
+ warnings: string[]
570
+ ): Promise<DetectedCommand> {
571
+ const configFile = configFileExists(cwd, '.swcrc');
572
+ const isDynamicConfig = isJavaScriptConfig(cwd, '.swcrc');
573
+
574
+ // Try to load the config using cosmiconfig if it's a JS config
575
+ if (isDynamicConfig) {
576
+ try {
577
+ const loadedConfig = await loadSwcConfig(cwd);
578
+
579
+ if (loadedConfig) {
580
+ return {
581
+ tool: 'swc',
582
+ configFile: loadedConfig.filepath,
583
+ isDynamicConfig: true,
584
+ outputDir: 'dist', // SWC doesn't have standard output config
585
+ warnings,
586
+ };
587
+ }
588
+ } catch {
589
+ warnings.push('Failed to load SWC configuration.');
590
+ }
591
+ }
592
+
593
+ return {
594
+ tool: 'swc',
595
+ configFile,
596
+ isDynamicConfig,
597
+ outputDir: 'dist', // Common default
598
+ warnings,
599
+ };
600
+ }