UNPKG

@swell/cli

Version:

Swell's command line interface/utility

231 lines (230 loc) 10.9 kB
import { Flags } from '@oclif/core'; import { CUSTOM_FRAMEWORK_SLUG, getManualDevHint, getProjectCommands, } from '../../../lib/apps/index.js'; import { PushAppCommand } from '../../../push-app-command.js'; import AppDev from '../dev.js'; export default class AppFrontendDev extends PushAppCommand { static examples = [ 'swell app frontend dev', 'swell app frontend dev --no-push', 'swell app frontend dev --proxy-port 3000', 'swell app frontend dev --storefront-select', 'swell app frontend dev --storefront-id <id>', ]; static flags = { ...AppDev.flags, 'proxy-port': Flags.integer({ description: 'specify the port for the frontend proxy', }), 'storefront-id': Flags.string({ description: 'identify a storefront to preview with and push theme files to', }), 'storefront-select': Flags.boolean({ default: false, description: 'prompt to select a storefront to preview with', }), 'app-dev': Flags.boolean({ description: 'indicates frontend app is running in app dev mode', default: false, }), yes: Flags.boolean({ char: 'y', description: 'skip prompts (non-interactive mode)', }), }; static orientation = { env: 'test', }; static summary = `Run a frontend app in dev mode from your local machine.`; async run() { const { flags } = await this.parse(AppFrontendDev); const { port, 'proxy-port': proxyPort, 'app-dev': isAppDev } = flags; const noPush = flags['no-push']; if (!(await this.ensureAppExists(undefined, false))) { return; } if (!noPush && !isAppDev) { await this.pushAppConfigs(); } if (this.app.type === 'storefront') { await this.getStorefrontToPush(flags); this.logStorefrontConnected(); } this.saveCurrentStorefront(); if (!isAppDev) { this.log(`Starting app dev server...\n`); } // When running as child of `swell app dev`, don't create a new tunnel. // The parent command already created the tunnel and handles routing. const serverPort = isAppDev && proxyPort ? proxyPort : await this.startProxyServer(proxyPort || port); await this.execFrontendProject(serverPort, isAppDev).catch((error) => { // SIGTERM/SIGINT during dev server shutdown is expected switch (error?.signal) { case 'SIGTERM': case 'SIGINT': { return; } default: { throw error; } } }); } async execFrontendProject(serverPort, isAppDev) { const projectType = await this.getFrontendProjectType(!isAppDev); if (!projectType) { return; } // Manual mode for unrecognized frameworks if (projectType.slug === CUSTOM_FRAMEWORK_SLUG) { const hintCommand = await getManualDevHint(this.appPath, serverPort); this.log(`Detected a frontend project but could not identify the framework.`); this.log(`Start your dev server manually on port ${serverPort}:\n`); this.log(` cd frontend && ${hintCommand}\n`); this.log(`The Swell tunnel is ready and will route traffic once your server is running.\n`); this.logLocalPreviewUrl(); if (!isAppDev) { this.watchForChanges(); } // Keep the process alive so the tunnel remains open await new Promise(() => { }); return; } // Get commands transformed for the detected package manager const { devCommand } = await getProjectCommands(this.appPath, projectType); let serverReadyDetected = false; await this.execFrontend(devCommand.replace('${PORT}', String(serverPort)), (string) => { // Shared: Hide Vite noise output common across frameworks if (/press h \+ enter to show help/i.test(string)) { return false; } // Framework-specific output handling and ready detection switch (projectType.slug) { case 'nextjs': { // 1. NOISE FILTER: Hide unwanted output if (string.includes('Using vars defined in .dev.vars')) { return false; } // 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel) if (/- local:\s+http:\/\//i.test(string) || /- network:\s+http:\/\//i.test(string) || /ready on http:\/\/localhost/i.test(string)) { return false; } // 3. READY DETECTION: Detect server ready, show our message if (!serverReadyDetected && (/✓ ready in \d+/i.test(string) || /✓ starting/i.test(string))) { serverReadyDetected = true; this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`); setTimeout(() => this.logLocalPreviewUrl(), 100); if (!isAppDev) { this.watchForChanges(); } } break; } case 'astro': { // 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel) if (string.includes('┃ Local') || string.includes('┃ Network')) { return false; } // 3. READY DETECTION: Detect server ready, show our message if (!serverReadyDetected && /watching for file changes/i.test(string)) { serverReadyDetected = true; this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`); setTimeout(() => this.logLocalPreviewUrl(), 100); if (!isAppDev) { this.watchForChanges(); } } break; } case 'angular': { // 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel) if (/➜\s+local:\s+http:\/\//i.test(string)) { return false; } // 3. READY DETECTION: Detect server ready, show our message if (!serverReadyDetected && (/application bundle generation complete/i.test(string) || /watch mode enabled/i.test(string))) { serverReadyDetected = true; this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`); setTimeout(() => this.logLocalPreviewUrl(), 100); if (!isAppDev) { this.watchForChanges(); } } break; } case 'hono': { // 2. URL FILTER: Hide localhost/network/debug URLs (confusing - users should use Swell tunnel) // Vite-based Hono apps output "➜ Local: http://localhost:PORT/" if (/➜\s+local:\s+http:\/\//i.test(string) || /➜\s+network:/i.test(string) || /➜\s+debug:\s+http:\/\//i.test(string)) { if (!serverReadyDetected) { serverReadyDetected = true; this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`); setTimeout(() => this.logLocalPreviewUrl(), 100); if (!isAppDev) { this.watchForChanges(); } } return false; } // 3. READY DETECTION: Native Hono dev server outputs "ready on http://localhost:PORT" if (!serverReadyDetected && /ready on http:\/\/localhost:\d+/i.test(string)) { serverReadyDetected = true; this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`); setTimeout(() => this.logLocalPreviewUrl(), 100); if (!isAppDev) { this.watchForChanges(); } return false; } break; } case 'react': case 'react-storefront': { // Vite emits the Local URL as its ready signal — no separate // detector needed. if (/➜\s+local:\s+http:\/\//i.test(string) || /➜\s+network:/i.test(string)) { if (!serverReadyDetected) { serverReadyDetected = true; this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`); setTimeout(() => this.logLocalPreviewUrl(), 100); if (!isAppDev) { this.watchForChanges(); } } return false; } break; } case 'nuxt': { // 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel) if (/➜ local:\s+http:\/\//i.test(string) || /➜ network:/i.test(string)) { return false; } // 3. READY DETECTION: Detect server ready, show our message if (!serverReadyDetected && /nuxt \d+\.\d+\.\d+/i.test(string)) { serverReadyDetected = true; this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`); setTimeout(() => this.logLocalPreviewUrl(), 100); if (!isAppDev) { this.watchForChanges(); } } break; } // No default } }); } }