Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically open a browser window when Dev Server starts #117

Merged
merged 4 commits into from
Oct 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
}