Files
cd41dc46d3 refactor(web): add codemod tool for feature architecture migration + migrate tx-notes (#7042)
* chore: add codemod tool for feature v3 architecture migration

Add a comprehensive two-phase codemod tool to automate migration of features
to the v3 architecture (lazy loading + feature handles).

Features:
- Phase 1 (Analyze): Scan feature and generate migration config
- Phase 2 (Execute): Apply transformations based on config
- Interactive CLI with dry-run support
- Automatic file reorganization and boilerplate generation
- JSCodeshift transforms for import updates

The tool automates ~50-70% of migration work:
- Creates contract.ts, feature.ts, index.ts
- Moves files to proper folder structure
- Updates import statements in consumers
- Adds TODO comments for manual steps

Remaining manual work:
- Complete useLoadFeature() migration
- Fix type errors
- Update tests

Location: tools/codemods/migrate-feature/
Usage: node tools/codemods/migrate-feature/dist/index.js --help

Co-authored-by: Hanzo Dev <dev@hanzo.ai>

* feat(tx-notes): migrate to v3 feature architecture

Migrate tx-notes feature to v3 architecture using the codemod tool.

Changes:
- Created contract.ts, feature.ts, index.ts with flat structure
- Reorganized files into components/ and services/ folders
- Converted components to default exports
- Updated consumer files to use useLoadFeature()
- Fixed test imports

Migration tool handled:
- Boilerplate generation
- File reorganization
- Consumer file identification

Manual steps completed:
- Fixed component exports (named → default)
- Fixed internal component imports in TxNoteForm
- Updated 2 consumer files with useLoadFeature()
- Fixed test file import path
- Removed old index.tsx

Tests:  All pass
Type-check:  No errors
Feature status:  Now using lazy loading + feature handles

Co-authored-by: Hanzo Dev <dev@hanzo.ai>

* feat(codemod): add automatic export pattern conversion

Add automatic conversion of named exports to default exports for components.

This addresses the biggest manual pain point from the pilot migration.
Components often use named exports (export function/const) but the v3
architecture requires default exports for the contract pattern.

Changes:
- New transform: transforms/exports.ts
  - Detects named function exports: export function Name()
  - Detects named const exports: export const Name =
  - Converts to default exports automatically
- Integrated into execute.ts as Step 3 (after file moves, before boilerplate)
- Processes all component files in components/ folder
- Works for both function declarations and const arrow functions

Testing:
 export function Component() → export default function Component()
 export const Component = () => {} → const Component = ...; export default Component

Impact:
- Automation increases from ~60% to ~70%
- Eliminates most tedious manual conversion work
- Reduces migration time by ~10 minutes per feature

Updated manual steps documentation to reflect this automation.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>

* feat(codemod): add automatic relative import updates

Add automatic update of relative imports after file reorganization.

This addresses the second biggest manual pain point from the pilot migration.
When files are moved during reorganization, their internal imports break and
need manual path recalculation.

Changes:
- New transform: transforms/relativeImports.ts
  - Parses import statements in moved files
  - Detects relative imports (./file or ../folder/file)
  - Recalculates paths after file moves
  - Handles both default and named imports
  - Converts named imports to default when needed (after export conversion)
- Integrated into execute.ts as Step 3.5 (after export conversion)
- Processes all moved files automatically
- Reports detailed update information

Example transformation:
Before move: features/tx-notes/TxNoteForm.tsx
  import { TxNote } from './TxNote'
  import { TxNoteInput } from './TxNoteInput'

After move: features/tx-notes/components/TxNoteForm/index.tsx
  import TxNote from '../TxNote'          //  Auto-updated path
  import TxNoteInput from '../TxNoteInput' //  Auto-updated path
  //  Auto-converted to default imports

Features:
- Handles default and named imports
- Handles type imports (import type)
- Updates paths for moved files
- Integrates with export conversion results
- Supports .ts, .tsx, .js, .jsx files

Impact:
- Automation increases from ~70% to ~80%
- Eliminates import path recalculation work
- Reduces migration time by ~5 minutes per feature

fix(tx-notes): optimize TxNote callback dependencies

Destructure feature exports to avoid adding entire feature object
to callback dependencies. This prevents unnecessary re-renders.

Before:
  const txNotes = useLoadFeature(TxNotesFeature)
  useCallback(..., [txNotes]) //  Entire object

After:
  const { encodeTxNote, TxNoteForm } = useLoadFeature(TxNotesFeature)
  useCallback(..., [encodeTxNote]) //  Only the function

Co-authored-by: Hanzo Dev <dev@hanzo.ai>

* docs: fix feature architecture hooks pattern to avoid Rules of Hooks violations (#7044)

* docs: fix feature architecture hooks pattern to avoid Rules of Hooks violations

The previous architecture attempted to lazy-load hooks and stub them,
which violates React's Rules of Hooks. Dynamically swapping a stub
function for a real hook changes the number of internal hook calls
between renders.

Changes:
- Hooks are now exported directly from index.ts (always loaded, not lazy)
- Hooks are NOT included in feature contracts or feature.ts
- Updated useLoadFeature proxy to not stub hooks (returns undefined)
- Hooks kept lightweight with minimal imports
- Heavy logic moved to lazy-loaded services
- Updated all documentation and examples

This ensures hooks are always the same function reference and never
violate Rules of Hooks while keeping components and services lazy-loaded.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>

* docs: update walletconnect and counterfactual feature comments for new hooks pattern

Both features were already compliant with the new hooks architecture (hooks
not in contracts/feature.ts), but had outdated comments that needed updating.

Changes:
- Updated contract.ts comments to remove hook stub references
- Clarified that hooks are not in contracts (exported from index.ts if public)
- Updated index.ts usage examples to show hook imports
- Added notes about Rules of Hooks compliance

Both features correctly follow the pattern:
- WalletConnect: hooks used internally only, not exported
- Counterfactual: hooks exported from index.ts (always loaded)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>

* refactor(web): remove redundant feature component checks

Remove unnecessary conditional checks for WalletConnectWidget and CounterfactualStatusButton since useLoadFeature always returns an object with proxy-based stubs that handle loading/disabled states automatically.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>

---------

Co-authored-by: Hanzo Dev <dev@hanzo.ai>

* refactor(codemod): update for new architecture - hooks are not lazy-loaded

BREAKING CHANGE: Hooks are now exported directly from index.ts instead of
being part of the lazy-loaded feature contract. This aligns with the updated
feature architecture that avoids Rules of Hooks violations.

Changes to codemod tool:
- templates.ts: Remove hooks from contract.ts generation
- templates.ts: Remove hooks from feature.ts generation
- templates.ts: Add direct hook exports in index.ts generation
- README.md: Update examples to show direct hook imports
- execute.ts: Update manual steps instructions

Changes to tx-notes (pilot feature):
- Update contract.ts comments (no hooks in contract)
- Update feature.ts comments (no hooks in lazy-loaded feature)
- Update index.ts comments and exports (hooks exported directly)

Rationale (from feature-architecture.md):
Lazy-loading hooks violates Rules of Hooks because the stub function has 0
internal hook calls while the real hook has N calls. Swapping between them
changes the number of hooks between renders, which React forbids.

The solution: Export hooks directly from index.ts (always loaded, not lazy).
Hooks should be kept lightweight with minimal imports. Heavy logic should be
in services (which are lazy-loaded).

Test results:
- Type-check: ✓ Clean
- Tests: ✓ All passing (tx-notes)
- Tool builds: ✓ No errors

Co-authored-by: Hanzo Dev <dev@hanzo.ai>

* refactor(codemod): reduce complexity in analyze.ts and execute.ts

Addresses code health issues flagged in PR review (CodeScene analysis).

Changes to analyze.ts:
- Extract parseDefaultImport() helper function
- Extract parseNamedImports() helper function
- Simplify categorizeExports() using functional approach
- Reduces nesting depth from 4 to 2 in parseImports()
- Improves maintainability and readability

Changes to execute.ts:
- Extract executeFileReorganizationStep() for Step 2
- Extract executeExportConversionStep() for Step 3
- Extract executeImportUpdateStep() for Step 3.5
- Extract executeBoilerplateGenerationStep() for Step 4
- Extract executeConsumerUpdateStep() for Step 5
- Reduces cyclomatic complexity of executeMigration()
- Main function now 10 lines instead of ~240 lines
- Each step is now a focused, testable function

Changes to fileStructure.ts:
- Export FileMove interface for use in other modules
- Enables proper type safety across transforms

Impact:
- Code health improved (reduced "Bumpy Road Ahead" violations)
- Easier to understand and maintain
- Better testability (each step can be tested independently)
- No functional changes - behavior remains identical

Co-authored-by: Hanzo Dev <dev@hanzo.ai>

---------

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-01-27 21:56:04 +01:00

28 lines
633 B
JSON

{
"name": "@safe-wallet/migrate-feature-codemod",
"version": "1.0.0",
"private": true,
"description": "Codemod tool to migrate features to v3 architecture",
"type": "module",
"bin": {
"migrate-feature": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"migrate": "node dist/index.js"
},
"dependencies": {
"@types/node": "^22.13.1",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"inquirer": "^11.1.0",
"jscodeshift": "^17.1.1",
"typescript": "~5.9.2"
},
"devDependencies": {
"@types/inquirer": "^9.0.7",
"@types/jscodeshift": "^0.12.0"
}
}