(cli *cli, projectDir, port string)
| 162 | } |
| 163 | |
| 164 | func runNormalMode(cli *cli, projectDir, port string) error { |
| 165 | if !isPortFree(port) { |
| 166 | return fmt.Errorf("port %s is already in use; please free it or choose another port", port) |
| 167 | } |
| 168 | |
| 169 | // 1. Set up the command. |
| 170 | cmd := exec.Command("npm", "run", "dev", "--", "--port", port) |
| 171 | cmd.Dir = projectDir |
| 172 | |
| 173 | // 2. Set up output pipes/redirection. |
| 174 | stdout, err := cmd.StdoutPipe() |
| 175 | if err != nil { |
| 176 | return fmt.Errorf("failed to capture stdout: %w", err) |
| 177 | } |
| 178 | |
| 179 | if cli.debug { |
| 180 | cmd.Stderr = os.Stderr |
| 181 | fmt.Println("\n🔄 Executing:", ansi.Cyan("npm run dev -- --port "+port)) |
| 182 | } |
| 183 | |
| 184 | // 3. Start the command asynchronously. |
| 185 | if err := cmd.Start(); err != nil { |
| 186 | return fmt.Errorf("failed to start 'npm run dev -- --port %s': %w", port, err) |
| 187 | } |
| 188 | |
| 189 | // 4. Print the success/info logs immediately after starting the server process. |
| 190 | server := fmt.Sprintf("http://localhost:%s", port) |
| 191 | |
| 192 | // 5. Wait for the command to exit and handle intentional stops (Ctrl+C). |
| 193 | readyChan := make(chan struct{}) |
| 194 | go func() { |
| 195 | scanner := bufio.NewScanner(stdout) |
| 196 | for scanner.Scan() { |
| 197 | line := scanner.Text() |
| 198 | fmt.Println(line) |
| 199 | if strings.Contains(line, "Local:") && strings.Contains(line, "http") { |
| 200 | close(readyChan) |
| 201 | return |
| 202 | } |
| 203 | } |
| 204 | }() |
| 205 | |
| 206 | select { |
| 207 | case <-readyChan: |
| 208 | fmt.Println("💡 " + ansi.Italic("Make changes to your code and view the live changes as we have HMR enabled!")) |
| 209 | _ = browser.OpenURL(server) |
| 210 | |
| 211 | case <-time.After(20 * time.Second): |
| 212 | fmt.Println("⏳ Dev server is taking longer than expected to start...") |
| 213 | } |
| 214 | |
| 215 | if err = cmd.Wait(); err != nil { |
| 216 | if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 130 { |
| 217 | fmt.Println(ansi.Bold("\n👋 Server stopped intentionally (Ctrl+C).")) |
| 218 | return nil |
| 219 | } |
| 220 | |
| 221 | return fmt.Errorf("dev server exited with an error: %w", err) |
no test coverage detected