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

@uniformdev/siphon-explorer

v1.0.2

Published

Siphon Explorer

Readme

Siphon Explorer

A local developer tool for browsing Sitecore content tree data exported to JSON files. Runs as a Node.js CLI that spins up a Next.js web app on localhost.

Purpose

When working with exported Sitecore databases, it's useful to be able to explore the content tree, inspect item fields, and follow references between items — without needing a running Sitecore instance. Siphon Explorer provides a read-only UI that mirrors the Sitecore Content Editor interface.

Features

Content tree (left panel)

  • Full Sitecore item hierarchy, expandable/collapsible, sorted by Sortorder
  • Virtual folder nodes — path segments missing from the index are shown as non-selectable grey 📁 folders so the tree is always structurally complete
  • "content" first under /sitecore — the content node is always sorted first among its siblings
  • "Data" child always first — under any page item (HasLayout: true), the "Data" child node is sorted to the top regardless of Sortorder
  • Icons — pages (HasLayout: true) show 🌐; items with a file on disk show a yellow CSS folder icon; items named Data with a file show a green folder icon; virtual path-gap folders show 📁; items in the index but without a JSON file show no icon and are not selectable
  • Expand all / Collapse all buttons in the panel header; Collapse all keeps the currently selected item's ancestors expanded
  • Child / descendant counts — nodes with children show gray counts on the right: {children} ({descendants} items {x}%, {descendantPages} pages {y}%) where item % is relative to all items in the dataset and page % is relative to total counted pages. By default (smart counting), only pages that are (a) descendants of a node named Home and (b) not inside a Data subfolder of another page are counted. The Legacy counting checkbox next to the sort control switches to the original behavior: all items with HasLayout under /sitecore/content are counted
  • SearchCtrl+E (or Cmd+E) opens a search box; results filter by item name with multi-word support (all words must match, e.g. cha eugene finds CHA Website Eugene's Copy); each result displays the same icon as in the tree (🌐, 🔴, folder, paper); when a GUID is entered, an additional lookup checks whether it matches any item's Item ID, Composition ID, or Entry IDs — if found, the matched item appears at the top of results with a label showing which ID type matched; Escape closes
  • Full Text Search — click "Full Text Search" link in the panel header to open a modal; searches across all field values; supports glob-style patterns: abc*def matches text containing abc then def anywhere after it (ignoring line breaks); indexing runs in the background on startup and is resumable across restarts via a search-cache.json file in the data directory

Item detail (right panel)

  • Header icon — the detail header shows the same icon as the tree (🌐, 🔴, folder, paper) instead of a static 🗃️
  • Quick Info section: Display name, Item ID, Item name, Item path, Template, Template ID, Language, Database
  • Item path breadcrumb — each path segment is a clickable link that navigates to that item; segments without a corresponding file on disk are shown as plain text (not clickable)
  • Fields grouped by section, sections sorted by Sortorder; field name and value separated by a fixed-width vertical divider
  • Referrers tab — shows all items whose field values contain the current item's GUID; uses the same background full-text index as Full Text Search; available on all items (pages and non-pages alike)
  • Hide empty fields checkbox — hides fields with no value; state persisted in localStorage
  • Hide system fields checkbox — hides fields whose name starts with __; state persisted in localStorage
  • GUID navigation — any GUID value (Item ID, Template ID, field values, pipe-separated multilist/treelist values) is rendered as a clickable blue link that navigates to that item
  • Resizable panels — drag the divider between the tree and detail panels to adjust the split; constrained between 200px and 480px
  • Copy buttons — each field value and Quick Info row shows a copy button (⎘) on hover that copies the raw value to the clipboard; turns green (✓) briefly on success

Navigation

  • URL persistence — selected item ID stored as ?id=<uuid>; F5 restores the same item with ancestors expanded
  • Browser back/forward — each item selection is a router.push entry; Back/Forward work as expected; the tree panel automatically scrolls to keep the selected item visible
  • Browser history titlesdocument.title is updated to Siphon Explorer — /sitecore/content/… on each selection, so Chrome history shows the item path rather than a generic URL
  • Default selection — on first load (no URL param), /sitecore/content is pre-selected if present

Usage

# Install dependencies (once)
npm install

# Start the browser
node bin/siphon-explorer.js ./data --port 3000

# Or if installed globally via npm link / npm install -g
siphon-explorer ./data --port 3000

The server bundles the React frontend with esbuild at startup (takes < 1 second), then serves everything from a single HTTP server — no Next.js, no build step required.

Then open http://localhost:3000 in your browser.

<data-dir> must be the root of the exported Sitecore data, containing an items/ subdirectory (see Data Format below).


Data Format

The tool expects the following structure inside the data directory:

<data-dir>/
  items/
    en/
      _index.json          # item index (preferred location)
      {uuid}.json          # one file per item
    en_index.json          # alternative index location (checked first)

Index file (_index.json or en_index.json)

A flat JSON object keyed by lowercase UUID without braces:

{
  "0de95ae4-41ab-4d01-9eb0-67441b7c2450": {
    "ItemID": "0de95ae4-41ab-4d01-9eb0-67441b7c2450",
    "Name": "content",
    "DisplayName": null,
    "Path": "/sitecore/content",
    "ParentId": "11111111-1111-1111-1111-111111111111",
    "Sortorder": null,
    "HasLayout": false,
    "TemplateName": "Main section",
    "TemplateID": "e3e2d58c-df95-4230-adc9-279924cece84",
    ...
  }
}

Special entries:

  • 00000000-0000-0000-0000-000000000000 — fake/virtual root used as a sentinel; skipped entirely
  • 11111111-1111-1111-1111-111111111111 — the real Sitecore root (/sitecore)

The HasLayout boolean is used to determine page icons (🌐) and "Data first" sort order.

Item files ({uuid}.json)

Filename uses curly braces and lowercase UUID: {0de95ae4-41ab-4d01-9eb0-67441b7c2450}.json

{
  "ItemID": "{0DE95AE4-41AB-4D01-9EB0-67441B7C2450}",
  "Name": "content",
  "DisplayName": null,
  "Path": "/sitecore/content",
  "TemplateName": "Main section",
  "TemplateID": "{E3E2D58C-DF95-4230-ADC9-279924CEE84}",
  "Language": "en",
  "Database": "web",
  "Fields": [
    {
      "ID": "some-guid",
      "Name": "__Display name",
      "DisplayName": "Display name",
      "Type": "Single-Line Text",
      "Value": "Content",
      "Section": {
        "Name": "Appearance",
        "Sortorder": 100
      }
    }
  ]
}

Subdirectories data/media and data/presentation are ignored.


Project Structure

siphon-explorer/
├── bin/
│   └── siphon-explorer.js        # CLI entry point — parses args, sets env vars, starts server
├── pages/
│   ├── _app.js             # Next.js app wrapper
│   ├── index.js            # Main UI: tree panel + detail panel
│   └── api/
│       ├── tree.js         # GET /api/tree — builds and returns the full tree
│       └── item/
│           └── [id].js     # GET /api/item/:id — returns a single item's JSON
├── styles/
│   └── globals.css         # All styles (no CSS modules, no Tailwind)
├── server.js               # Custom Next.js HTTP server
├── next.config.js
└── package.json

Architecture Notes

Server startup

bin/siphon-explorer.js sets DATA_DIR and PORT as environment variables, then require('../server'). The server starts Next.js in dev mode (no build step needed). NODE_ENV is intentionally left unset/as-is so dev mode is used by default.

Tree building (pages/api/tree.js)

The tree is built entirely from item paths, not from ParentId. This is more reliable because ParentId references can point to items not present in the index.

Algorithm:

  1. Build a byPath map (lowercase path → node) from the index. Each node includes hasLayout and hasFile (whether {id}.json exists on disk).
  2. For each real node, call ensureNode(parentPath) — this recursively creates virtual folder nodes for any missing path segments and links them upward all the way to the root
  3. Roots are detected as nodes not referenced as a child by any other node
  4. Children are sorted by Sortorder; under page nodes (hasLayout: true), a child named "Data" is always sorted first

Virtual nodes have id: null and virtual: true. They are expandable but not selectable, displayed with a 📁 icon and grey text.

Nodes whose {id}.json file does not exist on disk have hasFile: false. They appear in the tree (with no icon) but are not selectable and do not trigger a detail panel fetch.

Icon mapping summary:

| Condition | Icon | |-----------|------| | Virtual path-gap folder | 📁 emoji | | hasLayout: true (page) | 🌐 emoji | | name === "Data" and hasFile | Green CSS folder | | hasFile (regular item) | Yellow CSS folder | | hasFile: false (index-only) | (none) |

Item loading (pages/api/item/[id].js)

Accepts any GUID format (with/without braces, any case). Normalizes to {lowercase-uuid}.json and reads directly from <DATA_DIR>/items/en/.

UI state (pages/index.js)

State is managed with React hooks, no external state library.

Key state:

  • tree — full tree array from /api/tree
  • flatNodes — flat array of all selectable nodes (hasFile: true), used for search
  • selectedId — currently selected item ID
  • expandedIdsSet of node keys that are expanded; real nodes use their UUID, virtual nodes use their path string
  • treeLoaded — boolean, set once when the tree fetch completes
  • hideEmpty / hideSystem — field filter flags, persisted in localStorage

Initial expansion (important)

expandedIds is set only in the initial selection effect (not in the tree-fetch effect). This avoids a React strict-mode bug where the tree fetch runs twice — the second run would overwrite the correctly-expanded ancestors with only root IDs.

The initial selection effect runs once when both treeLoaded and router.isReady become true. It builds the full toExpand set from scratch:

  • Root-level node keys
  • All ancestor keys of the selected item (using node.id ?? node.path so virtual ancestors are included)
  • The selected item's own key

Selection flow

  1. Page load — initial selection effect reads ?id= from URL and expands ancestors, or falls back to /sitecore/content; sets document.title to the item path
  2. User click / GUID link / path breadcrumbselectAndExpand updates state immediately and calls router.push (adds to browser history); document.title is updated in the routeChangeComplete event (fired after history.pushState completes) so each history entry is stamped with the correct path
  3. Browser back/forward — a useEffect on router.query.id detects when the URL changes externally and calls applySelection to sync state

Note on title timing: document.title is managed imperatively (no <title> in JSX) to prevent Next.js from resetting it to the default on every render, which would corrupt history entry titles.

Tree search

flatNodes is built once when the tree loads. The useMemo-derived searchResults splits the query on whitespace and requires all words to appear in the item name (case-insensitive substring). Capped at 60 results.

GUID detection

FieldValue handles three cases in order:

  1. Single standalone GUID — entire value is a GUID; rendered as one GuidLink
  2. Pipe-separated GUID list — value split by | where every non-empty token is a GUID (Multilist, Treelist fields); each token rendered as a GuidLink
  3. Text with embedded GUIDs — any other value (short or long, including rich text HTML and Sitecore XML) is scanned with GUID_SCAN_RE via renderInlineGuids, which splits the string at every GUID and interleaves plain text with GuidLink elements

In all three cases, a Links bulleted list is rendered below the field value for every GUID found. Each bullet shows:

• {GUID}  /item/path
• {GUID}  — Item Not Found

The GUID is a clickable link. Items present in the index show their path; items not in the index show "Item Not Found" in red.

Two GUID formats are recognised during inline scanning:

  • Hyphenated{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} with or without braces
  • Compact 32-hexF8508D17FFF349AD81D91E38B71E94D6 with or without braces; negative lookarounds prevent matching a substring of a longer hex string; automatically expanded to hyphenated form for navigation

normalizeGuid handles both formats and always returns a lowercase hyphenated UUID or null.

nodeById is a useMemo-derived map (id → node) built from flatNodes in Home and passed down to ItemDetailFieldValue to resolve paths for the bullet list without any extra API calls.

QuickInfoRow renders Item ID and Template ID as GUID links via the same normalizeGuid check.

Resizable panel splitter

A 5px .panel-resizer div sits between .tree-panel and .detail-panel. On mousedown, global mousemove/mouseup listeners track the cursor's clientX and update panelWidth state (clamped to 200–480px), which is applied as an inline width style on .tree-panel. The CSS min/max-width constraints are removed from .tree-panel; clamping is done entirely in the JS handler.

Path breadcrumb

Each segment of item.Path is rendered as a link only if its full cumulative path exists in navigablePaths — a Set<string> of lowercase paths derived from flatNodes. Segments without a matching file on disk are rendered as plain text.


CI/CD

GitHub Actions workflow (.github/workflows/ci.yml) runs on every push/PR to main:

  1. build job — installs dependencies (npm ci) and runs npm run build (esbuild bundle verification)
  2. release-and-publish job — on successful push to main (skips chore: bump version commits):
    • Publishes the current version to npm (npm publish --access public)
    • Tags the commit with v{version}
    • Bumps the patch version in package.json for the next release
    • Commits chore: bump version to v{next} and pushes with the tag

To release a major/minor version, manually edit the version in package.json before merging (e.g. 1.0.02.0.0); CI will publish that version and then bump to 2.0.1.

Requires an NPM_TOKEN repository secret for publishing.


Known Limitations / Future Work

  • English only — index and item files are read from items/en/ only; no language switcher
  • Read-only — no editing capability
  • ~~No field-value search~~ — full text search across all field values is now supported via the Full Text Search modal
  • No icons by template — page items use 🌐, others use a CSS document icon; no per-template icon mapping
  • Large field values — values over 300 characters are shown as a pre-formatted block but not truncated/collapsed
  • Items outside the tree — if a GUID link points to an item not in the tree (e.g. from another database), the detail panel loads correctly via the API but the tree does not scroll to or highlight it
  • No media previewdata/media is ignored