Skip to content
Open
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
2 changes: 2 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,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
36 changes: 26 additions & 10 deletions src/fsdocs-tool/BuildCommand.fs
Original file line number Diff line number Diff line change
Expand Up @@ -819,11 +819,11 @@
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 wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
var wsUri = wsProtocol + "//" + window.location.host + "/websocket";
function init()
{
websocket = new WebSocket(wsUri);
Expand All @@ -850,8 +850,6 @@
</script>
"""

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

let connectedClients = ConcurrentDictionary<WebSocket, unit>()

let socketHandler (webSocket: WebSocket) (context: HttpContext) =
Expand Down Expand Up @@ -1560,9 +1558,15 @@
// 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 +1877,7 @@
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 +2329,9 @@
abstract port_option: int
default x.port_option = 0

abstract root_override_option: string option
default x.root_override_option = None

[<Verb("convert",
HelpText =
"convert a single document (.md, .fsx, .ipynb) to HTML or another output format without building a full documentation site")>]
Expand Down Expand Up @@ -2441,7 +2448,7 @@

let fsiEvaluator =
if this.eval then
Some(FsiEvaluator(options = [| "--multiemit-" |]) :> IFsiEvaluator)

Check warning on line 2451 in src/fsdocs-tool/BuildCommand.fs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

It is recommended that objects supporting the IDisposable interface are created using the syntax 'new Type(args)', rather than 'Type(args)' or 'Type' as a function value representing the constructor, to indicate that resources may be owned by the generated value

Check warning on line 2451 in src/fsdocs-tool/BuildCommand.fs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

It is recommended that objects supporting the IDisposable interface are created using the syntax 'new Type(args)', rather than 'Type(args)' or 'Type' as a function value representing the constructor, to indicate that resources may be owned by the generated value

Check warning on line 2451 in src/fsdocs-tool/BuildCommand.fs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

It is recommended that objects supporting the IDisposable interface are created using the syntax 'new Type(args)', rather than 'Type(args)' or 'Type' as a function value representing the constructor, to indicate that resources may be owned by the generated value

Check warning on line 2451 in src/fsdocs-tool/BuildCommand.fs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

It is recommended that objects supporting the IDisposable interface are created using the syntax 'new Type(args)', rather than 'Type(args)' or 'Type' as a function value representing the constructor, to indicate that resources may be owned by the generated value
else
None

Expand Down Expand Up @@ -2504,3 +2511,12 @@

[<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