Автоматизация миграции

Ручное обновление импортов и паттернов в крупном проекте занимает много времени. Автоматизация через codemod, lint-правила и скрипты сокращает этот процесс до нескольких минут.


jscodeshift — автоматические трансформации кода

jscodeshift — инструмент для автоматического переписывания JavaScript/TypeScript кода на основе AST.

npm install --global jscodeshift

Codemod: замена дефолтного импорта на именованный

// transforms/timeago-named-imports.js
module.exports = function(fileInfo, api) {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);

  // Найти: import timeago from 'timeago.js'
  root.find(j.ImportDeclaration, {
    source: { value: 'timeago.js' },
  }).forEach(path => {
    const defaultSpecifier = path.node.specifiers.find(
      s => s.type === 'ImportDefaultSpecifier'
    );

    if (!defaultSpecifier) return;

    const localName = defaultSpecifier.local.name; // Имя переменной

    // Найти все uses: timeago.format(...) → format(...)
    const usedMethods = new Set();
    root.find(j.MemberExpression, { object: { name: localName } })
      .forEach(memberPath => {
        usedMethods.add(memberPath.node.property.name);
        j(memberPath.parent).replaceWith(
          j.callEx * pression(
            j.identifier(memberPath.node.property.name),
            memberPath.parent.node.arguments
          )
        );
      });

    // Заменить дефолтный импорт на именованный
    path.node.specifiers = Array.from(usedMethods).map(name =>
      j.importSpecifier(j.identifier(name))
    );
  });

  return root.toSource();
};
jscodeshift -t transforms/timeago-named-imports.js src/

Codemod: обновление пути локалей

// transforms/timeago-locale-paths.js
module.exports = function(fileInfo, api) {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);

  root.find(j.ImportDeclaration).forEach(path => {
    const src = path.node.source.value;

    // Заменить старые пути на новые
    if (src.startsWith('timeago.js/locales/')) {
      const locale = src.replace('timeago.js/locales/', '');
      path.node.source = j.stringLiteral(`timeago.js/esm/lang/${locale}`);
    }
  });

  return root.toSource();
};

ESLint: lint-правила для выявления устаревших паттернов

// .eslintrc.js
module.exports = {
  rules: {
    // Запретить дефолтный импорт timeago.js
    'no-restricted-imports': ['error', {
      patterns: [{
        group: ['timeago.js'],
        importNames: ['default'],
        message: 'Используйте именованные импорты: import { format, render } from "timeago.js"',
      }],
    }],
  },
};
npx eslint src/ --fix

Скрипт поиска устаревших паттернов

#!/bin/bash
# find-legacy-timeago.sh

echo "=== Дефолтные импорты timeago.js ==="
grep -r "import timeago from 'timeago.js'" src/ --include="*.ts" --include="*.tsx"

echo "=== Старые пути локалей ==="
grep -r "timeago.js/locales/" src/ --include="*.ts" --include="*.tsx"

echo "=== render без cancel ==="
grep -rn "render(" src/ --include="*.tsx" | grep -v "cancel" | head -20

TypeScript: автоматическая проверка типов

# Выявить все ошибки типов после обновления
npx tsc --noEmit 2>&1 | grep timeago

Автоматическое тестирование совместимости

// scripts/test-migration.ts
import { execSync } from 'child_process';

const VERSIONS = ['3.0.2', '4.0.0', '4.0.2'];

for (const version of VERSIONS) {
  console.log(`Testing timeago.js@${version}...`);
  execSync(`npm install timeago.js@${version} --no-save`);
  execSync('npm test -- --testPathPattern=contract');
  console.log(`✓ ${version} passes`);
}

// Восстановить актуальную версию
execSync('npm install');

npm scripts для автоматизации

{
  "scripts": {
    "migrate:timeago": "jscodeshift -t transforms/timeago-named-imports.js src/ && jscodeshift -t transforms/timeago-locale-paths.js src/",
    "check:timeago": "eslint src/ --rule 'no-restricted-imports: error' && tsc --noEmit",
    "audit:timeago": "bash scripts/find-legacy-timeago.sh"
  }
}

Интеграция в CI

# .github/workflows/migration-check.yml
name: Migration Check

on: [push, pull_request]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - name: Check for deprecated timeago patterns
        run: npm run audit:timeago
      - name: TypeScript check
        run: npx tsc --noEmit
      - name: Tests
        run: npm test

Откат через Git

Если автоматическая трансформация дала неверный результат:

git diff --stat  # Посмотреть изменения
git diff         # Детальный diff

# Откатить конкретный файл
git checkout -- src/components/TimeAgo.tsx

# Откатить всё
git checkout -- .

Codemod трансформации рекомендуется применять маленькими порциями с промежуточными коммитами.