Skip to main content
Version: next

Migration audit rules (2.x to 3.x)

Audit rules for projects migrating from Front-Commerce 2.x or still carrying 2.x compatibility code.

A 2.x to 3.x migration is a long-running effort during which compatibility layers, temporary degradations, and leftover 2.x code coexist with native 3.x code. These rules verify that this transitional state is controlled: every compatibility mechanism is inventoried, shrinking, and has an exit plan.

Scope

This section only applies to projects migrating from Front-Commerce 2.x or still carrying 2.x compatibility code. Skip it entirely for native 3.x projects.

AUDIT-MIG-01 — Keep @front-commerce/compat usage inventoried and decreasing

Severity: important — Detection: static

Rule: Every import from @front-commerce/compat is counted, the count decreases over time, and a written removal plan assigns each remaining usage to a ticket or milestone.

Why: The compat package exists to enable a progressive migration, not as a permanent dependency. New Front-Commerce projects must not use it, and features built on compat APIs miss the performance and correctness improvements of the native 3.x equivalents. An uninventoried compat surface means the migration has stalled without anyone deciding it.

How to check:

grep -rn "@front-commerce/compat" app/ extensions/ --include="*.ts" --include="*.tsx" | wc -l
grep -rln "@front-commerce/compat" app/ extensions/ | sort

Record the total and the file list in the audit report and compare with the previous audit. A violation is: a count that grew since the last measurement, compat imports in files created after the migration started (new features must use native APIs), or the absence of any tracked plan for the remaining usages. If @front-commerce/compat is absent from package.json, this rule and most of this section pass trivially.

AUDIT-MIG-02 — Leave no v2 module or middleware orphaned

Severity: critical — Detection: static

Rule: Every code directory in the repository is either referenced from front-commerce.config.ts (directly or through an extension's definition) or deleted; no v2-era module, Express middleware, or server extension survives unreferenced.

Why: In 2.x, modules were wired through .front-commerce.js; in 3.x, only what an extension registers runs. A directory that was never re-wired still sits in the repository, looks implemented, and silently does nothing. This is a real incident pattern: a referrer-policy middleware and custom image proxies were "migrated" as files but never registered — the security header and the proxied images disappeared from production without any error.

How to check: Build the reference graph from the config entry point:

cat front-commerce.config.ts
grep -rn "defineExtension\|export default" extensions/*/index.ts 2>/dev/null

Then list top-level code directories and match them against the extensions declared in the config:

ls extensions/ src/ 2>/dev/null
grep -rn "\.front-commerce\.js\|onServerInit\|middleware" src/ extensions/ 2>/dev/null | head -20

A violation is any directory containing loaders, middleware, GraphQL modules, or routes that no declared extension imports — especially a leftover src/ tree from the v2 layout. For each orphan, decide: re-register it through an extension, or delete it and record the intentional feature removal. Compare the v2 module list (from the pre-migration .front-commerce.js, available in git history) against what the 3.x config registers to catch silent losses.

AUDIT-MIG-03 — Keep a single source of truth for configuration

Severity: important — Detection: static

Rule: Each configuration concern (stores, website, caching, analytics, cookies services…) is defined in exactly one place — the 3.x app/config/ directory — and the v2 src/config/ tree contains no live duplicate.

Why: During migration, config files get copied from src/config/ to app/config/ and the original is often kept "for reference". Two files then drift: a fix applied to the dead copy has no effect, and a developer reading the wrong one reasons from stale values. Real projects have shipped incidents caused by editing the unused copy.

How to check:

ls src/config/ 2>/dev/null && ls app/config/
for f in $(ls src/config/ 2>/dev/null); do [ -e "app/config/$f" ] && echo "DUPLICATE: $f"; done
grep -rn "src/config" app/ extensions/ front-commerce.config.ts 2>/dev/null

A violation is any file present under both trees, or any import still pointing into src/config/ while an app/config/ twin exists. The fix is mechanical: confirm which copy is actually imported, merge any drift into it, and delete the other. If src/config/ is entirely unreferenced, delete the whole directory.

AUDIT-MIG-04 — Remove patches targeting the previous major version

Severity: important — Detection: static

Rule: Every patch file (patch-package, pnpm patch) applies to a dependency version actually installed, and no patch targets a package or toolchain that only existed in the 2.x stack (webpack, 2.x Front-Commerce internals).

Why: A patch written for the v2 toolchain either fails the install or — worse — applies to nothing and lulls the team into believing the fix is still active. A real project carried a committed webpack patch on a v3/Vite codebase: the patched behavior was silently gone, and the patch directory suggested otherwise to every reader.

How to check:

ls patches/ 2>/dev/null
grep -n "patchedDependencies" package.json pnpm-workspace.yaml 2>/dev/null

For each patch, compare the package name and version in the patch filename (for example webpack+4.46.0.patch) against the installed version:

pnpm why <package> 2>/dev/null || echo "NOT INSTALLED"

A violation is a patch whose target package is absent from the lockfile, whose version no longer matches, or that patches a 2.x-only tool (webpack, babel plugins) in a Vite-based project. Delete stale patches; for still-needed ones, record why upstream cannot ship the fix.

AUDIT-MIG-05 — Scope the v2 catch-all route to a known, shrinking page list

Severity: important — Detection: static

Rule: If the project still uses the v2 compatibility catch-all route (_main.$.tsx delegating to compat LoadableRoutes), the list of pages it serves is explicit, tracked, and shrinking; every newly migrated page gets a native Remix route and is removed from the catch-all's scope.

Why: The catch-all renders v2-style routes through the compat layer, without the benefits of native Remix routes: loader-based data fetching, route-level meta, granular error boundaries, and shouldRevalidate control. When its scope is unknown, pages believed migrated may still be served by the compat path, and the migration cannot be declared done.

How to check:

ls app/routes/ | sort
cat "app/routes/_main.\$.tsx" 2>/dev/null
grep -rn "LoadableRoutes\|web/moduleRoutes" app/ src/ --include="*.tsx" --include="*.js" 2>/dev/null

A violation is a catch-all present with no accompanying inventory of which URLs it still serves, or a v2 route file (in the compat routes tree) whose URL also has a native app/routes/ file — the native one shadows it, leaving dead code. Cross-check the inventory against the sitemap or the route list from the v2 project. When the catch-all serves zero pages, delete it along with the compat route definitions.

AUDIT-MIG-06 — Document and ticket every temporary degradation

Severity: important — Detection: manual

Rule: Every capability deliberately degraded during the migration (Apollo SSR disabled, link preloading disabled, a feature stubbed out) is written down with the date it was introduced, the user-visible impact, and a ticket for its resolution.

Why: A degradation accepted "for two weeks" without a ticket becomes the permanent state of production. SSR disabled for client-side Apollo queries, for example, means SEO-critical content is invisible to crawlers — acceptable as a conscious step, harmful as a forgotten default.

How to check: Gather the candidates, then require evidence for each:

grep -rn "v2_compat" front-commerce.config.ts
grep -rn -i "TODO\|FIXME\|temporar\|disabled" front-commerce.config.ts app/config/ | head -20

For each flag or disabling found (useApolloClientSSR: false while the app still runs SEO-critical Apollo queries, preload options turned off, commented out features), ask for the ticket reference and the documented impact. A violation is any degradation that no document or ticket accounts for — the judgment of what counts as "degraded" versus "intended" is why this rule is manual. Record each undocumented degradation as its own finding with an owner.

AUDIT-MIG-07 — Delete one-shot migration scripts after execution

Severity: minor — Detection: static

Rule: Codemod outputs, data-fixing scripts, and other run-once migration tooling are removed from the repository once executed; anything kept is moved out of the build path and documented.

Why: A migration script that already ran is dead code with sharp edges: rerunning it can corrupt data, and its presence in scripts/ or bin/ suggests it is part of the normal workflow. It also drags obsolete dependencies into the lockfile.

How to check:

ls scripts/ bin/ tools/ 2>/dev/null
grep -n "migrate\|codemod" package.json
grep -rn "TODO (Codemod generated)" app/ extensions/ src/ 2>/dev/null | wc -l

A violation is a committed one-shot script with no README explaining why it is kept, a package.json script entry invoking a migration that already ran, or remaining TODO (Codemod generated) comments — those mark manual migration steps the codemods could not complete, and each one is unfinished migration work, not a regular TODO.

AUDIT-MIG-08 — Inventory v2 library shims with a removal plan

Severity: important — Detection: static

Rule: Every shim, alias, or stub kept for a 2.x-era library (react-router v5 APIs, react-helmet-async, analytics.js-style wrappers) is listed with the components depending on it and a planned removal milestone.

Why: 3.x replaces these libraries at the framework level: routing comes from @remix-run/react (react-router v6 underneath) and head management from route meta functions — react-helmet-async was removed from the framework. Shims keep old call sites compiling, but they freeze the project on unmaintained API surfaces and can conflict with the framework's own behavior (two head managers fighting over tags, two router contexts).

How to check:

grep -rn "react-helmet\|react-router-dom" app/ extensions/ src/ package.json --include="*.ts" --include="*.tsx" --include="*.json" 2>/dev/null
grep -n "alias" vite.config.ts front-commerce.config.ts 2>/dev/null

A violation is: react-router-dom or react-helmet-async still in package.json, a Vite alias mapping a v2 library to a local stub without an inventory of its consumers, or components still using <Helmet> instead of route meta exports for SEO-relevant tags (title, description, canonical) — head tags rendered client-side only are invisible to crawlers that do not execute JavaScript. For each shim, the report lists its dependents (the grep output above) and the milestone where it disappears.

AUDIT-MIG-09 — Keep v2_compat flags minimal and justified

Severity: important — Detection: static

Rule: Every flag enabled under v2_compat in front-commerce.config.ts corresponds to a pattern the codebase actually still uses, and no flag remains enabled after its last consumer was migrated.

Why: The v2_compat flags (useApolloClientQueries, useApolloClientSSR, …) exist to keep deprecated patterns working during the transition, at a cost: useApolloClientSSR for instance adds an SSR rendering pass for Apollo queries. A flag left enabled after the code it served is gone imposes that cost for nothing and hides the fact that the migration step is actually complete.

How to check: Cross-check each enabled flag against its consumers:

grep -n -A10 "v2_compat" front-commerce.config.ts
grep -rn "graphql(" app/ extensions/ --include="*.tsx" | grep -v "\.graphql\|gql\`" | head
grep -rn "useQuery\|@apollo/client" app/ extensions/ --include="*.tsx" | head

A violation is: useApolloClientQueries enabled while no component uses the Apollo graphql() HOC or Apollo client-side queries anymore, or useApolloClientSSR enabled while no remaining Apollo query renders SEO-critical content. Conversely, Apollo usages present with the matching flag disabled indicate a latent hydration bug. For each enabled flag, the report names at least one remaining consumer — a flag with zero consumers must be turned off in the same audit cycle.