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 🙏

© 2025 – Pkg Stats / Ryan Hefner

jam-my-stack

v1.0.49

Published

> A set of simple IndieWeb Jamstack publishing syndication tools

Downloads

129

Readme

jam-my-stack 🥞

A set of simple IndieWeb Jamstack publishing syndication tools

Published at https://www.npmjs.com/package/jam-my-stack

npm version

These simple scripts enrich your Jamstack-site by adding/manipulating/whatever (meta)data, such as extra posts, indexing, and so forth. A primary example of these tools in action is my own site https://brainbaking.com - inspect how it's used at https://git.brainbaking.com/wgroeneveld/brainbaking

Are you looking for a way to receive webmentions? See https://git.brainbaking.com/wgroeneveld/go-jamming !

The tools

Usage:

  1. yarn add jam-my-stack
  2. const { mastodon, goodreads } = require('jam-my-stack')

1. Mastodon

1.1 parseFeed

An async function that parses your Fediverse-compatible feed (Mastodon/Pleroma/...) and converts entries to .md Markdown files for your Jamstack to enjoy.

Usage example:

    await mastodon.parseFeed({
        notesdir: `${__dirname}/content/notes`,
        url: "https://chat.brainbaking.com/users/wouter/feed",
        utcOffset: 60,
        titleCount: 50,
        titlePrefix: "Note: "
    })

Options and their default values:

  • utcOffset: 60 (= GMT+1, that's where I am!) (in minutes, see day.js docs
  • titleCount: 50. Will add "..." and trim if title length bigger.
  • titlePrefix: "". Will add before title (e.g. "Note: ")
  • ignoreReplies: false. If true, will not process in-reply-to items.

Note that this does not delete the notes dir with every call. It simply checks if there isn't already a file with the same name (based on the publication date), and adds one if not.

Example feed entry:

  <entry>
  <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
  <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
  <id>https://chat.brainbaking.com/objects/77a3ecfb-47e1-4d7a-a24a-8b779d80a8ac</id>
  <title>I pulled the Google plug and installed LineageOS: https://brainbaking.com/post/2021/03/getting-ri...</title>
  <content type="html">I pulled the Google plug and installed LineageOS: &lt;a href=&quot;https://brainbaking.com/post/2021/03/getting-rid-of-tracking-using-lineageos/&quot; rel=&quot;ugc&quot;&gt;https://brainbaking.com/post/2021/03/getting-rid-of-tracking-using-lineageos/&lt;/a&gt; Very impressed so far! Also rely on my own CalDAV server to replace GCalendar. Any others here running &lt;a class=&quot;hashtag&quot; data-tag=&quot;lineageos&quot; href=&quot;https://chat.brainbaking.com/tag/lineageos&quot; rel=&quot;tag ugc&quot;&gt;#lineageos&lt;/a&gt; for privacy reasons?</content>
  <published>2021-03-01T19:03:35.273023Z</published>
  <updated>2021-03-01T19:03:35.273023Z</updated>
  <ostatus:conversation ref="https://chat.brainbaking.com/contexts/ff9aa62e-3357-41ad-951d-15f6ad506424">
    https://chat.brainbaking.com/contexts/ff9aa62e-3357-41ad-951d-15f6ad506424
  </ostatus:conversation>
  <link href="https://chat.brainbaking.com/contexts/ff9aa62e-3357-41ad-951d-15f6ad506424" rel="ostatus:conversation"/>
    <summary></summary>
    <link type="application/atom+xml" href='https://chat.brainbaking.com/objects/77a3ecfb-47e1-4d7a-a24a-8b779d80a8ac' rel="self"/>
    <link type="text/html" href='https://chat.brainbaking.com/objects/77a3ecfb-47e1-4d7a-a24a-8b779d80a8ac' rel="alternate"/>
    <category term="lineageos"></category>
      <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://chat.brainbaking.com/users/wouter"/>
</entry>

This generates the file 01h20m03s35.md (it assumes UTC times in the feed and adjusts according to specified utcOffset, such as GMT+1 in this example), with contents:

---
source: "https://chat.brainbaking.com/objects/77a3ecfb-47e1-4d7a-a24a-8b779d80a8ac"
title: "I pulled the Google plug and installed LineageOS: https://brainbaking.com/post/2021/03/getting-ri..."
date: "2021-03-01T19:03:35"
---

I pulled the Google plug and installed LineageOS: <a href="https://brainbaking.com/post/2021/03/getting-rid-of-tracking-using-lineageos/" rel="ugc">https://brainbaking.com/post/2021/03/getting-rid-of-tracking-using-lineageos/</a> Very impressed so far! Also rely on my own CalDAV server to replace GCalendar. Any others here running <a class="hashtag" data-tag="lineageos" href="https://chat.brainbaking.com/tag/lineageos" rel="tag ugc">#lineageos</a> for privacy reasons?

See implementation for more details and features.

Also parsers:

  • <link rel="enclosure"/> image types (see render-enclosures.ejs) ejs template, that is appended to the Markdown file if any are found. Styling is up to you...
  • ... @https://blah.com/blie hi there - this is a in-reply-to toot which adds context frontmatter, so your html renderer can use the correct IndieWeb classes. This should also enable webmention sending since you mention the URL. If you "at" a valid Mastodon user, it will automatically do this.

2. Goodreads

2.1 createWidget

An async function that reads and modifies Goodreads JS widget embed code, converting low-res book covers to hi-res ones if possible. This omits possible Goodread cookies and cross-domain mishaps.

Usage example:

    const widget = await goodreads.createWidget("https://www.goodreads.com/review/grid_widget/5451893.Wouter's%20bookshelf:%20read?cover_size=medium&hide_link=&hide_title=&num_books=12&order=d&shelf=read&sort=date_added&widget_id=1496758344")
    await fsp.writeFile(`${__dirname}/static/js/goodreads.js`, widget, 'utf-8')

3. Lunr

As of version 1.0.30, Lunr functionality was removed in favor of https://pagefind.app/

With Pagefind, there's no need to integrate it into jam-my-stack, greatly simplifying things and reducing the index file size.

4. HowLongToBeat

4.1 howlong

Adds https://howlongtobeat.com/ game length (MainGame) and an ID to your front matter (keys howlongtobeat_id and howlongtobeat_hrs), provided you first added a property called game_name. (This gets substituted).

It also downloads a thumbnail of the cover image as cover.jpg in the same relative directory as the source article if you provided the dir as an option. The downloaded thumbnail is automatically optimized for the web using mogrify (this will emit a warning if you do not have ImageMagick installed locally).

So, Frontmatter like this:

---
title: Diablo 3 my Review
hltb_name: Diablo 3
---

Gets subsituted by something like this:

---
title: Diablo 3 my Review
howlongtobeat_id: 62129
howlongtobeat_hrs: 20.5
---

In your Hugo template, add a link to https://howlongtobeat.com/game?id={howlongtobeat_id} and you're all set!

⚠️ Jam-my-stack searchers for hltb_name in frontmatter; if it is not present it will not parse HLTB entries. This is the distinction between HLTB and BGG.

Usage example:

  await howlong({
    postDir: `${__dirname}/content`,
    downloadDir: `${__dirname}/static`)

It will print out games and metadata it found.

Suppose the above diablo-3.md lives in content/games/switch/diablo-3, then a cover.jpg will be automatically downloaded in static/games/switch/diablo-3/ and that directory will be created if not yet existing.

Working example: https://jefklakscodex.com/games/diablo-3/ (on the left side). Check out the Hugo template to use the properties at https://git.brainbaking.com/wgroeneveld/jefklakscodex.

5. Webmentions

In cooperation with https://git.brainbaking.com/wgroeneveld/go-jamming

5.1 getWebmentions

Calls the get webmention endpoint, sorts by date, adds metadata such as relative date (relativeTarget, property), and returns data. Could be written in a data folder for Hugo to parse, for example.

Parameters: first domain, second the config for the endpoint and token. Usage example:

await getWebmentions("brainbaking.com", {
  endpoint: 'https://jam.brainbaking.com',
  token: 'lol'
})

5.1 send

Calls the set webmention endpoint using a PUT. Based on the RSS feed, see the go-jamming README.

Same as getWebmentions.

6. YouTube

Thanks to ideas from rubenerd.com and his video.sh script. This downloads a thumbnail using youtube-dl, smacks a play button on it using convert, and stores that in the specified folder. Use in conjunction with a Hugo shortcode to get rid of YouTube's iframes!

This method will fail if you do not have ImageMagick installed locally.

Usage example:

  await download({
    postDir: 'somewhere/posts',
    downloadDir: 'static/youtube-thumbs',
    overlayImg: 'playbtn.png'
  })

It scans all .md files in the posts dir for {{< youtube xxx >}} shortcodes.

7. BoardGameGeek

Similarly to HowLongToBeat, BoardGameGeek stats of games can be fetched and put into frontmatter as well.

⚠️ Jam-my-stack searchers for bgg_name in frontmatter; if it is not present it will not parse BGG entries. This is the distinction between BGG and HLTB.

In order for the BGG XML API v2 to work, you will need to request an authorization token. Once that's done, add it to your environment variables:

export JAM_MY_STACK_BGG_TOKEN="my-token-wthout-bearer-and-a-space"