Files
distributor/docs/internal/publish.md

3.8 KiB

Publish

Purpose

internal/publish plans and executes publication for one validated source bundle and one destination.

Inputs and outputs

Inputs are a source bundle, source backend, destination backend, pipeline id, destination id, publish policy, transform policy, optional link policy, transformer resolver, transfer policy, path mapping mode, destination bundle path, existing destination state, and whether explicit force was requested for the current run.

Output is a plan with an action, reason, optional primary URL, and selected source or generated outputs. Execution writes selected source files, generated files, and .distributor.json for publish or replacement actions.

Actions

Supported actions are publish_new, replace_older, force_replace, skip_same, skip_destination_newer, fail_conflict, and fail_unmanaged.

Failure behavior

Planning fails when request fields are incomplete, publish and transform policies are invalid, selected outputs collide, HTML output is requested without Markdown inputs, destination state is invalid, destination content is unmanaged without force, or transfer policy maps the comparison outcome to failure.

Execution fails if a write, delete, state serialization, or context check fails. Outputs written during a failed publish attempt are cleaned up through managed deletion where possible.

Boundaries

The package publishes source files and Markdown-to-HTML outputs. Markdown sidecar mode writes same-directory .html outputs, and Markdown index mode writes index.html. Backend behavior is supplied through internal/storage; app runtime supplies local, SSH, and S3 backends.

The package uses internal/state for destination comparison, internal/storage for IO, and the shared internal/config publish/transform policy helper for request validation. It resolves transforms through a narrow resolver supplied by the caller; concrete transform registration is owned by the app layer. It does not parse CLI flags, load config files, or choose which source bundles a destination receives.

The app layer computes the destination bundle path before planning. preserve_relative destinations pass the source-root-relative bundle path. fixed destinations pass an empty destination bundle path, which means the destination backend root, and pass only the newest selected source bundle for that destination.

When link config is present, publish planning builds per-output URLs from links.base_url, the destination bundle path, and each output path. index.html outputs use directory-style URLs. The primary URL is selected from planned outputs according to the destination primary policy.

Safety

Normal replacement deletes only outputs recorded in existing destination state plus .distributor.json. Forced replacement deletes the bounded destination bundle path before writing outputs and state. Failed writes trigger cleanup of outputs written during the failed attempt where practical.

Tests

Before changing publish behavior, inspect tests under internal/publish and run tests under internal/app.

Invariants

  • Publish planning is deterministic for the same source, destination state, policies, and transform outputs.
  • Destination bundle paths are caller-supplied and are interpreted relative to the destination backend root.
  • URL generation uses URL path semantics and does not infer public URLs from backend configuration.
  • Normal replacement deletes only managed paths recorded in existing state plus .distributor.json.
  • Forced replacement is explicit per run and deletes only within the destination bundle path.
  • Publish execution writes destination state after selected outputs are written.
  • Transform implementations are resolved through an interface supplied by the caller.
  • Unmanaged destination content is overwritten only by explicit forced replacement.