npm package: @set2333/upload-script-helpers
Description: Helper library for upload scripts
Latest version: 1.0.4

# Upload Scripts Helpers

Набор утилит для загрузки и обработки данных для дальнейшей обработки в Pentaho.

## Установка

```bash
npm install @set2333/upload-script-helpers
```

## Требования

- Node.js >= 12.0.0

## Быстрый старт

```typescript
import { 
  EmailManager, 
  FileManager, 
  Logger, 
  ParserXLS, 
  Validator, 
  ApiManager, 
  JsonMapper 
} from '@set2333/upload-script-helpers'

// Создание логгера
const logger = new Logger({
  displayTypes: ['console', { type: 'file', options: { logFile: 'app.log' } }]
})

// Управление файлами
const fileManager = new FileManager({
  workDirectory: './work-dir',
  logger
})

// Работа с API
const apiManager = new ApiManager({
  baseUrl: 'https://api.example.com',
  headers: { 'Authorization': 'Bearer token' }
})

// Получение данных
const apiData = await apiManager.fetch({ page: 1, limit: 100 })

// Сохранение данных
fileManager.saveJSONFile(apiData, 'api-response.json')
```

## Модули

### Logger
Система логирования с поддержкой консоли и файлов.

**Опции:**
- `messageTypes` - типы сообщений для логирования: `['log', 'warn', 'error']`
- `displayTypes` - способы вывода: `['console']` или `[{ type: 'file', options: { logFile: 'app.log', logDir: './logs' } }]`

**Методы:**
- `log(message: string, ...args: unknown[])` - обычное сообщение
- `warn(message: string, ...args: unknown[])` - предупреждение
- `error(message: string, ...args: unknown[])` - ошибка
- `start(message: string)` - начало процесса с временной меткой
- `end(message: string)` - конец процесса с временной меткой
- `duration(processName: string)` - длительность процесса

**Пример:**
```typescript
const logger = new Logger({
  displayTypes: [
    'console',
    { type: 'file', options: { logFile: 'app.log', logDir: './logs' } }
  ]
})

logger.start('Обработка данных')
logger.log('Загружаем файл...')
logger.warn('Файл большой, это может занять время')
logger.duration('Обработка данных')
logger.end('Обработка завершена')
```

### FileManager
Управление файлами и рабочими директориями.

**Опции:**
- `workDirectory` (string) - путь к рабочей директории
- `logger` (ILogger | null) - логгер для вывода сообщений

**Методы:**
- `saveJSONFile(data: unknown, fileName: string)` - сохранение JSON файла
- `saveAttachment(attachmentData: Buffer, fileName: string)` - сохранение вложения

**Пример:**
```typescript
const fileManager = new FileManager({
  workDirectory: './work-dir',
  logger
})

// Сохранение JSON данных
const userData = { users: [{ name: 'John', age: 30 }] }
fileManager.saveJSONFile(userData, 'users.json')

// Сохранение вложения из email
const attachmentBuffer = Buffer.from('file content')
fileManager.saveAttachment(attachmentBuffer, 'document.xlsx')
```

### EmailManager
Работа с email через IMAP и SMTP.

**Опции:**
- `credentials` - данные для подключения к почтовому серверу
- `searchRules` - правила поиска писем
- `logger` - логгер для вывода сообщений

**Методы:**
- `connect()` - подключение к серверу
- `disconnect()` - отключение от сервера
- `getEmails()` - получение писем с вложениями
- `sendEmail(options)` - отправка письма

**Пример:**
```typescript
const emailManager = new EmailManager({
  credentials: {
    host: 'imap.gmail.com',
    port: 993,
    user: 'your-email@gmail.com',
    pass: 'your-password',
    smtpHost: 'smtp.gmail.com',
    smtpPort: 587,
    smtpUser: 'your-email@gmail.com',
    smtpPass: 'your-password'
  },
  searchRules: {
    since: '2024-01-01',
    before: '2024-12-31',
    attachmentMask: /\.xlsx$/
  },
  logger
})

await emailManager.connect()
const emails = await emailManager.getEmails()

for (const email of emails) {
  fileManager.saveAttachment(email.buffer, email.attachment)
}

await emailManager.sendEmail({
  to: 'recipient@example.com',
  subject: 'Обработка завершена',
  text: 'Данные успешно обработаны'
})

await emailManager.disconnect()
```

### ParserXLS
Парсинг Excel файлов (.xlsx).

**Опции:**
- `data` - Buffer с данными Excel файла
- `fileName` - имя файла
- `usedSheet` - название листа для парсинга
- `logger` - логгер для вывода сообщений

**Методы:**
- `getColumnNames()` - получение названий колонок
- `parse(options)` - парсинг данных в объекты

**Пример:**
```typescript
const parser = new ParserXLS({
  data: excelBuffer,
  fileName: 'data.xlsx',
  usedSheet: 'Sheet1',
  logger
})

// Получение названий колонок
const columns = parser.getColumnNames()
console.log('Колонки:', columns)

// Парсинг данных
const parsedData = await parser.parse({
  fieldNames: ['name', 'age', 'email', null], // null игнорирует колонку
  initDataRow: (fileName) => ({ 
    source: fileName, 
    processed: false 
  })
})

console.log('Данные:', parsedData)
```

### Validator
Валидация Excel файлов перед обработкой.

**Опции:**
- `validationRules` - массив правил валидации
- `usedSheet` - название листа для валидации
- `logger` - логгер для вывода сообщений

**Типы правил:**
- `sheetExists` - проверка существования листа
- `sheetHasData` - проверка наличия данных
- `columnHasName` - проверка названия колонки
- `columnHasType` - проверка типа данных в колонке

**Пример:**
```typescript
const validator = new Validator({
  validationRules: [
    { type: 'sheetExists' },
    { type: 'sheetHasData' },
    { type: 'columnHasName', options: [0, 'Name'] },
    { type: 'columnHasType', options: [1, 'number'] }
  ],
  usedSheet: 'Sheet1',
  logger
})

const errors = await validator.validate(excelBuffer)
if (errors) {
  console.error('Ошибки валидации:', errors)
} else {
  console.log('Файл прошел валидацию')
}
```

### ApiManager
Работа с HTTP API.

**Опции:**
- `baseUrl` - базовый URL API
- `headers` - заголовки запросов
- `method` - HTTP метод (по умолчанию GET)
- `logger` - логгер для вывода сообщений

**Методы:**
- `fetch(params?)` - выполнение HTTP запроса

**Пример:**
```typescript
const apiManager = new ApiManager({
  baseUrl: 'https://api.example.com',
  headers: {
    'Authorization': 'Bearer your-token',
    'Content-Type': 'application/json'
  },
  method: 'GET',
  logger
})

// GET запрос с параметрами
const data = await apiManager.fetch({
  page: 1,
  limit: 100,
  search: 'test'
})

// POST запрос (изменяем метод)
apiManager['method'] = 'POST'
const result = await apiManager.fetch({ name: 'New Item' })
```

### JsonMapper
Преобразование JSON данных.

**Опции:**
- `nameRules` - правила переименования полей
- `valueRules` - правила преобразования значений
- `excludeRules` - поля для исключения
- `dataExpansion` - функция для расширения данных

**Методы:**
- `map(data)` - преобразование данных

**Пример:**
```typescript
const jsonMapper = new JsonMapper({
  nameRules: {
    'user_name': 'name',
    'user_age': 'age'
  },
  valueRules: {
    'age': (value) => Number(value),
    'email': (value) => String(value).toLowerCase()
  },
  excludeRules: ['password', 'secret'],
  dataExpansion: (data) => ({
    ...data,
    processedAt: new Date().toISOString()
  })
})

const originalData = {
  user_name: 'John',
  user_age: '30',
  email: 'JOHN@EXAMPLE.COM',
  password: 'secret123'
}

const mappedData = jsonMapper.map(originalData)
console.log(mappedData)
// { name: 'John', age: 30, email: 'john@example.com', processedAt: '2024-01-01T12:00:00.000Z' }
```

## Полный пример использования

Пример скрипта, который получает данные из email, валидирует Excel файлы, парсит их и сохраняет результат:

```typescript
import { 
  EmailManager, 
  FileManager, 
  Logger, 
  ParserXLS, 
  Validator, 
  JsonMapper 
} from '@set2333/upload-script-helpers'

async function processEmailAttachments() {
  const logger = new Logger({
    displayTypes: ['console', { type: 'file', options: { logFile: 'process.log' } }]
  })

  const fileManager = new FileManager({
    workDirectory: './processed-data',
    logger
  })

  const emailManager = new EmailManager({
    credentials: {
      host: 'imap.gmail.com',
      port: 993,
      user: process.env.EMAIL_USER!,
      pass: process.env.EMAIL_PASS!,
      smtpHost: 'smtp.gmail.com',
      smtpPort: 587,
      smtpUser: process.env.EMAIL_USER!,
      smtpPass: process.env.EMAIL_PASS!
    },
    searchRules: {
      since: '2024-01-01',
      before: '2024-12-31',
      attachmentMask: /\.xlsx$/
    },
    logger
  })

  const validator = new Validator({
    validationRules: [
      { type: 'sheetExists' },
      { type: 'sheetHasData' },
      { type: 'columnHasName', options: [0, 'Name'] },
      { type: 'columnHasType', options: [1, 'number'] }
    ],
    usedSheet: 'Data',
    logger
  })

  const jsonMapper = new JsonMapper({
    nameRules: {
      'Name': 'name',
      'Age': 'age',
      'Email': 'email'
    },
    valueRules: {
      'age': (value) => Number(value),
      'email': (value) => String(value).toLowerCase()
    },
    dataExpansion: (data) => ({
      ...data,
      processedAt: new Date().toISOString(),
      source: 'email-attachment'
    })
  })

  try {
    logger.start('Обработка email вложений')

    await emailManager.connect()
    const emails = await emailManager.getEmails()

    logger.log(`Найдено писем с вложениями: ${emails.length}`)

    for (const email of emails) {
      logger.log(`Обрабатываем файл: ${email.attachment}`)

      // Валидация файла
      const validationErrors = await validator.validate(email.buffer)
      if (validationErrors) {
        logger.error(`Файл ${email.attachment} не прошел валидацию:`, validationErrors)
        continue
      }

      // Парсинг Excel файла
      const parser = new ParserXLS({
        data: email.buffer,
        fileName: email.attachment,
        usedSheet: 'Data',
        logger
      })

      const parsedData = await parser.parse({
        fieldNames: ['name', 'age', 'email']
      })

      // Преобразование данных
      const mappedData = parsedData.map(row => jsonMapper.map(row))

      // Сохранение результата
      const resultFileName = `processed_${email.attachment.replace('.xlsx', '.json')}`
      fileManager.saveJSONFile(mappedData, resultFileName)

      logger.log(`Файл ${email.attachment} успешно обработан`)
    }

    await emailManager.sendEmail({
      to: 'admin@example.com',
      subject: 'Обработка завершена',
      text: `Обработано файлов: ${emails.length}`
    })

    await emailManager.disconnect()
    logger.end('Обработка email вложений')

  } catch (error) {
    logger.error('Ошибка при обработке:', error)
    throw error
  }
}

// Запуск скрипта
processEmailAttachments().catch(console.error)
```

## Готовый к использованию пакет

Пакет поставляется уже скомпилированным и готовым к использованию:
- `dist/index.js` - CommonJS версия (минифицированная)
- `dist/index.mjs` - ES модули версия (минифицированная)
- `dist/index.d.ts` - TypeScript типы

## Разработка

```bash
# Установка зависимостей
npm install

# Сборка
npm run build

# Разработка с отслеживанием изменений
npm run dev

# Проверка типов
npm run typecheck

# Запуск тестов
npm test

# Запуск тестов один раз
npm run test:run

# Проверка линтера
npm run lint

# Автоисправление ошибок линтера
npm run lint:fix

# Запуск pre-commit проверок
npm run pre-commit

# Запуск pre-publish проверок
npm run pre-publish
```

## Git хуки

Проект использует Husky для автоматических проверок:

### Pre-commit хук
Автоматически запускается перед каждым коммитом:
- Исправляет ошибки ESLint в измененных файлах
- Проверяет типы TypeScript
- Запускает `lint-staged`

### Pre-publish хук
Автоматически запускается перед публикацией:
- Проверяет код линтером
- Проверяет типы TypeScript
- Запускает все тесты
- Собирает проект

Хуки настроены в `.husky/` директории и автоматически устанавливаются при `npm install`.

## Публикация

```bash
# Публикация с поднятием patch версии (1.0.0 -> 1.0.1)
npm run publish:patch

# Публикация с поднятием minor версии (1.0.0 -> 1.1.0)
npm run publish:minor

# Публикация с поднятием major версии (1.0.0 -> 2.0.0)
npm run publish:major
```

Скрипт автоматически:
- Поднимает версию в package.json
- Собирает проект
- Публикует в npm

> **Примечание**: Git команды убраны из скрипта. Версию нужно обновлять в git вручную.

## Поддерживаемые форматы

- CommonJS (`require`)
- ES модули (`import`)
- TypeScript типы