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

wiki-plugin-farmmanager

v0.4.1

Published

Federated Wiki - Farm Manager Plugin

Readme

Federated Wiki - FarmManager Plugin

Plugin for managing a wiki instance as a farm of multiple wiki sites.

The focus of this plugin is to allow for programmatic, centralized management of a wiki farm by a farm administrator. It is intended to supersede wiki-plugin-register by providing administrative oversight rather than self-service registration.

Design Philosophy

  • Admin-Focused: All features are designed to be used by a farm administrator. Access will be restricted to admin users.
  • JSON API: The server-side component will expose a well-defined API for managing sites. This ensures the API is predictable and can be used by other tools, not just our web interface.
  • Clear Separation: The backend (API) and frontend (web interface) will be clearly separated. The web interface will be a client of the API.
  • Safety: Destructive operations (like deleting a site) are non-destructive by default and require confirmation.

API Design

Authentication for all endpoints will be handled by a middleware that checks app.securityhandler.isAdmin(req).

Endpoints

  • GET /plugin/farmmanager/sites

    • Action: List all wiki sites in the farm.
    • Response Body: An array of site objects.
    • [{ "name": "site1.myfarm.com", "owner": {...}, "pages": 12, "storageBytes": 845231, "lastModified": "2026-06-18T20:15:03.000Z", "status": "active" }, ...]
  • POST /plugin/farmmanager/sites

    • Action: Create a new wiki site.
    • Request Body: { "domain": "newsite", "owner": "Bob" }
    • Response Body: The new site object.
  • GET /plugin/farmmanager/sites/:domain

    • Action: Get detailed information for a single site.
    • URL Parameter: :domain is the full domain of the site (e.g., site1.myfarm.com).
    • Response Body: A single site object.

Site object

Both GET endpoints return site objects with these fields:

  • name — the site's domain.
  • owner — owner record from status/owner.json ({ "name": ..., ... }).
  • pages — number of page files in the site.
  • storageBytes — total disk usage of the site, in bytes. Computed by recursively summing every file under the site directory (pages/, assets/ uploads, status/, and status.json) — the same figure as du -sb <site>, and a truer measure of footprint than pages.
  • lastModified — ISO 8601 timestamp of the newest file anywhere in the site (a last-activity signal), or null if the site has no files.
  • status — lifecycle state (see Site states).

storageBytes and lastModified are computed by walking the site directory on each request. For farms with very large asset directories this adds I/O to the list endpoint; add caching if it becomes a concern. Cumulative network egress is intentionally out of scope for this release — see the Roadmap.

  • PATCH /plugin/farmmanager/sites/:domain
    • Action: Partially update a site's properties (preserves unspecified fields).
    • Request Body: { "owner": {"name": "Carol", ...} } or { "status": "active" } or both
    • Status values: active, readonly, or archived (see Site states). This is the canonical, reversible way to move a site between states in either direction. The legacy value inactive is accepted as an alias of archived.
    • Response Body: The updated site object.

Site states

A site's status (stored in <site>/status.json and surfaced on every site object) is one of three states. Every transition is explicit, reversible in both directions via PATCH …/sites/:domain { "status": … }, and non-destructive — page content is always retained on disk (only DELETE …?hard=true removes anything).

| State | Pages served? | Edits / forks / new pages? | Content on disk | On-site indicator | |-------|---------------|----------------------------|-----------------|-------------------| | active | yes | allowed | retained | none | | readonly | yes (readable) | blocked | retained | subtle top banner | | archived | no — every page returns a notice | blocked | retained | full-page "Site archived" notice |

  • active — normal, fully editable site.
  • readonly — pages are still served and readable, but edits, forks, and new pages are blocked. A subtle banner tells viewers the owner can't edit here. Idempotent: re-applying readonly keeps the original readOnlyAt timestamp; returning to active clears it.
  • archived — the site is taken offline: every page read returns a "this site has been archived and is no longer served; its content is preserved" notice, and all writes are blocked. Content is retained on disk and the site can be restored at any time by PATCH-ing the status back to active (or readonly). Records an idempotent archivedAt timestamp. This is the non-destructive, reversible replacement for the old soft-delete; the legacy on-disk value inactive is treated as archived.

How enforcement works (plugin-only, no wiki-server changes). In farm mode each site runs its own server instance; the plugin re-reads the site's status.json per request, so a state change made from the admin site takes effect immediately.

  • Writes — wiki-server gates every content-mutating route (edit/add/remove/move/create/fork via PUT /page/:slug/action, page DELETE, favicon upload, recycler deletes) behind a single securityhandler.isAuthorized(req) check, while public reads never call it. The plugin wraps that handler to deny whenever the current state blocks writes (readonly and archived). isAdmin is left intact, so the farm-manager API can always flip the state back.
  • Reads — public page reads consult no security hook, but every page read funnels through pagehandler.get(slug, cb) (the .html and .json page routes both call it). For archived the plugin wraps that method to return the notice page instead of real content, taking the site offline without deleting anything. The notice is served with a normal 200 status on purpose: the wiki client treats a non-2xx page fetch as a missing page and wouldn't render the body, so a 4xx/5xx would degrade to a generic "page not found" rather than showing the notice. readonly is unaffected — it still serves real pages.

On-site read-only indicator (plugin-only). The wiki client always loads /theme/style.css (served from <site>/status/theme/style.css). When a site goes readonly the plugin writes a small, clearly-delimited banner rule into that stylesheet, and removes only that block when the site leaves readonly — any owner-authored theme CSS is preserved. Because CSS cannot read the per-request isOwner flag, the banner shows to every visitor of a read-only site, not only the owner. (archived needs no banner — visitors get the full notice page instead.)

Known limitation. archived replaces page reads with the notice, but wiki-server's system JSON endpoints (/system/sitemap.json, /system/slugs.json, /system/site-index.json) don't route through pagehandler.get, so slug/index metadata remains reachable by direct request. Sealing those too would require also wrapping the sitemap/search handlers — out of scope for now.

  • DELETE /plugin/farmmanager/sites/:domain
    • Action: Archive a wiki site (soft delete — non-destructive). Writes status.json with status: "archived". Equivalent to PATCH { "status": "archived" }; PATCH is the canonical path, but DELETE is kept for backward compatibility.
    • Query Parameters:
      • hard=true - Permanently delete the site directory and all its contents (irreversible). This is the only destructive operation.
    • Response Body: { "status": "ok", "message": "Site site1.myfarm.com archived." }

Web Interface Design

The client component (farmmanager plugin item) renders an admin dashboard inside a wiki page. It lists every site in the farm with its owner, page count, storage (human-readable), last-modified date, and a status badge (active / read-only / archived). Each row shows a one-click button for every state the site is not currently in (e.g. an active site offers Make read-only and Archive), so every transition is reachable in both directions. It calls the JSON API above, so it inherits the same admin-only access control; non-admin or non-farm responses are surfaced as an inline message. Add it to a page like any other plugin item (a farmmanager story item), ideally on your designated admin site.

Development Plan

We can build this in phases.

  • Phase 1: Backend API

    1. Setup: Create the basic server/server.js file and admin security middleware.
    2. List Sites (Read): Implement the GET /sites endpoint. This will involve scanning the data directory for site folders and reading their owner.json and status.json.
    3. Create Site: Implement the POST /sites endpoint. This can borrow logic from wiki-plugin-register but will be wrapped in admin security.
    4. Deactivate Site: Implement the DELETE /sites/:domain endpoint. This will involve creating or updating a status.json file in the site's directory.
    5. Update Site: Implement the PUT /sites/:domain endpoint to change the owner.json.
  • Phase 2: Frontend UI

    • TBD after the backend API is stable.

Roadmap

  • Per-site network egress (deferred — not in this release): Track cumulative bytes served per site (e.g. an egressBytes field on the site object). Unlike storage, wiki-server exposes no per-site egress hook, so this needs a response-byte-counting middleware forced ahead of the core routes plus a persisted per-site counter (e.g. status/usage.json). Deliberately left out for now; revisit when there's a concrete need.
  • Search/Filter: Add a search or filter feature to the UI for farms with many sites.
  • UI Pattern Documentation: Create documentation for the pattern of building plugins that render as components within a wiki page.

Architecture Notes

Site-Level Plugin with Farm-Level Scope

This plugin follows the standard FedWiki plugin architecture, which means it is loaded per-site when you navigate to a specific site in the farm. However, its purpose is to manage farm-level resources (all sites in the farm).

What this means in practice:

  1. Access Pattern: You must first navigate to a specific site (e.g., http://localhost) to load the plugin. Once loaded, the API endpoints are available and can manage all sites in the farm.

  2. Admin Site Approach: In practice, designate one site as your "admin site" (e.g., localhost or admin.example.com) and always access the farm management API through that site.

  3. Data Directory Resolution: The plugin receives argv.data pointing to the current site's directory (e.g., /data-farm/localhost). It uses path.resolve(argv.data, '..') to access the parent farm directory and enumerate all sites.

  4. Security Context: Admin authentication and authorization are tied to the site you're accessing the API from, which aligns with FedWiki's per-site security model.

This is consistent with how other farm-management plugins (like wiki-plugin-register) operate in the FedWiki ecosystem.

Build

npm install npm run build

License

MIT