Skip to content

Commit

Permalink
Automatically open a browser window when Dev Server starts (#117)
Browse files Browse the repository at this point in the history
* Automatically open dev server in system browser

* Allow opting out of automatically opening in browser

* Update readme with new behavior
  • Loading branch information
yonihemi authored Oct 2, 2020
1 parent a34ffd4 commit 0731ccb
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 5 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ new [Tokamak](https://tokamak.dev/) project, while `carton init --template basic
currently).

The `carton dev` command builds your project with the SwiftWasm toolchain and starts an HTTP server
that hosts your WebAssembly executable and a corresponding JavaScript entrypoint that loads it. Open
[http://127.0.0.1:8080/](http://127.0.0.1:8080/) in your browser to see the app running. You can
that hosts your WebAssembly executable and a corresponding JavaScript entrypoint that loads it. The app, reachable at [http://127.0.0.1:8080/](http://127.0.0.1:8080/), will automatically open in your default web browser. You can
edit the app source code in your favorite editor and save it, `carton` will immediately rebuild the
app and reload all browser tabs that have the app open. You can also pass a `--verbose` flag to
keep the build process output available, otherwise stale output is cleaned up from your terminal
Expand Down
4 changes: 4 additions & 0 deletions Sources/carton/Commands/Dev.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ struct Dev: ParsableCommand {
@Option(name: .shortAndLong, help: "Set the HTTP port the app will run on.")
var port = 8080

@Flag(name: .long, help: "Skip automatically opening app in system browser.")
var skipAutoOpen = false

static let configuration = CommandConfiguration(
abstract: "Watch the current directory, host the app, rebuild on change."
)
Expand Down Expand Up @@ -84,6 +87,7 @@ struct Dev: ParsableCommand {
// swiftlint:disable:next force_try
package: try! toolchain.package.get(),
verbose: verbose,
skipAutoOpen: skipAutoOpen,
terminal,
port: port
).run()
Expand Down
46 changes: 43 additions & 3 deletions Sources/carton/Server/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ final class Server {
private let watcher: Watcher
private var builder: ProcessRunner?
private let app: Application
private let localURL: String
private let skipAutoOpen: Bool

init(
builderArguments: [String],
Expand All @@ -47,12 +49,16 @@ final class Server {
customIndexContent: String?,
package: SwiftToolchain.Package,
verbose: Bool,
skipAutoOpen: Bool,
_ terminal: InteractiveWriter,
port: Int
) throws {
watcher = try Watcher(pathsToWatch)

var env = Environment(name: verbose ? "development" : "production", arguments: ["vapor"])
localURL = "http://127.0.0.1:\(port)/"
self.skipAutoOpen = skipAutoOpen

try LoggingSystem.bootstrap(from: &env)
app = Application(env)
app.configure(
Expand All @@ -67,6 +73,8 @@ final class Server {
self?.connections.remove($0)
}
)
// Listen to Vapor App lifecycle events
app.lifecycle.use(self)

watcher.publisher
.flatMap(maxPublishers: .max(1)) { changes -> AnyPublisher<String, Never> in
Expand All @@ -80,11 +88,11 @@ final class Server {
return ProcessRunner(builderArguments, terminal)
.publisher
.handleEvents(receiveCompletion: { [weak self] in
guard case .finished = $0 else { return }
guard case .finished = $0, let self = self else { return }

terminal.write("\nBuild completed successfully\n", inColor: .green, bold: false)
terminal.logLookup("The app is currently hosted at ", "http://127.0.0.1:\(port)/")
self?.connections.forEach { $0.send("reload") }
terminal.logLookup("The app is currently hosted at ", self.localURL)
self.connections.forEach { $0.send("reload") }
})
.catch { _ in Empty().eraseToAnyPublisher() }
.eraseToAnyPublisher()
Expand All @@ -102,3 +110,35 @@ final class Server {
}
}
}

extension Server: LifecycleHandler {
public func didBoot(_ application: Application) throws {
guard !skipAutoOpen else { return }
openInSystemBrowser(url: localURL)
}

/// Attempts to open the specified URL string in system browser on macOS and Linux.
/// - Returns: true if launching command returns successfully.
@discardableResult
private func openInSystemBrowser(url: String) -> Bool {
#if os(macOS)
let openCommand = "open"
#elseif os(Linux)
let openCommand = "xdg-open"
#else
return false
#endif
let process = Process(
arguments: [openCommand, url],
outputRedirection: .none,
verbose: false,
startNewProcessGroup: true
)
do {
try process.launch()
return true
} catch {
return false
}
}
}

0 comments on commit 0731ccb

Please sign in to comment.