Add docs.build RPC method #104
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#104
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.buildParams:
{ path: string }Returns:
{ job_id: string }Wraps:
DocSite::build()atlib.rs:120— directly, without thenew/generatepreludeNote:
docs.jobStatusshould reportoutput_pathpointing at<path>/buildImplementation steps
hero_docs buildsubcommand existshandle_docs_build(id, params, config) -> RpcResponseincrates/hero_books_server/src/web/rpc.rsdocs_build_<input_hash>sohandle_docs_job_statusprefix-strip logic atrpc.rs:1508-1519can recoveroutput_path"docs.build"atrpc.rs:155openrpc.json, bumpinfo.versionrpc.rs:9-28Acceptance Criteria
hero_docs buildsubcommand existsdocs.jobStatusreturns correctoutput_path=<path>/buildrawdaGastan referenced this issue2026-04-26 15:06:11 +00:00
Implementation Spec for Issue #104
Objective
Expose
DocSite::build()directly over the OpenRPC interface asdocs.build, without thenew/generateprelude. Unlikedocs.installTemplate/docs.updateTemplate, the issue requiresdocs.jobStatusto returnoutput_path=<path>/build— so the user-suppliedpathmust be recoverable when polling status. We achieve this by encoding the path into the action name with URL-safe base64 (no padding) instead of a one-way hash, while preserving the dedup property ofsubmit_or_dedup_docs_job(same path produces the same encoded action name).Requirements
docs.buildis dispatchable fromcrates/hero_books_server/src/web/rpc.rsand reaches a new handlerhandle_docs_build.crates/hero_books_server/openrpc.jsonand the inline schema inrpc_spec.rs(noexamples).info.versioninopenrpc.jsonis not bumped — single bump per rolling PR (already done in #102).## Docssection in therpc.rsfile-level doc-header gains adocs.buildbullet.hero_docs(src/bin/hero_docs.rs) gains a newbuildsubcommand:hero_docs build --path <path>. It callshero_books_docusaurus::build::build(&path)directly — no heroscript loading.config.hero_docs_bin, neverDocSitein-process.docs_build_<base64url(path)>usingbase64::engine::general_purpose::URL_SAFE_NO_PAD. No separate input hash — the encoded path itself serves as the dedup key. Same path always produces the same name; different paths produce different names.handle_docs_job_statusis extended to strip thedocs_build_prefix, base64url-decode the suffix, and surfaceoutput_path=<decoded_path>/buildwhen the job is indonestate.RpcResponse::invalid_params(-32602); internal failures useRpcResponse::error(id, -32000, ...).Files to Modify/Create
src/bin/hero_docs.rs— newBuild(BuildArgs)variant, args struct, runner.crates/hero_books_server/src/web/rpc.rs—handle_docs_build, dispatch arm,handle_docs_job_statusextended fordocs_build_prefix → output_path, doc-header bullet, prefix-recognition comment update, two unit tests.crates/hero_books_server/src/web/rpc_spec.rs— inlinedocs.buildentry.crates/hero_books_server/openrpc.json—docs.buildmethod entry.No new files.
Implementation Plan
Step 1 — Add
buildsubcommand tohero_docsFiles:
src/bin/hero_docs.rsCommandsenum withBuild(BuildArgs).main()alongside the existing arms.Dependencies: none.
Step 2 — Add
handle_docs_buildhandlerFiles:
crates/hero_books_server/src/web/rpc.rshandle_docs_update_templatewith signaturefn handle_docs_build(id: Option<Value>, params: Option<Value>, config: &ServerConfig) -> RpcResponse.path(required, non-empty). Reject empty / missing withRpcResponse::invalid_params(id, "missing or empty 'path' parameter").format!("{} build --path {}", hero_docs, path_q)usingshell_quote.submit_or_dedup_docs_job(id, config, &action_name, &script).Dependencies: Step 1 (subcommand must exist for runtime; not required for compilation).
Step 3 — Wire dispatch arm
Files:
crates/hero_books_server/src/web/rpc.rsdocs.updateTemplateand beforedocs.jobStatus:Dependencies: Step 2.
Step 4 — Extend
handle_docs_job_statusto surfaceoutput_pathfordocs_build_*Files:
crates/hero_books_server/src/web/rpc.rsif mapped_state == "done"block stripsdocs_new_/docs_generate_and joins with<cache>/<hash>/build. Extend it:docs_new_/docs_generate_strip-list (unchanged).docs_build_:output_path. The state and any error tail still flow through.docs_new_*/docs_generate_*→<cache>/<hash>/builddocs_build_*→<base64url-decoded path>/builddocs_install_template_*/docs_update_template_*→ nooutput_path(path not encoded)docs_dev_*/docs_publish_*/docs_publish_dev_*from #105-#107 → nooutput_pathDependencies: Step 2.
Step 5 — Update doc-header
Files:
crates/hero_books_server/src/web/rpc.rs## Docssection, insertdocs.buildafterdocs.updateTemplate:Dependencies: none.
Step 6 — Add
docs.buildentries (openrpc.json + rpc_spec.rs)Files:
crates/hero_books_server/openrpc.json,crates/hero_books_server/src/web/rpc_spec.rsopenrpc.json: insert new method object betweendocs.updateTemplateanddocs.jobStatus:rpc_spec.rs: insert the same entry into the inline schema betweendocs.updateTemplateanddocs.jobStatus.info.version(already at0.1.6for this rolling PR).Dependencies: Step 3.
Step 7 — Add unit tests
Files:
crates/hero_books_server/src/web/rpc.rstest_docs_build_missing_params— same shape astest_docs_update_template_missing_params: missing/empty/no-params all return-32602.test_docs_build_action_name_round_trip— encode a sample path withURL_SAFE_NO_PAD, prependdocs_build_, then strip the prefix and decode, asserting the original path comes back. This locks the encoding choice and guards against future drift.Dependencies: Step 2.
Step 8 — Verify and live-test
Files: none (read-only check + cargo + curl).
cargo check -p hero_books_serverandcargo check --bin hero_docssucceed.cargo test -p hero_books_server --lib— 20 passing (two new), no regressions.cargo run --bin hero_docs -- build --help.output_pathreturns<path>/buildoncestate == "done"(or expected failure tail ifbunnot installed).Dependencies: Steps 1-7.
Acceptance Criteria
hero_docs build --helpprints usage with--path(required).crates/hero_books_server/src/web/rpc.rsdefineshandle_docs_build.docs.buildis wired into the dispatcher betweendocs.updateTemplateanddocs.jobStatus.docs_build_<base64url_no_pad(path)>; same path produces the same action name (idempotent dedup).handle_docs_job_statusreturnsoutput_path=<path>/buildfordocs_build_*jobs indonestate, by strip-then-base64-decode.handle_docs_job_statussilently omitsoutput_pathrather than emitting a bogus value.## Docsdoc-header inrpc.rslistsdocs.build.crates/hero_books_server/openrpc.jsonandrpc_spec.rsinline schema both contain thedocs.buildmethod entry.info.versioninopenrpc.jsonis unchanged at"0.1.6".cargo check -p hero_books_serverandcargo check --bin hero_docssucceed.cargo test -p hero_books_server --libpasses (with two new tests).Notes
action_idfield is a sqlite TEXT column with no fixed limit, so even pathological cases comfortably fit. Documented for future maintainers.input_hash: dedup is naturally stable on the encoded path. Adding a hash on top would force two callers with the same path to compute the same hash anyway, so there's no benefit./tmp/xand/tmp/x/are treated as distinct paths and produce differentoutput_pathvalues (/tmp/x/buildvs/tmp/x//build). If we later want canonicalisation, we'd canonicalise before encoding so the dedup key changes accordingly. Out of scope for #104.handle_docs_job_statusnow grows a third arm (afterdocs_new_/docs_generate_and before the silent fall-throughs). Future #105-#107 may add similar arms (or stay silent) — keep the comment block accurate as each child lands.docs.installTemplateordocs.generatehas run there first).bunruntime required, same as #102/#103.Test Results
Suite:
cargo test -p hero_books_server --libTotal: 23 — Passed: 23 — Failed: 0
New tests added with this change (4):
test_docs_build_missing_params— missing/empty/no-paramsdocs.buildreturns-32602.test_derive_docs_output_path_for_docs_build— fordocs_build_<base64url(path)>action names, the helper returns<path>/build. Tested with three paths including one containing spaces and special characters.test_derive_docs_output_path_for_docs_new_and_generate— preserves the existing contract:docs_new_<hash>/docs_generate_<hash>→<cache>/<hash>/build.test_derive_docs_output_path_returns_none_for_install_update_template— install/update template, dev, and unknown prefixes returnNone.test_derive_docs_output_path_returns_none_for_malformed_docs_build— invalid base64 or valid base64 of non-UTF-8 bytes returnsNone. Decoder fails closed.Build & lint
cargo check -p hero_books_server— OKcargo check --bin hero_docs— OKcargo clippy -p hero_books_server --lib --no-deps— silent (zero warnings)cargo clippy --bin hero_docs --no-deps— silentcargo test --release— 23/23Spec/impl parity
INSTRUCTIONS_OPENRPC.md §Verification diff is empty —
openrpc.jsonandrpc.rsdispatcher agree, withdocs.buildlisted in both.CLI smoke test
Live end-to-end RPC test (against running hero_books_server + hero_proc)
rpc.discoverlistsdocs.buildwith requiredpathpathreturns-32602 "missing or empty 'path' parameter"docs.buildfor/tmp/test_build_rpcreturns{"job_id":"89"}job_id: "89"job_id: "90"action_id = docs_build_L3RtcC90ZXN0X2J1aWxkX3JwYw(decodes back to/tmp/test_build_rpc)output_pathintegration confirmed viadocs.new: returnedstate: donewithoutput_path: /home/rawda/hero/var/books/.docusaurus_cache/c3cf0e37cf92889c/build— samederive_docs_output_pathhelper code path that servesdocs.build.output_pathcorrectly omitted whenstate == failed(guarded byif mapped_state == "done")A direct end-to-end
docs.buildtostate == donewould require a fully-prepared Docusaurus site (config.ts that resolves all modules), which needs heroscript content viadocs.generatefirst — circular for this verification. Combined unit-level proof ofderive_docs_output_path(the only logic specific to this change) plus live integration proof viadocs.new(same handler call site) covers the entireoutput_pathcode path.info.versionUnchanged at
0.1.6— single bump per rolling PR (#102 already did it).Implementation Summary
docs.buildis now exposed over JSON-RPC, satisfying the issue requirement thatdocs.jobStatusreturnsoutput_path = <path>/buildfor completed build jobs. The user-supplied path is preserved by encoding it directly into the hero_proc action name with URL-safe base64 (no padding) instead of a one-way hash, so the decoder can recover it on status polls.This is a slight pattern shift from #102 / #103: the install/update template family uses hashed action names and produces no
output_path.docs.builduses path-encoded action names and produces a recoverableoutput_path.Files changed (this iteration)
src/bin/hero_docs.rs— newBuild(BuildArgs)subcommand wrappinghero_books_docusaurus::build::build(&path).crates/hero_books_server/src/web/rpc.rshandle_docs_buildhandler — encodes path withbase64::engine::general_purpose::URL_SAFE_NO_PADinto action namedocs_build_<encoded>. No separate input hash — the encoded path itself serves as the dedup key."docs.build"betweendocs.updateTemplateanddocs.jobStatus.derive_docs_output_path(action_name, cache_dir)— refactor that pulls the path-recovery logic out ofhandle_docs_job_status's closure into a pure function. Three buckets:docs_new_*/docs_generate_*-><cache>/<hash>/build;docs_build_*-><base64url-decoded path>/build; everything else ->None. The call site inhandle_docs_job_statusis now a one-liner.## Docsdoc-header updated.crates/hero_books_server/src/web/rpc_spec.rs— inline schema gains adocs.buildentry.crates/hero_books_server/openrpc.json— new method entry.info.versionunchanged at0.1.6.crates/hero_books_server/openrpc.client.generated.rs— auto-regenerated.Tests (no-bugs pass)
cargo clippy -p hero_books_server --lib --no-deps— silent.cargo clippy --bin hero_docs --no-deps— silent.docs.newreturnsoutput_pathcorrectly via the samederive_docs_output_pathhelper that servesdocs.build.action_idformat (docs_build_<base64url>decodes to original path).Notes
Nonerather than emitting a bogusoutput_path— locked bytest_derive_docs_output_path_returns_none_for_malformed_docs_build.docs.buildpre-condition: the path must contain a fully-prepared Docusaurus site (template + heroscript-generated content). Samebunruntime requirement as #102/#103.rawdaGastan referenced this issue2026-04-26 15:48:26 +00:00
rawdaGastan referenced this issue2026-04-26 16:20:59 +00:00
rawdaGastan referenced this issue2026-04-27 09:23:12 +00:00