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:
parent
a34ffd4249
commit
0731ccb36f
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue