Ошибка при обработке документов:
Произошла ошибка: TypeError: Cannot read properties of undefined (reading 'eachRow')
    at removeDuplicatesInFile (file:///Users/bokkdanil/Projects/SEO/Seo-Report-Generator/work_with_docs/clearDublicate_mergeDocs.js:138:13)
    at async removeDuplicatesInAllFiles (file:///Users/bokkdanil/Projects/SEO/Seo-Report-Generator/work_with_docs/clearDublicate_mergeDocs.js:169:7)
    at async processFiles (file:///Users/bokkdanil/Projects/SEO/Seo-Report-Generator/work_with_docs/clearDublicate_mergeDocs.js:175:3)

Вот код:
import ExcelJS from 'exceljs';
import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import config from "./config.js"; 

const directoryPath = config.directoryPath

// Конфигурация пар файлов и соответствующих столбцов
const filePairs = [
  {
    sourceFileName: 'Смешанный контент.xlsx', // Из какого документа
    targetFileName: 'Безопасность. Смешанный контент.xlsx', // В какой документ
    sourceColumns: ['Где найдено', 'Содержимое, загружаемое через HTTP'], // Из каких столбцов перенести данные
    targetColumns: ['Источник', 'Назначение'], // В какие столбцы перенести данные
    keyColumns: ['Источник', 'Назначение'], // Столбцы для проверки дубликатов
  },
  {
    sourceFileName: 'Страницы содержащие битые ссылки.xlsx',
    targetFileName: 'Коды ответа. Ошибка (4xx).xlsx',
    sourceColumns: ['Страница с битой ссылкой', 'Битая ссылка','Код ответа'],
    targetColumns: ['Источник', 'Назначение','Код ответа'],
    keyColumns: ['Источник', 'Назначение', 'Код ответа'], 
  },
  {
    sourceFileName: 'Закрытые от индексации, но есть в sitemap.xml.xlsx',
    targetFileName: 'Sitemap. Неиндексируемые URL в Sitemap.xlsx',
    sourceColumns: ['Страница с битой ссылкой', 'Битая ссылка','Код ответа'],
    targetColumns: ['Источник', 'Назначение','Код ответа'],
    keyColumns: ['Источник', 'Назначение', 'Код ответа'], 
  },
  {
    sourceFileName: 'Разрешённые для индексации, но нет в sitemap.xml.xlsx',
    targetFileName: 'Sitemap. URL не в Sitemap.xlsx',
    sourceColumns: ['Нет в sitemap, но разрешены для индексации', 'HTTP Code'],
    targetColumns: ['Адрес', 'Код ответа'],
    keyColumns: ['Адрес', 'Код ответа'], 
  },
  {
    sourceFileName: 'Изображения с пустым Alt.xlsx',
    targetFileName: 'Изображения. Отсутствует Alt.xlsx',
    sourceColumns: ['SRC изображения', 'Страница, где находится'],
    targetColumns: ['Назначение', 'Источник'],
    keyColumns: ['Назначение', 'Источник'], 
  },
  {
    sourceFileName: 'Ссылки. Страницы без внутренних исходящих ссылок.xlsx',
    targetFileName: 'Нет исходящих.xlsx',
    sourceColumns: ['Адрес', 'Кол-во входящих ссылок'],
    targetColumns: ['Страница', 'Внутренние ссылки'],
    keyColumns: ['Страница', 'Внутренние ссылки'], 
  },
  {
    sourceFileName: 'Страницы с тегом noindex.xlsx',
    targetFileName: 'Директивы. Noindex.xlsx',
    sourceColumns: ['URL'],
    targetColumns: ['Источник'],
    keyColumns: ['Источник'], 
  },
  {
    sourceFileName: 'Тощие страницы.xlsx',
    targetFileName: 'Контент. Страницы где мало контента.xlsx',
    sourceColumns: ['Страница', 'Кол-во слов'],
    targetColumns: ['Адрес', 'Количество слов'],
    keyColumns: ['Адрес', 'Количество слов'], 
  },
  {
    sourceFileName: 'Страницы с одинаковым Title.xlsx',
    targetFileName: 'Title. Дубликаты.xlsx',
    sourceColumns: ['Действие', 'Title'],
    targetColumns: ['Адрес', 'Title 1'],
    keyColumns: ['Адрес', 'Title 1'], 
  },
  {
    sourceFileName: 'Страницы с одинаковым Description.xlsx',
    targetFileName: 'Description. Дубликаты.xlsx',
    sourceColumns: ['Действие', 'Description'],
    targetColumns: ['Адрес', 'Description 1'],
    keyColumns: ['Адрес', 'Description 1'], 
  },
  {
    sourceFileName: 'Страницы c Title = H1.xlsx',
    targetFileName: 'Title. Такой же как и H1.xlsx',
    sourceColumns: ['Страница', 'TITLE'],
    targetColumns: ['Адрес', 'Title 1'],
    keyColumns:  ['Адрес', 'Title 1'], 
  },
  {
    sourceFileName: 'Страницы без Title.xlsx',
    targetFileName: 'Title. Отсутствует.xlsx',
    sourceColumns: ['Страница'],
    targetColumns: ['Адрес'],
    keyColumns:  ['Адрес'], 
  },
  {
    sourceFileName: 'Страницы без H1.xlsx',
    targetFileName: 'H1. Пропущенные.xlsx',
    sourceColumns: ['Страница'],
    targetColumns: ['Адрес'],
    keyColumns: ['Адрес'], 
  },
  {
    sourceFileName: 'Страницы без Description.xlsx',
    targetFileName: 'Description. Отсутствуют.xlsx',
    sourceColumns: ['Страница'],
    targetColumns: ['Адрес'],
    keyColumns: ['Адрес'], 
  },
];

function getColumnIndices(headerRow, columns) {
  const columnIndices = {};
  headerRow.eachCell((cell, colNumber) => {
    const header = cell.value;
    if (columns.includes(header)) {
      columnIndices[header] = colNumber;
    }
  });
  // Добавляем недостающие столбцы
  columns.forEach((column) => {
    if (!columnIndices[column]) {
      const newColNumber = headerRow.cellCount + 1;
      headerRow.getCell(newColNumber).value = column;
      columnIndices[column] = newColNumber;
    }
  });
  return columnIndices;
}

async function removeDuplicatesInFile(filePath) {
  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.readFile(filePath);
  const worksheet = workbook.getWorksheet(1);

  const rowValuesSet = new Set();
  const rowsToRemove = [];

  worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
    if (rowNumber === 1) return; // Пропустить заголовок
    const rowValues = row.values.slice(1).join('||'); // Объединяем значения ячеек
    if (rowValuesSet.has(rowValues)) {
      rowsToRemove.push(rowNumber);
    } else {
      rowValuesSet.add(rowValues);
    }
  });

  // Удаляем дубликаты, начиная с конца, чтобы не сместить индексы
  rowsToRemove.reverse().forEach((rowNumber) => {
    worksheet.spliceRows(rowNumber, 1);
  });

  if (rowsToRemove.length > 0) {
    await workbook.xlsx.writeFile(filePath);
    console.log(chalk.green(
      `Удалено ${rowsToRemove.length} дубликат(ов) в файле ${path.basename(
        filePath
      )}`
    ));
  }
}

async function removeDuplicatesInAllFiles() {
  const files = fs.readdirSync(directoryPath);
  for (const file of files) {
    const filePath = path.join(directoryPath, file);
    const ext = path.extname(file).toLowerCase();
    if (ext === '.xlsx' || ext === '.xls') {
      await removeDuplicatesInFile(filePath);
    }
  }
}

async function processFiles() {
  await removeDuplicatesInAllFiles();

  for (const filePair of filePairs) {
    const sourceFilePath = path.join(directoryPath, filePair.sourceFileName);
    const targetFilePath = path.join(directoryPath, filePair.targetFileName);

    const sourceFileExists = fs.existsSync(sourceFilePath);
    const targetFileExists = fs.existsSync(targetFilePath);

    if (sourceFileExists && targetFileExists) {
      console.log(
        `Обработка файлов: ${filePair.sourceFileName} и ${filePair.targetFileName}`
      );

      // Чтение исходного файла
      const sourceWorkbook = new ExcelJS.Workbook();
      await sourceWorkbook.xlsx.readFile(sourceFilePath);
      const sourceWorksheet = sourceWorkbook.getWorksheet(1);

      // Чтение целевого файла
      const targetWorkbook = new ExcelJS.Workbook();
      await targetWorkbook.xlsx.readFile(targetFilePath);
      const targetWorksheet = targetWorkbook.getWorksheet(1);

      // Получение индексов столбцов
      const sourceHeaderRow = sourceWorksheet.getRow(1);
      const sourceColumnIndices = getColumnIndices(
        sourceHeaderRow,
        filePair.sourceColumns
      );

      const targetHeaderRow = targetWorksheet.getRow(1);
      const targetColumnIndices = getColumnIndices(
        targetHeaderRow,
        filePair.targetColumns
      );

      // Извлечение данных из исходного файла
      const dataToAppend = [];
      sourceWorksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
        if (rowNumber === 1) return; // Пропустить заголовок
        const dataRow = {};
        filePair.sourceColumns.forEach((col) => {
          const colIndex = sourceColumnIndices[col];
          dataRow[col] = row.getCell(colIndex).value;
        });
        dataToAppend.push(dataRow);
      });

      // Отображение данных на целевые столбцы
      const mappedData = dataToAppend.map((dataRow) => {
        const mappedRow = {};
        filePair.sourceColumns.forEach((sourceCol, index) => {
          const targetCol = filePair.targetColumns[index];
          mappedRow[targetCol] = dataRow[sourceCol];
        });
        return mappedRow;
      });

      // Удаление дубликатов
      const existingRowsSet = new Set();
      targetWorksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
        if (rowNumber === 1) return;
        const keyValues = filePair.keyColumns
          .map((col) => {
            const colIndex = targetColumnIndices[col];
            return row.getCell(colIndex).value;
          })
          .join('||');
        existingRowsSet.add(keyValues);
      });

      let newRowsAdded = 0;
      mappedData.forEach((dataRow) => {
        const keyValues = filePair.keyColumns
          .map((col) => dataRow[col])
          .join('||');
        if (!existingRowsSet.has(keyValues)) {
          existingRowsSet.add(keyValues);
          const newRow = targetWorksheet.addRow();
          Object.entries(dataRow).forEach(([header, value]) => {
            const colIndex = targetColumnIndices[header];
            newRow.getCell(colIndex).value = value;
          });
          newRowsAdded++;
        }
      });

      console.log(
        `Добавлено ${newRowsAdded} новых строк в ${filePair.targetFileName}`
      );

      // Сохранение и удаление исходного файла
      await targetWorkbook.xlsx.writeFile(targetFilePath);
      fs.unlinkSync(sourceFilePath);
      console.log(`Удален файл ${filePair.sourceFileName}`);
    } else if (sourceFileExists) {
      console.log(chalk.yellow(`Только исходный файл существует: ${filePair.sourceFileName}`));
    } else if (targetFileExists) {
      console.log(chalk.yellow(`Только целевой файл существует: ${filePair.targetFileName}`));
    } else {
      console.log(
        `Файлы не найдены: ${filePair.sourceFileName} и ${filePair.targetFileName}`
      );
    }
  }
}

processFiles().catch((error) => {
  console.error('Произошла ошибка:', error);
});