This commit is contained in:
Samuel Attard 2021-04-06 18:52:39 -07:00
Родитель dee13eb576
Коммит aeea088cd1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 191FEF027779CC6C
9 изменённых файлов: 362 добавлений и 96 удалений

2
.gitignore поставляемый
Просмотреть файл

@ -1 +1,3 @@
node_modules
dist
*error.log

7
.prettierrc.json Normal file
Просмотреть файл

@ -0,0 +1,7 @@
{
"trailingComma": "all",
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"parser": "typescript"
}

Просмотреть файл

@ -1,7 +1,7 @@
{
"name": "chromium-bot",
"version": "1.0.0",
"main": "src/index.js",
"main": "dist/index.js",
"license": "MIT",
"dependencies": {
"body-parser": "^1.19.0",
@ -10,6 +10,16 @@
"node-fetch": "^2.6.1"
},
"scripts": {
"start": "node src/index.js"
"build": "tsc",
"start": "node dist/index.js",
"prettier:check": "prettier --check \"src/**/*.ts\"",
"prettier:write": "prettier --write \"src/**/*.ts\""
},
"devDependencies": {
"@types/express": "^4.17.11",
"@types/node": "^14.14.37",
"@types/node-fetch": "^2.5.9",
"prettier": "^2.2.1",
"typescript": "^4.2.3"
}
}

Просмотреть файл

@ -1,7 +1,9 @@
const fetch = require('node-fetch').default;
import fetch from 'node-fetch';
module.exports = async function handleChromiumReviewUnfurl(url, message_ts, channel) {
const match = /^https:\/\/chromium-review\.googlesource\.com\/c\/([a-z0-9]+)\/([a-z0-9]+)\/\+\/([0-9]+)/g.exec(url);
export async function handleChromiumReviewUnfurl(url: string, message_ts: string, channel: string) {
const match = /^https:\/\/chromium-review\.googlesource\.com\/c\/([a-z0-9]+)\/([a-z0-9]+)\/\+\/([0-9]+)/g.exec(
url,
);
if (match) {
const repo = `${match[1]}%2F${match[2]}`;
const niceRepo = `${match[1]}/${match[2]}`;
@ -13,13 +15,18 @@ module.exports = async function handleChromiumReviewUnfurl(url, message_ts, chan
const details = JSON.parse(detailsText.substr(4));
const { project, subject, owner, labels, current_revision, revisions } = details;
const commit = revisions[current_revision].commit;
const { message, author: { date } } = commit;
const messageWithoutSubject = message.startsWith(subject) ? message.substr(subject.length + 1).trim() : message;
const {
message,
author: { date },
} = commit;
const messageWithoutSubject = message.startsWith(subject)
? message.substr(subject.length + 1).trim()
: message;
const unfurl = await fetch('https://slack.com/api/chat.unfurl', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SLACK_TOKEN}`,
Authorization: `Bearer ${process.env.SLACK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
@ -30,22 +37,27 @@ module.exports = async function handleChromiumReviewUnfurl(url, message_ts, chan
[url]: {
color: '#4D394B',
author_name: owner.name,
author_icon: owner.avatars && owner.avatars.length ? owner.avatars[owner.avatars.length - 1].url : ':void',
author_link: `https://chromium-review.googlesource.com/q/author:${encodeURIComponent(owner.email)}`,
author_icon:
owner.avatars && owner.avatars.length
? owner.avatars[owner.avatars.length - 1].url
: ':void',
author_link: `https://chromium-review.googlesource.com/q/author:${encodeURIComponent(
owner.email,
)}`,
fallback: `[${niceRepo}] #${cl} ${subject}`,
title: `#${cl} ${subject}`,
title_link: url,
footer_icon: 'https://chromium-review.googlesource.com/favicon.ico',
text: messageWithoutSubject,
footer: `<https://source.chromium.org/chromium/${niceRepo}|${niceRepo}>`,
ts: (new Date(date)).getTime(),
ts: new Date(date).getTime(),
// TODO: Labels? CQ status?
// fields: [{
// }]
}
}
})
},
},
}),
});
if (unfurl.status === 200) {
const resp = await unfurl.json();

Просмотреть файл

@ -1,12 +1,12 @@
const fetch = require('node-fetch').default;
import fetch from 'node-fetch';
const { Policy, ConstantBackoff } = require('cockatiel');
import { Policy, ConstantBackoff } from 'cockatiel';
function parseBugIdentifier(url) {
function parseBugIdentifier(url: string) {
const parsed = new URL(url);
if (parsed.host === 'bugs.chromium.org') {
// https://bugs.chromium.org/p/chromium/issues/detail?id=1195924
const number = parseInt(parsed.searchParams.get('id'), 10);
const number = parseInt(parsed.searchParams.get('id') || '', 10);
if (isNaN(number)) return null;
const match = /^https:\/\/bugs\.chromium\.org\/p\/([a-z0-9]+)\/issues\/detail/g.exec(url);
@ -15,7 +15,7 @@ function parseBugIdentifier(url) {
return {
project: match[1],
number,
}
};
} else if (parsed.host === 'crbug.com') {
// https://crbug.com/12345
const number = parseInt(parsed.pathname.slice(1), 10);
@ -30,10 +30,7 @@ function parseBugIdentifier(url) {
return null;
}
const retry = Policy.handleAll()
.retry()
.attempts(3)
.backoff(new ConstantBackoff(250));
const retry = Policy.handleAll().retry().attempts(3).backoff(new ConstantBackoff(250));
async function getMonorailToken() {
return retry.execute(async () => {
@ -52,13 +49,36 @@ async function getMonorailToken() {
async function getMonorailHeaders() {
return {
"accept": "application/json",
"content-type": "application/json",
"x-xsrf-token": await getMonorailToken()
}
accept: 'application/json',
'content-type': 'application/json',
'x-xsrf-token': await getMonorailToken(),
};
}
module.exports = async function handleChromiumBugUnfurl(url, message_ts, channel) {
type MonorailIssue = {
statusRef: {
meansOpen: boolean;
};
reporterRef: {
displayName: string;
userId: number;
};
projectName: string;
localId: number;
summary: string;
openedTimestamp: number;
componentRefs?: {
path: string;
}[];
labelRefs?: {
label: string;
}[];
};
type MonorailComments = {
content: string;
}[];
export async function handleChromiumBugUnfurl(url: string, message_ts: string, channel: string) {
const bugIdentifier = parseBugIdentifier(url);
if (!bugIdentifier) return false;
@ -77,18 +97,20 @@ module.exports = async function handleChromiumBugUnfurl(url, message_ts, channel
const commentResponse = fetch('https://bugs.chromium.org/prpc/monorail.Issues/ListComments', {
method: 'POST',
headers,
body: monorailQuery
})
body: monorailQuery,
});
if ((await response).status !== 200) return false;
if ((await commentResponse).status !== 200) return false;
const { issue } = JSON.parse((await (await response).text()).slice(4));
const { comments } = JSON.parse((await (await commentResponse).text()).slice(4));
const { issue }: { issue: MonorailIssue } = JSON.parse((await (await response).text()).slice(4));
const { comments }: { comments: MonorailComments } = JSON.parse(
(await (await commentResponse).text()).slice(4),
);
const unfurl = await fetch('https://slack.com/api/chat.unfurl', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SLACK_TOKEN}`,
Authorization: `Bearer ${process.env.SLACK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
@ -107,22 +129,46 @@ module.exports = async function handleChromiumBugUnfurl(url, message_ts, channel
text: comments[0].content,
footer: `<https://bugs.chromium.org/p/${issue.projectName}|crbug/${issue.projectName}>`,
ts: issue.openedTimestamp * 1000,
fields: [issue.componentRefs && issue.componentRefs.length ? {
title: 'Components',
value: issue.componentRefs.map(ref => `• <https://bugs.chromium.org/p/${issue.projectName}/issues/list?q=component%3A${encodeURIComponent(ref.path)}|\`${ref.path.replace(/>/g, '→')}\`>`).join('\n'),
short: true,
} : null, issue.labelRefs && issue.labelRefs.length ? {
title: 'Labels',
value: issue.labelRefs.map(ref => `• <https://bugs.chromium.org/p/${issue.projectName}/issues/list?q=label%3A${encodeURIComponent(ref.label)}|\`${ref.label}\`>`).join('\n'),
short: true,
} : null, {
title: 'Comments',
value: `${comments.length}`,
short: true,
}].filter(Boolean)
}
}
})
fields: [
issue.componentRefs && issue.componentRefs.length
? {
title: 'Components',
value: issue.componentRefs
.map(
(ref) =>
`• <https://bugs.chromium.org/p/${
issue.projectName
}/issues/list?q=component%3A${encodeURIComponent(
ref.path,
)}|\`${ref.path.replace(/>/g, '→')}\`>`,
)
.join('\n'),
short: true,
}
: null,
issue.labelRefs && issue.labelRefs.length
? {
title: 'Labels',
value: issue.labelRefs
.map(
(ref) =>
`• <https://bugs.chromium.org/p/${
issue.projectName
}/issues/list?q=label%3A${encodeURIComponent(ref.label)}|\`${ref.label}\`>`,
)
.join('\n'),
short: true,
}
: null,
{
title: 'Comments',
value: `${comments.length}`,
short: true,
},
].filter(Boolean),
},
},
}),
});
if (unfurl.status === 200) {
const resp = await unfurl.json();
@ -130,6 +176,6 @@ module.exports = async function handleChromiumBugUnfurl(url, message_ts, channel
console.error('Failed to unfurl', resp);
}
}
return true;
}

Просмотреть файл

@ -1,34 +1,73 @@
const fetch = require('node-fetch').default;
import fetch from 'node-fetch';
async function getGrimoireMetadata() {
type GrimoireMeta = {
token: string;
endpoint: string;
};
async function getGrimoireMetadata(): Promise<GrimoireMeta> {
const response = await fetch('https://source.chromium.org/');
const text = await response.text()
const data = text.split('var GRIMOIRE_CONFIG = \'')[1].split(`'`)[0]
const grimoireConfig = JSON.parse(data.replace(/\\x([\d\w]{2})/gi, (match, grp) => {
return String.fromCharCode(parseInt(grp, 16));
}).replace(/\\n/gi, ''));
const token = grimoireConfig[0];
const endpoint = grimoireConfig[6][1];
const text = await response.text();
const data = text.split("var GRIMOIRE_CONFIG = '")[1].split(`'`)[0];
const grimoireConfig = JSON.parse(
data
.replace(/\\x([\d\w]{2})/gi, (match, grp) => {
return String.fromCharCode(parseInt(grp, 16));
})
.replace(/\\n/gi, ''),
);
const token: string = grimoireConfig[0];
const endpoint: string = grimoireConfig[6][1];
return {
token,
endpoint,
};
}
async function getFileContents(grimoire, parent, project, projectKey, branch, fileName) {
const response = await fetch(`${grimoire.endpoint}/$rpc/devtools.grimoire.FileService/GetContentsStreaming?%24httpHeaders=X-Goog-Api-Key%3A${grimoire.token}%0D%0AX-Goog-Api-Client%3Agrpc-web%2F1.0.0%20grimoire%2F1.0.0%2Buyti2atju1zl.be6of0mawakc.code.codebrowser-frontend-oss-20210330.07_p0%0D%0AX-Server-Timeout%3A60%0D%0AContent-Type%3Aapplication%2Fjson%2Bprotobuf%0D%0AX-User-Agent%3Agrpc-web-javascript%2F0.1%0D%0A`, {
"headers": {
"origin": "https://source.chromium.org",
"content-type": "application/x-www-form-urlencoded;charset=UTF-8",
type DeepArrayOfUnknowns = Array<unknown | string | DeepArrayOfUnknowns>;
async function getFileContents(
grimoire: GrimoireMeta,
parent: string,
project: string,
projectKey: string,
branch: string,
fileName: string,
) {
const response = await fetch(
`${grimoire.endpoint}/$rpc/devtools.grimoire.FileService/GetContentsStreaming?%24httpHeaders=X-Goog-Api-Key%3A${grimoire.token}%0D%0AX-Goog-Api-Client%3Agrpc-web%2F1.0.0%20grimoire%2F1.0.0%2Buyti2atju1zl.be6of0mawakc.code.codebrowser-frontend-oss-20210330.07_p0%0D%0AX-Server-Timeout%3A60%0D%0AContent-Type%3Aapplication%2Fjson%2Bprotobuf%0D%0AX-User-Agent%3Agrpc-web-javascript%2F0.1%0D%0A`,
{
headers: {
origin: 'https://source.chromium.org',
'content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
},
body: JSON.stringify([
[
[[null, `${project}/${projectKey}`, null, null, parent], null, branch],
fileName,
null,
null,
null,
null,
[],
],
true,
null,
true,
null,
null,
null,
null,
true,
]),
method: 'POST',
},
"body": JSON.stringify([[[[null,`${project}/${projectKey}`,null,null,parent],null,branch],fileName,null,null,null,null,[]],true,null,true,null,null,null,null,true]),
"method": "POST"
});
const data = JSON.parse(await response.text());
);
const data: DeepArrayOfUnknowns = JSON.parse(await response.text());
const best = { str: '', n: 0 };
function search(arr) {
function search(arr: DeepArrayOfUnknowns) {
for (const item of arr) {
if (typeof item === 'string') {
const n = item.split('\n').length;
@ -37,11 +76,9 @@ async function getFileContents(grimoire, parent, project, projectKey, branch, fi
best.n = n;
}
} else if (Array.isArray(item)) {
const result = search(item);
if (result) return result;
search(item);
}
}
return null;
}
search(data);
@ -50,14 +87,14 @@ async function getFileContents(grimoire, parent, project, projectKey, branch, fi
const MAX_SLACK_MSG_LENGTH = 7000;
function maybeTruncate(longContents) {
if (longContents.length <= MAX_SLACK_MSG_LENGTH) return longContents
function maybeTruncate(longContents: string) {
if (longContents.length <= MAX_SLACK_MSG_LENGTH) return longContents;
return longContents.slice(0, MAX_SLACK_MSG_LENGTH) + '...';
}
const INDENT_BREAKPOINT = 2;
function indentLength(line) {
function indentLength(line: string): number {
let i = 0;
while (line[i] === ' ') {
i++;
@ -65,7 +102,7 @@ function indentLength(line) {
return i;
}
function removeOverIndent(contents) {
function removeOverIndent(contents: string): string {
const lines = contents.split('\n');
if (!lines.length) return contents;
@ -76,18 +113,20 @@ function removeOverIndent(contents) {
if (minIndent - INDENT_BREAKPOINT <= 0) return contents;
return lines.map(l => l.slice(minIndent)).join('\n');
return lines.map((l) => l.slice(minIndent)).join('\n');
}
module.exports = async function handleChromiumSourceUnfurl(url, message_ts, channel) {
export async function handleChromiumSourceUnfurl(url: string, message_ts: string, channel: string) {
const parsed = new URL(url);
if (parsed.hostname !== 'source.chromium.org') return false;
const match = /^https:\/\/source\.chromium\.org\/([a-z0-9]+)\/([a-z0-9]+)\/([a-z0-9]+)\/\+\/([a-z0-9]+):([^;]+)(?:;l=([0-9]+(?:-[0-9]+)?))?/.exec(url);
const match = /^https:\/\/source\.chromium\.org\/([a-z0-9]+)\/([a-z0-9]+)\/([a-z0-9]+)\/\+\/([a-z0-9]+):([^;]+)(?:;l=([0-9]+(?:-[0-9]+)?))?/.exec(
url,
);
if (!match) return false;
const [,parent, project, projectKey, branch, fileName, lineRange] = match;
const [, parent, project, projectKey, branch, fileName, lineRange] = match;
const grimoire = await getGrimoireMetadata();
let contents = await getFileContents(grimoire, parent, project, projectKey, branch, fileName);
if (lineRange) {
@ -95,7 +134,10 @@ module.exports = async function handleChromiumSourceUnfurl(url, message_ts, chan
if (!isNaN(start)) {
let end = parseInt(lineRange.split('-')[1], 10);
if (isNaN(end)) end = start;
contents = contents.split('\n').slice(start - 1, end).join('\n');
contents = contents
.split('\n')
.slice(start - 1, end)
.join('\n');
contents = removeOverIndent(contents);
}
}
@ -103,7 +145,7 @@ module.exports = async function handleChromiumSourceUnfurl(url, message_ts, chan
const unfurl = await fetch('https://slack.com/api/chat.unfurl', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SLACK_TOKEN}`,
Authorization: `Bearer ${process.env.SLACK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
@ -119,9 +161,9 @@ module.exports = async function handleChromiumSourceUnfurl(url, message_ts, chan
text: `\`\`\`\n${maybeTruncate(contents)}\n\`\`\``,
footer: `<https://source.chromium.org/${parent}/${project}/${projectKey}/+/${branch}|${project}/${projectKey}>`,
mrkdwn_in: ['text'],
}
}
})
},
},
}),
});
if (unfurl.status === 200) {
const resp = await unfurl.json();
@ -129,6 +171,6 @@ module.exports = async function handleChromiumSourceUnfurl(url, message_ts, chan
console.error('Failed to unfurl', resp);
}
}
return true;
}

Просмотреть файл

@ -1,8 +1,8 @@
const express = require('express');
import * as express from 'express';
const handleChromiumReviewUnfurl = require('./chromium-review');
const handleChromiumBugUnfurl = require('./crbug');
const handleChromiumSourceUnfurl = require('./crsource');
import { handleChromiumReviewUnfurl } from './chromium-review';
import { handleChromiumBugUnfurl } from './crbug';
import { handleChromiumSourceUnfurl } from './crsource';
const app = express();
app.use(require('body-parser').json());
@ -12,8 +12,8 @@ app.post('/slack-event', (req, res) => {
if (type === 'url_verification') {
return res.send(req.body.challenge);
} else if (type === 'event_callback') {
const { team_id, event, token } = req.body;
handleChromiumLink(team_id, event, token).catch(console.error);
const { team_id, event } = req.body;
handleChromiumLink(team_id, event).catch(console.error);
res.send('');
} else {
res.send('');
@ -24,7 +24,14 @@ app.listen(process.env.PORT || 8080, () => {
console.log('App listening');
});
async function handleChromiumLink(teamId, event, token) {
type SlackEvent = {
message_ts: string;
type: string;
channel: string;
links: { url: string }[];
};
async function handleChromiumLink(teamId: string, event: SlackEvent) {
if (event.type !== 'link_shared') return;
const { message_ts, channel, links } = event;

22
tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2020",
"lib": [
"es2017",
"dom"
],
"sourceMap": true,
"strict": true,
"outDir": "dist",
"types": [
"node"
],
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"incremental": true
},
"include": [
"src"
]
}

118
yarn.lock
Просмотреть файл

@ -2,6 +2,76 @@
# yarn lockfile v1
"@types/body-parser@*":
version "1.19.0"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==
dependencies:
"@types/connect" "*"
"@types/node" "*"
"@types/connect@*":
version "3.4.34"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901"
integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==
dependencies:
"@types/node" "*"
"@types/express-serve-static-core@^4.17.18":
version "4.17.19"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d"
integrity sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA==
dependencies:
"@types/node" "*"
"@types/qs" "*"
"@types/range-parser" "*"
"@types/express@^4.17.11":
version "4.17.11"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545"
integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "^4.17.18"
"@types/qs" "*"
"@types/serve-static" "*"
"@types/mime@^1":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
"@types/node-fetch@^2.5.9":
version "2.5.9"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.9.tgz#c04a12115aa436f189e39579272b305e477621b4"
integrity sha512-6cUyqLK+JBsATAqNQqk10jURoBFrzfRCDh4kaYxg8ivKhRPIpyBgAvuY7zM/3E4AwsYJSh5HCHBCJRM4DsCTaQ==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*", "@types/node@^14.14.37":
version "14.14.37"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e"
integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==
"@types/qs@*":
version "6.9.6"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1"
integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==
"@types/range-parser@*":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/serve-static@*":
version "1.13.9"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e"
integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==
dependencies:
"@types/mime" "^1"
"@types/node" "*"
accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@ -15,6 +85,11 @@ array-flatten@1.1.1:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
body-parser@1.19.0, body-parser@^1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
@ -41,6 +116,13 @@ cockatiel@^2.0.1:
resolved "https://registry.yarnpkg.com/cockatiel/-/cockatiel-2.0.1.tgz#2b59587d54d53d33ed49b601cfaab97cad9cc6e7"
integrity sha512-fAw5ToPGZSnoYl5NpW2zKnY/d4gAUeP4GuGnxm7W23ayTtZipwPrIv7h3z8pq0NMDISuCEkKGsXCk3bKkzb13A==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
@ -70,6 +152,11 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@ -149,6 +236,15 @@ finalhandler@~1.1.2:
statuses "~1.5.0"
unpipe "~1.0.0"
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@ -223,6 +319,18 @@ mime-db@1.45.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
mime-db@1.47.0:
version "1.47.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c"
integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==
mime-types@^2.1.12:
version "2.1.30"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d"
integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==
dependencies:
mime-db "1.47.0"
mime-types@~2.1.24:
version "2.1.28"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd"
@ -272,6 +380,11 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
prettier@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
proxy-addr@~2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@ -362,6 +475,11 @@ type-is@~1.6.17, type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
typescript@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"