mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 14:36:25 +00:00
feat(worker): convert 'branch' to typescript (#4449)
Co-Authored-By: Jamie Magee <JamieMagee@users.noreply.github.com>
This commit is contained in:
parent
4d6b98f9b0
commit
2a4de19c77
12 changed files with 165 additions and 91 deletions
|
@ -157,7 +157,7 @@ export interface Upgrade<T = Record<string, any>>
|
||||||
version?: string;
|
version?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ArtifactError {
|
export interface ArtifactError {
|
||||||
lockFile?: string;
|
lockFile?: string;
|
||||||
stderr?: string;
|
stderr?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,11 @@ import got from 'got';
|
||||||
import Git from 'simple-git/promise';
|
import Git from 'simple-git/promise';
|
||||||
import { RenovateConfig } from '../config/common';
|
import { RenovateConfig } from '../config/common';
|
||||||
|
|
||||||
|
export interface FileData {
|
||||||
|
name: string;
|
||||||
|
contents: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GotApiOptions {
|
export interface GotApiOptions {
|
||||||
useCache?: boolean;
|
useCache?: boolean;
|
||||||
hostType?: string;
|
hostType?: string;
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
const { logger } = require('../../logger');
|
import { logger } from '../../logger';
|
||||||
const { platform } = require('../../platform');
|
import { RenovateConfig } from '../../config';
|
||||||
|
import { platform } from '../../platform';
|
||||||
|
|
||||||
module.exports = {
|
export type AutomergeResult =
|
||||||
tryBranchAutomerge,
|
| 'automerged'
|
||||||
};
|
| 'automerge aborted - PR exists'
|
||||||
|
| 'branch status error'
|
||||||
|
| 'failed'
|
||||||
|
| 'no automerge'
|
||||||
|
| 'not ready';
|
||||||
|
|
||||||
async function tryBranchAutomerge(config) {
|
export async function tryBranchAutomerge(
|
||||||
|
config: RenovateConfig
|
||||||
|
): Promise<AutomergeResult> {
|
||||||
logger.debug('Checking if we can automerge branch');
|
logger.debug('Checking if we can automerge branch');
|
||||||
if (!(config.automerge && config.automergeType === 'branch')) {
|
if (!(config.automerge && config.automergeType === 'branch')) {
|
||||||
return 'no automerge';
|
return 'no automerge';
|
|
@ -1,11 +1,11 @@
|
||||||
const { logger } = require('../../logger');
|
import { logger } from '../../logger';
|
||||||
const { platform } = require('../../platform');
|
import { RenovateConfig } from '../../config';
|
||||||
|
import { platform } from '../../platform';
|
||||||
|
|
||||||
module.exports = {
|
/** TODO: Proper return type */
|
||||||
prAlreadyExisted,
|
export async function prAlreadyExisted(
|
||||||
};
|
config: RenovateConfig
|
||||||
|
): Promise<any | null> {
|
||||||
async function prAlreadyExisted(config) {
|
|
||||||
logger.trace({ config }, 'prAlreadyExisted');
|
logger.trace({ config }, 'prAlreadyExisted');
|
||||||
if (config.recreateClosed) {
|
if (config.recreateClosed) {
|
||||||
logger.debug('recreateClosed is true');
|
logger.debug('recreateClosed is true');
|
|
@ -1,12 +1,21 @@
|
||||||
import is from '@sindresorhus/is';
|
import is from '@sindresorhus/is';
|
||||||
import { platform } from '../../platform';
|
import minimatch from 'minimatch';
|
||||||
|
import { FileData, platform } from '../../platform';
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import { RenovateConfig } from '../../config';
|
||||||
|
|
||||||
const minimatch = require('minimatch');
|
export type CommitConfig = RenovateConfig & {
|
||||||
const { logger } = require('../../logger');
|
baseBranch?: string;
|
||||||
|
branchName: string;
|
||||||
|
commitMessage: string;
|
||||||
|
excludeCommitPaths?: string[];
|
||||||
|
updatedPackageFiles: FileData[];
|
||||||
|
updatedArtifacts: FileData[];
|
||||||
|
};
|
||||||
|
|
||||||
export { commitFilesToBranch };
|
export async function commitFilesToBranch(
|
||||||
|
config: CommitConfig
|
||||||
async function commitFilesToBranch(config) {
|
): Promise<boolean> {
|
||||||
let updatedFiles = config.updatedPackageFiles.concat(config.updatedArtifacts);
|
let updatedFiles = config.updatedPackageFiles.concat(config.updatedArtifacts);
|
||||||
// istanbul ignore if
|
// istanbul ignore if
|
||||||
if (is.nonEmptyArray(config.excludeCommitPaths)) {
|
if (is.nonEmptyArray(config.excludeCommitPaths)) {
|
|
@ -1,17 +1,25 @@
|
||||||
import is from '@sindresorhus/is';
|
import is from '@sindresorhus/is';
|
||||||
import { platform } from '../../platform';
|
import { FileData, platform } from '../../platform';
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import { get } from '../../manager';
|
||||||
|
import { RenovateConfig } from '../../config';
|
||||||
|
import { UpdateArtifactsConfig, ArtifactError } from '../../manager/common';
|
||||||
|
|
||||||
const { logger } = require('../../logger');
|
export interface PackageFilesResult {
|
||||||
const { get } = require('../../manager');
|
artifactErrors: ArtifactError[];
|
||||||
|
parentBranch: string;
|
||||||
|
updatedPackageFiles: FileData[];
|
||||||
|
updatedArtifacts: FileData[];
|
||||||
|
}
|
||||||
|
|
||||||
export { getUpdatedPackageFiles };
|
export async function getUpdatedPackageFiles(
|
||||||
|
config: RenovateConfig & UpdateArtifactsConfig
|
||||||
async function getUpdatedPackageFiles(config) {
|
): Promise<PackageFilesResult> {
|
||||||
logger.debug('manager.getUpdatedPackageFiles()');
|
logger.debug('manager.getUpdatedPackageFiles()');
|
||||||
logger.trace({ config });
|
logger.trace({ config });
|
||||||
const updatedFileContents = {};
|
const updatedFileContents: Record<string, string> = {};
|
||||||
const packageFileManagers = {};
|
const packageFileManagers: Record<string, string> = {};
|
||||||
const packageFileUpdatedDeps = {};
|
const packageFileUpdatedDeps: Record<string, string[]> = {};
|
||||||
const lockFileMaintenanceFiles = [];
|
const lockFileMaintenanceFiles = [];
|
||||||
for (const upgrade of config.upgrades) {
|
for (const upgrade of config.upgrades) {
|
||||||
const { manager, packageFile, depName } = upgrade;
|
const { manager, packageFile, depName } = upgrade;
|
||||||
|
@ -68,8 +76,8 @@ async function getUpdatedPackageFiles(config) {
|
||||||
name,
|
name,
|
||||||
contents: updatedFileContents[name],
|
contents: updatedFileContents[name],
|
||||||
}));
|
}));
|
||||||
const updatedArtifacts = [];
|
const updatedArtifacts: FileData[] = [];
|
||||||
const artifactErrors = [];
|
const artifactErrors: ArtifactError[] = [];
|
||||||
for (const packageFile of updatedPackageFiles) {
|
for (const packageFile of updatedPackageFiles) {
|
||||||
const manager = packageFileManagers[packageFile.name];
|
const manager = packageFileManagers[packageFile.name];
|
||||||
const updatedDeps = packageFileUpdatedDeps[packageFile.name];
|
const updatedDeps = packageFileUpdatedDeps[packageFile.name];
|
||||||
|
@ -82,7 +90,7 @@ async function getUpdatedPackageFiles(config) {
|
||||||
config
|
config
|
||||||
);
|
);
|
||||||
if (is.nonEmptyArray(results)) {
|
if (is.nonEmptyArray(results)) {
|
||||||
for (/** @type any */ const res of results) {
|
for (const res of results) {
|
||||||
const { file, artifactError } = res;
|
const { file, artifactError } = res;
|
||||||
if (file) {
|
if (file) {
|
||||||
updatedArtifacts.push(file);
|
updatedArtifacts.push(file);
|
||||||
|
@ -109,7 +117,7 @@ async function getUpdatedPackageFiles(config) {
|
||||||
config
|
config
|
||||||
);
|
);
|
||||||
if (is.nonEmptyArray(results)) {
|
if (is.nonEmptyArray(results)) {
|
||||||
for (/** @type any */ const res of results) {
|
for (const res of results) {
|
||||||
const { file, artifactError } = res;
|
const { file, artifactError } = res;
|
||||||
if (file) {
|
if (file) {
|
||||||
updatedArtifacts.push(file);
|
updatedArtifacts.push(file);
|
|
@ -1,26 +1,52 @@
|
||||||
const { DateTime } = require('luxon');
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
const { logger, setMeta } = require('../../logger');
|
import { logger, setMeta } from '../../logger';
|
||||||
const schedule = require('./schedule');
|
import { isScheduledNow } from './schedule';
|
||||||
const { getUpdatedPackageFiles } = require('./get-updated');
|
import { getUpdatedPackageFiles } from './get-updated';
|
||||||
const { getAdditionalFiles } = require('../../manager/npm/post-update');
|
import {
|
||||||
const { commitFilesToBranch } = require('./commit');
|
getAdditionalFiles,
|
||||||
const { getParentBranch } = require('./parent');
|
AdditionalPackageFiles,
|
||||||
const { tryBranchAutomerge } = require('./automerge');
|
} from '../../manager/npm/post-update';
|
||||||
const { setStability, setUnpublishable } = require('./status-checks');
|
import { commitFilesToBranch, CommitConfig } from './commit';
|
||||||
const { prAlreadyExisted } = require('./check-existing');
|
import { getParentBranch } from './parent';
|
||||||
const prWorker = require('../pr');
|
import { tryBranchAutomerge } from './automerge';
|
||||||
const { appName, appSlug } = require('../../config/app-strings');
|
import {
|
||||||
const { platform } = require('../../platform');
|
setStability,
|
||||||
const { emojify } = require('../../util/emoji');
|
setUnpublishable,
|
||||||
|
StabilityConfig,
|
||||||
|
UnpublishableConfig,
|
||||||
|
} from './status-checks';
|
||||||
|
import { prAlreadyExisted } from './check-existing';
|
||||||
|
import { ensurePr, checkAutoMerge } from '../pr';
|
||||||
|
import { appName, appSlug } from '../../config/app-strings';
|
||||||
|
import { RenovateConfig } from '../../config';
|
||||||
|
import { platform } from '../../platform';
|
||||||
|
import { emojify } from '../../util/emoji';
|
||||||
|
|
||||||
const { isScheduledNow } = schedule;
|
export type BranchConfig = RenovateConfig &
|
||||||
|
StabilityConfig &
|
||||||
|
UnpublishableConfig &
|
||||||
|
CommitConfig;
|
||||||
|
|
||||||
module.exports = {
|
export type ProcessBranchResult =
|
||||||
processBranch,
|
| 'already-existed'
|
||||||
};
|
| 'automerged'
|
||||||
|
| 'done'
|
||||||
|
| 'error'
|
||||||
|
| 'needs-approval'
|
||||||
|
| 'needs-pr-approval'
|
||||||
|
| 'not-scheduled'
|
||||||
|
| 'no-work'
|
||||||
|
| 'pending'
|
||||||
|
| 'pr-created'
|
||||||
|
| 'pr-edited'
|
||||||
|
| 'pr-hourly-limit-reached';
|
||||||
|
|
||||||
async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
|
export async function processBranch(
|
||||||
|
branchConfig: BranchConfig,
|
||||||
|
prHourlyLimitReached?: boolean,
|
||||||
|
packageFiles?: AdditionalPackageFiles
|
||||||
|
): Promise<ProcessBranchResult> {
|
||||||
const config = { ...branchConfig };
|
const config = { ...branchConfig };
|
||||||
const dependencies = config.upgrades
|
const dependencies = config.upgrades
|
||||||
.map(upgrade => upgrade.depName)
|
.map(upgrade => upgrade.depName)
|
||||||
|
@ -418,7 +444,7 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`There are ${config.errors.length} errors and ${config.warnings.length} warnings`
|
`There are ${config.errors.length} errors and ${config.warnings.length} warnings`
|
||||||
);
|
);
|
||||||
const pr = await prWorker.ensurePr(config);
|
const pr = await ensurePr(config);
|
||||||
// TODO: ensurePr should check for automerge itself
|
// TODO: ensurePr should check for automerge itself
|
||||||
if (pr === 'needs-pr-approval') {
|
if (pr === 'needs-pr-approval') {
|
||||||
return 'needs-pr-approval';
|
return 'needs-pr-approval';
|
||||||
|
@ -504,7 +530,7 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
|
||||||
await platform.ensureCommentRemoval(pr.number, topic);
|
await platform.ensureCommentRemoval(pr.number, topic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const prAutomerged = await prWorker.checkAutoMerge(pr, config);
|
const prAutomerged = await checkAutoMerge(pr, config);
|
||||||
if (prAutomerged) {
|
if (prAutomerged) {
|
||||||
return 'automerged';
|
return 'automerged';
|
||||||
}
|
}
|
||||||
|
@ -530,7 +556,8 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
|
||||||
return 'done';
|
return 'done';
|
||||||
}
|
}
|
||||||
|
|
||||||
function rebaseCheck(config, branchPr) {
|
// TODO: proper typings
|
||||||
|
function rebaseCheck(config: RenovateConfig, branchPr: any): boolean {
|
||||||
const titleRebase = branchPr.title && branchPr.title.startsWith('rebase!');
|
const titleRebase = branchPr.title && branchPr.title.startsWith('rebase!');
|
||||||
const labelRebase =
|
const labelRebase =
|
||||||
branchPr.labels && branchPr.labels.includes(config.rebaseLabel);
|
branchPr.labels && branchPr.labels.includes(config.rebaseLabel);
|
|
@ -1,12 +1,13 @@
|
||||||
const { logger } = require('../../logger');
|
import { logger } from '../../logger';
|
||||||
const { appSlug } = require('../../config/app-strings');
|
import { appSlug } from '../../config/app-strings';
|
||||||
const { platform } = require('../../platform');
|
import { RenovateConfig } from '../../config';
|
||||||
|
import { platform } from '../../platform';
|
||||||
|
|
||||||
module.exports = {
|
type ParentBranch = { parentBranch: string | undefined; isModified?: boolean };
|
||||||
getParentBranch,
|
|
||||||
};
|
|
||||||
|
|
||||||
async function getParentBranch(config) {
|
export async function getParentBranch(
|
||||||
|
config: RenovateConfig
|
||||||
|
): Promise<ParentBranch> {
|
||||||
const { branchName } = config;
|
const { branchName } = config;
|
||||||
// Check if branch exists
|
// Check if branch exists
|
||||||
const branchExists = await platform.branchExists(branchName);
|
const branchExists = await platform.branchExists(branchName);
|
|
@ -1,35 +1,30 @@
|
||||||
import is from '@sindresorhus/is';
|
import is from '@sindresorhus/is';
|
||||||
|
import later from 'later';
|
||||||
const later = require('later');
|
import moment from 'moment-timezone';
|
||||||
const moment = require('moment-timezone');
|
import { logger } from '../../logger';
|
||||||
const { logger } = require('../../logger');
|
|
||||||
|
|
||||||
export { hasValidTimezone, hasValidSchedule, isScheduledNow };
|
|
||||||
|
|
||||||
const scheduleMappings = {
|
const scheduleMappings = {
|
||||||
'every month': 'before 3am on the first day of the month',
|
'every month': 'before 3am on the first day of the month',
|
||||||
monthly: 'before 3am on the first day of the month',
|
monthly: 'before 3am on the first day of the month',
|
||||||
};
|
};
|
||||||
|
|
||||||
function fixShortHours(input) {
|
function fixShortHours(input: string): string {
|
||||||
return input.replace(/( \d?\d)((a|p)m)/g, '$1:00$2');
|
return input.replace(/( \d?\d)((a|p)m)/g, '$1:00$2');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function hasValidTimezone(
|
||||||
* @returns {[boolean] | [boolean, string]}
|
timezone: string
|
||||||
*/
|
): [boolean] | [boolean, string] {
|
||||||
function hasValidTimezone(timezone) {
|
|
||||||
if (!moment.tz.zone(timezone)) {
|
if (!moment.tz.zone(timezone)) {
|
||||||
return [false, `Invalid timezone: ${timezone}`];
|
return [false, `Invalid timezone: ${timezone}`];
|
||||||
}
|
}
|
||||||
return [true];
|
return [true];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function hasValidSchedule(
|
||||||
* @returns {[boolean] | [boolean, string]}
|
schedule: string[] | null | 'at any time'
|
||||||
*/
|
): [boolean] | [boolean, string] {
|
||||||
function hasValidSchedule(schedule) {
|
let message: string;
|
||||||
let message;
|
|
||||||
if (
|
if (
|
||||||
!schedule ||
|
!schedule ||
|
||||||
schedule === 'at any time' ||
|
schedule === 'at any time' ||
|
||||||
|
@ -70,7 +65,7 @@ function hasValidSchedule(schedule) {
|
||||||
return [true, ''];
|
return [true, ''];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isScheduledNow(config) {
|
export function isScheduledNow(config) {
|
||||||
let configSchedule = config.schedule;
|
let configSchedule = config.schedule;
|
||||||
logger.debug(`Checking schedule(${configSchedule}, ${config.timezone})`);
|
logger.debug(`Checking schedule(${configSchedule}, ${config.timezone})`);
|
||||||
if (
|
if (
|
|
@ -1,13 +1,14 @@
|
||||||
const { logger } = require('../../logger');
|
import { logger } from '../../logger';
|
||||||
const { appSlug, urls } = require('../../config/app-strings');
|
import { appSlug, urls } from '../../config/app-strings';
|
||||||
const { platform } = require('../../platform');
|
import { RenovateConfig } from '../../config';
|
||||||
|
import { platform } from '../../platform';
|
||||||
|
|
||||||
module.exports = {
|
async function setStatusCheck(
|
||||||
setStability,
|
branchName: string,
|
||||||
setUnpublishable,
|
context: string,
|
||||||
};
|
description: string,
|
||||||
|
state: string
|
||||||
async function setStatusCheck(branchName, context, description, state) {
|
) {
|
||||||
const existingState = await platform.getBranchStatusCheck(
|
const existingState = await platform.getBranchStatusCheck(
|
||||||
branchName,
|
branchName,
|
||||||
context
|
context
|
||||||
|
@ -27,7 +28,12 @@ async function setStatusCheck(branchName, context, description, state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setStability(config) {
|
export type StabilityConfig = RenovateConfig & {
|
||||||
|
stabilityStatus: string;
|
||||||
|
branchName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function setStability(config: StabilityConfig) {
|
||||||
if (!config.stabilityStatus) {
|
if (!config.stabilityStatus) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -44,7 +50,15 @@ async function setStability(config) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setUnpublishable(config) {
|
export type UnpublishableConfig = RenovateConfig & {
|
||||||
|
unpublishSafe?: boolean;
|
||||||
|
canBeUnpublished?: boolean;
|
||||||
|
branchName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function setUnpublishable(
|
||||||
|
config: UnpublishableConfig
|
||||||
|
): Promise<void> {
|
||||||
if (!config.unpublishSafe) {
|
if (!config.unpublishSafe) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
|
@ -182,6 +182,7 @@
|
||||||
"@types/later": "1.2.5",
|
"@types/later": "1.2.5",
|
||||||
"@types/lodash": "4.14.144",
|
"@types/lodash": "4.14.144",
|
||||||
"@types/luxon": "1.15.2",
|
"@types/luxon": "1.15.2",
|
||||||
|
"@types/moment-timezone": "0.5.12",
|
||||||
"@types/nock": "10.0.3",
|
"@types/nock": "10.0.3",
|
||||||
"@types/node": "11.13.22",
|
"@types/node": "11.13.22",
|
||||||
"@types/node-emoji": "1.8.1",
|
"@types/node-emoji": "1.8.1",
|
||||||
|
|
|
@ -1236,6 +1236,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||||
|
|
||||||
|
"@types/moment-timezone@0.5.12":
|
||||||
|
version "0.5.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.12.tgz#0fb680c03db194fe8ff4551eaeb1eec8d3d80e9f"
|
||||||
|
integrity sha512-hnHH2+Efg2vExr/dSz+IX860nSiyk9Sk4pJF2EmS11lRpMcNXeB4KBW5xcgw2QPsb9amTXdsVNEe5IoJXiT0uw==
|
||||||
|
dependencies:
|
||||||
|
moment ">=2.14.0"
|
||||||
|
|
||||||
"@types/nock@10.0.3":
|
"@types/nock@10.0.3":
|
||||||
version "10.0.3"
|
version "10.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/nock/-/nock-10.0.3.tgz#dab1d18ffbccfbf2db811dab9584304eeb6e1c4c"
|
resolved "https://registry.yarnpkg.com/@types/nock/-/nock-10.0.3.tgz#dab1d18ffbccfbf2db811dab9584304eeb6e1c4c"
|
||||||
|
@ -6163,7 +6170,7 @@ moment-timezone@0.5.26:
|
||||||
dependencies:
|
dependencies:
|
||||||
moment ">= 2.9.0"
|
moment ">= 2.9.0"
|
||||||
|
|
||||||
moment@2.24.0, "moment@>= 2.9.0", moment@^2.10.6:
|
moment@2.24.0, "moment@>= 2.9.0", moment@>=2.14.0, moment@^2.10.6:
|
||||||
version "2.24.0"
|
version "2.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||||
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||||
|
|
Loading…
Reference in a new issue