Skip to content
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
38 changes: 33 additions & 5 deletions src/search/pagefind.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@ module PageFind
using NodeJS_22_jll: npx, npm, node
using HypertextLiteral: @htl

"""
npm_command(shim, args...; dir) -> Cmd

Command that runs one of NodeJS jll's `npm` / `npx` file products with `args`, in `dir`.
"""
function npm_command(
shim::AbstractString,
args::AbstractString...;
dir::AbstractString
)
Sys.iswindows() || return Cmd(`$(shim) $(String[args...])`; dir = dir)

wincmd_arg(arg::AbstractString) =
Base.shell_escape_wincmd(occursin(' ', arg) ? "\"$(arg)\"" : arg)

line = join((wincmd_arg(arg) for arg in (shim * ".cmd", args...)), ' ')
return Cmd(Cmd(["cmd.exe", "/S /C \"$(line)\""]); windows_verbatim = true, dir = dir)
end

function inject_script!(custom_scripts, rootpath)
pushfirst!(custom_scripts, joinpath("assets", "default", "pagefind_integration.js"))
pushfirst!(custom_scripts, joinpath("pagefind", "pagefind.js"))
Expand Down Expand Up @@ -34,20 +53,29 @@ function build_search_index(root, docs, config, rootpath)
# To fix this, we wrap all uses of npx and npm inside `node() do ...`
# which will automatically adjust the necessary environment variables.
node() do _
if !success(Cmd(`$(npx) pagefind -V`; dir = root))
if !success(npm_command(npx, "pagefind", "-V"; dir = root))
@info "Installing pagefind into $root."
if !success(Cmd(`$(npm) install pagefind`; dir = root))
if !success(npm_command(npm, "install", "pagefind"; dir = root))
error("Could not install pagefind.")
end
end

pattern = "*/{$(join(config.index_versions, ","))}/**/*.{html}"

out_path = joinpath(root, "pagefind")
mktempdir() do dir
mktempdir() do sitedir
# pagefind doesn't look at symlinks, so we resolve them here:
cp(root, dir; follow_symlinks = true, force = true)
run(`$(npx) pagefind --site $(dir) --output-path $(out_path) --glob $(pattern) --root-selector article`)
cp(root, sitedir; follow_symlinks = true, force = true)
run(
npm_command(
npx, "pagefind",
"--site", sitedir,
"--output-path", out_path,
"--glob", pattern,
"--root-selector", "article";
dir = root,
)
)
end
end

Expand Down
69 changes: 69 additions & 0 deletions test/pagefind.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Test
using MultiDocumenter
using NodeJS_22_jll: npm, npx

const PageFind = MultiDocumenter.PageFind

@testset "npm_command" begin
@testset "posix runs the shim directly" begin
if !Sys.iswindows()
# bin/npm and bin/npx are the npm CLI's JavaScript with a `#!/usr/bin/env node`
# shebang there, so they need no interpreter of their own.
cmd = PageFind.npm_command(
"/js/bin/npx", "pagefind", "-V"; dir = "/root"
)
@test cmd == Cmd(`/js/bin/npx pagefind -V`; dir = "/root")
@test cmd.exec == ["/js/bin/npx", "pagefind", "-V"]
@test cmd.dir == "/root"

# a space in a path is Julia's to escape here, not ours
spaced = PageFind.npm_command(
"/js b/npx", "install", "pagefind"; dir = "/r"
)
@test spaced.exec == ["/js b/npx", "install", "pagefind"]
end
end

@testset "windows goes through cmd.exe /S /C" begin
if Sys.iswindows()
cmd = PageFind.npm_command(
"C:\\js\\bin\\npx", "pagefind", "-V"; dir = "C:\\root"
)
# the .cmd sibling, wrapped in the form Base documents for cmd.exe
@test cmd == Cmd(
Cmd(["cmd.exe", "/S /C \"C:\\js\\bin\\npx.cmd pagefind -V\""]);
windows_verbatim = true, dir = "C:\\root",
)
# windows_verbatim has to be set, or Julia would re-quote the line we assembled
@test cmd != Cmd(
Cmd(["cmd.exe", "/S /C \"C:\\js\\bin\\npx.cmd pagefind -V\""]);
dir = "C:\\root",
)
@test cmd.dir == "C:\\root"

# /S strips the outer quote pair, so a path with a space keeps its own quotes and
# reaches the program intact -- the case a bare `cmd /c $path` gets wrong
spaced = PageFind.npm_command(
"C:\\Users\\John Doe\\bin\\npx", "pagefind", "-V"; dir = "C:\\r"
)
@test last(spaced.exec) ==
"/S /C \"\"C:\\Users\\John Doe\\bin\\npx.cmd\" pagefind -V\""

# the glob we pass contains none of cmd.exe's metacharacters, so it survives as-is
glob = "*/{stable,dev}/**/*.{html}"
globbed = PageFind.npm_command(
"C:\\js\\bin\\npx", "pagefind", "--glob", glob; dir = "C:\\r"
)
@test occursin("--glob $(glob)", last(globbed.exec))
end
end

@testset "the shims this platform needs exist" begin
@test isfile(npx)
@test isfile(npm)
if Sys.iswindows()
@test isfile(npx * ".cmd")
@test isfile(npm * ".cmd")
end
end
end
9 changes: 8 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ using Test
include("documentertools.jl")
end

@testset "pagefind" begin
include("pagefind.jl")
end

clonedir = mktempdir()
outpath = joinpath(@__DIR__, "out")
rootpath = "/MultiDocumenter.jl/"
Expand Down Expand Up @@ -145,7 +149,10 @@ MultiDocumenter.make(
@test isfile(outpath, "inf", "stable", "index.html")
end

@test read(joinpath(outpath, "inf", "index.html"), String) == """
# Git may check out the cloned docs with CRLF line endings (e.g. on Windows,
# where core.autocrlf defaults to true), so we normalize before comparing.
index_html = normalize_newlines(read(joinpath(outpath, "inf", "index.html"), String))
@test index_html == """
<!--This file is automatically generated by Documenter.jl-->
<meta http-equiv="refresh" content="0; url=./stable/"/>
"""
Expand Down