mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-10-30 15:19:21 +00:00
Upstream merge brings 52 commits from safe-global/safe-wallet-monorepo into the luxfi/safe-wallet@dev fork. Conflicts resolved: - yarn.lock: deleted (we are on pnpm) - apps/web/package.json: kept pnpm version + brand build scripts (build:safe / build:lux / build:hanzo) plus upstream's new generate-version.mjs step - apps/mobile/.../Signer/appKit.ts: took upstream's import pattern for shared/projectId, shared/metadata, shared/mmkvStorageAdapter Yarn sweep applied to 42 files (docs, github-actions, workflows, AGENTS, specs, codemods, scripts, READMEs). Net result: zero yarn-* command references anywhere outside node_modules. ⯠pnpm is the law in this fork. No yarn.
326 lines
10 KiB
TypeScript
326 lines
10 KiB
TypeScript
/**
|
|
* Phase 2: Execute migration based on config
|
|
*/
|
|
|
|
import * as fs from 'fs'
|
|
import * as path from 'path'
|
|
import type { FeatureConfig, MigrationResult } from './types.js'
|
|
import { getFeaturePath, writeFile, formatTypeScript, findFiles } from './utils.js'
|
|
import { generateContractTemplate, generateFeatureTemplate, generateIndexTemplate } from './templates.js'
|
|
import {
|
|
ensureFolderStructure,
|
|
planFileReorganization,
|
|
executeFileReorganization,
|
|
type FileMove,
|
|
} from './transforms/fileStructure.js'
|
|
import { transformConsumerFile } from './transforms/imports.js'
|
|
import { convertExportsInFiles } from './transforms/exports.js'
|
|
import { updateImportsInMovedFiles } from './transforms/relativeImports.js'
|
|
|
|
/**
|
|
* Step 2: Handle file reorganization
|
|
*/
|
|
function executeFileReorganizationStep(config: FeatureConfig, result: MigrationResult, dryRun: boolean): FileMove[] {
|
|
console.log('š Step 2: Planning file reorganization...')
|
|
const moves = planFileReorganization(config)
|
|
|
|
if (moves.length > 0) {
|
|
console.log(` Found ${moves.length} file(s) to reorganize`)
|
|
const moveResult = executeFileReorganization(moves, dryRun)
|
|
|
|
if (!moveResult.success) {
|
|
result.errors.push(...moveResult.errors)
|
|
result.success = false
|
|
} else {
|
|
result.filesMoved = moves.map((m) => m.to)
|
|
}
|
|
} else {
|
|
console.log(' No files need reorganization')
|
|
}
|
|
|
|
return moves
|
|
}
|
|
|
|
/**
|
|
* Step 3: Convert exports to default pattern
|
|
*/
|
|
function executeExportConversionStep(
|
|
featurePath: string,
|
|
result: MigrationResult,
|
|
dryRun: boolean,
|
|
): Map<string, string> {
|
|
console.log('š Step 3: Converting exports to default pattern...')
|
|
|
|
const componentFiles: string[] = []
|
|
const componentsPath = path.join(featurePath, 'components')
|
|
|
|
if (fs.existsSync(componentsPath)) {
|
|
const componentFolders = fs.readdirSync(componentsPath).filter((name) => {
|
|
const fullPath = path.join(componentsPath, name)
|
|
return fs.statSync(fullPath).isDirectory()
|
|
})
|
|
|
|
for (const folder of componentFolders) {
|
|
const indexFile = path.join(componentsPath, folder, 'index.tsx')
|
|
if (fs.existsSync(indexFile)) {
|
|
componentFiles.push(indexFile)
|
|
}
|
|
}
|
|
}
|
|
|
|
const componentExports = new Map<string, string>()
|
|
|
|
if (componentFiles.length > 0) {
|
|
const exportResult = convertExportsInFiles(componentFiles, dryRun)
|
|
|
|
if (!dryRun) {
|
|
exportResult.conversions.forEach((c) => {
|
|
componentExports.set(c.file, c.exportName)
|
|
})
|
|
}
|
|
|
|
if (dryRun) {
|
|
console.log(` Would convert ${exportResult.filesConverted} component(s) to default exports`)
|
|
} else {
|
|
if (exportResult.filesConverted > 0) {
|
|
console.log(` ā Converted ${exportResult.filesConverted} component(s) to default exports`)
|
|
exportResult.conversions.forEach((c) => {
|
|
console.log(` - ${path.basename(path.dirname(c.file))}: ${c.exportName}`)
|
|
})
|
|
} else {
|
|
console.log(' No export conversions needed')
|
|
}
|
|
|
|
if (exportResult.errors.length > 0) {
|
|
result.errors.push(...exportResult.errors)
|
|
}
|
|
}
|
|
} else {
|
|
console.log(' No component files found')
|
|
}
|
|
|
|
return componentExports
|
|
}
|
|
|
|
/**
|
|
* Step 3.5: Update relative imports in moved files
|
|
*/
|
|
function executeImportUpdateStep(
|
|
moves: FileMove[],
|
|
componentExports: Map<string, string>,
|
|
result: MigrationResult,
|
|
dryRun: boolean,
|
|
): void {
|
|
if (moves.length === 0) return
|
|
|
|
console.log('š Step 3.5: Updating relative imports...')
|
|
|
|
const importUpdateResult = updateImportsInMovedFiles(moves, componentExports, dryRun)
|
|
|
|
if (dryRun) {
|
|
if (importUpdateResult.filesUpdated > 0) {
|
|
console.log(
|
|
` Would update imports in ${importUpdateResult.filesUpdated} file(s) (${importUpdateResult.totalUpdates} import(s))`,
|
|
)
|
|
} else {
|
|
console.log(' No import updates needed')
|
|
}
|
|
} else {
|
|
if (importUpdateResult.filesUpdated > 0) {
|
|
console.log(
|
|
` ā Updated imports in ${importUpdateResult.filesUpdated} file(s) (${importUpdateResult.totalUpdates} import(s))`,
|
|
)
|
|
importUpdateResult.details.forEach((d) => {
|
|
console.log(` - ${path.basename(path.dirname(d.file))}: ${d.updates} import(s)`)
|
|
})
|
|
} else {
|
|
console.log(' No import updates needed')
|
|
}
|
|
|
|
if (importUpdateResult.errors.length > 0) {
|
|
result.errors.push(...importUpdateResult.errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Step 4: Generate boilerplate files
|
|
*/
|
|
function executeBoilerplateGenerationStep(
|
|
config: FeatureConfig,
|
|
featurePath: string,
|
|
result: MigrationResult,
|
|
dryRun: boolean,
|
|
): void {
|
|
console.log('š Step 4: Generating boilerplate files...')
|
|
|
|
// Generate contract.ts
|
|
const contractPath = path.join(featurePath, 'contract.ts')
|
|
if (!fs.existsSync(contractPath)) {
|
|
const contractContent = formatTypeScript(generateContractTemplate(config))
|
|
|
|
if (dryRun) {
|
|
console.log(` Would create: contract.ts`)
|
|
} else {
|
|
const success = writeFile(contractPath, contractContent)
|
|
if (success) {
|
|
result.filesCreated.push(contractPath)
|
|
console.log(' ā Created contract.ts')
|
|
} else {
|
|
result.errors.push('Failed to create contract.ts')
|
|
result.success = false
|
|
}
|
|
}
|
|
} else {
|
|
result.warnings.push('contract.ts already exists, skipping')
|
|
}
|
|
|
|
// Generate feature.ts
|
|
const featureFilePath = path.join(featurePath, 'feature.ts')
|
|
if (!fs.existsSync(featureFilePath)) {
|
|
const featureContent = formatTypeScript(generateFeatureTemplate(config))
|
|
|
|
if (dryRun) {
|
|
console.log(` Would create: feature.ts`)
|
|
} else {
|
|
const success = writeFile(featureFilePath, featureContent)
|
|
if (success) {
|
|
result.filesCreated.push(featureFilePath)
|
|
console.log(' ā Created feature.ts')
|
|
} else {
|
|
result.errors.push('Failed to create feature.ts')
|
|
result.success = false
|
|
}
|
|
}
|
|
} else {
|
|
result.warnings.push('feature.ts already exists, skipping')
|
|
}
|
|
|
|
// Generate index.ts
|
|
const indexPath = path.join(featurePath, 'index.ts')
|
|
if (!fs.existsSync(indexPath)) {
|
|
const indexContent = formatTypeScript(generateIndexTemplate(config))
|
|
|
|
if (dryRun) {
|
|
console.log(` Would create: index.ts`)
|
|
} else {
|
|
const success = writeFile(indexPath, indexContent)
|
|
if (success) {
|
|
result.filesCreated.push(indexPath)
|
|
console.log(' ā Created index.ts')
|
|
} else {
|
|
result.errors.push('Failed to create index.ts')
|
|
result.success = false
|
|
}
|
|
}
|
|
} else {
|
|
result.warnings.push('index.ts already exists, skipping')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Step 5: Update consumer files
|
|
*/
|
|
function executeConsumerUpdateStep(config: FeatureConfig, result: MigrationResult, dryRun: boolean): void {
|
|
console.log('š Step 5: Updating consumer files...')
|
|
|
|
if (config.consumers.length === 0) {
|
|
console.log(' No consumers found')
|
|
return
|
|
}
|
|
|
|
console.log(` Found ${config.consumers.length} consumer file(s)`)
|
|
|
|
for (const consumer of config.consumers) {
|
|
const transformResult = transformConsumerFile(consumer.filePath, config.featureName, dryRun)
|
|
|
|
if (transformResult.success) {
|
|
result.filesModified.push(consumer.filePath)
|
|
} else {
|
|
result.errors.push(`Failed to transform ${consumer.filePath}: ${transformResult.error}`)
|
|
}
|
|
}
|
|
|
|
if (!dryRun) {
|
|
console.log(` ā Updated ${result.filesModified.length} consumer file(s)`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute the complete migration
|
|
*/
|
|
export async function executeMigration(config: FeatureConfig, dryRun: boolean = false): Promise<MigrationResult> {
|
|
const result: MigrationResult = {
|
|
success: true,
|
|
filesCreated: [],
|
|
filesModified: [],
|
|
filesMoved: [],
|
|
errors: [],
|
|
warnings: [],
|
|
}
|
|
|
|
const featurePath = getFeaturePath(config.featureName)
|
|
|
|
console.log(`\nš Starting migration for feature: ${config.featureName}`)
|
|
console.log(dryRun ? ' (DRY RUN - no files will be modified)\n' : '')
|
|
|
|
// Step 1: Ensure folder structure
|
|
console.log('š Step 1: Setting up folder structure...')
|
|
ensureFolderStructure(config, dryRun)
|
|
|
|
// Step 2: Handle file reorganization
|
|
const moves = executeFileReorganizationStep(config, result, dryRun)
|
|
|
|
// Step 3: Convert exports to default pattern
|
|
const componentExports = executeExportConversionStep(featurePath, result, dryRun)
|
|
|
|
// Step 3.5: Update relative imports
|
|
executeImportUpdateStep(moves, componentExports, result, dryRun)
|
|
|
|
// Step 4: Generate boilerplate files
|
|
executeBoilerplateGenerationStep(config, featurePath, result, dryRun)
|
|
|
|
// Step 5: Update consumer files
|
|
executeConsumerUpdateStep(config, result, dryRun)
|
|
|
|
// Summary
|
|
console.log('\n' + '='.repeat(60))
|
|
if (result.success) {
|
|
console.log('ā
Migration completed successfully!')
|
|
} else {
|
|
console.log('ā Migration completed with errors')
|
|
}
|
|
|
|
console.log('\nSummary:')
|
|
console.log(` Files created: ${result.filesCreated.length}`)
|
|
console.log(` Files modified: ${result.filesModified.length}`)
|
|
console.log(` Files moved: ${result.filesMoved.length}`)
|
|
|
|
if (result.warnings.length > 0) {
|
|
console.log(`\nā ļø Warnings:`)
|
|
result.warnings.forEach((w) => console.log(` - ${w}`))
|
|
}
|
|
|
|
if (result.errors.length > 0) {
|
|
console.log(`\nā Errors:`)
|
|
result.errors.forEach((e) => console.log(` - ${e}`))
|
|
}
|
|
|
|
// Manual steps
|
|
console.log('\nš Manual steps required:')
|
|
console.log(' 1. Review generated contract.ts and adjust public API as needed')
|
|
console.log(' 2. Review consumer files and complete the migration:')
|
|
console.log(' - Add: const feature = useLoadFeature(FeatureNameFeature)')
|
|
console.log(' - Replace: <Component /> ā <feature.Component />')
|
|
console.log(' - Import hooks directly: import { useMyHook } from "@/features/feature-name"')
|
|
console.log(' - Replace: service.method() ā feature.service?.method() (check $isReady for services)')
|
|
console.log(' 3. Keep hooks lightweight - move heavy imports to services if needed')
|
|
console.log(' 4. Update test file imports if needed')
|
|
console.log(' 5. Run: pnpm --filter @safe-global/web type-check')
|
|
console.log(' 6. Run: pnpm --filter @safe-global/web lint')
|
|
console.log(' 7. Run: pnpm --filter @safe-global/web test')
|
|
console.log('=' + '='.repeat(59))
|
|
|
|
return result
|
|
}
|