kugarocks/bookstack-content-sync

Pulling BookStack content into a local directory, making changes, and pushing it back.

Maintainers

Package info

github.com/kugarocks/bookstack-content-sync

pkg:composer/kugarocks/bookstack-content-sync

Statistics

Installs: 33

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.0 2026-04-16 17:25 UTC

This package is auto-updated.

Last update: 2026-04-17 02:08:40 UTC


README

Pulling BookStack content into a local directory, making changes, and pushing it back.

Requirements

  • PHP >=8.2
  • BookStack >=26.03

Quick Start

Initialize Directory

Create a local content directory and starter sync.json:

php artisan bookstack:init-content-dir /path/to/content

init-content-dir

This command creates the target directory if needed, writes sync.json, and reminds you which environment variables to export before running a pull.

{
    "version": 1,
    "app_url": "http://localhost:8080",
    "bookstack_path": "/path/to/bookstack",
    "content_path": "content",
    "env_vars": {
        "token_id": "BOOKSTACK_API_TOKEN_ID",
        "token_secret": "BOOKSTACK_API_TOKEN_SECRET"
    }
}

Pull Content

php artisan bookstack:pull-content /path/to/content

pull-content

Push Plan

php artisan bookstack:push-content /path/to/content

push-plan

Push Execution

php artisan bookstack:push-content /path/to/content --execute

push-execution

How it Works

This system performs a one-way sync from local content to BookStack by computing state differences and applying them to the remote.

Push Mechanism

  • Sync is one-way: local content is the source of truth.
  • bookstack:push-content builds the current local state.
  • It compares this state with the previous snapshot.json.
  • Based on the diff, it determines and executes the required remote actions.

Behavior & Constraints

  • Data constraint
    • A book can belong to only one shelf at a time.
  • Naming & ordering
    • Prefixes such as 01-xxx, 02-xxx are used to define ordering.
    • Items are sorted lexicographically based on these prefixes.
  • Tag ordering
    • Tag order is preserved in local files and treated as part of content changes.
  • Renaming behavior
    • Renaming local files or directories only updates the file field in snapshot.json.
    • It does not affect the identity of the corresponding remote entity.

Slug Behavior

  • Official BookStack does not preserve custom slugs for content entities via the API.
  • Hosts with custom slug support enable this behavior.
  • If the requested slug is not preserved, push-content treats the remote slug as the source of truth.
  • The command emits a warning and rewrites both the local file slug and the snapshot.json slug to match the remote value.

Empty Page Behavior

  • Because the BookStack API does not support empty page content, remote sync uses a reserved transport placeholder while local empty pages remain "".
  • When pushing an empty page, the remote transport uses the reserved placeholder <!-- bookstack-content-sync:empty-page:v1 -->.
  • When pulling, that placeholder is decoded back to "", and snapshot.json plus content hashing continue to use the decoded empty-string value.

Installation

Packagist

composer require kugarocks/bookstack-content-sync

Local Development

For local development against an unpublished working tree, add the local repository to the BookStack host composer.json:

{
  "repositories": [
    {
      "type": "path",
      "url": "../bookstack-content-sync",
      "options": {
        "symlink": true
      }
    }
  ]
}

Then require the package from the BookStack host:

composer require kugarocks/bookstack-content-sync:*@dev

The @dev suffix is only needed for local path installation while the package is resolved as dev-main.

Global Wrapper

Create a global command that can be run from any content directory:

# From a BookStack installation
ln -sf /path/to/bookstack/vendor/kugarocks/bookstack-content-sync/bin/bookstack-sync /usr/local/bin/bookstack-sync

# From this repository
ln -sf /path/to/bookstack-content-sync/bin/bookstack-sync /usr/local/bin/bookstack-sync

The wrapper reads sync.json in your current directory and runs the matching BookStack artisan command using bookstack_path.

Setup

Set the global BookStack path (one-time setup):

bookstack-sync config set-bookstack-path /path/to/bookstack

This creates ~/.config/bookstack-content-sync/config.json with your BookStack installation path.

Usage

# Initialize a new content directory
bookstack-sync init /path/to/content

# Pull content
bookstack-sync pull

# Generate push plan
bookstack-sync push

# Execute push
bookstack-sync push --execute

Configuration

Path resolution priority:

  1. bookstack_path in current directory's sync.json
  2. Global config at ~/.config/bookstack-content-sync/config.json
  3. Error if neither is found

Example sync.json with project-specific path:

{
    "version": 1,
    "app_url": "http://localhost:8080",
    "bookstack_path": "/path/to/bookstack",
    "content_path": "content",
    "env_vars": {
        "token_id": "BOOKSTACK_API_TOKEN_ID",
        "token_secret": "BOOKSTACK_API_TOKEN_SECRET"
    }
}

When running bookstack-sync init, the bookstack_path is automatically written to sync.json using the global config value.

Testing

Install dependencies and run the full package test suite:

composer install
composer test

Test boundaries:

  • Unit tests validate isolated sync logic.
  • Integration tests execute real local file reads and writes in temporary directories.
  • Integration tests do not call a real BookStack server; HTTP is mocked through the package test shim.

Run only unit tests:

composer test-unit

Run only integration tests:

composer test-integration

Run pull-focused tests:

composer test-pull

Run push-focused tests:

composer test-push