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

eslint-config-canopy

v5.5.1

Published

A standard eslint configuration for Canopy frontend developers

Readme

canopy-eslint

A standard eslint config for Canopy frontend developers. This assumes that you're using babel and babel-eslint babel-eslint.

Installation

yarn add -D eslint-config-canopy

Usage

Your project needs to use the new ESLint v9 flat config system. Create an eslint.config.mjs file in your project root (Remove .eslintrc if it exists):

import canopyConfig from "eslint-config-canopy";

export default [
  { // Specify directories/files to ignore here
    ignores: ["src/create-edit-client-old/**/*"],
  },
  ...canopyConfig,
  { // Override rules here
    rules: {
      "@typescript-eslint/no-unused-vars": "off",
    },
  },
];

Plugin rules

This package ships a canopy ESLint plugin with Canopy-specific rules. Most of them are already enabled at warn by the shared config exported from this package, so extending eslint-config-canopy turns them on. See the table below for what each one reports, and docs/rules/ for the details.

The plugin is also exposed on its own via the eslint-config-canopy/plugin sub-export, so you can register it directly to change a severity, disable a rule, or opt into one that ships off by default:

import canopyConfig from "eslint-config-canopy";
import canopyPlugin from "eslint-config-canopy/plugin";

export default [
  ...canopyConfig,
  {
    plugins: { canopy: canopyPlugin },
    rules: {
      "canopy/no-cp-class-in-tw": "error",
    },
  },
];

Network access

The shared config also restricts raw network access, using ESLint's built-in rules rather than a Canopy one:

| Restriction | Rule | | --- | --- | | The fetch global | no-restricted-globals — use fetcher!sofe, which adds auth headers, the CSRF token, tenant context, error routing and Sentry breadcrumbs | | axios imports | no-restricted-imports — same reason |

no-restricted-globals resolves through scope, so a locally declared or imported fetch (a node-fetch shim, a polyfill, a parameter) is not reported — only the ambient browser global. Server-side packages where fetch() is correct can turn this off with 'no-restricted-globals': 'off'.

Available rules

| Rule | Description | | --- | --- | | canopy/no-cp-class-in-tw | Disallows Canopy cp-* class tokens inside tw(...) calls. Tailwind's tw() helper applies a per-app prefix to every token, so tw("cp-body") produces a broken class like fo-cp-body. Use always("cp-body", tw(...)) or place the cp-* class outside tw(). Offers a suggestion fix for flat string-literal calls. | | canopy/no-class-ternary | Disallows a class-selecting ternary (both branches non-empty) in a JSX className attribute or a tw(...) / always(...) call. Use toggle(cond, whenTrue, whenFalse) instead. Auto-fixable. | | canopy/no-conditional-class | Disallows an empty-branch ternary (cond ? "x" : "") or a cond && "x" short-circuit in a JSX className attribute or a tw(...) / always(...) call. Use maybe(cond, "x") instead. Auto-fixable. | | canopy/no-window-auth-globals | Disallows reading window.loggedInUser, window.tenant, or window.betas. These are snapshots that never update and are undefined before auth resolves. Use useWithUserAndTenant() / UserTenantProps / useBetas() from cp-client-auth!sofe. Writes are allowed, so bootstraps and test mocks are unaffected. | | canopy/no-tolocalestring-for-dates | Disallows .toLocaleDateString(), .toLocaleTimeString(), and construction of an Intl.DateTimeFormat. Use Luxon DateTime with a Canopy preset such as DateTime.DATE_SHORT. .toLocaleString() is not reported — it is Luxon's own correct API and is also how numbers get thousands separators. | | canopy/no-hardcoded-font-size | Disallows font sizes no theme can reach: Tailwind arbitrary lengths like text-[13px] and literal fontSize values in style props. The named scale (text-sm…text-9xl) is allowed — it resolves through the Tailwind theme. Colours, alignment, and decoration sharing the text- prefix are not reported, nor are computed values or inherit. | | canopy/no-license-check-for-feature-gating | Disallows hasLicense() imported from cp-client-auth or cp-client-auth!sofe (including aliases) when its result is used as a condition (if, ternary, &&/||, or behind !). ?? is not reported, nor is a same-named local that shadows the import. A license is what the firm bought; a permission is what the user may do — use useHasAccess() to gate features. Reading license state for billing, seat counts, or analytics is not reported. | | canopy/require-staletime-in-usequery | Requires an explicit staleTime when useQuery / useInfiniteQuery options are an object literal. Without it a query refetches on every mount, which in the single-spa shell means every navigation. Query-factory calls, spreads, and the legacy positional signature are not reported. | | canopy/require-subscribe-cleanup | Requires an effect that calls .subscribe() in its own scope to return a cleanup function, so the subscription does not outlive the component. Any returned value satisfies the rule; nested functions are not searched, which also catches a cleanup returned from the wrong function. | | canopy/require-subscribe-error-handler | Requires an error handler on .subscribe() when the value handler is an inline function — a second positional argument, an error key, or a spread observer satisfies it. Without one, stream errors are swallowed and never reach Sentry. Handlers passed by reference are not reported, since they are indistinguishable from a Pusher channel name; exactly one non-RxJS receiver (a Zustand store) is reported ecosystem-wide. | | canopy/comment-length | Limits a comment to a configurable length (default 240 characters, ~2-3 lines). A run of consecutive // lines is measured together, so switching prose to stacked // lines does not evade the limit; each /* */ block is measured whole. Exempt are structured JSDoc blocks (a /** */ carrying at least one @tag) and any comment containing a URL; a tagless doc block and a directive comment are not, so a genuinely long comment otherwise needs an explicit eslint-disable. |

Design-system rules

eslint-config-canopy/design-system is an opt-in export that checks how Understory components are styled, using @shadcn/lint. It is not part of the default config. Add it after canopyConfig:

import canopyConfig from "eslint-config-canopy";
import canopyDesignSystem from "eslint-config-canopy/design-system";

export default [
  ...canopyConfig,
  ...canopyDesignSystem,
];

Requirements, from @shadcn/lint: ESLint 9.30 or later, Node.js 20.19 or later, and Tailwind v4.

A component counts as Understory when it is imported from @canopytax/understory. Classes are read from className and from the arguments to tw, always, maybe and toggle.

| Rule | Level | Reports | | --- | --- | --- | | shadcn/no-restyle | error | A className on an Understory component that changes more than its layout. Margin, width and position are allowed; padding, color and typography are not. | | shadcn/no-raw-colors | error | Tailwind palette colors such as bg-blue-500. | | shadcn/no-arbitrary-values | error | Arbitrary values such as w-[137px]. |

All three report at error, so a repo that turns on the export must fix its findings first or turn off individual rules in its own eslint.config.

These are allowed. The color and arbitrary-value exceptions apply to no-raw-colors and no-arbitrary-values only; on an Understory component, no-restyle still reports a color class.

| Allowed | Reason | | --- | --- | | cp-* and cps-* classes | Understory and legacy canopy-styleguide classes. Tailwind does not generate them. | | bg-[var(--cp-color-*)] and the same form for text, border, fill, stroke, outline, ring, divide | Understory's --cp-color-* variables are a supported way to apply color. | | The gray scale, such as bg-gray-100 | Understory's theme.css maps gray onto its own variables, but the name collides with Tailwind's default gray, so no-raw-colors would otherwise report it. Understory's brand, error, warning and success scales are not Tailwind palette names and are never reported. | | text-[…] | Sizes are checked by canopy/no-hardcoded-font-size. | | grid-cols-[…] and grid-rows-[…] | Grid templates have no scale to use instead. | | Padding and gap on CpWell, CpCard, CpCardBody, CpCardHeader, CpCardFooter, CpArea | These containers leave their spacing to the caller. | | Gap, but not padding, on CpModalBody, CpModalFooter, CpOverlayBody | These sections set their own padding. Gap only spaces their children. |

Not included yet:

  • shadcn/no-unknown-classes is planned. Repos whose Tailwind entry is generated by canopy-webpack-config need a lint entry file first.
  • shadcn/no-inline-styles and shadcn/require-static-classes report too much on current code.