Code quality audit rules
Audit rules for the code health of a Front-Commerce project — tooling, dependencies, GraphQL conventions, error handling, and upgrade readiness.
Code quality findings rarely break a store today; they break the next upgrade, the next incident investigation, or the next developer's week. These rules check the signals that real audits use to estimate a project's maintenance cost: green tooling, a lean dependency tree, framework-aligned GraphQL code, and error handling that surfaces problems instead of hiding them.
AUDIT-CODE-01 — Keep lint and typecheck at zero errors and warnings
Severity: important — Detection: static
Rule: lint and TypeScript checking run without any error or warning on the
project's main branch.
Why: A red or noisy baseline makes new problems invisible: nobody notices warning 51 appearing among 50 tolerated ones. It also blocks safe upgrades — you cannot tell framework-induced breakage from pre-existing debt.
How to check:
pnpm lint 2>&1 | tee lint-output.txt
npx tsc --noEmit 2>&1 | tee typecheck-output.txt
(Adapt to the project's package manager and scripts; check package.json for
lint and typecheck scripts first.) Any error or warning is a violation.
Attach both outputs to the audit report. Also flag suppression debt: count
eslint-disable, @ts-ignore, and @ts-expect-error occurrences
(grep -rn "eslint-disable\|@ts-ignore\|@ts-expect-error" app extensions --include="*.ts*" | wc -l)
and report the trend.
AUDIT-CODE-02 — Remove unused and obsolete dependencies
Severity: minor — Detection: static
Rule: Every entry in package.json dependencies is imported by
application code; server-only or build-only packages are not in dependencies
when they belong in devDependencies.
Why: Unused dependencies (a real audit found 13, including express,
lodash, and compression) enlarge the install and attack surface, slow CI,
trigger pointless security-update work, and mislead developers about what the
project actually uses.
How to check:
npx knip # or: npx depcheck
Cross-check each reported package manually before flagging (config-file-only usage, CLI usage in scripts, and Vite plugins produce false positives):
grep -rn "from ['\"]lodash" app extensions --include="*.ts*" | head
A package with zero import sites, zero scripts usage, and zero config
references is a violation. Also flag duplicated utilities: lodash alongside
native equivalents, multiple date libraries, multiple HTTP clients.
AUDIT-CODE-03 — Stay close to the current Front-Commerce version
Severity: important — Detection: static
Rule: The project runs a Front-Commerce version at most 2–3 minor releases behind the latest, or has a documented, estimated upgrade plan.
Why: Each skipped minor accumulates migration steps, deprecations, and
security fixes. Past 2–3 minors, upgrades stop being routine chores and become
projects of their own — and critical fixes can no longer be applied by a simple
bump. Minor upgrades are designed to be cheap
(pnpm update "@front-commerce/*@X.Y.Z" plus the migration guide).
How to check:
# Installed version
node -p "require('@front-commerce/core/package.json').version"
# Latest published version
npm view @front-commerce/core version
Compare minor versions. A gap greater than 3 minors with no written upgrade plan
(ticket, estimate, target date) is a violation; 2–3 minors behind is a warning.
Also verify all @front-commerce/* packages are on the same version:
grep -h '"@front-commerce/' package.json
Mixed versions across @front-commerce/* packages are always a violation.
AUDIT-CODE-04 — Follow the GraphQL schema conventions
Severity: minor — Detection: static
Rule: Custom mutations return a dedicated <Action>MutationSuccess type
implementing MutationSuccessInterface, and the schema is designed as a graph:
fields return objects (product: Product) and ID identifiers, not raw scalar
ids (productId: Int).
Why: Generic success payloads and raw ids push loader logic into the client
(extra round-trips to resolve the id) and break when id formats change. The
MutationSuccess naming is what Front-Commerce codemods and tooling rely on
during upgrades — deviating types are skipped by automated migrations.
How to check:
# Custom typeDefs
grep -rn "typeDefs" extensions/ app/ --include="*.ts" -l
# Mutations not following the convention
grep -rn "extend type Mutation" extensions/ -A 10 --include="*.ts" \
| grep -E "\): (Boolean|String|Int|ID|MutationSuccess)\b"
# Raw id fields in the schema
grep -rnE "[a-z]Id(s)?: (Int|String)!?" extensions/ --include="*.ts"
Violations: a custom mutation returning Boolean, a bare scalar, or the shared
generic MutationSuccess when it mutates a domain entity (return
<Action>MutationSuccess implements MutationSuccessInterface exposing the
mutated entity); a field named xxxId typed Int/String where the entity
type exists in the schema.
AUDIT-CODE-05 — Structure GraphQL modules as definition plus lazy runtime
Severity: important — Detection: static
Rule: Every custom GraphQL module keeps its index.ts synchronous and light
(namespace, dependencies, typeDefs, loadRuntime) and loads resolvers and
contextEnhancer lazily through loadRuntime: () => import("./runtime").
Why: The module definition is evaluated at build time in contexts that must
not pull server dependencies (schema generation, codegen, client bundles).
Inlining resolvers in index.ts drags loaders, HTTP clients, and their
transitive dependencies into those contexts, slowing startup and — in the worst
case — breaking the build when a server-only import reaches a client bundle.
How to check:
# Modules declaring resolvers or contextEnhancer in the definition
grep -rn "createGraphQLModule" extensions/ app/ --include="*.ts" -l \
| xargs grep -ln "resolvers:\|contextEnhancer:"
# Definitions importing runtime code statically
grep -rn "createGraphQLModule" extensions/ --include="*.ts" -l \
| xargs grep -n "^import .*loader\|^import axios\|^import .*client" -i
A createGraphQLModule call containing resolvers or contextEnhancer
directly (instead of loadRuntime: () => import("./runtime")) is a violation.
So is an index.ts module definition with static value imports of loaders, HTTP
clients, or SDKs.
AUDIT-CODE-06 — Build cross-request services once, not per request
Severity: important — Detection: static
Rule: Services that are not tied to a single request — HTTP clients, SDK
instances, registries, providers — are constructed once and registered through
dependency injection in the extension's onServerServicesInit, then read from
services.DI.get(...) in contextEnhancer. The contextEnhancer only forwards
or builds per-request objects (loaders bound to the current user).
Why: Instantiating a client in contextEnhancer rebuilds it on every
GraphQL request: connection pools, caches, and warmup are thrown away each time,
degrading latency under load. The inverse mistake is worse: mutating a
process-wide singleton with per-request state (an authenticated client, a user
token) leaks one user's state into another's concurrent request.
How to check:
# Instantiation inside contextEnhancer bodies
grep -rn "contextEnhancer" extensions/ --include="*.ts" -A 15 \
| grep -nE "new [A-Z][A-Za-z]*(Client|Registry|Provider|Sdk|Store|Api)"
# DI usage (the expected pattern)
grep -rn "onServerServicesInit\|services.DI.register\|services.DI.get" extensions/ --include="*.ts"
A new <Client|Registry|Provider> inside contextEnhancer whose constructor
takes only configuration (no request/user data) is a violation: move it to a DI
factory registered in onServerServicesInit. Conversely, flag any DI-held
singleton whose methods are called with per-request credentials to mutate its
internal state (setAuthenticatedClient(...)-style APIs) — per-request state
belongs in per-request loaders.
AUDIT-CODE-07 — Handle errors loudly with typed domain errors
Severity: important — Detection: static
Rule: Custom code never swallows an error silently (a catch returning
[]/null/undefined without logging), types caught errors as unknown
narrowed with instanceof, and throws named domain error classes instead of
generic new Error("...") for recurring conditions.
Why: A silent catch turns an API outage into "the page shows an empty list
and nothing else in production" — undiagnosable without redeploying.
catch (error: any) plus error.message crashes when a non-Error value is
thrown. Generic errors force every caller to parse message strings instead of
instanceof-narrowing, and rewrapping without { cause } destroys the stack
trace that production debugging needs.
How to check:
# Silent catches
grep -rn "catch" extensions/ app/ --include="*.ts*" -A 3 \
| grep -B 2 "return \[\]\|return null\|return undefined\|^\s*}\s*$"
# any-typed catches
grep -rnE "catch \((e|err|error): any\)" extensions/ app/ --include="*.ts*"
# Generic errors in loaders/services
grep -rnE "throw new Error\(" extensions/ --include="*.ts" | grep -v spec
Violations: a catch block with neither a log call (logger.) nor a throw;
catch (error: any); error.message accessed without an instanceof Error
guard; repeated throw new Error("X not found") where a XNotFoundError class
should exist; rewrapping without { cause: error }. A debug(...) call alone
does not count as logging — debug flags are off in production.
AUDIT-CODE-08 — Remove deprecated APIs and 2.x leftovers
Severity: important — Detection: static
Rule: The project uses no deprecated Front-Commerce API and carries no 2.x compatibility code, unless an in-progress, documented migration explains it.
Why: Deprecated APIs are removed at the next major: every remaining call
site is a guaranteed upgrade blocker, discovered under time pressure. 2.x idioms
(withProps HOCs, config/ module overrides, v2 import paths) also mislead new
developers into extending the project the wrong way.
How to check:
# Deprecation warnings at build/boot (also see runtime logs)
pnpm build 2>&1 | grep -i "deprecat"
# Known v2 leftovers
grep -rn "front-commerce/src\|web/theme\|withProps\|makeCommandDispatcher" \
app extensions --include="*.ts*" --include="*.js*"
# Project usage of symbols the installed packages mark @deprecated
grep -rln "@deprecated" node_modules/@front-commerce/*/dist 2>/dev/null \
| head # then grep the project for the deprecated symbols found
Cross-reference findings with the migration guides for the versions between the
project's version and the latest. Any deprecated call site without a linked
migration ticket is a violation. Projects still migrating from 2.x are covered
by the dedicated AUDIT-MIG-* rules instead.
AUDIT-CODE-09 — Read configuration through Front-Commerce, not process.env
Severity: important — Detection: static
Rule: Application code reads configuration through Front-Commerce's
configuration system (configuration providers, config in GraphQL context,
usePublicConfig client-side), not through direct process.env access.
Client-exposed variables use the FRONT_COMMERCE_WEB_* prefix exclusively.
Why: Direct process.env reads bypass validation, defaults, and per-store
scoping, and fail silently when a variable is missing (an undefined
propagating deep into a request). Worse, a server variable interpolated into
client code leaks secrets into the public bundle — only FRONT_COMMERCE_WEB_*
variables are designed to be shipped to the browser.
How to check:
# Direct env access in application code
grep -rn "process.env\|import.meta.env" app extensions --include="*.ts*" \
| grep -v "FRONT_COMMERCE_WEB_" | grep -v ".spec."
# Server env vars reaching client-side files (theme components, entry.client)
grep -rn "process.env" app/theme app/entry.client.tsx --include="*.ts*"
Each process.env read outside a configuration provider definition and outside
build tooling (vite.config, scripts) is a violation: move it into a
configuration provider with a schema and defaults. Any non-FRONT_COMMERCE_WEB_
variable referenced from client-side code is a critical finding — check whether
a secret already shipped in the public bundle
(grep -rn "API_KEY\|SECRET\|TOKEN" .front-commerce/dist/client 2>/dev/null).
AUDIT-CODE-10 — Test custom business logic
Severity: important — Detection: static
Rule: Custom loaders, mappers, and other business logic in extensions/
have automated tests (*.spec.ts colocated with the source).
Why: Loaders and mappers encode the project's actual business rules (pricing, mapping backend payloads, eligibility) and are exactly the code that breaks on backend API changes and Front-Commerce upgrades. Front-Commerce loaders are designed as plain modules to be testable in isolation — audited projects that skip this routinely ship regressions that a 10-line spec would have caught.
How to check:
# Inventory business logic vs. specs
find extensions -name "*.ts" | grep -iE "loader|mapper|adapter|service" | grep -v spec
find extensions app -name "*.spec.ts" -o -name "*.spec.tsx" | wc -l
# Does a test script even exist and pass?
grep -n '"test"' package.json && pnpm test -- --run
Zero spec files in a project with custom loaders is a violation. Otherwise, list each loader/mapper/adapter without a sibling spec and report the ratio. Prioritize by risk: money-related mappers and authentication-adjacent loaders untested are individual findings; a missing spec on a trivial pass-through is not.
AUDIT-CODE-11 — Keep debug routes and one-shot scripts out of the repository
Severity: minor — Detection: static
Rule: The repository contains no committed debug routes, test endpoints,
commented-out experiments, or one-shot scripts outside a dedicated, documented
scripts/ folder.
Why: A forgotten app/routes/test.tsx or api.debug.tsx is a public
endpoint in production — at best noise for crawlers, at worst an information
leak or an unguarded mutation. Dead experiment files mislead maintenance work
and inflate the upgrade surface for free.
How to check:
# Suspicious route names
ls app/routes | grep -iE "test|debug|tmp|poc|demo|old|copy|bak"
# One-shot scripts and dead files elsewhere
git ls-files | grep -iE "\.(bak|old|orig)$|/(tmp|poc|sandbox)/"
# Leftover console debugging in app code
grep -rn "console.log\|console.debug" app extensions --include="*.ts*" | grep -v spec
Each match is a violation unless the file is intentionally shipped (a documented demo route behind an environment guard, for example). For any debug route found, also check whether it performs mutations or exposes internal data — if so, escalate it to the security section of the report.