diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f544f4ff2..00f7fe7f15 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -288,7 +288,10 @@ Monitoring ~/Developer/swift-docc/Sources/SwiftDocC/SwiftDocC.docc for changes.. ``` And if you navigate to you'll see -the rendered documentation for `SwiftDocC`. +the rendered documentation for `SwiftDocC`. If your browser isn't running on +the same computer as Swift-DocC, you may need to provide the server's public +host name or IP address (or just `0.0.0.0`) with the `--host` option to allow +the browser to connect. ### Using Docker to Test Swift-DocC for Linux diff --git a/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift index 3d9fecd240..7d51624e95 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift @@ -53,6 +53,7 @@ public final class PreviewAction: Action, RecreatingContext { var logHandle = LogHandle.standardOutput + let host: String let port: Int var convertAction: ConvertAction @@ -75,6 +76,7 @@ public final class PreviewAction: Action, RecreatingContext { /// Creates a new preview action from the given parameters. /// /// - Parameters: + /// - host: The host name used by the preview server. /// - port: The port number used by the preview server. /// - convertAction: The action used to convert the documentation bundle before preview. /// On macOS, this action will be reused to convert documentation each time the source is modified. @@ -84,6 +86,7 @@ public final class PreviewAction: Action, RecreatingContext { /// is performed. /// - Throws: If an error is encountered while initializing the documentation context. public init( + host: String, port: Int, createConvertAction: @escaping () throws -> ConvertAction, workspace: DocumentationWorkspace = DocumentationWorkspace(), @@ -95,6 +98,7 @@ public final class PreviewAction: Action, RecreatingContext { } // Initialize the action context. + self.host = host self.port = port self.createConvertAction = createConvertAction self.convertAction = try createConvertAction() @@ -108,13 +112,14 @@ public final class PreviewAction: Action, RecreatingContext { @available(*, deprecated, message: "TLS support has been removed.") public convenience init( tlsCertificateKey: URL?, tlsCertificateChain: URL?, serverUsername: String?, - serverPassword: String?, port: Int, + serverPassword: String?, host: String, port: Int, createConvertAction: @escaping () throws -> ConvertAction, workspace: DocumentationWorkspace = DocumentationWorkspace(), context: DocumentationContext? = nil, printTemplatePath: Bool = true) throws { try self.init( + host: host, port: port, createConvertAction: createConvertAction, workspace: workspace, @@ -163,13 +168,13 @@ public final class PreviewAction: Action, RecreatingContext { // Preview the output and monitor the source bundle for changes. do { print(String(repeating: "=", count: 40), to: &logHandle) - if let previewURL = URL(string: "http://localhost:\(port)") { + if let previewURL = URL(string: "http://\(host):\(port)") { print("Starting Local Preview Server", to: &logHandle) printPreviewAddresses(base: previewURL) print(String(repeating: "=", count: 40), to: &logHandle) } - let to: PreviewServer.Bind = bindServerToSocketPath.map { .socket(path: $0) } ?? .localhost(port: port) + let to: PreviewServer.Bind = bindServerToSocketPath.map { .socket(path: $0) } ?? .localhost(host: host, port: port) servers[serverIdentifier] = try PreviewServer(contentURL: convertAction.targetDirectory, bindTo: to, logHandle: &logHandle) // When the user stops docc - stop the preview server first before exiting. diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift index 9e21c31be5..3be5f534d0 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift @@ -24,6 +24,7 @@ extension PreviewAction { { // Initialize the `PreviewAction` from the options provided by the `Preview` command try self.init( + host: previewOptions.host, port: previewOptions.port, createConvertAction: { try ConvertAction( diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift index 23efd9cfcb..0364f088d0 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift @@ -17,6 +17,16 @@ import Foundation public struct PreviewOptions: ParsableArguments { public init() { } + /// The host name to use for the preview web server. + /// + /// Defaults to `localhost`. + @Option( + name: .long, + help: ArgumentHelp( + "Host name to use for the preview web server.", + valueName: "host-name")) + public var host: String = "localhost" + /// The port number to use for the preview web server. /// /// Defaults to `8080`. diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift index 26d1a5941f..176e925b70 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Preview.swift @@ -23,9 +23,9 @@ extension Docc { public static var configuration = CommandConfiguration( abstract: "Convert documentation inputs and preview the documentation output.", usage: """ - docc preview [] [--port ] [--additional-symbol-graph-dir ] - docc preview [] [--port ] [--additional-symbol-graph-dir ] [--output-dir ] - docc preview [] [--port ] [--additional-symbol-graph-dir ] [--output-dir ] [] [] [] [] [] [] [] + docc preview [] [--host ] [--port ] [--additional-symbol-graph-dir ] + docc preview [] [--host ] [--port ] [--additional-symbol-graph-dir ] [--output-dir ] + docc preview [] [--host ] [--port ] [--additional-symbol-graph-dir ] [--output-dir ] [] [] [] [] [] [] [] """, discussion: """ The 'preview' command extends the 'convert' command by running a preview server and monitoring the documentation input for modifications to rebuild the documentation. diff --git a/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift b/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift index 3c98706ba9..71590625cf 100644 --- a/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift +++ b/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift @@ -45,16 +45,16 @@ final class PreviewServer { /// The server did not find the content directory. case pathNotFound(String) /// Cannot bind the server to the given address - case cannotStartServer(port: Int) + case cannotStartServer(host: String, port: Int) /// The given port is not available - case portNotAvailable(port: Int) + case portNotAvailable(host: String, port: Int) var errorDescription: String { switch self { case .failedToStart: return "Failed to start preview server" case .pathNotFound(let path): return "The preview content path '\(path)' is not found" - case .cannotStartServer(let port): return "Can't start the preview server on port \(port)" - case .portNotAvailable(let port): return "Port \(port) is not available at the moment, " + case .cannotStartServer(let host, let port): return "Can't start the preview server on host \(host) and port \(port)" + case .portNotAvailable(let host, let port): return "Port \(port) is not available on host \(host) at the moment, " + "try a different port number by adding the option '--port XXXX' " + "to your command invocation where XXXX is the desired (free) port." } @@ -72,15 +72,15 @@ final class PreviewServer { /// A list of server-bind destinations. public enum Bind: CustomStringConvertible { /// A port on the local machine. - case localhost(port: Int) + case localhost(host: String, port: Int) /// A file socket on disk. case socket(path: String) var description: String { switch self { - case .localhost(port: let port): - return "localhost:\(port)" + case .localhost(host: let host, port: let port): + return "\(host):\(port)" case .socket(path: let path): return path } @@ -146,21 +146,21 @@ final class PreviewServer { do { // Bind to the given destination switch bindTo { - case .localhost(let port): - channel = try bootstrap.bind(host: "localhost", port: port).wait() + case .localhost(let host, let port): + channel = try bootstrap.bind(host: host, port: port).wait() case .socket(let path): channel = try bootstrap.bind(unixDomainSocketPath: path).wait() } } catch let error as NIO.IOError where error.errnoCode == EADDRINUSE { // The given port is not available. switch bindTo { - case .localhost(let port): throw Error.portNotAvailable(port: port) + case .localhost(let host, let port): throw Error.portNotAvailable(host: host, port: port) default: throw error } } catch { // Cannot bind the given address/port. switch bindTo { - case .localhost(let port): throw Error.cannotStartServer(port: port) + case .localhost(let host, let port): throw Error.cannotStartServer(host: host, port: port) default: throw error } } diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift index b507e902c0..188b521b2c 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift @@ -103,6 +103,7 @@ class PreviewActionIntegrationTests: XCTestCase { // tlsCertificateChain: nil, // serverUsername: nil, // serverPassword: nil, +// host: "localhost", // port: 8080, // We ignore this value when we set the `bindServerToSocketPath` property below. // createConvertAction: createConvertAction) else { // XCTFail("Could not create preview action from parameters") @@ -268,7 +269,7 @@ class PreviewActionIntegrationTests: XCTestCase { func testThrowsHumanFriendlyErrorWhenCannotStartServerOnAGivenPort() throws { // Binding an invalid address - try assert(bindPort: -1, expectedErrorMessage: "Can't start the preview server on port -1") + try assert(bindPort: -1, expectedErrorMessage: "Can't start the preview server on host localhost and port -1") } func assert(bindPort: Int, expectedErrorMessage: String, file: StaticString = #file, line: UInt = #line) throws { @@ -301,6 +302,7 @@ class PreviewActionIntegrationTests: XCTestCase { } guard let preview = try? PreviewAction( + host: "localhost", port: bindPort, createConvertAction: createConvertAction) else { XCTFail("Could not create preview action from parameters", file: file, line: line) @@ -375,6 +377,7 @@ class PreviewActionIntegrationTests: XCTestCase { } guard let preview = try? PreviewAction( + host: "localhost", port: 0, // Use port 0 to pick a random free port number createConvertAction: createConvertAction) else { XCTFail("Could not create preview action from parameters") @@ -404,7 +407,7 @@ class PreviewActionIntegrationTests: XCTestCase { let boundPort = try XCTUnwrap(servers[preview.serverIdentifier]?.channel.localAddress?.port) // Try to start another preview on the same port - try assert(bindPort: boundPort, expectedErrorMessage: "Port \(boundPort) is not available at the moment, try a different port number") + try assert(bindPort: boundPort, expectedErrorMessage: "Port \(boundPort) is not available on host localhost at the moment, try a different port number") try preview.stop() @@ -449,6 +452,7 @@ class PreviewActionIntegrationTests: XCTestCase { } guard let preview = try? PreviewAction( + host: "localhost", port: 8080, // We ignore this value when we set the `bindServerToSocketPath` property below. createConvertAction: createConvertAction) else { XCTFail("Could not create preview action from parameters") diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewServerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewServerTests.swift index db3ea62910..389d118ceb 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewServerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewServerTests.swift @@ -201,7 +201,7 @@ class PreviewServerTests { } func testPreviewServerBindDescription() { - let localhostBind = PreviewServer.Bind.localhost(port: 1234) + let localhostBind = PreviewServer.Bind.localhost(host: "localhost", port: 1234) XCTAssertEqual("\(localhostBind)", "localhost:1234") let socketBind = PreviewServer.Bind.socket(path: "/tmp/file.sock") XCTAssertEqual("\(socketBind)", "/tmp/file.sock")