npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vault2confluence

v0.4.2

Published

One-way sync of a local Obsidian/markdown vault to a Confluence Cloud page tree

Downloads

252

Readme

vault2confluence

One-way sync of a local markdown vault (Obsidian or otherwise) onto a page tree in Confluence Cloud. Local files are the single source of truth: every run mirrors the source folder's structure onto Confluence exactly, overwriting existing content and trashing any Confluence page under the target root that no longer has a matching local file/folder.

Installation

From npm (Recommended)

npm install -g vault2confluence

Or from source

git clone https://github.com/huayueh/vault2confluence.git
cd vault2confluence
npm install
npm run build
npm link

Setup & Authentication

Auth is via environment variables (not CLI flags — configure once, reuse across runs). Either export them in your shell profile, or create ~/.vault2confluence in your home directory — the CLI loads it automatically on every run, regardless of what directory you run the command from. Never commit credentials to version control.

# ~/.vault2confluence
[email protected]
CONFLUENCE_API_TOKEN=<from https://id.atlassian.com/manage-profile/security/api-tokens>

Must be a classic (unscoped) API token — the client talks to the site-specific /wiki/rest/api/... URL directly with Basic Auth, which scoped tokens (routed through api.atlassian.com/ex/confluence/<cloudid>/... instead) don't support here. When creating the token, use the plain "Create API token" button, not "...with scopes".

Usage

vault2confluence sync \
  --source /path/to/vault/folder \
  --target "https://yourorg.atlassian.net/wiki/spaces/ENG/pages/123456789/Page+Title" \
  --prefix "myproject" \
  [--dry-run] \
  [--yes]
  • --source: Required. Local folder (walked recursively) or a single .md file.
    • When --source is a single file, it creates or updates that single page under --target without trashing sibling pages.
  • --target: Required. A Confluence page URL. This page must already exist — the tool never creates or renames the root, only the tree underneath it. The page ID is parsed straight out of the URL; short links (/wiki/x/...) aren't supported, paste the full address-bar URL.
  • --prefix: Required. Short project prefix code (e.g. li, lp, eng) to namespace all Confluence page titles, preventing space-wide title collisions.
    • Numbered titles: 01-overview $\to$ 01-li-overview, 1.1-ui-signup $\to$ 1.1-li-ui-signup.
    • Non-numbered titles: account-management $\to$ li-account-management, glossary $\to$ li-glossary.
  • --dry-run: prints every planned create/update/delete without calling any write API. Always run this first against a new root.
  • --yes: skips the interactive confirmation prompt before trashing orphaned pages (for non-interactive use in directory mode). Without it, you'll be prompted once with the full list of pages that are about to be trashed.

Run with --dry-run during development: npm run dev -- sync --source ... --target ... --dry-run.

Design (why it works this way)

  • Hierarchy: source folders mirror onto container pages 1:1; markdown files become leaf pages. A folder's body is its own index.md/_index.md if present, otherwise an auto-generated list of its direct children (regenerated every run) — it's a navigational stand-in, not meant to hold real content of its own.
  • Identity: pages are matched by title, scoped to space + immediate parent — there's no stored page ID anywhere (not even in frontmatter), specifically so pre-existing Confluence pages that predate this tool are found and updated in place rather than duplicated. Trade-off: renaming a local file creates a new page and orphans the old one.
  • Frontmatter is invisible in the page body. It's parsed and stripped, never rendered. It has exactly two operational uses: tags → Confluence labels (domain/x → domain:x), and links → folded into the same link-resolution index used for inline [[wikilinks]]. Everything else in frontmatter (summary, generated.*, warnings, sources, etc.) is parsed only so it doesn't break anything, then discarded.
  • Always overwrite: no drift detection, no "was this hand-edited in Confluence" check. Local is truth.
  • Deletion: the entire page subtree under --target is considered exclusively owned by this sync. Anything there without a matching local file/folder gets trashed (Confluence's default DELETE — recoverable by a space admin for the retention window), gated by a confirmation prompt unless --yes.
  • Wikilinks ([[note]], [[note|alias]], ![[embed]]) are masked to placeholder tokens in the raw text before the markdown parser ever sees them — not patched up afterward in the AST. This matters because | inside a table cell is otherwise indistinguishable from a GFM table separator to the parser (confirmed against a real file in this vault: 03-iam-and-auth/../02-architecture/index.md has a wikilink-with-alias inside a table cell that would otherwise get split into two cells).
    • Unresolved or ambiguous targets fall back to plain text + a warning, never a crash.
    • ![[note-name]] (transclusion) has no Confluence equivalent — rendered as a link to that note instead, with a warning.
    • Malformed nested-bracket artifacts (found in real vault content, likely from a prior automated tool) degrade to readable-ish plain text rather than throwing.
  • Images: both ![alt](relative.png) and ![[embed.png]] resolve against the note's own directory (then the vault root as a fallback), upload as Confluence attachments, and embed via <ac:image>. Images not found on disk are skipped with a warning, not fatal.
  • Callouts (> [!warning] ...) map onto Confluence's four panel macros (info/tip/note/warning) — Obsidian has ~15 callout types, Confluence has four, so this is a many-to-one collapse (see CALLOUT_TO_PANEL in src/markdown/macros.ts).
  • Dead file:// links (absolute local paths into source code, common in this vault) are dropped — kept as visible text only, with a warning — since they're meaningless off the author's machine.

Known gap: Mermaid

Confluence Cloud has a native "Mermaid diagram" macro, but its exact storage-format markup (macro key + schema) hasn't been captured yet. Mermaid blocks currently render as a plain code block (safe, valid, readable — just not an actual diagram), with a warning on every sync. To wire up real diagram rendering:

  1. Create a page manually in the Confluence editor, insert a Mermaid diagram block.
  2. Fetch that page's body via the API in storage representation (GET /rest/api/content/{id}?expand=body.storage).
  3. Copy the literal <ac:structured-macro ...> markup you get back into mermaidBlock() in src/markdown/macros.ts, replacing the current code-block fallback.

Project layout

src/
  cli.ts                    entrypoint (commander)
  confluence/
    auth.ts                 env-var auth loading
    targetUrl.ts             --target URL -> {baseUrl, pageId}
    client.ts                REST API wrapper (content, attachments, labels, trash)
  markdown/
    frontmatter.ts           split + parse frontmatter; tags -> labels
    linkIndex.ts              title -> target index, built once per run
    wikilinks.ts              wikilink masking/resolution (pre-parse + AST-level)
    macros.ts                 storage-format fragment builders (panels, code, mermaid stub, images)
    toStorageFormat.ts        the markdown AST -> Confluence storage-format serializer
  sync/
    walk.ts                   walks --source into the folder/file tree
    render.ts                 tree node -> {storage, labels, attachments}
    syncer.ts                 orchestrates create/update/upload/label/delete against the API
  util/
    logger.ts, mime.ts, prompt.ts

Status

Validated against live Confluence Cloud instances with production vault content (80+ pages synced across deeply nested hierarchies). Confirmed: frontmatter stripping, tag→label conversion, wikilink resolution (including the table/alias collision bug), dead-link handling, mermaid fallback, folder-index generation, and image attachment upload. Always run --dry-run first and review the planned actions, especially on the very first run against any given root.