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

@mrhenry/shopify-toolkit

v0.1.14

Published

Shopify theme tooling for downstream repos: canonical theme JSON formatting and a @shopify/cli range

Readme

@mrhenry/shopify-toolkit

The tools a Shopify theme repo needs on its own machine: the canonical form of the theme JSON Shopify owns (owned theme JSON), a mr-shopify-toolkit command that applies it, and a pinned @shopify/cli.

It is the npm half of mrhenry/shopify-toolkit, the repository that also hosts the reusable PR workflows. The canonical JSON definition lives here (src/theme-json.mjs) and the workflow action re-exports it, so a merge performed in CI and a file formatted on a laptop end up with the same bytes.

Install

npx @mrhenry/shopify-toolkit init

Requires Node >= 22.12. @shopify/cli comes along as a dependency, as a ^ range rather than a pin, and the repository's own workflows install @shopify/cli@latest. Nothing writes the CLI version down twice, so CI cannot run a different CLI than a laptop does; test/toolkit-package.test.mjs fails if a pin comes back.

mr-shopify-toolkit init

Sets up a repo that already holds a theme for the toolkit's PR workflows. It asks for the store's shop name first, then writes what the workflows read and call:

| Written | Contents | | --- | --- | | shopify.theme.toml | two tables: [environments.default] (the developer's: path, reconciliation-strategy, store, theme-editor-sync) and [environments.ci] (automation: path, store). store-password goes in both when one was given | | package.json | this package as a devDependency, plus dev, format-json and shopify scripts | | .github/workflows/theme-pr.yml | the caller template, with OWNER replaced | | .github/workflows/theme-mirror.yml | the purchased-theme caller template, with OWNER replaced; written only when a purchased theme id is given | | .github/workflows/preset-backup.yml | the preset-backup caller template, with OWNER replaced | | .github/action-chain-policy.json | the ALLOW entry that lets the preset backup's self-update step mint its write token, merged into any existing policy | | .github/dependabot.yml | monthly Dependabot presets for npm and GitHub Actions, grouped, with a 7-day cooldown and a limit of 10 open PRs | | .gitignore | node_modules and .DS_Store, appended if missing | | .editorconfig | 2-space indentation for theme files, tabs for .vscode/*.json | | .vscode/settings.json | "files.associations": { "*.json": "jsonc" }, merged into existing settings, or left alone when the existing one is annotated (see below) | | .vscode/extensions.json | "recommendations": ["shopify.theme-check-vscode"], appended to existing recommendations, or left alone when the existing file is annotated (see below) |

npx mr-shopify-toolkit init                 # asks for the shop name and storefront password
npx mr-shopify-toolkit init --shop my-store # non-interactive
npx mr-shopify-toolkit init path/to/theme   # somewhere other than the cwd

Options: --shop <name>, --store-password <value>, --owner <owner> (defaults to mrhenry), --path <theme path> (the path in the TOML, for a theme that is not at the repo root), --workflow <file.yml>.

npm run dev — written as mr-shopify-toolkit logout-if-expired; shopify theme dev — clears a dead theme session, then serves the theme locally against the store in shopify.theme.toml, so a fresh repo does not have to remember the command. These three scripts (dev, format-json, shopify) are the ones init owns: they are written even when the repo already defines them, while every other key in scripts is left alone.

The shop name may be given as my-store, my-store.myshopify.com or a store URL; it is normalized to the .myshopify.com domain. It is the one thing init cannot default, so it is asked for — with --shop for scripts, and from stdin when that is a pipe rather than a terminal. The storefront password is asked for next; it is optional, and Enter leaves the key out.

init does not write a theme key. To develop against a specific theme, add theme = "<id>" to [environments.default] by hand — the table npm run dev reads — and never to [environments.ci], which the PR workflows select. A theme key in the selected environment is load-bearing: --development-context, which is how every preview theme is pushed, is exclusive: ["theme"] in the CLI, so it makes every theme push abort, and no flag rescues it. A live theme also needs allow-live = true in [environments.default].

The password prompt is masked, and the value is never echoed back. Note that store-password is the storefront password of a password-protected store — it is not the password key, which is the Theme Access token the workflows authenticate with and which must never be committed (it comes from the THEME_ACCESS_TOKEN secret). init therefore prints a line saying it wrote a committed password; use it only for a store whose storefront password you are happy to see in git.

Nothing that already exists is overwritten: an existing shopify.theme.toml, workflow, .github/dependabot.yml, .github/action-chain-policy.json, .editorconfig, .vscode/settings.json or .vscode/extensions.json is kept (and reported as kept), an existing package.json keeps its own fields, and a second init changes nothing. The action-chain policy is the one merged file: an entry for another chained workflow is preserved and the preset caller's ALLOW entry is appended. What init cannot do -- the THEME_ACCESS_TOKEN secret and the toolkit repo's Actions access -- is printed as the next steps.

Both .vscode files are JSONC, and rewriting one would drop the comments in it, so a file that carries comments or trailing commas is reported instead of rewritten, with the line to add by hand ("*.json": "jsonc" under files.associations, or "shopify.theme-check-vscode" under recommendations). A package.json that cannot be read at all is reported the same way: the rest of the setup still happens and init still exits 0.

The recommended extension is Shopify's theme check, which surfaces the same offenses the Theme Check Report check run shows, in the editor. init appends it to the recommendations a repo already has rather than writing the array itself, so nothing the repo recommends is dropped or reordered.

mr-shopify-toolkit format-json

Rewrites every owned theme JSON file into the canonical form its admin editor writes: an auto-generated header, 2-space indentation, no comments, no trailing commas, one trailing newline. Not minified.

npx mr-shopify-toolkit format-json            # the working directory is the theme root
npx mr-shopify-toolkit format-json path/to/theme

Only these files are touched, because they are the files the admin editors rewrite (Shopify's JSON with comments documentation):

| Path | Files | Header | | --- | --- | --- | | config/settings_data.json | the saved theme settings | theme editor | | templates/**/*.json | JSON templates, e.g. templates/cart.json | theme editor | | sections/**/*.json | section groups, e.g. sections/header-group.json | theme editor | | locales/**/*.json | locale files, e.g. locales/cs.json | language editor |

Two headers exist because two editors write these files: the admin theme editor owns config/, templates/ and sections/, the admin language editor owns locales/, and each words the header accordingly. The command picks the right one per path.

Everything else is author-owned and left byte-for-byte alone, including config/settings_schema.json, config/markets.json and every .liquid file.

Input is read as JSONC, so a file that still carries comments or trailing commas is formatted rather than rejected. Forward slashes are normalized to /: a locale file pulled from a store may hold href=\"\/admin\", and the canonical form holds href=\"/admin\". The value parses identically either way, and the merge already compares those files by parsed value, so it is not treated as a change.

Keys keep their position. A JavaScript object enumerates array-index keys such as "404" before every string key, so a plain parse-and-reprint would move a locale's templates.404 to the top of templates. The key order is therefore read from the text itself, and only the values, indentation, escaping and header are normalized.

A file that cannot be parsed at all is reported and skipped, and the command still exits 1 at the end. Files that are already canonical are not rewritten, so their mtime does not move.

Exit codes: 0 done — including a directory with no owned files to rewrite — 1 a file could not be parsed, 2 the arguments were not understood. The CLI itself exits 0 for --help, 1 when no command is given, and 2 for an unknown one.

mr-shopify-toolkit logout-if-expired

Clears a dead Shopify CLI theme session so the next shopify theme dev can sign in again instead of failing on a stale token.

npx mr-shopify-toolkit logout-if-expired            # silent unless it acts
npx mr-shopify-toolkit logout-if-expired --verbose  # say what it found

The Shopify CLI has no auth status — auth only has login and logout — so the only way to tell a live session from a revoked or expired one is to use it. This command probes with shopify theme list --json: read-only, no prompts, and it uses the same OAuth theme session theme dev uses (pull/push use a store auth session instead). The probe runs with stdin closed, its output captured, and the CLI told it is non-interactive (CI=1, SHOPIFY_FLAG_NO_INPUT=1), so it can never open a login prompt or print to the user. Without that, an absent session makes theme list start the device flow — it opens the browser, prints the confirmation code to the captured stderr, and polls until it times out, stalling npm run dev before theme dev ever runs.

| The probe | What the command does | | --- | --- | | exits 0 | the session is valid: nothing, exit 0 | | fails with an auth signature (Service is not valid for authentication, Invalid API key or access token, GraphQL Error (Code: 401), Token refresh failed) | runs shopify auth logout, prints one line, exit 0 | | says authorization is required / no session | nothing, exit 0 — theme dev will ask for a login itself | | fails for any other reason (network, missing CLI) | nothing — logging out on an unknown error would be a guess — reports the error, exit non-zero |

A scope error (missing … access scope) is reported, never logged out: the token is valid, and a logout would throw it away without fixing anything.

The generated dev script runs this guard first, joined with ; rather than &&, so a probe that fails for a reason that is not a dead session cannot stop the dev server.

API

The formatter is built on the same module the workflows use, which is exported for downstream tooling that has to write these files itself:

import { autoGeneratedHeaderFor, isOwnedThemeJson, jsonKeyOrder, parseThemeJson, serializeThemeJson } from '@mrhenry/shopify-toolkit'

isOwnedThemeJson('templates/cart.json')            // true
isOwnedThemeJson('config/settings_schema.json')    // false
autoGeneratedHeaderFor('locales/cs.json')          // the language editor's header
serializeThemeJson({ order: ['main'] }, 'templates/index.json') // header + 2-space JSON

// Rewriting a file you parsed yourself? Pass its key order to keep `"404"` put.
serializeThemeJson(value, rel, jsonKeyOrder(text))

Development

Run all three from the repository root, which is where npm ci installs the workspace; nothing is defined twice per package.

npm ci          # from the repository root; installs the workspace too
npm test        # node --test
npm run lint    # eslint, zero warnings

Keep the published package dependency-free apart from @shopify/cli: the workflow action imports src/theme-json.mjs directly from this directory, so anything it needs must be a Node built-in.

The templates/*.yml files are the caller workflows init writes (theme-pr.yml always; theme-mirror.yml only with a purchased theme id; preset-backup.yml always). Each body must stay identical to the repository's matching examples/*.yml; test/toolkit-package.test.mjs fails when one drifts.