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

@freva-org/portal-builder

v2612.0.1

Published

FP-001 build-time portal framework: closed YAML in, one immutable static artifact out. Astro compiles the pages; nothing about the site is fetched at runtime.

Readme

@freva-org/portal-builder

Build a Freva portal from declarative inputs. Closed YAML in, one immutable static artifact out.

Everything about the site — its routes, navigation, theme, landing blocks and prose — is decided while the artifact is built. The generated browser application still calls live APIs for live things (data search, STAC, authentication), but it never asks a service what the site is. That single rule is why a portal built this way keeps working when the settings service is down, why a deep link is a real file, and why two builds of the same inputs produce the same bytes.

This is the build-time line. The existing @freva-org/portal package publishes a prebuilt runtime site and remains a maintenance line during migration; the two coexist deliberately and do not share an executable name.

Install

The supported CI path is the canonical image, pinned by digest:

docker run --rm --network=none \
  -v "$PWD:/src:ro" -v "$PWD/build:/out" \
  ghcr.io/freva-org/portal-builder@sha256:<digest> \
  build --source-root /src --config /src/portal/portal.yaml --out /out/portal

For local development against Node:

npm install --save-dev @freva-org/portal-builder
npx freva-portal-builder validate --source-root . --config portal/portal.yaml

The npm path installs neither Python nor a browser; the image provides both.

  • reStructuredText needs the pinned freva-portal-rst helper with its exact Docutils. The builder checks the whole handshake and stops (FP1701) if it does not match.

  • Mermaid diagrams are drawn at build time in Chromium, driven by Playwright. Playwright is an optional peer dependency and its browsers are a separate download, so a site with diagrams needs both:

    npm install --save-dev playwright
    npx playwright install --with-deps chromium   # --with-deps: Chromium's system libraries (Linux, root)

    Or set FREVA_PORTAL_CHROMIUM to an existing Chromium executable. Without a browser the build stops with FP1702, and its hint names the command for the cause it hit (no Playwright, no downloaded browser, or missing system libraries). A site with no diagram never starts a browser.

  • STAC Browser materials, for a portal that enables the component, are prepared by freva-portal-builder prepare-stac --out <dir> (below). It needs git, npm and the network; build never does.

The commands

freva-portal-builder validate   --source-root . --config portal/portal.yaml
freva-portal-builder build      --source-root . --config portal/portal.yaml --out build/portal
freva-portal-builder dev        --config portal/portal.yaml
freva-portal-builder preview    --dir build/portal
freva-portal-builder verify     --dir build/portal
freva-portal-builder host-check --dir build/portal --url https://portal.example.org/
freva-portal-builder migrate    --from ui-manifest.json --out portal/
freva-portal-builder prepare-stac --out .stac-materials        # only if stac-browser is enabled
freva-portal-builder prepare-playground --source-root . --config portal/portal.yaml --out .python-materials
freva-portal-builder prepare-notebook --source-root . --config portal/portal.yaml --out .notebook  # only with pythonPlayground.notebook

--source-root is the trust anchor and is mandatory for validate and build: every config, landing, prose, asset, download and subsite input must resolve inside it after normalization and after the filesystem has canonicalized it. Symlinks are refused. The output tree must be outside every declared input root, and that is checked before anything is created.

There is no --runtime, no --api-settings and no silent HTTP fallback.

A minimal site

site/
├── portal.yaml
├── landings/home.yaml
├── content/guide.md
└── assets/{logo.svg,favicon.svg}
schemaVersion: 1

site:
  id: example-research
  title: Example Research Portal
  language: en
  # The exact absolute site-base URL. Its pathname is the one and only base path.
  canonicalUrl: https://portal.example.org/
  identity:
    logo: ./assets/logo.svg
    favicon: ./assets/favicon.svg

theme:
  preset: default

rendering:
  profile: portal-content-v1
  sources:
    - root: ./content
      mount: /docs/

landings:
  home:
    path: /
    source: ./landings/home.yaml

examples/minimal-portal and examples/full-portal in this repository are working fictional consumers; the full one exercises every part of the contract.

What the configuration can and cannot say

| You describe | You do not describe | | ------------------------------------------------ | -------------------------------- | | Site identity, chrome, a registered theme preset | CSS, fonts, or component styling | | Named landings composed from framework blocks | Astro components or JavaScript | | Content roots of Markdown and RST | Renderer plugins | | Public service URLs by kind | Credentials of any kind | | Which components are compiled in | How they are implemented | | Trusted documentation subsites, explicitly | Arbitrary active files |

Every portal-owned object is closed: an unknown key is an error with a JSON Pointer and a line, never a quietly ignored extension point.

Components

Three built-in components, each enabled by one field:

| Kind | Service kind | Route | What "disabled" means | | -------------- | ------------- | ---------------------------- | ------------------------------------------------------------------ | | databrowser | databrowser | required when enabled | no route, no navigation entry, no module in the bundle, no request | | stac-browser | stac | required when enabled | additionally no copied application files | | auth | auth | one technical callback route | no callback page, no account control, no auth client |

Absence is proved from the recorded build graph and copy manifest in component-evidence.json, not inferred from filenames. Hiding a component with CSS satisfies nothing.

The artifact

build/portal/
├── index.html                 # and one real HTML file per route
├── 404.html
├── _portal/                   # content-hashed framework assets
├── identity/                  # sanitized logo and favicon
├── portal-manifest.json       # routes, components, services, files, cache classes
├── input-manifest.json        # every input hashed, plus builder/schema/profile identity
├── component-evidence.json    # what the bundler and the copier actually did
├── host-policy.json           # what a conforming host must do
├── BUILDINFO.json
└── checksums.sha256

The output directory is the document root, mounted at the pathname of site.canonicalUrl. For https://example.org/portal/, index.html is served as /portal/; the builder does not emit dist/portal/index.html, and serving the same directory at / is a deployment error rather than a supported relocation.

Documentation

Licence

BSD-3-Clause.