mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-10-30 15:19:21 +00:00
* 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 * 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 * 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. * 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 * 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. * 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) * 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. --------- * 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 * 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 <[email protected]>
4.2 KiB
4.2 KiB
Quick Start Guide
Installation
The tool is already installed as a workspace. From the repository root:
# Build the tool (only needed once or after changes)
yarn workspace @safe-wallet/migrate-feature-codemod build
Basic Usage
1. See what needs migration
node tools/codemods/migrate-feature/dist/index.js list
This shows all features and their migration status.
2. Analyze a feature
node tools/codemods/migrate-feature/dist/index.js analyze hypernative
This will:
- Scan the feature directory
- Discover all exports (components, hooks, services)
- Find all consumer files
- Prompt to save config to
.codemod/hypernative.config.json
3. Review the generated config
cat .codemod/hypernative.config.json
Edit if needed to:
- Adjust which exports are public
- Change the feature flag name
- Add notes
4. Execute migration (dry run first)
node tools/codemods/migrate-feature/dist/index.js execute hypernative --dry-run
This previews what will change without modifying files.
5. Execute for real
node tools/codemods/migrate-feature/dist/index.js execute hypernative
This will:
- Create
contract.ts,feature.ts,index.ts - Reorganize files into proper folders
- Update import statements in consumers
- Add TODO comments for manual steps
6. Complete manual migration
The tool adds TODO comments in consumer files showing what needs to be done:
// Before (old way)
import { MyComponent } from '@/features/myfeature/components/MyComponent'
// After (new way with useLoadFeature)
import { MyFeature } from '@/features/myfeature'
import { useLoadFeature } from '@/features/__core__'
const feature = useLoadFeature(MyFeature)
return <feature.MyComponent />
7. Verify
yarn workspace @safe-global/web type-check
yarn workspace @safe-global/web lint
yarn workspace @safe-global/web test
Common Scenarios
Interactive mode (recommended for first use)
node tools/codemods/migrate-feature/dist/index.js analyze --interactive
Migrate multiple features
# Analyze each feature
for feature in hypernative safe-shield swap; do
node tools/codemods/migrate-feature/dist/index.js analyze $feature
done
# Review configs, then execute
for feature in hypernative safe-shield swap; do
node tools/codemods/migrate-feature/dist/index.js execute $feature
done
Custom config location
node tools/codemods/migrate-feature/dist/index.js analyze myfeature -o custom-config.json
node tools/codemods/migrate-feature/dist/index.js execute --config custom-config.json
Troubleshooting
"Feature not found"
Make sure you're in the repository root and the feature exists in apps/web/src/features/.
"Failed to transform imports"
Some complex import patterns may not be handled automatically. Check the console output for details and update those files manually.
Type errors after migration
This is expected. You need to:
- Complete the consumer file migrations (replace direct imports with feature handle)
- Ensure all imports in
feature.tsare correct - Run
yarn workspace @safe-global/web type-checkto see all errors
What the Tool Does
Automatically:
- ✅ Creates boilerplate files (
contract.ts,feature.ts,index.ts) - ✅ Organizes files into
components/,hooks/,services/folders - ✅ Updates import statements to remove internal paths
- ✅ Adds feature handle import
- ✅ Adds TODO comments for manual steps
Manually required:
- ⚠️ Update consumer components to use
useLoadFeature() - ⚠️ Replace direct component/hook usage with feature handle
- ⚠️ Fix type errors
- ⚠️ Update tests to mock the feature handle
- ⚠️ Verify everything works
Next Steps
For complete documentation, see:
README.md- Full tool documentationapps/web/docs/feature-architecture.md- Architecture guide
Tips
- Start small: Migrate simple features first to get familiar with the process
- One at a time: Don't migrate multiple features simultaneously
- Dry run first: Always use
--dry-runto preview changes - Review diffs: Use git to review all changes before committing
- Test thoroughly: Run tests after each migration