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

@open-xchange/demo-content

v1.0.2

Published

Demo content for OX App Suite: fill demo contexts with mail, calendars, tasks, contacts and files from a dataset.

Readme

@open-xchange/demo-content

Demo content for OX App Suite. A dataset describes the people of a demo and what they have: mail, appointments, tasks, contacts, Drive files, portal widgets, signatures, mail filters and settings. demo-content checks a dataset, turns its generated parts into items you can edit, and fills it into a running App Suite by logging in as each user over the HTTP API.

It is the fill behind the provisioning UI's Demo content panel, and works on its own against any App Suite you have logins for. It needs Node.js 24 or later; nothing has to be installed:

npx @open-xchange/demo-content validate ./my-dataset

demo-content guide prints this file.

Commands

A <dataset> argument is a dataset directory when it contains a slash or exists on disk, otherwise the name of a dataset shipped with the provisioning UI (default, for instance). Give a directory in the current directory as ./name.

A shipped dataset is fetched from the provisioning UI through your cluster's API server, with your own kubeconfig: the UI serves its datasets on a port only the API server's service proxy reaches, and the provisioning editors may use it. --datasets <namespace>/<service> names another installation's UI (the default is the main one, provisioning-operator/provisioning-operator-ui); --datasets local reads the datasets next to the package, in a checkout of the repository. When the UI runs a newer major version of this package, the CLI says which version to update to.

datasets

npx @open-xchange/demo-content datasets

Lists the shipped datasets, name and description.

validate

npx @open-xchange/demo-content validate ./my-dataset

Checks dataset.yaml and every file it references, and lists every problem at once.

expand

npx @open-xchange/demo-content expand ./my-dataset --seed 42                      # to stdout
npx @open-xchange/demo-content expand ./my-dataset --seed 42 --out ./my-dataset-42

Runs the dataset's generators with the seed and writes the result as authored items, so a random start becomes a file to edit: reroll the seed until the start looks right, expand once, then change, delete and add items by hand. The same seed always gives the same items. --out writes a complete dataset directory, the dataset's files copied along, and never overwrites the source.

fill, with a users file

cat > users.yaml <<'YAML'
- { login: david@1234, password: … }
- { login: anna@1234, password: … }
YAML
npx @open-xchange/demo-content fill ./my-dataset \
  --api-url https://appsuite.example.com --users users.yaml --anchor 2026-10-08

Logs in as each user and creates the dataset's items. Users are taken in slot order: the first user gets the first persona's content, users after the last persona get none. Logins are as typed on the login page. --api-url is the App Suite host; the API prefix (/api or /appsuite/api) is found by asking, and a URL ending in either skips that.

A generating dataset without --seed picks one and prints it with the result, so the same fill can be repeated. Without --anchor the dates count from now. --anchor takes:

  • a day, 2026-10-08: every relative date counts from that day;
  • an instant, 2026-10-08T10:30:00+02:00, an RFC 2822 date or Unix seconds: the dates count from that day, and the instant is also the fill's moment, so the same dataset, seed and instant give the same dates and times every time. Each user gets the instant's day in their own timezone. Without an offset the instant is read in this machine's timezone, so give one.

The fill record keeps an instant as ISO 8601 UTC, and a reset reuses it. For screenshots or a recording, freeze the browser's clock to the same instant. Failed items are listed and make the exit code 1; a failed login fails the whole fill before anything is created. Filling twice creates everything twice: wipe the mail and start from a fresh context.

fill, into a Context of the provisioning operator

npx @open-xchange/demo-content fill ./my-dataset --context appsuite-main/sales-demo

With --context <namespace>/<name>, the users, their passwords and the context id come from that Context in the cluster, read with your own kubeconfig, and only once the operator reports it Ready. Reading the passwords needs what the UI's viewers have; recording the fill and finding the host need what its editors have. The host is the first one of the namespace's VirtualService that routes to the HTTP API Service; --api-url overrides it.

KUBECONFIG and ~/.kube/config are read as kubectl reads them; --kubeconfig <file> names another, and --kubeconfig - reads one from stdin, for a kubeconfig made on the fly that should not land in a file. A kubeconfig that logs in through an exec plugin (kubectl oidc-login) needs that plugin where the CLI runs.

A Context with neither users nor usersFrom gets the dataset's people as its users first, named and addressed as the UI names them, with the demo's mail tag. The fill waits until the operator has created them (up to ten minutes) and then fills them. Their addresses are in the Context's spec.mxDomain; without one, give the operator's mail domain with --mx-domain <domain>.

The fill is recorded on the Context as the UI records it, in the annotation provisioning.open-xchange.com/content-fill, so the UI shows it. The record says where the dataset came from: source: { type: shipped, version } for a shipped dataset, source: { type: directory, sha256 } for a directory, the sum over dataset.yaml and every file it references. A Context filled before needs --again, since a second fill adds everything again.

The UI's reset can fill a shipped dataset again on its own, but not a directory, which only exists where you ran the CLI. Reset such a demo without filling it, then fill it again:

npx @open-xchange/demo-content fill ./my-dataset --context appsuite-main/sales-demo

wipe

npx @open-xchange/demo-content wipe --api-url https://appsuite.example.com --users users.yaml

Empties each user's mailbox, for a clean refill: every mail and every non-standard mail folder goes; calendar, contacts, tasks and Drive stay. It prints what it removed per user. Mail outlives a deleted context, since mailboxes are keyed by address, and nothing here checks whether another context uses the same addresses: that is your call. There is no undo.

personas

npx @open-xchange/demo-content personas kestrel-trading ./my-dataset

Prints the named datasets' personas as Context users, a Markdown page with a users: block per dataset: what a demo made from a dataset's people holds, with placeholders for the mail tag and domain. Without a name it prints every shipped dataset, which needs a checkout of the repository.

guide

Prints this file.

Writing a dataset

A dataset is a directory holding dataset.yaml and the mails, files and avatars it references. name is lowercase letters, digits, - and _. The quickest start is a copy of a shipped one:

npx @open-xchange/demo-content expand default --seed 1 --out ./my-dataset
name: default
locale: en_US                 # generators' locale: de_DE, en_US, en_GB, fr_FR
personas:                     # their order is the slot order
  - { key: david, givenName: David, surname: Smith, avatar: avatars/david.png }
  - { key: anna, givenName: Anna, surname: Bush }
mails:                        # keyed by the recipient persona
  david:
    - { from: anna, eml: eml/welcome.eml, daysAgo: 1, time: '11:13' }
    - { from: { name: News, email: [email protected] }, subject: Hi, text: Body, daysAgo: 0, time: '07:30' }
appointments:
  david:
    - { title: Standup, weekday: 1, time: '09:00', durationMinutes: 15, series: weekdays, attendees: [anna] }
generate:                     # background volume, reproducible from a seed
  mails: { perPersona: 40, withinDays: 30 }
  appointments: { perPersona: 5, withinWeeks: 2 }

Sections

Sections keyed by persona: folders, categories, mails, appointments, tasks, contacts, files, snippets, mailFilters, portal (lists) and settings (a map of namespace//path keys; settings.all applies to every persona the fill logs in as, and a persona's own entry wins, so no persona may be called all). People are always named by persona key. Unknown fields are reported, not ignored.

  • Personas. A persona's company, department, position, home address, avatar and birthday go on its user's profile when it is filled; its locale and timezone go on the user when the demo is made from the dataset's people.
  • Folders. A mail folder goes to the top of the account, where "My folders" lists it; an address book with public: true is shared with everyone in the context, the persona administering it. A mail, appointment, task or contact can name one of its owner's folders of the matching module with folder:; a file's folder: is a Drive path the fill creates.
  • Mails. A mail can go to another persona instead of its owner, sit in folder: Sent or Drafts (with draft: true), be flagged, carry a color (red, blue, green, gray, purple, lightgreen, orange, pink, lightblue, yellow; it also flags the mail) and be snoozed until snooze: { weekday, week, time } (08:00 unless a time is given). A mail dated later than the moment of the fill (now, or the anchor's instant) moves to the day before, since the server would stamp it with the delivery time.
  • Categories. A category is a name, a #rrggbb color and an optional icon (bi/cart.svg); a mail's categories names the owner's or the four every user has (Important, Business, Private, Meeting). A persona with categories gets the categories feature switched on, which needs middleware 8.19 or later to show.
  • Appointments. attendees are keys or { persona, status } (needs-action, accepted, declined, tentative), and reminderMinutes sets the alarm.
  • Tasks. participants delegate a task to other personas, each a key or { persona, status, comment }: with a status (accepted, declined, tentative) the fill logs in as that person and answers, then deletes the mail that tells the owner about the answer. A participant without a user in the demo is left out. A task with done: true is finished.
  • Contacts. A contact is someone outside the demo, in the persona's address book: a givenName, surname or company, up to three emails, phones, a business address, an avatar and a birthday. A contact list is { list, members }: members are addresses, and one that belongs to a contact listed before it points at that card.
  • Snippets. A signature or a template with a name and HTML content; a signature may be the default for new mail or for reply, and sits above or below the quote.
  • Mail filters. A header test for value that moves the mail into one of the owner's mail folders or gives it a color; { vacation: { from, until, subject, text } } is the automatic reply, its days given as { weekday, week }.

Dates

Dates are relative, so a demo looks current whenever it is filled: daysAgo, weekday plus an optional week offset, dueInDays, and a persona's birthday: { inDays, age }. They count from the fill's anchor (--anchor, a day or an instant, else now) in the user's own timezone. {{date weekday=4 week=1}} in an eml file, a mail's text or a snippet becomes that day, written out ("Thursday, October 15").

Files

Paths are relative to the dataset directory and must stay inside it.

A story.md next to dataset.yaml is optional: plain Markdown for whoever presents the demo, saying who the company and the people are and what to show in each app. The provisioning UI shows it next to the dataset. HTML in it is shown as text, and links only go to http(s) and mailto.

Trying it

validate while writing, expand to see what a seed generates, then a fill of a fresh demo: fill --context for a Context of the provisioning operator, or fill --users for any App Suite. To try a changed draft on the same demo, reset it without filling (or wipe its mail) and fill again.

License

AGPL-3.0-or-later. See LICENSE.