import { execSync } from 'child_process'; import axios from 'axios'; import semver from 'semver'; import { readFileSync, writeFileSync } from 'fs'; import { CommandModule } from 'yargs'; type PackageName = string; type Version = string; const SPACE_NAME = '@dpa'; const CHANGELOG_URL = 'https://static-basket-03.wbbasket.ru/vol48/ritmcore/changelog.md'; export const updateCommand: CommandModule = { command: 'update', describe: 'Обновить все RITM пакеты и проверить CHANGELOG.md', handler: async () => { console.log('\n📦 Проверка обновлений...'); const packages = getPackages(); const result = []; const changelog = await getChangelogContent(CHANGELOG_URL); let hasUpdates = false; // Сначала проверим все пакеты и соберем обновления for (const [packageName, currentVersion] of Object.entries(packages)) { const latestVersion = getLatestVersion(packageName); console.log(`\n📦 ${packageName}: ${currentVersion} → ${latestVersion}`); if (semver.lt(currentVersion, latestVersion)) { const breakingChanges = extractBreakingChanges(changelog); if (breakingChanges && !result.includes(breakingChanges)) { result.push(breakingChanges); } // Обновляем версию в package.json updatePackageVersion(packageName, latestVersion); hasUpdates = true; console.log(`✅ Запланировано обновление до ${latestVersion}`); } else { console.log(`✅ ${packageName} актуальный.`); } } // Если есть обновления, устанавливаем все пакеты за один раз if (hasUpdates) { console.log('\n🚀 Устанавливаем все обновления...'); execSync('npm install', { stdio: 'inherit' }); console.log('\n🎉 Все пакеты успешно обновлены!'); } else { console.log('\n🎉 Все пакеты уже актуальны!'); } console.log(result.join('')); console.log(`🔗 CHANGELOG: ${CHANGELOG_URL}`); }, }; function getPackages() { const pkg = JSON.parse(readFileSync('./package.json', 'utf-8')); const deps = pkg.dependencies || {}; const dpaPackages: Record = {}; for (const [name, version] of Object.entries(deps)) { if (name.startsWith(SPACE_NAME)) { if (typeof version !== 'string') continue; dpaPackages[name] = version.replace(/\^|~/, ''); } } return dpaPackages; } function getLatestVersion(packageName: PackageName) { return execSync(`npm show ${packageName} version`).toString().trim(); } async function getChangelogContent(url: string) { const res = await axios.get(url); return res.data; } function updatePackageVersion(packageName: PackageName, version: Version) { const pkgPath = './package.json'; const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')); if (pkg.dependencies && pkg.dependencies[packageName]) { // Сохраняем префикс (~ или ^) если он был const currentVersion = pkg.dependencies[packageName]; const prefix = currentVersion.startsWith('~') ? '~' : currentVersion.startsWith('^') ? '^' : ''; pkg.dependencies[packageName] = `${prefix}${version}`; writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)); } } function extractBreakingChanges(changelog: string): string | undefined { const lines = changelog.split('\n'); const results: string[] = []; let currentVersion = ''; let isInBreakingChangesSection = false; let breakingChanges: string[] = []; for (const line of lines) { const versionMatch = line.match(/^##\s+(\d+\.\d+\.\d+)/); if (versionMatch) { if (breakingChanges.length > 0) { results.push( `\nBreaking changes in ${currentVersion}:\n${breakingChanges.join( '\n' )}` ); breakingChanges = []; } currentVersion = versionMatch[1]; isInBreakingChangesSection = false; continue; } if (/^###\s+.*breaking changes/i.test(line)) { isInBreakingChangesSection = true; continue; } if (/^###\s+/.test(line)) { isInBreakingChangesSection = false; } if (isInBreakingChangesSection) { breakingChanges.push(line.trim()); } } if (breakingChanges.length > 0 && currentVersion) { results.push( `\nBreaking changes in ${currentVersion}:\n${breakingChanges.join('\n')}` ); } return results.length > 0 ? results.join('\n\n') : undefined; }