Другое

Руководство по поддержке флага --dry-run для генераторов Nx

Узнайте, как сделать ваш кастомный генератор Nx поддерживающим флаг --dry-run при вызове других генераторов. Исправьте предупреждение и реализуйте корректную симуляцию dry-run с помощью этого полного руководства.

Как сделать так, чтобы кастомный генератор Nx поддерживал флаг --dry-run при вызове другого генератора?

Я разрабатываю кастомный генератор Nx, который создает библиотеку и изменяет рабочее пространство (например, обновляет правила ESLint). Генератор работает корректно, но когда я запускаю его с флагом --dry-run, Nx отображает следующее предупреждение:

“ПРИМЕЧАНИЕ: Этот генератор не поддерживает --dry-run. Если вы запускаете это в Nx Console, он должен выполниться корректно после нажатия кнопки ‘Generate’.”

Предупреждение исчезает, если я удаляю вызов другого встроенного генератора (например, libraryGenerator из @nx/angular/generators), и dry-run работает как ожидается. Похоже, что вызов другого генератора внутри моего кастомного отключает симуляцию dry-run.

Вот упрощенный пример моего генератора:

typescript
import { Tree, formatFiles } from '@nx/devkit';
import { libraryGenerator } from '@nx/angular/generators';

export default async function myGenerator(tree: Tree, options: any) {
  await libraryGenerator(tree, {
    ...options,
    name: 'demo-lib',
  });

  // еще редактирования, все с использованием tree.read/write...
  await formatFiles(tree);
}

Есть ли способ сделать так, чтобы составной генератор все еще поддерживал флаг --dry-run?

Поддержка флага --dry-run в кастомных генераторах Nx

Чтобы ваш кастомный генератор Nx поддерживал флаг --dry-run при вызове другого генератора, необходимо правильно распространять контекст dry-run через параметры генератора. Проблема возникает потому, что вызываемый генератор не осведомлен о режиме dry-run, поэтому вам нужно явно передать флаг dry-run и правильно его обработать.

Содержание


Понимание флага --dry-run в Nx

Nx использует архитектуру на основе Tree (дерева), где все операции с файловой системой проходят через виртуальную файловую систему. Флаг --dry-run позволяет пользователям симулировать, какие изменения были бы сделаны без фактического изменения их рабочего пространства. Это особенно полезно для:

  • Предварительного просмотра изменений перед их применением
  • Обучающих целей
  • Сценариев скриптов, где вы хотите сначала проверить изменения

Когда включен режим --dry-run, Nx должен отображать все файловые операции, которые были бы выполнены, но пропустить фактическую запись на диск.

Основная проблема с составными генераторами

Проблема, с которой вы сталкиваетесь, возникает потому, что при вызове другого генератора внутри вашего кастомного генератора вы не передаете контекст dry-run. Вызываемый генератор (например, libraryGenerator) не знает, что он работает в режиме dry-run, поэтому он ведет себя так, как будто вносит реальные изменения.

Рассмотрим ваш код:

typescript
export default async function myGenerator(tree: Tree, options: any) {
  await libraryGenerator(tree, {
    ...options,
    name: 'demo-lib',
  });
  // ...
}

Объект options не содержит информацию о режиме dry-run, поэтому libraryGenerator продолжает работать в обычном режиме.

Решение: Распространение флага --dry-run

Решение заключается в проверке, работает ли ваш генератор в режиме dry-run, и передаче этой информации вызываемому генератору. Вот как это реализовать:

typescript
import { Tree, formatFiles } from '@nx/devkit';
import { libraryGenerator } from '@nx/angular/generators';
import { stripIndents } from '@nx/devkit/src/utils/strip-indents';

export default async function myGenerator(tree: Tree, options: any) {
  // Проверяем, работаем ли мы в режиме dry-run
  const isDryRun = options.dryRun || false;
  
  // Передаем флаг dry-run вызываемому генератору
  await libraryGenerator(tree, {
    ...options,
    name: 'demo-lib',
    dryRun: isDryRun,
  });

  // Ваша кастомная логика - убедитесь, что она также учитывает dry-run
  if (!isDryRun) {
    // Выполняем фактические изменения только при работе не в режиме dry-run
    // Пример: обновление конфигурации ESLint
    const eslintConfig = tree.read('eslint.config.js');
    if (eslintConfig) {
      const updatedConfig = modifyEslintConfig(eslintConfig.toString());
      tree.write('eslint.config.js', updatedConfig);
    }
  }

  // formatFiles также должен учитывать dry-run
  if (!isDryRun) {
    await formatFiles(tree);
  }
}

// Вспомогательная функция для модификации конфигурации ESLint
function modifyEslintConfig(config: string): string {
  return stripIndents`
    ${config}
    
    // Кастомные правила ESLint, добавленные my-generator
    rules: {
      'my-custom-rule': 'error'
    }
  `;
}

Ключевые моменты решения:

  1. Обнаружение режима dry-run: Проверьте options.dryRun, чтобы определить, работает ли генератор в режиме симуляции
  2. Распространение флага: Передайте dryRun: isDryRun вызываемому генератору
  3. Условная логика: Выполняйте фактические файловые операции только при работе не в режиме dry-run
  4. Вспомогательные функции: Создавайте функции, которые можно легко тестировать и которые работают последовательно в обоих режимах

Альтернативный подход: Использование вспомогательных функций генераторов

Для более сложных сценариев вы можете создать вспомогательные функции, которые последовательно обрабатывают логику dry-run:

typescript
import { Tree, formatFiles } from '@nx/devkit';
import { libraryGenerator } from '@nx/angular/generators';
import { stripIndents } from '@nx/devkit/src/utils/strip-indents';

// Вспомогательная функция, учитывающая dry-run
export async function safeUpdateFile(tree: Tree, path: string, content: string, isDryRun: boolean) {
  if (!isDryRun) {
    tree.write(path, content);
    console.log(`✏️  ${path}`);
  } else {
    console.log(`📝 Запись в: ${path}`);
    console.log(`Предпросмотр содержимого:\n${content.substring(0, 100)}...`);
  }
}

export default async function myGenerator(tree: Tree, options: any) {
  const isDryRun = options.dryRun || false;
  
  // Вызов генератора библиотеки с поддержкой dry-run
  await libraryGenerator(tree, {
    ...options,
    name: 'demo-lib',
    dryRun: isDryRun,
  });

  // Использование вспомогательных функций для последовательного поведения dry-run
  const eslintConfig = tree.read('eslint.config.js');
  if (eslintConfig) {
    const updatedConfig = modifyEslintConfig(eslintConfig.toString());
    await safeUpdateFile(tree, 'eslint.config.js', updatedConfig, isDryRun);
  }

  // Форматирование файлов только при работе не в режиме dry-run
  if (!isDryRun) {
    await formatFiles(tree);
  }
}

Этот подход обеспечивает более последовательное ведение журнала и поведение в вашем генераторе.


Лучшие практики для поддержки dry-run

1. Всегда проверяйте флаг dry-run

Перед выполнением любых файловых операций проверяйте, работаете ли вы в режиме dry-run:

typescript
const isDryRun = options.dryRun || false;

if (!isDryRun) {
  // Фактические файловые операции здесь
}

2. Предоставляйте четкий вывод в режиме dry-run

В режиме dry-run четко указывайте, что произошло бы:

typescript
if (isDryRun) {
  console.log(`📋 Режим dry-run - обновление: ${filePath}`);
  console.log(`📋 Предпросмотр содержимого:\n${content.substring(0, 200)}...`);
}

3. Обрабатывайте все вызовы генераторов

Убедитесь, что все вызовы генераторов внутри вашего кастомного генератора также получают флаг dry-run:

typescript
await someOtherGenerator(tree, {
  ...options,
  dryRun: isDryRun,
  // другие параметры...
});

4. Используйте встроенные утилиты Nx

Используйте существующие утилиты Nx, которые поддерживают dry-run:

typescript
import { addDependenciesToPackageJson } from '@nx/devkit';

await addDependenciesToPackageJson(
  tree,
  { 'my-package': '^1.0.0' },
  {},
  isDryRun
);

5. Тестируйте вашу реализацию dry-run

Создавайте тесты для проверки поведения dry-run:

typescript
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { myGenerator } from './generator';

describe('my-generator', () => {
  it('должен поддерживать режим dry-run', async () => {
    const tree = createTreeWithEmptyWorkspace();
    
    await myGenerator(tree, { 
      name: 'test-lib',
      dryRun: true,
    });
    
    // Проверяем, что фактические изменения не были внесены
    expect(tree.exists('libs/test-lib')).toBeFalsy();
  });
});

Заключение

Чтобы ваш кастомный генератор Nx поддерживал флаг --dry-run при вызове другого генератора, вам необходимо:

  1. Обнаруживать режим dry-run, проверяя options.dryRun
  2. Распространять флаг на все вызываемые генераторы
  3. Реализовывать условную логику, которая выполняет фактические файловые операции только при работе не в режиме dry-run
  4. Предоставлять четкий вывод в режиме dry-run, чтобы помочь пользователям понять, что произошло бы
  5. Тестировать вашу реализацию, чтобы убедиться, что она правильно работает в обоих режимах

Ключевое понимание заключается в том, что поддержка dry-run должна обрабатываться на каждом уровне вашего генератора - не только в основной логике, но и в любых вызовах генераторов или файловых операциях, которые вы выполняете. Следуя этим шаблонам, вы создадите генераторы, которые обеспечивают последовательный и полезный пользовательский опыт независимо от того, работают ли они в режиме симуляции или фактического выполнения.

Помните, что предупреждение, которое вы видели (“Этот генератор не поддерживает --dry-run”), исчезнет после правильной реализации поддержки dry-run, поскольку Nx распознает, что ваш генератор может правильно обрабатывать режим симуляции.

Авторы
Проверено модерацией
Модерация