@dockstat/outline-sync
v1.2.4
Published
Bidirectional sync for Outline wiki to local folders with CI/CD support.
Downloads
491
Readme
Outline Sync
Bidirectional sync for Outline wiki to local folders with CI/CD support.
Installation
bun install -g @dockstat/outline-syncFeatures
- ✅ Sync Outline → Local
- ✅ Sync Local → Outline
- ✅ Push local changes to Outline (new
pushcommand) - ✅ Compare modification dates (mtime) and frontmatter
updatedAtto detect changes - ✅ Custom path mapping for specific documents
- ✅ Collection filtering (include/exclude)
- ✅ Frontmatter metadata preservation
- ✅ CI/CD integration
- ✅ File watching
What's new
Three main improvements were added:
Push command
outline-sync pushlets you push local changes up to Outline without performing a fullsync/syncDownfirst.- It compares local file timestamps against the remote document
updatedAtand only pushes files that appear newer locally.
Modification-date comparison
- When detecting local changes, the tool now prefers a frontmatter
updatedAttimestamp (if present) and falls back to the file system modification time (mtime). - This allows more accurate determination of whether a local edit should be pushed.
- When detecting local changes, the tool now prefers a frontmatter
Duplicate-folder fix (collection/document name collision)
- Fixed an issue where a collection and a top-level document shared the same title (for example: collection "DockStat" and a root document titled "DockStat") and the sync created a nested duplicate folder like
./dockstat/dockstat/README.md. - Now, when a root document's sanitized title equals the collection folder name, the file is written as
./<collection>/README.md(no extra nested folder). This produces a single./dockstat/README.mdfor the example above and avoids confusing directory nesting. - This behavior preserves existing custom paths while preventing accidental duplicate folders when collection and document titles collide.
- Fixed an issue where a collection and a top-level document shared the same title (for example: collection "DockStat" and a root document titled "DockStat") and the sync created a nested duplicate folder like
Configuration
Create outline-sync.config.json (recommended):
{
"url": "https://your-outline.com",
"token": "your_api_token",
"outputDir": "./outline-docs",
"includeCollections": ["Engineering", "Product"],
"excludeCollections": [],
"customPaths": {
"example-doc-id": "../../README.md",
"another-doc-id": "custom/path/document.md"
}
}You can also use environment variables:
export OUTLINE_URL=https://your-outline.com
export OUTLINE_TOKEN=your_api_token
export OUTLINE_OUTPUT_DIR=./outline-docsOr pass options via CLI (these take precedence over env/config file).
Usage
Commands
outline-sync init
Create a sampleoutline-sync.config.jsonin the current directory.outline-sync sync
One-time sync from Outline → local. Pulls documents and writes frontmatter (includingupdatedAt) into eachREADME.md.outline-sync watch
Watches youroutputDirfor local changes and pushes them to Outline as they happen (bidirectional).outline-sync ci
CI/CD friendly flow: performs asyncDown(pulls remote docs and caches theirupdatedAt), finds local files newer than the cached remote timestamps, and pushes those changes.outline-sync push(new)
Push local changes to Outline by comparing each local file against the actual remote documentupdatedAt. This does NOT perform an initialsyncDown. For each local markdown file:- The tool reads frontmatter for an
id(document ID). If missing, the file is skipped. - It prefers frontmatter
updatedAt(if present) when comparing timestamps. - Otherwise it falls back to the file system
mtime. - It fetches the remote document's
updatedAt(if not already cached) and compares. - If the local timestamp is later than remote
updatedAt, the file is pushed. - If the remote document cannot be fetched (deleted/permission issues), the file is included in the push list so you can inspect it.
- The tool reads frontmatter for an
Examples
Sync all collections (uses config/env or CLI args for credentials if required):
outline-sync syncWatch and auto-push local edits:
outline-sync watch --include "Engineering"CI job (pull then push any local changes that are newer than remote):
outline-sync ci --exclude "Private,Draft"Push local changes (no initial sync; queries remote per-file):
outline-sync pushPush while specifying config/credentials inline:
outline-sync push --url https://my.outline.app --token <YOUR_TOKEN> --output ./outline-docsHow change detection works
When deciding whether a local file should be pushed upward, the tool uses this priority:
- frontmatter
updatedAt(if present in the markdown file's frontmatter) - file system
mtime(the file's modification time on disk)
That local timestamp is compared against the remote document's updatedAt. If the local timestamp is newer, the file is scheduled to be pushed.
Notes:
outline-sync ciwill populate an internal cache of remoteupdatedAtvalues during the initialsyncDownand use that to avoid fetching each remote doc again.outline-sync pushwill fetch remoteupdatedAtper-document as it evaluates each file (useful for CI runs where you didn't perform asyncDownfirst).- Frontmatter must contain a valid
id(document ID) for a file to be considered for push. Ifidis missing the file will be skipped.
Frontmatter format
Each synced file includes frontmatter with metadata similar to:
---
id: doc-id-123
title: My Document
collectionId: col-456
parentDocumentId: null
updatedAt: 2025-01-01T12:34:56.000Z
urlId: my-document-urlid
---The updatedAt field is written by sync when pulling from Outline. If you edit a file and update the frontmatter updatedAt manually to a newer timestamp, the tool will honor that value when deciding to push.
Safety notes & best practices
- Always ensure your frontmatter
idis present if you expect a file to be pushed back to Outline. - When using
pushin CI, be aware it will fetch remote metadata for every document being considered; this will incur API calls. - If a file cannot be matched to a remote document (missing id or fetch error), it will be surfaced so you can investigate before pushing.
CI/CD example (GitHub Actions)
name: Outline Sync
on:
push:
branches: [main]
schedule:
- cron: "0 */6 * * *"
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- run: bun install
- run: bun run ci
env:
OUTLINE_URL: ${{ secrets.OUTLINE_URL }}
OUTLINE_TOKEN: ${{ secrets.OUTLINE_TOKEN }}
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "docs: sync from Outline"Troubleshooting
- If you see files being pushed unexpectedly, inspect their frontmatter
updatedAtvalues and filemtime. - If push fails due to permissions, confirm the API token has the required document update scope.
- For large repos, consider running
syncin a scheduled job and usingpushonly when necessary to reduce API calls.
