UNPKG

cnpmcore

Version:
898 lines โ€ข 95.8 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PackageSyncerService = exports.RegistryNotMatchError = void 0; const node_os_1 = __importDefault(require("node:os")); const promises_1 = require("node:timers/promises"); const promises_2 = require("node:fs/promises"); const tegg_1 = require("@eggjs/tegg"); const aop_1 = require("@eggjs/tegg/aop"); const lodash_1 = require("lodash"); const semver_1 = __importDefault(require("semver")); const NPMRegistry_1 = require("../../common/adapter/NPMRegistry"); const PackageUtil_1 = require("../../common/PackageUtil"); const FileUtil_1 = require("../../common/FileUtil"); const Task_1 = require("../../common/enum/Task"); const AbstractService_1 = require("../../common/AbstractService"); const TaskRepository_1 = require("../../repository/TaskRepository"); const PackageRepository_1 = require("../../repository/PackageRepository"); const PackageVersionDownloadRepository_1 = require("../../repository/PackageVersionDownloadRepository"); const UserRepository_1 = require("../../repository/UserRepository"); const Task_2 = require("../entity/Task"); const UserService_1 = require("./UserService"); const TaskService_1 = require("./TaskService"); const PackageManagerService_1 = require("./PackageManagerService"); const CacheService_1 = require("./CacheService"); const RegistryManagerService_1 = require("./RegistryManagerService"); const egg_errors_1 = require("egg-errors"); const ScopeManagerService_1 = require("./ScopeManagerService"); const EventCorkerAdvice_1 = require("./EventCorkerAdvice"); const constants_1 = require("../../common/constants"); function isoNow() { return new Date().toISOString(); } class RegistryNotMatchError extends egg_errors_1.BadRequestError { } exports.RegistryNotMatchError = RegistryNotMatchError; let PackageSyncerService = class PackageSyncerService extends AbstractService_1.AbstractService { async createTask(fullname, options) { const [scope, name] = (0, PackageUtil_1.getScopeAndName)(fullname); const pkg = await this.packageRepository.findPackage(scope, name); // sync task request registry is not same as package registry if (pkg && pkg.registryId && options?.registryId) { if (pkg.registryId !== options.registryId) { throw new RegistryNotMatchError(`package ${fullname} is not in registry ${options.registryId}`); } } return await this.taskService.createTask(Task_2.Task.createSyncPackage(fullname, options), true); } async findTask(taskId) { return await this.taskService.findTask(taskId); } async findTaskLog(task) { return await this.taskService.findTaskLog(task); } async findExecuteTask() { return await this.taskService.findExecuteTask(Task_1.TaskType.SyncPackage); } get allowSyncDownloadData() { const config = this.config.cnpmcore; if (config.enableSyncDownloadData && config.syncDownloadDataSourceRegistry && config.syncDownloadDataMaxDate) { return true; } return false; } async syncDownloadData(task, pkg) { if (!this.allowSyncDownloadData) { return; } const fullname = pkg.fullname; const start = '2011-01-01'; const end = this.config.cnpmcore.syncDownloadDataMaxDate; const registry = this.config.cnpmcore.syncDownloadDataSourceRegistry; const remoteAuthToken = await this.registryManagerService.getAuthTokenByRegistryHost(registry); const logs = []; let downloads; logs.push(`[${isoNow()}][DownloadData] ๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง Syncing "${fullname}" download data "${start}:${end}" on ${registry} ๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง`); const failEnd = 'โŒโŒโŒโŒโŒ ๐Ÿšฎ give up ๐Ÿšฎ โŒโŒโŒโŒโŒ'; try { const { data, status, res } = await this.npmRegistry.getDownloadRanges(registry, fullname, start, end, { remoteAuthToken }); downloads = data.downloads || []; logs.push(`[${isoNow()}][DownloadData] ๐Ÿšง HTTP [${status}] timing: ${JSON.stringify(res.timing)}, downloads: ${downloads.length}`); } catch (err) { const status = err.status || 'unknow'; logs.push(`[${isoNow()}][DownloadData] โŒ Get download data error: ${err}, status: ${status}`); logs.push(`[${isoNow()}][DownloadData] ${failEnd}`); await this.taskService.appendTaskLog(task, logs.join('\n')); return; } const datas = new Map(); for (const item of downloads) { // { // "day": "2021-09-21", // "downloads": 45 // }, const day = item.day; const [year, month, date] = day.split('-'); const yearMonth = parseInt(`${year}${month}`); if (!datas.has(yearMonth)) { datas.set(yearMonth, []); } const counters = datas.get(yearMonth); counters.push([date, item.downloads]); } for (const [yearMonth, counters] of datas.entries()) { await this.packageVersionDownloadRepository.saveSyncDataByMonth(pkg.packageId, yearMonth, counters); logs.push(`[${isoNow()}][DownloadData] ๐ŸŸข ${yearMonth}: ${counters.length} days`); } logs.push(`[${isoNow()}][DownloadData] ๐ŸŸข๐ŸŸข๐ŸŸข๐ŸŸข๐ŸŸข ${registry}/${fullname} ๐ŸŸข๐ŸŸข๐ŸŸข๐ŸŸข๐ŸŸข`); await this.taskService.appendTaskLog(task, logs.join('\n')); } async syncUpstream(task) { const registry = this.npmRegistry.registry; const fullname = task.targetName; const remoteAuthToken = await this.registryManagerService.getAuthTokenByRegistryHost(registry); let logs = []; let logId = ''; logs.push(`[${isoNow()}][UP] ๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง Waiting sync "${fullname}" task on ${registry} ๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง`); const failEnd = `โŒโŒโŒโŒโŒ Sync ${registry}/${fullname} ๐Ÿšฎ give up ๐Ÿšฎ โŒโŒโŒโŒโŒ`; try { const { data, status, res } = await this.npmRegistry.createSyncTask(fullname, { remoteAuthToken }); logs.push(`[${isoNow()}][UP] ๐Ÿšง HTTP [${status}] timing: ${JSON.stringify(res.timing)}, data: ${JSON.stringify(data)}`); logId = data.logId; } catch (err) { const status = err.status || 'unknow'; // ๅฏ่ƒฝไผšๆŠ›ๅ‡บ AggregateError ๅผ‚ๅธธ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError logs.push(`[${isoNow()}][UP] โŒ Sync ${fullname} fail, create sync task error: ${err}, status: ${status} ${err instanceof AggregateError ? err.errors : ''}`); logs.push(`[${isoNow()}][UP] ${failEnd}`); await this.taskService.appendTaskLog(task, logs.join('\n')); return; } if (!logId) { logs.push(`[${isoNow()}][UP] โŒ Sync ${fullname} fail, missing logId`); logs.push(`[${isoNow()}][UP] ${failEnd}`); await this.taskService.appendTaskLog(task, logs.join('\n')); return; } const startTime = Date.now(); const maxTimeout = this.config.cnpmcore.sourceRegistrySyncTimeout; let logUrl = ''; let offset = 0; let useTime = Date.now() - startTime; while (useTime < maxTimeout) { // sleep 1s ~ 6s in random const delay = process.env.NODE_ENV === 'test' ? 100 : 1000 + Math.random() * 5000; await (0, promises_1.setTimeout)(delay); try { const { data, status, url } = await this.npmRegistry.getSyncTask(fullname, logId, offset, { remoteAuthToken }); useTime = Date.now() - startTime; if (!logUrl) { logUrl = url; } const log = data && data.log || ''; offset += log.length; if (data && data.syncDone) { logs.push(`[${isoNow()}][UP] ๐ŸŽ‰ Sync ${fullname} success [${useTime}ms], log: ${logUrl}, offset: ${offset}`); logs.push(`[${isoNow()}][UP] ๐Ÿ”— ${registry}/${fullname}`); await this.taskService.appendTaskLog(task, logs.join('\n')); return; } logs.push(`[${isoNow()}][UP] ๐Ÿšง HTTP [${status}] [${useTime}ms], offset: ${offset}`); await this.taskService.appendTaskLog(task, logs.join('\n')); logs = []; } catch (err) { useTime = Date.now() - startTime; const status = err.status || 'unknow'; logs.push(`[${isoNow()}][UP] ๐Ÿšง HTTP [${status}] [${useTime}ms] error: ${err}`); } } // timeout logs.push(`[${isoNow()}][UP] โŒ Sync ${fullname} fail, timeout, log: ${logUrl}, offset: ${offset}`); logs.push(`[${isoNow()}][UP] ${failEnd}`); await this.taskService.appendTaskLog(task, logs.join('\n')); } isRemovedInRemote(remoteFetchResult) { const { status, data } = remoteFetchResult; // deleted or blocked if (status === 404 || status === 451) { return true; } const hasMaintainers = data?.maintainers && data?.maintainers.length !== 0; if (hasMaintainers) { return false; } // unpublished const timeMap = data.time || {}; if (timeMap.unpublished) { return true; } // security holder // test/fixtures/registry.npmjs.org/security-holding-package.json let isSecurityHolder = true; for (const versionInfo of Object.entries(data.versions || {})) { const [v, info] = versionInfo; // >=0.0.1-security <0.0.2-0 const isSecurityVersion = semver_1.default.satisfies(v, '^0.0.1-security'); const isNpmUser = info?._npmUser?.name === 'npm'; if (!isSecurityVersion || !isNpmUser) { isSecurityHolder = false; break; } } return isSecurityHolder; } // sync deleted package, deps on the syncDeleteMode // - ignore: do nothing, just finish the task // - delete: remove the package from local registry // - block: block the package, update the manifest.block, instead of delete versions // ๆ นๆฎ syncDeleteMode ้…็ฝฎ๏ผŒๅค„็†ๅˆ ๅŒ…ๅœบๆ™ฏ // - ignore: ไธๅšไปปไฝ•ๅค„็†๏ผŒ็›ดๆŽฅ็ป“ๆŸไปปๅŠก // - delete: ๅˆ ้™คๅŒ…ๆ•ฐๆฎ๏ผŒๅŒ…ๆ‹ฌ manifest ๅญ˜ๅ‚จ // - block: ่ฝฏๅˆ ้™ค ๅฐ†ๅŒ…ๆ ‡่ฎฐไธบ block๏ผŒ็”จๆˆทๆ— ๆณ•็›ดๆŽฅไฝฟ็”จ async syncDeletePkg({ task, pkg, logUrl, url, logs, data }) { const fullname = task.targetName; const failEnd = `โŒโŒโŒโŒโŒ ${url || fullname} โŒโŒโŒโŒโŒ`; const syncDeleteMode = this.config.cnpmcore.syncDeleteMode; logs.push(`[${isoNow()}] ๐ŸŸข Package "${fullname}" was removed in remote registry, response data: ${JSON.stringify(data)}, config.syncDeleteMode = ${syncDeleteMode}`); // pkg not exists in local registry if (!pkg) { task.error = `Package not exists, response data: ${JSON.stringify(data)}`; logs.push(`[${isoNow()}] โŒ ${task.error}, log: ${logUrl}`); logs.push(`[${isoNow()}] ${failEnd}`); await this.taskService.finishTask(task, Task_1.TaskState.Fail, logs.join('\n')); this.logger.info('[PackageSyncerService.executeTask:fail-404] taskId: %s, targetName: %s, %s', task.taskId, task.targetName, task.error); return; } if (syncDeleteMode === constants_1.SyncDeleteMode.ignore) { // ignore deleted package logs.push(`[${isoNow()}] ๐ŸŸข Skip remove since config.syncDeleteMode = ignore`); } else if (syncDeleteMode === constants_1.SyncDeleteMode.block) { // block deleted package await this.packageManagerService.blockPackage(pkg, 'Removed in remote registry'); logs.push(`[${isoNow()}] ๐ŸŸข Block the package since config.syncDeleteMode = block`); } else if (syncDeleteMode === constants_1.SyncDeleteMode.delete) { // delete package await this.packageManagerService.unpublishPackage(pkg); logs.push(`[${isoNow()}] ๐ŸŸข Delete the package since config.syncDeleteMode = delete`); } // update log logs.push(`[${isoNow()}] ๐Ÿ“ Log URL: ${logUrl}`); logs.push(`[${isoNow()}] ๐Ÿ”— ${url}`); await this.taskService.finishTask(task, Task_1.TaskState.Success, logs.join('\n')); this.logger.info('[PackageSyncerService.executeTask:remove-package] taskId: %s, targetName: %s', task.taskId, task.targetName); } // ๅˆๅง‹ๅŒ–ๅฏนๅบ”็š„ Registry // 1. ไผ˜ๅ…ˆไปŽ pkg.registryId ่Žทๅ– (registryId ไธ€็ป่ฎพ็ฝฎ ไธๅบ”ๆ”นๅ˜) // 1. ๅ…ถๆฌกไปŽ task.data.registryId (ๅˆ›ๅปบๅ•ๅŒ…ๅŒๆญฅไปปๅŠกๆ—ถไผ ๅ…ฅ) // 2. ๆŽฅ็€ๆ นๆฎ scope ่ฟ›่กŒ่ฎก็ฎ— (ไฝœไธบๅญๅŒ…ไพ่ต–ๅŒๆญฅๆ—ถๅ€™๏ผŒๆ—  registryId) // 3. ๆœ€ๅŽ่ฟ”ๅ›ž default registryId (ๅฏ่ƒฝ default registry ไนŸไธๅญ˜ๅœจ) async initSpecRegistry(task, pkg = null, scope) { const registryId = pkg?.registryId || task.data.registryId; let targetHost = this.config.cnpmcore.sourceRegistry; let registry = null; // ๅฝ“ๅ‰ไปปๅŠกไฝœไธบ deps ๅผ•ๅ…ฅๆ—ถ๏ผŒไธไผš้…็ฝฎ registryId // ๅކๅฒ Task ๅฏ่ƒฝๆฒกๆœ‰้…็ฝฎ registryId if (registryId) { registry = await this.registryManagerService.findByRegistryId(registryId); } else if (scope) { const scopeModel = await this.scopeManagerService.findByName(scope); if (scopeModel?.registryId) { registry = await this.registryManagerService.findByRegistryId(scopeModel?.registryId); } } // ้‡‡็”จ้ป˜่ฎค็š„ registry if (!registry) { registry = await this.registryManagerService.ensureDefaultRegistry(); } // ๆ›ดๆ–ฐ targetHost ๅœฐๅ€ // defaultRegistry ๅฏ่ƒฝ่ฟ˜ๆœชๅˆ›ๅปบ if (registry?.host) { targetHost = registry.host; } this.npmRegistry.setRegistryHost(targetHost); return registry; } // ็”ฑไบŽ cnpmcore ๅฐ† version ๅ’Œ tag ไฝœไธบไธคไธช็‹ฌ็ซ‹็š„ changes ไบ‹ไปถๅˆ†ๅ‘ // ๆ™ฎ้€š็‰ˆๆœฌๅ‘ๅธƒๆ—ถ๏ผŒ็Ÿญๆ—ถ้—ดๅ†…ไผšๆœ‰ไธคๆก็›ธๅŒ task ่ฟ›่กŒๅŒๆญฅ // ๅฐฝ้‡ไฟ่ฏ่ฏปๅ–ๅ’Œๅ†™ๅ…ฅ้ƒฝ้œ€ไฟ่ฏไปปๅŠกๅน‚็ญ‰๏ผŒ้œ€่ฆ็กฎไฟ changes ๅœจๅŒๆญฅไปปๅŠกๅฎŒๆˆๅŽๅ†่งฆๅ‘ // ้€š่ฟ‡ DB ๅ”ฏไธ€็ดขๅผ•ๆฅไฟ่ฏไปปๅŠกๅน‚็ญ‰๏ผŒๆ’ๅ…ฅๅคฑ่ดฅไธๅฝฑๅ“ pkg.manifests ๆ›ดๆ–ฐ // ้€š่ฟ‡ eventBus.cork/uncork ๆฅๆš‚็ผ“ไบ‹ไปถ่งฆๅ‘ async executeTask(task) { const fullname = task.targetName; const [scope, name] = (0, PackageUtil_1.getScopeAndName)(fullname); const { tips, skipDependencies: originSkipDependencies, syncDownloadData, forceSyncHistory, specificVersions } = task.data; let pkg = await this.packageRepository.findPackage(scope, name); const registry = await this.initSpecRegistry(task, pkg, scope); const registryHost = this.npmRegistry.registry; const remoteAuthToken = registry.authToken; let logs = []; if (tips) { logs.push(`[${isoNow()}] ๐Ÿ‘‰๐Ÿ‘‰๐Ÿ‘‰๐Ÿ‘‰๐Ÿ‘‰ Tips: ${tips} ๐Ÿ‘ˆ๐Ÿ‘ˆ๐Ÿ‘ˆ๐Ÿ‘ˆ๐Ÿ‘ˆ`); } const taskQueueLength = await this.taskService.getTaskQueueLength(task.type); const taskQueueHighWaterSize = this.config.cnpmcore.taskQueueHighWaterSize; const taskQueueInHighWaterState = taskQueueLength >= taskQueueHighWaterSize; const skipDependencies = taskQueueInHighWaterState ? true : !!originSkipDependencies; const syncUpstream = !!(!taskQueueInHighWaterState && this.config.cnpmcore.sourceRegistryIsCNpm && this.config.cnpmcore.syncUpstreamFirst && registry.name === constants_1.PresetRegistryName.default); const logUrl = `${this.config.cnpmcore.registry}/-/package/${fullname}/syncs/${task.taskId}/log`; this.logger.info('[PackageSyncerService.executeTask:start] taskId: %s, targetName: %s, attempts: %s, taskQueue: %s/%s, syncUpstream: %s, log: %s', task.taskId, task.targetName, task.attempts, taskQueueLength, taskQueueHighWaterSize, syncUpstream, logUrl); logs.push(`[${isoNow()}] ๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง Syncing from ${registryHost}/${fullname}, skipDependencies: ${skipDependencies}, syncUpstream: ${syncUpstream}, syncDownloadData: ${!!syncDownloadData}, forceSyncHistory: ${!!forceSyncHistory} attempts: ${task.attempts}, worker: "${node_os_1.default.hostname()}/${process.pid}", taskQueue: ${taskQueueLength}/${taskQueueHighWaterSize} ๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง๐Ÿšง`); if (specificVersions) { logs.push(`[${isoNow()}] ๐Ÿ‘‰ syncing specific versions: ${specificVersions.join(' | ')} ๐Ÿ‘ˆ`); } logs.push(`[${isoNow()}] ๐Ÿšง log: ${logUrl}`); if (registry?.name === constants_1.PresetRegistryName.self) { logs.push(`[${isoNow()}] โŒโŒโŒโŒโŒ ${fullname} has been published to the self registry, skip sync โŒโŒโŒโŒโŒ`); await this.taskService.finishTask(task, Task_1.TaskState.Fail, logs.join('\n')); this.logger.info('[PackageSyncerService.executeTask:fail] taskId: %s, targetName: %s, invalid registryId', task.taskId, task.targetName); return; } if (pkg && pkg?.registryId !== registry?.registryId) { if (pkg.registryId) { logs.push(`[${isoNow()}] โŒโŒโŒโŒโŒ ${fullname} registry is ${pkg.registryId} not belong to ${registry?.registryId}, skip sync โŒโŒโŒโŒโŒ`); await this.taskService.finishTask(task, Task_1.TaskState.Fail, logs.join('\n')); this.logger.info('[PackageSyncerService.executeTask:fail] taskId: %s, targetName: %s, invalid registryId', task.taskId, task.targetName); return; } // ๅคšๅŒๆญฅๆบไน‹ๅ‰ๆฒกๆœ‰ registryId // publish() ็‰ˆๆœฌไธๅ˜ๆ—ถ๏ผŒไธไผšๆ›ดๆ–ฐ registryId // ๅœจๅŒๆญฅๅ‰๏ผŒ่ฟ›่กŒๆ›ดๆ–ฐๆ“ไฝœ pkg.registryId = registry?.registryId; await this.packageRepository.savePackage(pkg); } if (syncDownloadData && pkg) { await this.syncDownloadData(task, pkg); logs.push(`[${isoNow()}] ๐ŸŸข log: ${logUrl}`); logs.push(`[${isoNow()}] ๐ŸŸข๐ŸŸข๐ŸŸข๐ŸŸข๐ŸŸข Sync "${fullname}" download data success ๐ŸŸข๐ŸŸข๐ŸŸข๐ŸŸข๐ŸŸข`); await this.taskService.finishTask(task, Task_1.TaskState.Success, logs.join('\n')); this.logger.info('[PackageSyncerService.executeTask:success] taskId: %s, targetName: %s', task.taskId, task.targetName); return; } if (syncUpstream) { await this.taskService.appendTaskLog(task, logs.join('\n')); logs = []; // create sync task on sourceRegistry and skipDependencies = true await this.syncUpstream(task); } if (this.config.cnpmcore.syncPackageBlockList.includes(fullname)) { task.error = `stop sync by block list: ${JSON.stringify(this.config.cnpmcore.syncPackageBlockList)}`; logs.push(`[${isoNow()}] โŒ ${task.error}, log: ${logUrl}`); logs.push(`[${isoNow()}] โŒโŒโŒโŒโŒ ${fullname} โŒโŒโŒโŒโŒ`); await this.taskService.finishTask(task, Task_1.TaskState.Fail, logs.join('\n')); this.logger.info('[PackageSyncerService.executeTask:fail-block-list] taskId: %s, targetName: %s, %s', task.taskId, task.targetName, task.error); return; } let registryFetchResult; try { registryFetchResult = await this.npmRegistry.getFullManifests(fullname, { remoteAuthToken }); } catch (err) { const status = err.status || 'unknown'; task.error = `request manifests error: ${err}, status: ${status}`; logs.push(`[${isoNow()}] โŒ Synced ${fullname} fail, ${task.error}, log: ${logUrl}`); logs.push(`[${isoNow()}] โŒโŒโŒโŒโŒ ${fullname} โŒโŒโŒโŒโŒ`); this.logger.info('[PackageSyncerService.executeTask:fail-request-error] taskId: %s, targetName: %s, %s', task.taskId, task.targetName, task.error); await this.taskService.retryTask(task, logs.join('\n')); return; } const { url, data, headers, res, status } = registryFetchResult; /* c8 ignore next 13 */ if (status >= 500 || !data) { // GET https://registry.npmjs.org/%40modern-js%2Fstyle-compiler?t=1683348626499&cache=0, status: 522 // registry will response status 522 and data will be null // > TypeError: Cannot read properties of null (reading 'readme') task.error = `request manifests response error, status: ${status}, data: ${JSON.stringify(data)}`; logs.push(`[${isoNow()}] โŒ response headers: ${JSON.stringify(headers)}`); logs.push(`[${isoNow()}] โŒ Synced ${fullname} fail, ${task.error}, log: ${logUrl}`); logs.push(`[${isoNow()}] โŒโŒโŒโŒโŒ ${fullname} โŒโŒโŒโŒโŒ`); this.logger.info('[PackageSyncerService.executeTask:fail-request-error] taskId: %s, targetName: %s, %s', task.taskId, task.targetName, task.error); await this.taskService.retryTask(task, logs.join('\n')); return; } let readme = data.readme || ''; if (typeof readme !== 'string') { readme = JSON.stringify(readme); } // "time": { // "created": "2021-03-27T12:30:23.891Z", // "0.0.2": "2021-03-27T12:30:24.349Z", // "modified": "2021-12-08T14:59:57.264Z", const timeMap = data.time || {}; const failEnd = `โŒโŒโŒโŒโŒ ${url || fullname} โŒโŒโŒโŒโŒ`; const contentLength = headers['content-length'] || '-'; logs.push(`[${isoNow()}] HTTP [${status}] content-length: ${contentLength}, timing: ${JSON.stringify(res.timing)}`); if (this.isRemovedInRemote(registryFetchResult)) { await this.syncDeletePkg({ task, pkg, logs, logUrl, url, data }); return; } const versionMap = data.versions || {}; const distTags = data['dist-tags'] || {}; // show latest information if (distTags.latest) { logs.push(`[${isoNow()}] ๐Ÿ“– ${fullname} latest version: ${distTags.latest ?? '-'}, published time: ${JSON.stringify(timeMap[distTags.latest])}`); } // 1. save maintainers // maintainers: [ // { name: 'bomsy', email: 'b4bomsy@gmail.com' }, // { name: 'jasonlaster11', email: 'jason.laster.11@gmail.com' } // ], let maintainers = data.maintainers; const maintainersMap = {}; const users = []; let changedUserCount = 0; if (!Array.isArray(maintainers) || maintainers.length === 0) { // https://r.cnpmjs.org/webpack.js.org/sync/log/61dbc7c8ff747911a5701068 // https://registry.npmjs.org/webpack.js.org // security holding package will not contains maintainers, auto set npm and npm@npmjs.com to maintainer // "description": "security holding package", // "repository": "npm/security-holder" if (data.description === 'security holding package' || data.repository === 'npm/security-holder') { maintainers = data.maintainers = [{ name: 'npm', email: 'npm@npmjs.com' }]; } else { // try to use latest tag version's maintainers instead const latestPackageVersion = distTags.latest && versionMap[distTags.latest]; if (latestPackageVersion && Array.isArray(latestPackageVersion.maintainers)) { maintainers = latestPackageVersion.maintainers; logs.push(`[${isoNow()}] ๐Ÿ“– Use the latest version(${latestPackageVersion.version}) maintainers instead`); } } } if (Array.isArray(maintainers) && maintainers.length > 0) { logs.push(`[${isoNow()}] ๐Ÿšง Syncing maintainers: ${JSON.stringify(maintainers)}`); for (const maintainer of maintainers) { if (maintainer.name && maintainer.email) { maintainersMap[maintainer.name] = maintainer; const { changed, user } = await this.userService.saveUser(registry?.userPrefix, maintainer.name, maintainer.email); users.push(user); if (changed) { changedUserCount++; logs.push(`[${isoNow()}] ๐ŸŸข [${changedUserCount}] Synced ${maintainer.name} => ${user.name}(${user.userId})`); } } } } if (users.length === 0) { // check unpublished // https://r.cnpmjs.org/-/package/babel-plugin-autocss/syncs/61e4be46c7cbfac94d2ec597/log // { // "name": "babel-plugin-autocss", // "time": { // "created": "2021-10-29T08:21:56.032Z", // "0.0.1": "2021-10-29T08:21:56.206Z", // "modified": "2022-01-14T12:34:23.941Z", // "unpublished": { // "time": "2022-01-14T12:34:23.941Z", // "versions": [ // "0.0.1" // ] // } // } // } // invalid maintainers, sync fail task.error = `invalid maintainers: ${JSON.stringify(maintainers)}`; logs.push(`[${isoNow()}] โŒ ${task.error}, log: ${logUrl}`); logs.push(`[${isoNow()}] ${failEnd}`); await this.taskService.finishTask(task, Task_1.TaskState.Fail, logs.join('\n')); this.logger.info('[PackageSyncerService.executeTask:fail-invalid-maintainers] taskId: %s, targetName: %s, %s', task.taskId, task.targetName, task.error); return; } let lastErrorMessage = ''; const dependenciesSet = new Set(); const { data: existsData } = await this.packageManagerService.listPackageFullManifests(scope, name); const { data: abbreviatedManifests } = await this.packageManagerService.listPackageAbbreviatedManifests(scope, name); const existsVersionMap = existsData?.versions ?? {}; const existsVersionCount = Object.keys(existsVersionMap).length; const abbreviatedVersionMap = abbreviatedManifests?.versions ?? {}; // 2. save versions if (specificVersions && !this.config.cnpmcore.strictSyncSpecivicVersion && !specificVersions.includes(distTags.latest)) { logs.push(`[${isoNow()}] ๐Ÿ“ฆ Add latest tag version "${fullname}: ${distTags.latest}"`); specificVersions.push(distTags.latest); } const versions = specificVersions ? Object.values(versionMap).filter(verItem => specificVersions.includes(verItem.version)) : Object.values(versionMap); // ๅ…จ้‡ๅŒๆญฅๆ—ถ่ทณ่ฟ‡ๆŽ’ๅบ const sortedAvailableVersions = specificVersions ? versions.map(item => item.version).sort(semver_1.default.rcompare) : []; // ๅœจstrictSyncSpecivicVersionๆจกๅผไธ‹๏ผˆไธๅŒๆญฅlatest๏ผ‰ไธ”ๆ‰€ๆœ‰ไผ ๅ…ฅ็š„versionๅ‡ไธๅฏ็”จ if (specificVersions && sortedAvailableVersions.length === 0) { logs.push(`[${isoNow()}] โŒ `); task.error = 'There is no available specific versions, stop task.'; logs.push(`[${isoNow()}] ${task.error}, log: ${logUrl}`); logs.push(`[${isoNow()}] โŒโŒโŒโŒโŒ ${fullname} โŒโŒโŒโŒโŒ`); await this.taskService.finishTask(task, Task_1.TaskState.Fail, logs.join('\n')); this.logger.info('[PackageSyncerService.executeTask:fail-empty-list] taskId: %s, targetName: %s, %s', task.taskId, task.targetName, task.error); return; } if (specificVersions) { // specific versions may not in manifest. const notAvailableVersionList = specificVersions.filter(i => !sortedAvailableVersions.includes(i)); logs.push(`[${isoNow()}] ๐Ÿšง Syncing specific versions: ${sortedAvailableVersions.join(' | ')}`); if (notAvailableVersionList.length > 0) { logs.push(`๐Ÿšง Some specific versions are not available: ๐Ÿ‘‰ ${notAvailableVersionList.join(' | ')} ๐Ÿ‘ˆ`); } } else { logs.push(`[${isoNow()}] ๐Ÿšง Syncing versions ${existsVersionCount} => ${versions.length}`); } const updateVersions = []; const differentMetas = []; let syncIndex = 0; for (const item of versions) { const version = item.version; if (!version) continue; let existsItem = existsVersionMap[version]; let existsAbbreviatedItem = abbreviatedVersionMap[version]; const shouldDeleteReadme = !!(existsItem && 'readme' in existsItem); if (pkg) { if (existsItem) { // check item on AbbreviatedManifests if (!existsAbbreviatedItem) { updateVersions.push(version); logs.push(`[${isoNow()}] ๐Ÿ› Remote version ${version} not exists on local abbreviated manifests, need to refresh`); } } if (existsItem && forceSyncHistory === true) { const pkgVer = await this.packageRepository.findPackageVersion(pkg.packageId, version); if (pkgVer) { logs.push(`[${isoNow()}] ๐Ÿšง [${syncIndex}] Remove version ${version} for force sync history`); await this.packageManagerService.removePackageVersion(pkg, pkgVer, true); existsItem = undefined; existsAbbreviatedItem = undefined; existsVersionMap[version] = undefined; abbreviatedVersionMap[version] = undefined; } } } if (existsItem) { // check metaDataKeys, if different value, override exists one // https://github.com/cnpm/cnpmjs.org/issues/1667 // need libc field https://github.com/cnpm/cnpmcore/issues/187 // fix _npmUser field since https://github.com/cnpm/cnpmcore/issues/553 const metaDataKeys = [ 'peerDependenciesMeta', 'os', 'cpu', 'libc', 'workspaces', 'hasInstallScript', 'deprecated', '_npmUser', 'funding', // https://github.com/cnpm/cnpmcore/issues/689 'acceptDependencies', ]; const ignoreInAbbreviated = ['_npmUser']; const diffMeta = {}; for (const key of metaDataKeys) { let remoteItemValue = item[key]; // make sure hasInstallScript exists if (key === 'hasInstallScript' && remoteItemValue === undefined) { if ((0, PackageUtil_1.detectInstallScript)(item)) { remoteItemValue = true; } } if (!(0, lodash_1.isEqual)(remoteItemValue, existsItem[key])) { diffMeta[key] = remoteItemValue; } else if (!ignoreInAbbreviated.includes(key) && existsAbbreviatedItem && !(0, lodash_1.isEqual)(remoteItemValue, existsAbbreviatedItem[key])) { // should diff exists abbreviated item too diffMeta[key] = remoteItemValue; } } // should delete readme if (shouldDeleteReadme) { diffMeta.readme = undefined; } if (!(0, lodash_1.isEmpty)(diffMeta)) { differentMetas.push([existsItem, diffMeta]); } continue; } syncIndex++; const description = item.description; // "dist": { // "shasum": "943e0ec03df00ebeb6273a5b94b916ba54b47581", // "tarball": "https://registry.npmjs.org/foo/-/foo-1.0.0.tgz" // }, const dist = item.dist; const tarball = dist && dist.tarball; if (!tarball) { lastErrorMessage = `missing tarball, dist: ${JSON.stringify(dist)}`; logs.push(`[${isoNow()}] โŒ [${syncIndex}] Synced version ${version} fail, ${lastErrorMessage}`); await this.taskService.appendTaskLog(task, logs.join('\n')); logs = []; continue; } const publishTimeISO = timeMap[version]; const publishTime = publishTimeISO ? new Date(publishTimeISO) : new Date(); const delay = Date.now() - publishTime.getTime(); logs.push(`[${isoNow()}] ๐Ÿšง [${syncIndex}] Syncing version ${version}, delay: ${delay}ms [${publishTimeISO}], tarball: ${tarball}`); let localFile; try { const { tmpfile, headers, timing } = await (0, FileUtil_1.downloadToTempfile)(this.httpclient, this.config.dataDir, tarball, { remoteAuthToken }); localFile = tmpfile; logs.push(`[${isoNow()}] ๐Ÿšง [${syncIndex}] HTTP content-length: ${headers['content-length']}, timing: ${JSON.stringify(timing)} => ${localFile}`); } catch (err) { if (err.name === 'DownloadNotFoundError' || err.name === 'DownloadStatusInvalidError') { this.logger.warn('Download tarball %s error: %s', tarball, err); } else { this.logger.error('Download tarball %s error: %s', tarball, err); } lastErrorMessage = `download tarball error: ${err}`; logs.push(`[${isoNow()}] โŒ [${syncIndex}] Synced version ${version} fail, ${lastErrorMessage}`); await this.taskService.appendTaskLog(task, logs.join('\n')); logs = []; continue; } if (!pkg) { pkg = await this.packageRepository.findPackage(scope, name); } const publishCmd = { scope, name, version, description, packageJson: item, readme, registryId: registry?.registryId, dist: { localFile, }, isPrivate: false, publishTime, skipRefreshPackageManifests: true, }; try { // ๅฝ“ version ่ฎฐๅฝ•ๅทฒ็ปๅญ˜ๅœจๆ—ถ๏ผŒ่ฟ˜้œ€่ฆๆ ก้ชŒไธ€ไธ‹ pkg.manifests ๆ˜ฏๅฆๅญ˜ๅœจ const publisher = users.find(user => user.displayName === item._npmUser?.name) || users[0]; const pkgVersion = await this.packageManagerService.publish(publishCmd, publisher); updateVersions.push(pkgVersion.version); logs.push(`[${isoNow()}] ๐ŸŽ‰ [${syncIndex}] Synced version ${version} success, packageVersionId: ${pkgVersion.packageVersionId}, db id: ${pkgVersion.id}`); } catch (err) { if (err.name === 'ForbiddenError') { logs.push(`[${isoNow()}] ๐Ÿ› [${syncIndex}] Synced version ${version} already exists, skip publish, try to set in local manifest`); // ๅฆ‚ๆžœ pkg.manifests ไธๅญ˜ๅœจ๏ผŒ้œ€่ฆ่กฅๅ……ไธ€ไธ‹ updateVersions.push(version); } else { err.taskId = task.taskId; this.logger.error(err); lastErrorMessage = `publish error: ${err}`; logs.push(`[${isoNow()}] โŒ [${syncIndex}] Synced version ${version} error, ${lastErrorMessage}`); } } await this.taskService.appendTaskLog(task, logs.join('\n')); logs = []; await (0, promises_2.rm)(localFile, { force: true }); if (!skipDependencies) { const dependencies = item.dependencies || {}; for (const dependencyName in dependencies) { dependenciesSet.add(dependencyName); } const optionalDependencies = item.optionalDependencies || {}; for (const dependencyName in optionalDependencies) { dependenciesSet.add(dependencyName); } } } // try to read package entity again after first sync if (!pkg) { pkg = await this.packageRepository.findPackage(scope, name); } if (!pkg || !pkg.id) { // sync all versions fail in the first time logs.push(`[${isoNow()}] โŒ All versions sync fail, package not exists, log: ${logUrl}`); logs.push(`[${isoNow()}] ${failEnd}`); task.error = lastErrorMessage; await this.taskService.finishTask(task, Task_1.TaskState.Fail, logs.join('\n')); this.logger.info('[PackageSyncerService.executeTask:fail] taskId: %s, targetName: %s, package not exists', task.taskId, task.targetName); return; } // 2.1 save differentMetas for (const [existsItem, diffMeta] of differentMetas) { const pkgVersion = await this.packageRepository.findPackageVersion(pkg.packageId, existsItem.version); if (pkgVersion) { await this.packageManagerService.savePackageVersionManifest(pkgVersion, diffMeta, diffMeta); updateVersions.push(pkgVersion.version); let diffMetaInfo = JSON.stringify(diffMeta); if ('readme' in diffMeta) { diffMetaInfo += ', delete exists readme'; } logs.push(`[${isoNow()}] ๐ŸŸข Synced version ${existsItem.version} success, different meta: ${diffMetaInfo}`); } } const removeVersions = []; // 2.3 find out remove versions for (const existsVersion in existsVersionMap) { if (!(existsVersion in versionMap)) { const pkgVersion = await this.packageRepository.findPackageVersion(pkg.packageId, existsVersion); if (pkgVersion) { await this.packageManagerService.removePackageVersion(pkg, pkgVersion, true); logs.push(`[${isoNow()}] ๐ŸŸข Removed version ${existsVersion} success`); } removeVersions.push(existsVersion); } } logs.push(`[${isoNow()}] ๐ŸŸข Synced updated ${updateVersions.length} versions, removed ${removeVersions.length} versions`); if (updateVersions.length > 0 || removeVersions.length > 0) { logs.push(`[${isoNow()}] ๐Ÿšง Refreshing manifests to dists ......`); const start = Date.now(); await this.taskService.appendTaskLog(task, logs.join('\n')); logs = []; await this.packageManagerService.refreshPackageChangeVersionsToDists(pkg, updateVersions, removeVersions); logs.push(`[${isoNow()}] ๐ŸŸข Refresh use ${Date.now() - start}ms`); } // 3. update tags // "dist-tags": { // "latest": "0.0.7" // }, const changedTags = []; const existsDistTags = existsData && existsData['dist-tags'] || {}; let shouldRefreshDistTags = false; for (const tag in distTags) { const version = distTags[tag]; const utf8mb3Regex = /[\u0020-\uD7FF\uE000-\uFFFD]/; if (!utf8mb3Regex.test(tag)) { logs.push(`[${isoNow()}] ๐Ÿšง invalid tag(${tag}: ${version}), tag name is out of utf8mb3, skip`); continue; } // ๆ–ฐ tag ๆŒ‡ๅ‘็š„็‰ˆๆœฌๆ—ขไธๅœจๅญ˜้‡ๆ•ฐๆฎ้‡Œ๏ผŒไนŸไธๅœจๆœฌๆฌกๅŒๆญฅ็‰ˆๆœฌๅˆ—่กจ้‡Œ // ไพ‹ๅฆ‚ latest ๅฏนๅบ”็š„ version ๅ†™ๅ…ฅๅคฑ่ดฅ่ทณ่ฟ‡ if (!existsVersionMap[version] && !updateVersions.includes(version)) { logs.push(`[${isoNow()}] ๐Ÿšง invalid tag(${tag}: ${version}), version is not exists, skip`); continue; } const changed = await this.packageManagerService.savePackageTag(pkg, tag, version); if (changed) { changedTags.push({ action: 'change', tag, version }); shouldRefreshDistTags = false; } else if (version !== existsDistTags[tag]) { shouldRefreshDistTags = true; logs.push(`[${isoNow()}] ๐Ÿšง Remote tag(${tag}: ${version}) not exists in local dist-tags`); } } // 3.1 find out remove tags for (const tag in existsDistTags) { if (!(tag in distTags)) { const changed = await this.packageManagerService.removePackageTag(pkg, tag); if (changed) { changedTags.push({ action: 'remove', tag }); shouldRefreshDistTags = false; } } } // 3.2 shoud add latest tag // ๅœจๅŒๆญฅ sepcific version ๆ—ถๅฆ‚ๆžœๆฒกๆœ‰ๅŒๆญฅ latestTag ็š„็‰ˆๆœฌไผšๅ‡บ็Žฐ latestTag ไธขๅคฑๆˆ–ๆŒ‡ๅ‘็‰ˆๆœฌไธๆญฃ็กฎ็š„ๆƒ…ๅ†ต if (specificVersions && this.config.cnpmcore.strictSyncSpecivicVersion) { // ไธๅ…่ฎธ่‡ชๅŠจๅŒๆญฅ latest ็‰ˆๆœฌ๏ผŒไปŽๅทฒๅŒๆญฅ็‰ˆๆœฌไธญ้€‰ๅ‡บ latest let latestStableVersion = semver_1.default.maxSatisfying(sortedAvailableVersions, '*'); // ๆ‰€ๆœ‰็‰ˆๆœฌ้ƒฝไธๆ˜ฏ็จณๅฎš็‰ˆๆœฌๅˆ™ๆŒ‡ๅ‘้ž็จณๅฎš็‰ˆๆœฌไฟ่ฏ latest ๅญ˜ๅœจ if (!latestStableVersion) { latestStableVersion = sortedAvailableVersions[0]; } if (!existsDistTags.latest || semver_1.default.rcompare(existsDistTags.latest, latestStableVersion) === 1) { logs.push(`[${isoNow()}] ๐Ÿšง patch latest tag from specific versions ๐Ÿšง`); changedTags.push({ action: 'change', tag: 'latest', version: latestStableVersion }); await this.packageManagerService.savePackageTag(pkg, 'latest', latestStableVersion); } } if (changedTags.length > 0) { logs.push(`[${isoNow()}] ๐ŸŸข Synced ${changedTags.length} tags: ${JSON.stringify(changedTags)}`); } if (shouldRefreshDistTags) { await this.packageManagerService.refreshPackageDistTagsToDists(pkg); logs.push(`[${isoNow()}] ๐ŸŸข Refresh dist-tags`); } // 4. add package maintainers await this.packageManagerService.savePackageMaintainers(pkg, users); // 4.1 find out remove maintainers const removedMaintainers = []; const existsMaintainers = existsData && existsData.maintainers || []; for (const maintainer of existsMaintainers) { const { name } = maintainer; if (!(name in maintainersMap)) { const user = await this.userRepository.findUserByName(`${registry?.userPrefix || 'npm:'}${name}`); if (user) { await this.packageManagerService.removePackageMaintainer(pkg, user); removedMaintainers.push(maintainer); } } } if (removedMaintainers.length > 0) { logs.push(`[${isoNow()}] ๐ŸŸข Removed ${removedMaintainers.length} maintainers: ${JSON.stringify(removedMaintainers)}`); } // 4.2 update package maintainers in dist // The event is initialized in the repository and distributed after uncork. // maintainers' information is updated in bulk to ensure consistency. if (!(0, lodash_1.isEqual)(maintainers, existsMaintainers)) { logs.push(`[${isoNow()}] ๐Ÿšง Syncing maintainers to package manifest, from: ${JSON.stringify(maintainers)} to: ${JSON.stringify(existsMaintainers)}`); await this.packageManagerService.refreshPackageMaintainersToDists(pkg); logs.push(`[${isoNow()}] ๐ŸŸข Syncing maintainers to package manifest done`); } // 5. add deps sync task for (const dependencyName of dependenciesSet) { const existsTask = await this.taskRepository.findTaskByTargetName(dependencyName, Task_1.TaskType.SyncPackage, Task_1.TaskState.Waiting); if (existsTask) { logs.push(`[${isoNow()}] ๐Ÿ“– Has dependency "${dependencyName}" sync task: ${existsTask.taskId}, db id: ${existsTask.id}`); continue; } const tips = `Sync cause by "${fullname}" dependencies, parent task: ${task.taskId}`; const dependencyTask = await this.createTask(dependencyName, { authorId: task.authorId, authorIp: task.authorIp, tips, }); logs.push(`[${isoNow()}] ๐Ÿ“ฆ Add dependency "${dependencyName}" sync task: ${dependencyTask.taskId}, db id: ${dependencyTask.id}`); } if (syncDownloadData) { await this.syncDownloadData(task, pkg); } // clean cache await this.cacheService.removeCache(fullname); logs.push(`[${isoNow()}] ๐Ÿ—‘๏ธ Clean cache`); logs.push(`[${isoNow()}] ๐Ÿ“ Log URL: ${logUrl}`); logs.push(`[${isoNow()}] ๐Ÿ”— ${url}`); task.error = lastErrorMessage; await this.taskService.finishTask(task, Task_1.TaskState.Success, logs.join('\n')); this.logger.info('[PackageSyncerService.executeTask:success] taskId: %s, targetName: %s', task.taskId, task.targetName); } }; exports.PackageSyncerService = PackageSyncerService; __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", TaskRepository_1.TaskRepository) ], PackageSyncerService.prototype, "taskRepository", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", PackageRepository_1.PackageRepository) ], PackageSyncerService.prototype, "packageRepository", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", PackageVersionDownloadRepository_1.PackageVersionDownloadRepository) ], PackageSyncerService.prototype, "packageVersionDownloadRepository", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", UserRepository_1.UserRepository) ], PackageSyncerService.prototype, "userRepository", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", NPMRegistry_1.NPMRegistry) ], PackageSyncerService.prototype, "npmRegistry", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", UserService_1.UserService) ], PackageSyncerService.prototype, "userService", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", TaskService_1.TaskService) ], PackageSyncerService.prototype, "taskService", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", PackageManagerService_1.PackageManagerService) ], PackageSyncerService.prototype, "packageManagerService", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", CacheService_1.CacheService) ], PackageSyncerService.prototype, "cacheService", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", Object) ], PackageSyncerService.prototype, "httpclient", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", RegistryManagerService_1.RegistryManagerService) ], PackageSyncerService.prototype, "registryManagerService", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", ScopeManagerService_1.ScopeManagerService) ], PackageSyncerService.prototype, "scopeManagerService", void 0); __decorate([ (0, aop_1.Pointcut)(EventCorkerAdvice_1.EventCorkAdvice), __metadata("design:type", Function), __metadata("design:paramtypes", [Task_2.Task]), __metadata("design:returntype", Promise) ], PackageSyncerService.prototype, "executeTask", null); exports.PackageSyncerService = PackageSyncerService = __decorate([ (0, tegg_1.SingletonProto)({ accessLevel: tegg_1.AccessLevel.PUBLIC, }) ], PackageSyncerService); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUGFja2FnZVN5bmNlclNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hcHAvY29yZS9zZXJ2aWNlL1BhY2thZ2VTeW5jZXJTZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7OztBQUFBLHNEQUF5QjtBQUN6QixtREFBa0Q7QUFDbEQsK0NBQXNDO0FBQ3RDLHNDQUlxQjtBQUNyQix5Q0FBMkM7QUFFM0MsbUNBQTBDO0FBQzFDLG9EQUE0QjtBQUM1QixrRUFBaUY7QUFDakYsMERBQWdGO0FBQ2hGLG9EQUEyRDtBQUMzRCxpREFBNkQ7QUFDN0Qsa0VBQStEO0FBQy9ELG9FQUFpRTtBQUNqRSwwRUFBNkc7QUFDN0csd0dBQXFHO0FBQ3JHLG9FQUFpRTtBQUNqRSx5Q0FBcUY7QUFFckYsK0NBQTRDO0FBQzVDLCtDQUE0QztBQUM1QyxtRUFBZ0U7QUFDaEUsaURBQThDO0FBRTlDLHFFQUFrRTtBQUVsRSwyQ0FBNkM7QUFDN0MsK0RBQTREO0FBQzVELDJEQUFzRDtBQUN0RCxzREFBNEU7QUFXNUUsU0FBUyxNQUFNO0lBQ2IsT0FBTyxJQUFJLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDO0FBQ2xDLENBQUM7QUFFRCxNQUFhLHFCQUFzQixTQUFRLDRCQUFlO0NBQ3pEO0FBREQsc0RBQ0M7QUFLTSxJQUFNLG9CQUFvQixHQUExQixNQUFNLG9CQUFxQixTQUFRLGlDQUFlO0lBMEJoRCxLQUFLLENBQUMsVUFBVSxDQUFDLFFBQWdCLEVBQUUsT0FBZ0M7UUFDeEUsTUFBTSxDQUFFLEtBQUssRUFBRSxJQUFJLENBQUUsR0FBRyxJQUFBLDZCQUFlLEVBQUMsUUFBUSxDQUFDLENBQUM7UUFDbEQsTUFBTSxHQUFHLEdBQUcsTUFBTSxJQUFJLENBQUMsaUJBQWlCLENBQUMsV0FBVyxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQztRQUNsRSw2REFBNkQ7UUFDN0QsSUFBSSxHQUFHLElBQUksR0FBRyxDQUFDLFVBQVUsSUFBSSxPQUFPLEVBQUUsVUFBVSxFQUFFO1lBQ2hELElBQUksR0FBRyxDQUFDLFVBQVUsS0FBSyxPQUFPLENBQUMsVUFBVSxFQUFFO2dCQUN6QyxNQUFNLElBQUkscUJBQXFCLENBQUMsV0FBVyxRQUFRLHVCQUF1QixPQUFPLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQzthQUNqRztTQUNGO1FBQ0QsT0FBTyxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLFdBQUksQ0FBQyxpQkFBaUIsQ0FBQyxRQUFRLEVBQUUsT0FBTyxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDNUYsQ0FBQztJQUVNLEtBQUssQ0FBQyxRQUFRLENBQUMsTUFBYztRQUNsQyxPQUFPLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDakQsQ0FBQztJQUVNLEtBQUssQ0FBQyx