Skip to content

Add mirror-publish command to publish an existing Dataverse draft - #41

Merged
timlichtenberg merged 3 commits into
mainfrom
tl/dataverse-publish-existing
Sep 11, 2026
Merged

timlichtenberg merged 3 commits into
mainfrom
tl/dataverse-publish-existing

Conversation

@timlichtenberg

Copy link
Copy Markdown
Member

Adds a mirror-publish subcommand that publishes an existing Dataverse draft dataset. It is publish-only and never creates a dataset. It takes a persistent identifier, calls the Dataverse native publish endpoint, and reports the published identifier.

publish_existing_dataverse_draft validates version_type against ('major', 'minor'), then the persistent identifier (must start with doi:, split into a non-empty prefix and suffix around a single /, and contain no whitespace), then calls DataverseClient(...).publish(...). The CLI reads DATAVERSE_TOKEN from the environment and exits with an error if it is unset; the token is never a command-line argument.

A mirror-publish.yaml workflow exposes the command through workflow_dispatch, with inputs persistent_id and version_type (choice of major or minor, default major). Both inputs bind to environment variables, not shell interpolation, so a crafted input cannot inject shell commands.

New unit tests cover the never-creates-a-dataset guarantee, argument forwarding, an already-published dataset (403), a non-JSON success body, malformed identifiers, invalid version types, the order of the two checks, a nonexistent identifier (404), and the CLI wiring. The 23 mirror-publish and publish_existing tests pass against this branch.

docs/Reference/cli.md and docs/How-to/mirror_dataset.md document the subcommand.

Split the create-draft and publish steps of the Zenodo-to-Dataverse
mirror into two deliberate actions. The new fwl-io mirror-publish
subcommand and publish_existing_dataverse_draft() publish an existing
draft by its persistent id and never create a dataset, so publishing is
a separate, auditable step run after a draft has been reviewed.

The persistent id must be of the form doi:<prefix>/<suffix>; a value
that lacks the prefix, the slash, or either part is rejected locally
with ValueError before any network call. The mirror-publish.yaml
workflow targets the default Dataverse URL and binds every dispatch
input to an environment variable, so a crafted input cannot redirect
the API token or inject a shell command.

Covered by unit tests for the never-creates-a-dataset property, the
persistent-id validation, and the CLI wiring; docs/Reference/cli.md and
docs/How-to/mirror_dataset.md document the subcommand.
The --version-type argparse choice bypassed the CLI's own exit-1
error contract by exiting 2 straight from argument parsing. Validate
it inside publish_existing_dataverse_draft instead, alongside the
existing persistent_id check, and restrict the workflow_dispatch
input to the same two values. Also fixes a stale mirror_dataset.md
reference to a dataverse_url input the publish workflow no longer
takes, and tightens the persistent_id test coverage to include an
empty-prefix DOI.
A whitespace-corrupted persistent id like 'doi: 10.34894/DEMO01' passed local validation and only failed later against the live Dataverse API; publish_existing_dataverse_draft now rejects any persistent id that contains whitespace. Adds a test for the version_type/persistent_id check order (version_type is checked first, so an invalid persistent id combined with an invalid version_type surfaces the version_type error).

Also folds the two identical --dataverse-url defaults in cli.py's mirror and mirror-publish subparsers into one DEFAULT_DATAVERSE_URL constant.
@timlichtenberg
timlichtenberg merged commit 353937d into main Sep 11, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant