From 7ae730d28e0e897ff6aea75758fb1cc1a99e96ef Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 5 Jun 2025 08:10:44 -0400 Subject: [PATCH 01/12] Make sidebar and navbar rendering much more flexible --- src/vitepress_config.jl | 54 ++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/vitepress_config.jl b/src/vitepress_config.jl index f3388c53..f7bdd2e1 100644 --- a/src/vitepress_config.jl +++ b/src/vitepress_config.jl @@ -106,10 +106,10 @@ function modify_config_file(doc, settings, deploy_decision, i_folder, base) # # Vitepress navbar and sidebar provided_page_list = doc.user.pages - sidebar_navbar_info = pagelist2str.((doc,), provided_page_list) - sidebar_navbar_string = join(sidebar_navbar_info, ",\n") - push!(replacers, "sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "sidebar: [\n$sidebar_navbar_string\n]\n") - push!(replacers, "nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "nav: [\n$sidebar_navbar_string\n]\n") + sidebar_info = sprint(print, pagelist2str(doc, provided_page_list, Val(:sidebar))) + navbar_info = sprint(print, pagelist2str(doc, provided_page_list, Val(:navbar))) + push!(replacers, "sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "sidebar: $(sidebar_info)\n") + push!(replacers, "nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "nav: $(navbar_info)\n") # # Title push!(replacers, "title: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => "title: '$(doc.user.sitename)'") @@ -158,10 +158,9 @@ function modify_config_file(doc, settings, deploy_decision, i_folder, base) return end -function _get_raw_text(element) -end +# Utility methods to get data about pages -function pagelist2str(doc, page::String) +function get_title(doc, page::AbstractString) # If no name is given, find the first header in the page, # and use that as the name. elements = collect(doc.blueprint.pages[page].mdast.children) @@ -174,35 +173,34 @@ function pagelist2str(doc, page::String) else Documenter.MDFlatten.mdflatten(elements[idx]) end - return "{ text: '$(replace(name, "'" => "\\'"))', link: '/$(splitext(page)[1])' }" # , $(sidebar_items(doc, page)) }" + return name end +get_title(doc, page::Pair{<: AbstractString, <: Any}) = first(page) -pagelist2str(doc, name_any::Pair{String, <: Any}) = pagelist2str(doc, first(name_any) => last(name_any)) - -function pagelist2str(doc, name_page::Pair{String, String}) +# Catch all method: just broadcast over any iterable assuming it is a collection +function pagelist2str(doc, pages, sidenav::Val) + contents = map(pages) do page + "{ " * pagelist2str(doc, page, sidenav) * " }" + end + return "[" * join(contents, ",\n") * "]" +end +function pagelist2str(doc, page::AbstractString, sidenav::Val) + name = get_title(doc, page) + return "text: '$(replace(name, "'" => "\\'"))', link: '/$(splitext(page)[1])'" # , $(sidebar_items(doc, page)) }" +end +function pagelist2str(doc, name_page::Pair{<: AbstractString, <: AbstractString}, sidenav::Val) name, page = name_page # This is the simplest and easiest case. - return "{ text: '$(replace(name, "'" => "\\'"))', link: '/$(splitext(page)[1])' }" # , $(sidebar_items(doc, page)) }" + return "text: '$(replace(name, "'" => "\\'"))', link: '/$(splitext(page)[1])'" # , $(sidebar_items(doc, page)) }" end - -function pagelist2str(doc, name_contents::Pair{String, <: AbstractVector}) +function pagelist2str(doc, name_contents::Pair{<: AbstractString, <: AbstractVector}, sidenav::Val) name, contents = name_contents # This is for nested stuff. Should work automatically but you never know... - rendered_contents = pagelist2str.((doc,), contents) - return "{ text: '$(replace(name, "'" => "\\'"))', collapsed: false, items: [\n$(join(rendered_contents, ",\n"))]\n }" # TODO: add a link here if the name is the same name as a file? -end - -function sidebar_items(doc, page::String) - # We look at the page elements, and obtain all level 1 and 2 headers. - elements = doc.blueprint.pages[page].elements - headers = filter(x -> x isa Union{MarkdownAST.Heading{1}, MarkdownAST.Heading{2}}, elements) - # If nothing is found, move on in life - if length(headers) ≤ 1 - return "" + rendered_contents = map(contents) do content + "{" * pagelist2str(doc, content, sidenav) * "}" end - # Otherwise, we return a collapsible tree of headers for each level 1 and 2 header. - items = headers - return "collapsed: true, items: [\n $(join(_item_link.((page,), items), ",\n"))\n]" + final_contents = join(rendered_contents, ",\n") + return "text: '$(replace(name, "'" => "\\'"))', collapsed: false, items: [\n$(final_contents)\n]" # TODO: add a link here if the name is the same name as a file? end function _item_link(page, item) From ba5ebb11f07ebdc895dad89cb50ff5b5a87abd90 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 5 Jun 2025 08:11:56 -0400 Subject: [PATCH 02/12] Implement this kind of flexible rendering in DV make.jl --- docs/make.jl | 48 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index afd8a82e..0d0f034d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -4,6 +4,45 @@ using DocumenterCitations using DocumenterInterLinks using LaTeXStrings +struct DecomposeInSidebar + path::String + pages::Any +end + +# So you can only really pull this trick once in a Julia session +# But because doc.user.pages is a Vector{Any}, we can't really do anything about it. +function DocumenterVitepress.pagelist2str(doc, ds::Vector{<: Any}, ::Val{:sidebar}) + println("Hello World!!!") + if !all(x -> x isa DecomposeInSidebar, ds) + # if this is false, invoke the default method. + return invoke(pagelist2str, Tuple{Any, Any, Val{:sidebar}}, doc, ds, Val(:sidebar)) + end + contents = DocumenterVitepress.pagelist2str.((doc,), ds, (Val(:sidebar),)) + ret = "{\n" * join(contents, ",\n") * "\n}" + println(ret) + return ret +end + +function DocumenterVitepress.pagelist2str(doc, ds::DecomposeInSidebar, ::Val{:sidebar}) + raw_contents = DocumenterVitepress.pagelist2str(doc, ds.pages, Val(:sidebar)) + contents = if raw_contents isa String + raw_contents + else + join(raw_contents, ",\n") + end + + return "\"/$(ds.path)/\": {\n$(contents)\n}" +end + +function DocumenterVitepress.pagelist2str(doc, ds::DecomposeInSidebar, ::Val{:navbar}) + return DocumenterVitepress.pagelist2str(doc, ds.pages, Val(:sidebar)) +end + +function Documenter.walk_navpages(ds::DecomposeInSidebar, parent, doc) + return Documenter.walk_navpages(ds.pages, parent, doc) +end + + # Handle DocumenterCitations integration - if you're running this, then you don't need anything here!! documenter_citations_dir = dirname(dirname(pathof(DocumenterCitations))) documenter_citations_docs_dir = joinpath(documenter_citations_dir, "docs") @@ -53,7 +92,7 @@ makedocs(; source = "src", build = "build", pages = [ - "Manual" => [ + DecomposeInSidebar("manual", "Manual" => [ "Get Started" => "manual/get_started.md", "Updating to DocumenterVitepress" => "manual/documenter_to_vitepress_docs_example.md", "Code" => "manual/code_example.md", @@ -63,12 +102,11 @@ makedocs(; "CSS Styling" => "manual/style_css.md", "Authors' badge" => "manual/author_badge.md", "GitHub Icon with Stars" => "manual/repo_stars.md", - ], - "Developers' documentation" => [ + ]), + DecomposeInSidebar("devs", "Developers' documentation" => [ "The rendering process" => "devs/render_pipeline.md", "Internal API" => "devs/internal_api.md", - ], - "api.md", + ]), ], plugins = [bib, links], ) From f2e1c8f8a0ea7eba6efae36863877d684eb29a3a Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 5 Jun 2025 15:02:24 -0400 Subject: [PATCH 03/12] Fix stack overflow --- src/vitepress_config.jl | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vitepress_config.jl b/src/vitepress_config.jl index f7bdd2e1..e44136a8 100644 --- a/src/vitepress_config.jl +++ b/src/vitepress_config.jl @@ -188,12 +188,23 @@ function pagelist2str(doc, page::AbstractString, sidenav::Val) name = get_title(doc, page) return "text: '$(replace(name, "'" => "\\'"))', link: '/$(splitext(page)[1])'" # , $(sidebar_items(doc, page)) }" end + +function pagelist2str(doc, name_page::Pair{<: Any, <: Any}, sidenav::Val) + name, page = name_page + # This is the simplest and easiest case. + return pagelist2str(doc, name => page, sidenav) # , $(sidebar_items(doc, page)) }" +end + +function pagelist2str(doc, name_page::Pair{<: Any, <: Nothing}, sidenav::Val) + return "" +end + function pagelist2str(doc, name_page::Pair{<: AbstractString, <: AbstractString}, sidenav::Val) name, page = name_page # This is the simplest and easiest case. return "text: '$(replace(name, "'" => "\\'"))', link: '/$(splitext(page)[1])'" # , $(sidebar_items(doc, page)) }" end -function pagelist2str(doc, name_contents::Pair{<: AbstractString, <: AbstractVector}, sidenav::Val) +function pagelist2str(doc, name_contents::Pair{<: AbstractString, <: AbstractArray}, sidenav::Val) name, contents = name_contents # This is for nested stuff. Should work automatically but you never know... rendered_contents = map(contents) do content From 5532dcdfbdfd73c191ea06ecd24d6b9a668034db Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Fri, 6 Jun 2025 07:48:13 -0400 Subject: [PATCH 04/12] only emit collapsed metadata if in sidebar --- src/vitepress_config.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vitepress_config.jl b/src/vitepress_config.jl index e44136a8..73de2470 100644 --- a/src/vitepress_config.jl +++ b/src/vitepress_config.jl @@ -211,7 +211,12 @@ function pagelist2str(doc, name_contents::Pair{<: AbstractString, <: AbstractArr "{" * pagelist2str(doc, content, sidenav) * "}" end final_contents = join(rendered_contents, ",\n") - return "text: '$(replace(name, "'" => "\\'"))', collapsed: false, items: [\n$(final_contents)\n]" # TODO: add a link here if the name is the same name as a file? + collapse = if sidenav === Val(:sidebar) + "collapsed: false," + else + "" + end + return "text: '$(replace(name, "'" => "\\'"))', $collapse items: [\n$(final_contents)\n]" # TODO: add a link here if the name is the same name as a file? end function _item_link(page, item) From 38d8d304c275786a5148e112c27f9f11b481071f Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Fri, 6 Jun 2025 08:52:02 -0400 Subject: [PATCH 05/12] add frontmatter processor --- src/DocumenterVitepress.jl | 1 + src/frontmatter.jl | 19 +++++++++++++++++++ src/writer.jl | 1 + 3 files changed, 21 insertions(+) create mode 100644 src/frontmatter.jl diff --git a/src/DocumenterVitepress.jl b/src/DocumenterVitepress.jl index 00229d1f..824ccaa2 100644 --- a/src/DocumenterVitepress.jl +++ b/src/DocumenterVitepress.jl @@ -15,6 +15,7 @@ const ASSETS = normpath(joinpath(@__DIR__, "..", "assets")) include("vitepress_interface.jl") include("vitepress_config.jl") +include("frontmatter.jl") include("writer.jl") include("ANSIBlocks.jl") diff --git a/src/frontmatter.jl b/src/frontmatter.jl new file mode 100644 index 00000000..78a8922a --- /dev/null +++ b/src/frontmatter.jl @@ -0,0 +1,19 @@ +# In Vitepress you can only have one frontmatter block. +# But users / other Documenter stages may inject multiple. +# So, we have a stage that will merge and render all frontmatter blocks +# before doing anything else. + +function merge_and_render_frontmatter(io::IO, mime::MIME"text/yaml", page, doc; kwargs...) + frontmatter = String[] + for block in page.mdast.children + element = block.element + if element isa MarkdownAST.CodeBlock && element.info == "@frontmatter" + push!(frontmatter, element.code) + elseif element isa Documenter.RawNode && startswith(element.text, "---") + push!(frontmatter, join(split(element.text, "\n")[2:end-1], "\n")) + end + end + println(io, "---") + println(io, join(frontmatter, "\n")) + println(io, "---") +end diff --git a/src/writer.jl b/src/writer.jl index d338fb1f..f67bd863 100644 --- a/src/writer.jl +++ b/src/writer.jl @@ -228,6 +228,7 @@ function render(doc::Documenter.Document, settings::MarkdownVitepress=MarkdownVi for (src, page) in doc.blueprint.pages # This is where you can operate on a per-page level. open(docpath(page.build, builddir, settings.md_output_path), "w") do io + merge_and_render_frontmatter(io, MIME("text/yaml"), page, doc) for node in page.mdast.children render(io, mime, node, page, doc; inventory) end From 74aa593984795b8ac7e0fdbcb223446ab54a742d Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Fri, 6 Jun 2025 08:52:08 -0400 Subject: [PATCH 06/12] don't emit in raw nodes --- src/writer.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/writer.jl b/src/writer.jl index f67bd863..3f2e8756 100644 --- a/src/writer.jl +++ b/src/writer.jl @@ -949,6 +949,9 @@ render(io::IO, mime::MIME"text/plain", node::MarkdownAST.Node, ::Documenter.Setu # Raw nodes are used to insert raw HTML into the output. We just print it as is. # TODO: what if the `raw` is not HTML? That is not addressed here but we ought to address it... function render(io::IO, ::MIME"text/plain", node::Documenter.MarkdownAST.Node, raw::Documenter.RawNode, page, doc; kwargs...) + if startswith(raw.text, "---") + return # this was already handled by frontmatter. + end return raw.name === :html ? println(io, raw.text, "\n") : nothing end From 480267f4aef68fd2938b76ab1aaefd1232789238 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Mon, 9 Jun 2025 20:51:40 -0400 Subject: [PATCH 07/12] Emit title and meta description in frontmatter --- src/frontmatter.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/frontmatter.jl b/src/frontmatter.jl index 78a8922a..f46ecfeb 100644 --- a/src/frontmatter.jl +++ b/src/frontmatter.jl @@ -13,6 +13,13 @@ function merge_and_render_frontmatter(io::IO, mime::MIME"text/yaml", page, doc; push!(frontmatter, join(split(element.text, "\n")[2:end-1], "\n")) end end + + if haskey(page.globals.meta, :Title) + pushfirst!(frontmatter, "title: \"$(page.globals.meta[:Title])\"") + end + if haskey(page.globals.meta, :Description) + pushfirst!(frontmatter, "description: \"$(page.globals.meta[:Description])\"") + end println(io, "---") println(io, join(frontmatter, "\n")) println(io, "---") From 9d26d5f68977a851092083f794ea8c44f0be39c0 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 10 Jun 2025 12:49:31 -0400 Subject: [PATCH 08/12] FIx deploy URLs --- src/vitepress_config.jl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vitepress_config.jl b/src/vitepress_config.jl index 73de2470..f76ded94 100644 --- a/src/vitepress_config.jl +++ b/src/vitepress_config.jl @@ -89,9 +89,14 @@ function modify_config_file(doc, settings, deploy_decision, i_folder, base) @info "Base is \"\" and ENV[\"CI\"] is not set so this is a local build. Not adding any additional base prefix based on the repository or deploy url and instead using absolute path \"/\" to facilitate serving docs locally." "/" elseif isnothing(settings.deploy_url) - "/" * splitpath(settings.repo)[end] # Get the last identifier of the repo path, i.e., `user/$repo`. + "/" * split(settings.repo, '/')[end] # Get the last identifier of the repo path, i.e., `user/$repo`. else - s_path = startswith(settings.deploy_url, r"http[s?]:\/\/") ? splitpath(settings.deploy_url)[2:end] : splitpath(settings.deploy_url) + s_path = if startswith(settings.deploy_url, r"http[s?]:\/\/") + frags = split(settings.deploy_url, '/') # "https", "", "my.custom.domain", "sub", "dir" + length(frags) > 3 ? frags[3:end] : "/" # |-> "sub", "dir" + else + split(settings.deploy_url, '/') # "sub", "dir" + end s = length(s_path) > 1 ? joinpath(s_path) : "" # ignore custom URL here isempty(s) ? "/" : "/$(s)" end From 0f7b6665096ae3d96ea993b48975f3b147d12f72 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 10 Jun 2025 12:55:25 -0400 Subject: [PATCH 09/12] Actually fix --- src/vitepress_config.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vitepress_config.jl b/src/vitepress_config.jl index f76ded94..5d580e4b 100644 --- a/src/vitepress_config.jl +++ b/src/vitepress_config.jl @@ -93,11 +93,11 @@ function modify_config_file(doc, settings, deploy_decision, i_folder, base) else s_path = if startswith(settings.deploy_url, r"http[s?]:\/\/") frags = split(settings.deploy_url, '/') # "https", "", "my.custom.domain", "sub", "dir" - length(frags) > 3 ? frags[3:end] : "/" # |-> "sub", "dir" + length(frags) >= 4 ? frags[4:end] : "/" # |-> "sub", "dir" else split(settings.deploy_url, '/') # "sub", "dir" end - s = length(s_path) > 1 ? joinpath(s_path) : "" # ignore custom URL here + s = join(s_path, '/') isempty(s) ? "/" : "/$(s)" end From 944cf2bea1d17390aa9cfa1b55abb33a6f029551 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 10 Jun 2025 17:02:16 -0400 Subject: [PATCH 10/12] More fixes for deploying to URL --- src/vitepress_config.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vitepress_config.jl b/src/vitepress_config.jl index 5d580e4b..4af763f8 100644 --- a/src/vitepress_config.jl +++ b/src/vitepress_config.jl @@ -93,7 +93,7 @@ function modify_config_file(doc, settings, deploy_decision, i_folder, base) else s_path = if startswith(settings.deploy_url, r"http[s?]:\/\/") frags = split(settings.deploy_url, '/') # "https", "", "my.custom.domain", "sub", "dir" - length(frags) >= 4 ? frags[4:end] : "/" # |-> "sub", "dir" + length(frags) >= 4 ? frags[4:end] : [""] # |-> "sub", "dir" else split(settings.deploy_url, '/') # "sub", "dir" end @@ -101,7 +101,7 @@ function modify_config_file(doc, settings, deploy_decision, i_folder, base) isempty(s) ? "/" : "/$(s)" end - base_str = deploy_abspath == "/" ? "base: '$(deploy_abspath)$(deploy_relpath)'" : "base: '$(deploy_abspath)/$(deploy_relpath)'" + base_str = endswith(deploy_abspath, "/") ? "base: '$(deploy_abspath)$(deploy_relpath)'" : "base: '$(deploy_abspath)/$(deploy_relpath)'" push!(replacers, "REPLACE_ME_DOCUMENTER_VITEPRESS_DEPLOY_ABSPATH" => deploy_abspath) push!(replacers, "base: 'REPLACE_ME_DOCUMENTER_VITEPRESS'" => base_str) From e2a972f80b1555ff6684050a79b75bbb2f3cdf69 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 17 Jun 2025 12:11:53 -0400 Subject: [PATCH 11/12] Make inventory writing optional It errors out for some things and I do not have time to debug --- src/writer.jl | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/writer.jl b/src/writer.jl index 3f2e8756..48e0573e 100644 --- a/src/writer.jl +++ b/src/writer.jl @@ -64,6 +64,8 @@ Base.@kwdef struct MarkdownVitepress <: Documenter.Writer assets = nothing "A version string to write to the header of the objects.inv inventory file. This should be a valid version number without a v prefix. Defaults to the version defined in the Project.toml file in the parent folder of the documentation root" inventory_version::Union{String,Nothing} = nothing + "Whether to write inventory or not" + write_inventory = true """ Sets the granularity of versions which should be kept. Options are :patch, :minor or :breaking (the default). You can use this to reduce the number of docs versions that coexist on your dev branch. With :patch, every patch @@ -217,12 +219,16 @@ function render(doc::Documenter.Document, settings::MarkdownVitepress=MarkdownVi end end - version = settings.inventory_version - if isnothing(version) - project_toml = joinpath(dirname(doc.user.root), "Project.toml") - version = _get_inventory_version(project_toml) + inventory = if settings.write_inventory + version = settings.inventory_version + if isnothing(version) + project_toml = joinpath(dirname(doc.user.root), "Project.toml") + version = _get_inventory_version(project_toml) + end + Inventory(; project=doc.user.sitename, version) + else + nothing end - inventory = Inventory(; project=doc.user.sitename, version) # Iterate over the pages, render each page separately for (src, page) in doc.blueprint.pages @@ -230,18 +236,25 @@ function render(doc::Documenter.Document, settings::MarkdownVitepress=MarkdownVi open(docpath(page.build, builddir, settings.md_output_path), "w") do io merge_and_render_frontmatter(io, MIME("text/yaml"), page, doc) for node in page.mdast.children - render(io, mime, node, page, doc; inventory) + kwargs = if settings.write_inventory + (; inventory = inventory) + else + (;) + end + render(io, mime, node, page, doc; kwargs...) end end - item = InventoryItem( - name = replace(splitext(src)[1], "\\" => "/"), - domain = "std", - role = "doc", - dispname = _pagetitle(page), - priority = -1, - uri = _get_inventory_uri(doc, page, nothing) - ) - push!(inventory, item) + if settings.write_inventory + item = InventoryItem( + name = replace(splitext(src)[1], "\\" => "/"), + domain = "std", + role = "doc", + dispname = _pagetitle(page), + priority = -1, + uri = _get_inventory_uri(doc, page, nothing) + ) + push!(inventory, item) + end end objects_inv = joinpath(builddir, settings.md_output_path, "public", "objects.inv") From 15b538f158fe0c39a503445527f46ca12e43b072 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 17 Jun 2025 14:10:15 -0400 Subject: [PATCH 12/12] only write inventory if asked to --- src/writer.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/writer.jl b/src/writer.jl index 48e0573e..0ed3cf9e 100644 --- a/src/writer.jl +++ b/src/writer.jl @@ -256,9 +256,10 @@ function render(doc::Documenter.Document, settings::MarkdownVitepress=MarkdownVi push!(inventory, item) end end - - objects_inv = joinpath(builddir, settings.md_output_path, "public", "objects.inv") - DocInventories.save(objects_inv, inventory) + if settings.write_inventory + objects_inv = joinpath(builddir, settings.md_output_path, "public", "objects.inv") + DocInventories.save(objects_inv, inventory) + end bases = determine_bases(deploy_decision.subfolder; settings.keep)