Automatically open a browser window when Dev Server starts (#117)

* Automatically open dev server in system browser

* Allow opting out of automatically opening in browser

* Update readme with new behavior
This commit is contained in:
yonihemi 2020-10-03 00:13:54 +08:00 committed by GitHub
parent a34ffd4249
commit 0731ccb36f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 48 additions and 5 deletions

View File

@ -49,8 +49,7 @@ new [Tokamak](https://tokamak.dev/) project, while `carton init --template basic
currently). currently).
The `carton dev` command builds your project with the SwiftWasm toolchain and starts an HTTP server 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 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
[http://127.0.0.1:8080/](http://127.0.0.1:8080/) in your browser to see the app running. You can
edit the app source code in your favorite editor and save it, `carton` will immediately rebuild the 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 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 keep the build process output available, otherwise stale output is cleaned up from your terminal

View File

@ -47,6 +47,9 @@ struct Dev: ParsableCommand {
@Option(name: .shortAndLong, help: "Set the HTTP port the app will run on.") @Option(name: .shortAndLong, help: "Set the HTTP port the app will run on.")
var port = 8080 var port = 8080
@Flag(name: .long, help: "Skip automatically opening app in system browser.")
var skipAutoOpen = false
static let configuration = CommandConfiguration( static let configuration = CommandConfiguration(
abstract: "Watch the current directory, host the app, rebuild on change." abstract: "Watch the current directory, host the app, rebuild on change."
) )
@ -84,6 +87,7 @@ struct Dev: ParsableCommand {
// swiftlint:disable:next force_try // swiftlint:disable:next force_try
package: try! toolchain.package.get(), package: try! toolchain.package.get(),
verbose: verbose, verbose: verbose,
skipAutoOpen: skipAutoOpen,
terminal, terminal,
port: port port: port
).run() ).run()

View File

@ -39,6 +39,8 @@ final class Server {
private let watcher: Watcher private let watcher: Watcher
private var builder: ProcessRunner? private var builder: ProcessRunner?
private let app: Application private let app: Application
private let localURL: String
private let skipAutoOpen: Bool
init( init(
builderArguments: [String], builderArguments: [String],
@ -47,12 +49,16 @@ final class Server {
customIndexContent: String?, customIndexContent: String?,
package: SwiftToolchain.Package, package: SwiftToolchain.Package,
verbose: Bool, verbose: Bool,
skipAutoOpen: Bool,
_ terminal: InteractiveWriter, _ terminal: InteractiveWriter,
port: Int port: Int
) throws { ) throws {
watcher = try Watcher(pathsToWatch) watcher = try Watcher(pathsToWatch)
var env = Environment(name: verbose ? "development" : "production", arguments: ["vapor"]) var env = Environment(name: verbose ? "development" : "production", arguments: ["vapor"])
localURL = "http://127.0.0.1:\(port)/"
self.skipAutoOpen = skipAutoOpen
try LoggingSystem.bootstrap(from: &env) try LoggingSystem.bootstrap(from: &env)
app = Application(env) app = Application(env)
app.configure( app.configure(
@ -67,6 +73,8 @@ final class Server {
self?.connections.remove($0) self?.connections.remove($0)
} }
) )
// Listen to Vapor App lifecycle events
app.lifecycle.use(self)
watcher.publisher watcher.publisher
.flatMap(maxPublishers: .max(1)) { changes -> AnyPublisher<String, Never> in .flatMap(maxPublishers: .max(1)) { changes -> AnyPublisher<String, Never> in
@ -80,11 +88,11 @@ final class Server {
return ProcessRunner(builderArguments, terminal) return ProcessRunner(builderArguments, terminal)
.publisher .publisher
.handleEvents(receiveCompletion: { [weak self] in .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.write("\nBuild completed successfully\n", inColor: .green, bold: false)
terminal.logLookup("The app is currently hosted at ", "http://127.0.0.1:\(port)/") terminal.logLookup("The app is currently hosted at ", self.localURL)
self?.connections.forEach { $0.send("reload") } self.connections.forEach { $0.send("reload") }
}) })
.catch { _ in Empty().eraseToAnyPublisher() } .catch { _ in Empty().eraseToAnyPublisher() }
.eraseToAnyPublisher() .eraseToAnyPublisher()
@ -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
}
}
}