NUMU Docs
Contact
APIThemes
Partner Apps
APIThemes
Partner Apps
  1. CLI & Vite plugin
  • šŸ—‚ļø Themes overview
  • šŸš€ Start here
  • Glossary
  • Theme engine
    • Architecture
    • BYOT contract
    • Customizer
    • Federation runtime
    • Page data contract
    • Theme manifest
  • Theme SDK
    • Components
    • Federation helpers
    • Hooks
    • SDK overview
    • Type definitions
  • CLI & Vite plugin
    • CLI commands
    • CLI overview
    • Lint rules
    • Section library
    • Vite plugin
  • Storefront host
    • API proxies
    • Built-in fallbacks
    • BYOT fork
    • Routing
    • Storefront overview
  1. CLI & Vite plugin

Lint rules

numu-theme lint runs 10 static-analysis rules. Each rule is self-contained at numu-theme-cli/src/lint/rules/<id>.ts and exports { id, description, check(ctx) }.

manifest-required-fields#

Catches: missing or malformed identity fields in theme.json.
Required: id (kebab-case ^[a-z0-9-]+$), name (bilingual object with at least en), version (semver), author (non-empty string).
Failures abort marketplace submission.
// āŒ
{ "id": "My Theme", "name": "My Theme" }

// āœ…
{ "id": "my-theme", "name": { "en": "My Theme" }, "version": "1.0.0", "author": "me@example.com" }

schema-registry-sync#

Catches: preset references to sections without a matching schema (and vice versa).
If theme.json preset uses section type hero but schemas/sections/hero.json doesn't exist, the customizer 500s at runtime — the merchant can't add or edit that section. Same in reverse: a schema file with no theme code to render it is dead weight.
āŒ theme.json references "hero" preset, but schemas/sections/hero.json is missing.
āŒ schemas/sections/banner.json declared, but no preset uses "banner".

locale-parity#

Catches: keys present in locales/en.default.json but missing in locales/ar.json (or any other locale).
// locales/en.default.json
{ "cart": { "title": "Cart", "checkout": "Checkout" } }

// locales/ar.json — missing "checkout"
{ "cart": { "title": "السلة" } }
→ āŒ locales/ar.json: missing key "cart.checkout"
The translation fallback would render the English key in an Arabic-locale store, which is bad UX. Backfill every key explicitly.

preset-schema-conformance#

Catches: preset settings that don't conform to the section's schema.
If schemas/sections/hero.json declares only headline and alignment, but the preset in theme.json sets background_image, the customizer will silently drop it on first save:
āŒ theme.json: preset "home.hero_1" sets "background_image"
   which is not declared in schemas/sections/hero.json
Also catches type mismatches (e.g. setting alignment: "diagonal" when the schema declares only left|center|right).

unused-settings#

Catches: declared settings_schema.json entries no theme code reads.
Heuristic: grep the source for settings.<id> and setting?.id === "<id>". If neither pattern matches, the setting is unreferenced.
⚠ settings_schema.json: "footer_padding_top" is declared but never read in src/
Warning by default (not an error), since false positives are possible (e.g. settings read indirectly via Object.keys(settings)). Add // numu-lint-ignore unused-settings to suppress per declaration.

img-missing-alt#

Catches: raw <img> tags without an alt attribute (including alt="" is fine — that's "decorative image").
// āŒ
<img src="/hero.jpg" />

// āœ…
<img src="/hero.jpg" alt="Summer collection hero" />

// āœ… — alt="" means "decorative, screen readers skip"
<img src="/divider.svg" alt="" />
Doesn't flag the SDK's <Image> component (which requires alt at the type level).

hardcoded-text#

Catches: 3+-word JSX text nodes that should probably go through t().
// āŒ
<h1>Shop our summer sale today</h1>

// āœ…
<h1>{t("home.summer_sale_heading")}</h1>
Heuristic: counts whitespace-separated words in JSXText nodes. 2 or fewer is fine (button labels like "Cart", "Buy now" are usually OK; longer copy isn't).
Warning by default. Themes targeting a single market may want to silence this rule via --rules exclusion.

inline-color-literal#

Catches: hex or rgb() literals in JSX style props.
// āŒ
<div style={{ color: "#FF0000", background: "rgb(15, 23, 42)" }} />

// āœ…
<div style={{ color: settings.color_text, background: settings.color_bg }} />

// āœ… — CSS variable from a theme color setting
<div style={{ color: "var(--color-text)" }} />
The whole point of the customizer is letting merchants change colors. Hard-coded colors defeat that.

forbidden-script-tag#

Catches: <script> tags in theme source components.
// āŒ
<script src="https://cdn.example.com/analytics.js" />
<script dangerouslySetInnerHTML={{ __html: "alert(1)" }} />
Forbidden at the AST level. Any inline script in a theme would bypass our CSP + admin review process. If you need third-party analytics, use useAnalytics() from the SDK — merchants configure pixels per store, not per theme.

use-app-no-availability-check#

Catches: useApp(slug) calls whose return value isn't branched on .available.
// āŒ
const app = useApp("reviews");
return <div>{app.data.average_rating}</div>;  // crashes if app not installed

// āœ…
const app = useApp("reviews");
if (!app.available) return null;
return <div>{app.data?.average_rating}</div>;
Phase 9 apps platform is optional — themes that integrate with apps must degrade gracefully when the app isn't installed on the merchant's store.

Running selectively#

The --rules flag takes a comma-separated id list. Listing none of the warning rules turns them all into a quiet shell — useful when you're prototyping and not ready to translate yet.

Suppressing per-line#

// numu-lint-ignore hardcoded-text
<h1>Internal admin tools</h1>
The comment must appear on the line before the offending construct. Use sparingly — --rules exclusion is usually cleaner.

Adding a rule (platform devs)#

To add a new rule:
1.
Create numu-theme-cli/src/lint/rules/<id>.ts:
import type { Rule } from "../runner";

export const rule: Rule = {
  id: "my-new-rule",
  description: "What this catches",
  check(ctx) {
    const issues = [];
    // walk ctx.sources / ctx.manifest / ctx.sectionSchemas / ctx.locales
    // push { severity: "error" | "warn", message, file?, line? }
    return issues;
  },
};
2.
Register it in runner.ts's RULES array.
3.
Update this page with the rule + an example.
4.
Add a test (currently informal — drop a test fixture under tests/).
Modified atĀ 2026-09-19 15:52:50
Previous
CLI overview
Next
Section library
Built with