Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
## [22.0.0-alpha.2] - 2026-03-13

### Added
* Add `--root` option to `fsdocs watch` to override the root URL for generated pages. Useful for serving docs via GitHub Codespaces, reverse proxies, or other remote hosting where `localhost` URLs are inaccessible. E.g. `fsdocs watch --root /` or `fsdocs watch --root https://example.com/docs/`. When not set, defaults to `http://localhost:<port>/` as before. [#924](https://github.com/fsprojects/FSharp.Formatting/issues/924)
* Fix `fsdocs watch` hot-reload WebSocket to connect using the page's actual host (`window.location.host`) instead of a hardcoded `localhost:<port>`, so hot-reload works correctly in GitHub Codespaces, behind reverse proxies, and over HTTPS. [#924](https://github.com/fsprojects/FSharp.Formatting/issues/924)
* Search dialog now auto-focuses the search input when opened, clears on close, and can be triggered with `Ctrl+K` / `Cmd+K` in addition to `/`.
* Add `dotnet fsdocs convert` command to convert a single `.md`, `.fsx`, or `.ipynb` file to HTML (or another output format) without building a full documentation site. [#811](https://github.com/fsprojects/FSharp.Formatting/issues/811)
* `fsdocs convert` now accepts the input file as a positional argument (e.g. `fsdocs convert notebook.ipynb -o notebook.html`). [#1019](https://github.com/fsprojects/FSharp.Formatting/pull/1019)
Expand Down
35 changes: 25 additions & 10 deletions src/fsdocs-tool/BuildCommand.fs
Original file line number Diff line number Diff line change
Expand Up @@ -819,11 +819,10 @@ module Serve =
let refreshEvent = FSharp.Control.Event<string>()

/// generate the script to inject into html to enable hot reload during development
let generateWatchScript (port: int) =
let tag =
"""
let generateWatchScript () =
"""
<script type="text/javascript">
var wsUri = "ws://localhost:{{PORT}}/websocket";
var wsUri = "ws://" + window.location.host + "/websocket";
function init()
{
websocket = new WebSocket(wsUri);
Expand All @@ -850,8 +849,6 @@ module Serve =
</script>
"""

tag.Replace("{{PORT}}", string<int> port)

let connectedClients = ConcurrentDictionary<WebSocket, unit>()

let socketHandler (webSocket: WebSocket) (context: HttpContext) =
Expand Down Expand Up @@ -1560,9 +1557,15 @@ type CoreBuildOptions(watch) =
// Adjust the user substitutions for 'watch' mode root
let userRoot, userParameters =
if watch then
let userRoot = sprintf "http://localhost:%d/" this.port_option

if userParametersDict.ContainsKey(ParamKeys.root) then
let userRoot =
match this.root_override_option with
| Some r -> r
| None -> sprintf "http://localhost:%d/" this.port_option

if
userParametersDict.ContainsKey(ParamKeys.root)
&& this.root_override_option.IsNone
then
printfn "ignoring user-specified root since in watch mode, root = %s" userRoot

let userParameters =
Expand Down Expand Up @@ -1873,7 +1876,7 @@ type CoreBuildOptions(watch) =
let getLatestWatchScript () =
if watch then
// if running in watch mode, inject hot reload script
[ ParamKeys.``fsdocs-watch-script``, Serve.generateWatchScript this.port_option ]
[ ParamKeys.``fsdocs-watch-script``, Serve.generateWatchScript () ]
else
// otherwise, inject empty replacement string
[ ParamKeys.``fsdocs-watch-script``, "" ]
Expand Down Expand Up @@ -2325,6 +2328,9 @@ type CoreBuildOptions(watch) =
abstract port_option: int
default x.port_option = 0

abstract root_override_option: string option
default x.root_override_option = None

/// Helpers for the <c>fsdocs convert</c> command.
module private ConvertHelpers =

Expand Down Expand Up @@ -2741,3 +2747,12 @@ type WatchCommand() =

[<Option("port", Required = false, Default = 8901, HelpText = "Port to serve content for http://localhost serving.")>]
member val port = 8901 with get, set

override x.root_override_option = if x.root = "" then None else Some x.root

[<Option("root",
Required = false,
Default = "",
HelpText =
"Override the root URL for generated pages. Useful for reverse proxies or GitHub Codespaces. E.g. --root / or --root https://example.com/docs/. When not set, defaults to http://localhost:<port>/.")>]
member val root = "" with get, set