Add docs.publishDev RPC method #107
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_books#107
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent: #101
What to add
Method:
docs.publishDevParams:
{ path: string, name: string }Returns:
{ job_id: string }Wraps:
DocSite::publish_dev()atlib.rs:135— rsyncs to dev destinationImplementation steps
hero_docs publish-devsubcommand existshandle_docs_publish_dev(id, params, config) -> RpcResponsehandle_docs_job_status"docs.publishDev"atrpc.rs:155openrpc.json, bumpinfo.versionrpc.rs:9-28Acceptance Criteria
hero_docs publish-devsubcommand exists{ job_id }rawdaGastan referenced this issue2026-04-27 09:23:12 +00:00
Implementation Spec for Issue #107
Objective
Expose
DocSite::publish_dev()over JSON-RPC asdocs.publishDev. Unlikedocs.publish(#106) which uses the default production rsync target,docs.publishDevuses the dev destinations defined per-site via the heroscript!!site.publish_devdirective (parsed byparse_publish(playbook, _, true)incrates/hero_books_docusaurus/src/heroscript.rs). This is the only place where dev destinations are actually defined in the codebase, so the handler must load DocSite from heroscript to honour them.This means
pathsemantics fordocs.publishDevdiffer from #102-#106: it is a heroscript path/URL (matchingdocs.generate's convention), not a build-directory path. The intentional difference reflects that dev publish destinations are per-site configured (not a single global default), while production publish (#106) has a sensible global fallback.Requirements
docs.publishDevis dispatchable fromcrates/hero_books_server/src/web/rpc.rsand reaches a new handlerhandle_docs_publish_dev.openrpc.jsonand the inlinerpc_spec.rsschema. Summary documents the heroscript-based path semantics, thenameoverride, and the no-output_pathpolicy.info.versionunchanged at0.1.6(one bump per rolling PR).## Docsdoc-header gainsdocs.publishDev.hero_docs(src/bin/hero_docs.rs) gains a newpublish-devsubcommand:hero_docs publish-dev --path <heroscript_path_or_url> --name <name>. It loadsDocSite::from_heroscript(&path), overridesdocsite.name = name, and callsdocsite.publish_dev(). Thenameoverride allows callers to pick a site name distinct from the heroscript's site.config.name (typical convention: append_dev).config.hero_docs_bin, neverDocSitein-process.docs_publish_dev_<input_hash>whereinput_hash = calculate_docs_input_hash(&[&path, &name]). Thepathhere is a heroscript path/URL; the hash treats it as opaque text.derive_docs_output_pathdoc-comment updated to mentiondocs_publish_dev_*explicitly. No code change in the helper — unknown prefixes already returnNone(the desired behaviour).RpcResponse::invalid_params(-32602); internal failures:RpcResponse::error(id, -32000, ...). Bothpathandnamerequired.Files to Modify/Create
src/bin/hero_docs.rs—PublishDev(PublishDevArgs)variant, args struct, runner that loads DocSite from heroscript and callspublish_dev().crates/hero_books_server/src/web/rpc.rshandle_docs_publish_devhandler (mirrorshandle_docs_publishshape).derive_docs_output_pathdoc-comment update —docs_publish_dev_*explicit (no longer "upcoming").test_docs_publish_dev_missing_params.test_derive_docs_output_path_returns_none_for_install_update_templateextended with one moreassert!(...is_none())fordocs_publish_dev_xxx.crates/hero_books_server/src/web/rpc_spec.rs— inlinedocs.publishDeventry.crates/hero_books_server/openrpc.json—docs.publishDevmethod entry.No new files.
Implementation Plan
Step 1 — Add
publish-devsubcommand tohero_docsFiles:
src/bin/hero_docs.rsCommandsenum withPublishDev(PublishDevArgs). Clap will derive the kebab-casepublish-devfromPublishDev.DocSite::from_heroscript, NOTfrom_heroscript_full. We don't need the export directory for publish;from_heroscriptis the cheaper variant that only parses the heroscript without scanning collections. Thepublish_dev()method callsbuild::publish(&self.path_build, &self.site.config.build_dest_dev, &self.name)— the dev destinations come from the heroscript's!!site.publish_devdirectives. If the heroscript has none,build::publishfalls back to a single defaultPublishDestusingnameas the site_name (same fallback asdocs.publish).main().Dependencies: none.
Step 2 — Add
handle_docs_publish_devhandlerFiles:
crates/hero_books_server/src/web/rpc.rshandle_docs_publishwith signaturefn handle_docs_publish_dev(id: Option<Value>, params: Option<Value>, config: &ServerConfig) -> RpcResponse.handle_docs_publishexactly (validatepath+name, hash both, format script, submit) with two changes:docs_publish_dev_(instead ofdocs_publish_).publish-dev(instead ofpublish).pathdiffers (heroscript vs build dir) — but the handler doesn't validate that distinction; it just passes the string through. The hero_docs runner does the heroscript loading.Dependencies: Step 1 (subcommand at runtime).
Step 3 — Wire dispatch arm
Files:
crates/hero_books_server/src/web/rpc.rsdocs.publishand beforedocs.jobStatus:Dependencies: Step 2.
Step 4 — Update doc-header and
derive_docs_output_pathdoc-commentFiles:
crates/hero_books_server/src/web/rpc.rs## Docsdoc-header: insertdocs.publishDevafterdocs.publish:derive_docs_output_path's bucket-3 comment sodocs_publish_dev_*appears explicitly (no longer "upcoming"):Dependencies: none.
Step 5 — Add
docs.publishDeventries (openrpc.json + rpc_spec.rs)Files:
crates/hero_books_server/openrpc.json,crates/hero_books_server/src/web/rpc_spec.rsdocs.publishanddocs.jobStatus:info.versionunchanged.Dependencies: Step 3.
Step 6 — Add unit test
Files:
crates/hero_books_server/src/web/rpc.rstest_docs_publish_dev_missing_params— same shape astest_docs_publish_missing_params. Five assertions covering missing/emptypath, missing/emptyname(with validpath), and no-paramscase.test_derive_docs_output_path_returns_none_for_install_update_templatewith one more line:Dependencies: Step 2.
Step 7 — Verify and live-test
cargo check,cargo clippy,cargo test --releaseall green.cargo run --bin hero_docs -- publish-dev --help.rpc.discoverlistsdocs.publishDevwith two required params.pathand missingnameeach return-32602.{"job_id": "<n>"}.(path, name)→ same id.name(or differentpath) → different id.docs.jobStatusreturns nooutput_path.action_id = docs_publish_dev_<hash>;timeout_ms = 600000.path— that's expected. We're verifying the RPC chain.Dependencies: Steps 1-6.
Acceptance Criteria
hero_docs publish-dev --helpprints usage with--pathand--name(both required).crates/hero_books_server/src/web/rpc.rsdefineshandle_docs_publish_dev.docs.publishDevis wired into the dispatcher betweendocs.publishanddocs.jobStatus.docs_publish_dev_<input_hash>where the hash includes bothpathandname.docs.jobStatusreturns nooutput_pathfordocs_publish_dev_*jobs (locked by extending the existing test).## Docsdoc-header listsdocs.publishDev.crates/hero_books_server/openrpc.jsonandrpc_spec.rsinline schema both containdocs.publishDev.info.versionis unchanged at"0.1.6".cargo check,cargo clippy,cargo testall pass — including the new test and the extended prefix test.Notes
docs.publishvsdocs.publishDev): the two methods take different kinds ofpathbecause the underlying behaviour is different. Production publish has a sensible global default rsync target, so build-dir + name is enough (#106). Dev publish destinations are per-site (defined via!!site.publish_devin heroscript), so heroscript loading is necessary to honour them — there's no global "dev" default in the codebase. This divergence is documented in the OpenRPC summaries and in the## Docsdoc-header.from_heroscript, notfrom_heroscript_full: we don't need to scan and export collections to publish.from_heroscriptparses just enough of the heroscript to populatesite.config(includingbuild_dest_dev) andpath_build. Cheaper, and avoids running collection scans that the publish operation doesn't need.nameoverride:DocSite::from_heroscriptsetsdocsite.name = site.config.name(from!!site.config name:'...'in heroscript). ThenameRPC param overrides this so callers can publish under a different site name without editing the heroscript. Convention: append_devto the production site name.build_dest_dev: if the heroscript has no!!site.publish_devdirectives,docsite.publish_dev()callsbuild::publishwith an empty destinations slice — which falls back to a single defaultPublishDestusingdocsite.nameas the site_name (same fallback as production publish). This is the same code path as #106's&[]empty slice.derive_docs_output_pathreturnsNonefordocs_publish_dev_*.info.version: unchanged at0.1.6. This is the final child of the rolling PR, so the version stays put.pathmust point to a valid heroscript file or URL; the resulting build path (docusaurus_config.path_build) must contain<path_build>/build/from a priordocs.build(ordocs.generate/docs.new);RSYNCD_SECRETmust be set on the server. Without these, the job lands instate: failedwith a clear error from eitherfrom_heroscriptorbuild::publish.Test Results
Suite:
cargo test -p hero_books_server --libTotal: 26 — Passed: 26 — Failed: 0
New test (1) and one existing test extended:
test_docs_publish_dev_missing_params— covers missing/emptypath, missing/emptyname(with validpath), and no-paramscase. Each returns-32602with the appropriate field-specific message.test_derive_docs_output_path_returns_none_for_install_update_template— extended withassert!(derive_docs_output_path("docs_publish_dev_zzz", cache).is_none()). Locks the no-output-path policy for the publish-dev family.Build & lint
cargo check -p hero_books_server— OKcargo check --bin hero_docs— OKcargo clippy -p hero_books_server --lib --no-deps— silentcargo clippy --bin hero_docs --no-deps— silentcargo test --release— 26/26Spec/impl parity
INSTRUCTIONS_OPENRPC.md §Verification diff is empty.
Build infrastructure note
The inline
get_openrpc_schemablock incrates/hero_books_server/src/web/rpc_spec.rsis one largejson!macro expansion. Adding thedocs.publishDeventry exceeded Rust's default macro recursion limit (128). Bumped the crate'srecursion_limitto256incrates/hero_books_server/src/lib.rswith an inline comment explaining why. No runtime impact; compile-time only.CLI smoke test
Live end-to-end RPC test (against running hero_books_server + hero_proc)
rpc.discoverlistsdocs.publishDevwithpath(required) andname(required)pathreturns-32602 "missing or empty 'path' parameter"namereturns-32602 "missing or empty 'name' parameter"{"job_id":"115"}for(path=/tmp/x.heroscript, name=my_site_dev)(path, name)dedupes tojob_id: "115"nameproduces a differentjob_id: "116"docs.jobStatusfor the failed job:state: failed, error tail"HeroScript directory not found: /tmp/x.heroscript", and nooutput_pathfieldaction_id = docs_publish_dev_<hash>,timeout_ms = 600000(default 10-min cap)The error from
from_heroscript()propagated correctly throughhero_docs publish-dev->hero_proc->docs.jobStatus, proving the heroscript-loading path is wired correctly. With a real heroscript file atpath, the handler would proceed to override the site name and calldocsite.publish_dev(), rsyncing tobuild_dest_devdestinations.Live discovery: all 9
docs.*methods exposeddocs.build,docs.dev,docs.generate,docs.installTemplate,docs.jobStatus,docs.new,docs.publish,docs.publishDev,docs.updateTemplate— 3 originals + 6 from the rolling PR (#102-#107).Backwards compatibility
Public RPC surface and
hero_docsCLI: strictly additive. Existing methods/subcommands unchanged. The newrecursion_limitattribute is a build-time setting only — no runtime effect.info.versionUnchanged at
0.1.6— single bump per rolling PR.Implementation Summary
docs.publishDevis now exposed over JSON-RPC. Unlikedocs.publish(#106) which uses the default production rsync target with a build-directory path,docs.publishDevtakes a heroscript path/URL and uses the dev destinations defined in heroscript via the!!site.publish_devdirective (parsed byparse_publish(playbook, _, true)incrates/hero_books_docusaurus/src/heroscript.rs).This is the first child issue with a different
pathsemantic from #102-#106 — and it's necessary because dev publish destinations are per-site configured (no global default exists in the codebase), while production publish has a sensible global fallback.Files changed (this iteration)
src/bin/hero_docs.rs— newPublishDev(PublishDevArgs)subcommand. Runner loadsDocSite::from_heroscript(&path), overridesdocsite.name, and callsdocsite.publish_dev().crates/hero_books_server/src/web/rpc.rshandle_docs_publish_devhandler — same shape ashandle_docs_publish, just different action prefix and subcommand name. Thepathvalidation is identical at the RPC layer (non-empty string); the heroscript-vs-build-dir distinction is enforced by the runner insidehero_docs."docs.publishDev"betweendocs.publishanddocs.jobStatus.## Docsdoc-header listsdocs.publishDev.derive_docs_output_pathdoc-comment finalised — all 9docs.*action prefixes are now explicit (no more "upcoming" placeholders).test_docs_publish_dev_missing_params(5 assertions).docs_publish_dev_zzz.crates/hero_books_server/src/web/rpc_spec.rs— inline schema gains adocs.publishDeventry.crates/hero_books_server/openrpc.json— new method entry.info.versionunchanged at0.1.6.crates/hero_books_server/openrpc.client.generated.rs— auto-regenerated.crates/hero_books_server/src/lib.rs— bumpedrecursion_limitto256. The inlineget_openrpc_schemajson!block hit Rust's default 128-recursion limit oncedocs.publishDevwas added. Build-time only; no runtime effect. Inline comment explains why.Tests
cargo clippysilent on both crates.docs.jobStatus. Job correctly failed with"HeroScript directory not found"when given a non-existent path — proving the heroscript-loading code path is invoked.Backwards compatibility
docs.publishDev. All existing methods unchanged.hero_docsCLI: only addspublish-devsubcommand. Existing subcommands unchanged.info.version: unchanged at0.1.6.recursion_limitbump: build-time attribute only; doesn't affect any runtime semantics.Notes
docs.publishvsdocs.publishDev): production publish has a global default rsync target so build-dir + name is sufficient. Dev publish destinations are per-site (defined via!!site.publish_devin heroscript), so heroscript loading is necessary. This divergence is documented in the OpenRPC summaries and the## Docsdoc-header.from_heroscript, notfrom_heroscript_full: publishing doesn't need collection scanning.from_heroscriptis the cheaper variant that only parses heroscript to populatesite.config(includingbuild_dest_dev) andpath_build.nameoverride: lets callers pick a site_name distinct fromsite.config.namewithout editing heroscript. Convention: append_dev(e.g.,my_docs->my_docs_dev).build_dest_dev: if the heroscript has no!!site.publish_devdirectives,docsite.publish_dev()falls back to a single defaultPublishDest(production rsync target) withdocsite.nameas the site_name. This is the same fallback asdocs.publish.pathmust exist and parse,<path_build>/build/must exist (from a priordocs.buildor similar),RSYNCD_SECRETmust be set on the server.Rolling PR status
This commit closes the rolling PR for #101: 6 of 6 children done.
docs.installTemplatedocs.updateTemplatedocs.build(withoutput_pathrecovery via base64url-encoded action names)docs.dev(long-running, no timeout, cancel viajob.cancelJSON-RPC)docs.publishdocs.publishDevPR is mergeable; all 9
docs.*methods discoverable via liverpc.discover.