INIT: move uptime migration helper to own repository

This commit is contained in:
Alexander Hesse 2023-01-25 17:35:52 +01:00
commit 4c567e247e
9 changed files with 581 additions and 0 deletions

8
.env.sample Normal file
View File

@ -0,0 +1,8 @@
# sample configuration for uptimeRobot to kuma migration
# get from UptimeRobotApi
UPTIME_ROBOT_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxx"
KUMA_URL="http://localhost:3001"
KUMA_USERNAME="admin"
KUMA_PASSWORD="password2"

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
dist/
node_modules/
.env

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v18.13.0

53
README.md Normal file
View File

@ -0,0 +1,53 @@
# UptimeRobot to Kuma migration
We migrated from UptimeRobot to UptimeKuma, but there was no fast way to achieve this, so
we wrote our own small migration helper.
## Getting started
Copy the `.env.sample` as `.env` and enter your UptimeRobot API key.
For testing, you can simply start UptimeKuma via Docker:
```shell
docker run --rm -p 3001:3001 --name uptime-kuma louislam/uptime-kuma:1
```
Ensure you finished the initial setup (simply open [localhost:3001](localhost:3001) in your browser) and
updated the credentials in the `.env` file.
To start the migration run:
```bash
# copy all your UptimeRobot monitors to your Kuma installation
yarn copy-monitors
# disable all UptimeRobot monitors
yarn disable-uptime-robot
# delete all your monitors from UptimeRobot
# DANGER!!! This is can not be undone
yarn delete-uptime-robot
```
## Production Migration
**Important Node:** This migration helper was writen specially for our use-case. So not all UptimeRobot
scenarios and features are implemented. So no garantie this will work 100% for you.
**Pro Tipp:** Before migrating, create a default notification that will get used as default.
## Architecture
### Fetching from UptimeRobot
This part was quite easy, because UptimeRobot got a good REST-API to fetch all monitors from
### Creating the monitors in Kuma
This was the hard part. Currently, Kuma does not provide any form of API. In the first version of this migration
helper, I tried to hook into the websocket connection of the UI and create monitors that way. This was really instabile
and resulted in many non-deterministic errors.
For this reason I switched to Playwright. This allows us the remote-control a browser, which will create
the monitors via the Kuma-UI.

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "kuma-migration",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc",
"copy-monitors": "yarn build && node dist/index.js copy-monitors",
"disable-uptime-robot": "yarn build && node dist/index.js disable-uptime-robot",
"delete-uptime-robot": "yarn build && node dist/index.js delete-uptime-robot",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^18.11.18",
"playwright": "^1.29.2",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"engines": {
"node": "18.13.0"
},
"dependencies": {
"axios": "^1.2.3",
"dotenv": "^16.0.3"
}
}

238
src/index.ts Normal file
View File

@ -0,0 +1,238 @@
import dotenv from "dotenv"
import {
UptimeRobotMonitor,
UptimeRobotGetMonitorResponse,
UptimeRobotEditMonitorResponse,
UptimeRobotDeleteMonitorResponse
} from "./types";
import {chromium, Page} from 'playwright';
import axios from "axios";
dotenv.config()
const UPTIME_ROBOT_API_KEY = process.env.UPTIME_ROBOT_API_KEY
const KUMA_URL = process.env.KUMA_URL
const KUMA_USERNAME = process.env.KUMA_USERNAME
const KUMA_PASSWORD = process.env.KUMA_PASSWORD
if (
!UPTIME_ROBOT_API_KEY ||
!KUMA_URL ||
!KUMA_USERNAME ||
!KUMA_PASSWORD
) {
console.warn('Required env variables are missing!')
console.warn('Copy ".env.sample" as ".env"')
process.exit(1)
}
const UPTIME_ROBOT_GET_MONITOR_API_PATH = "https://api.uptimerobot.com/v2/getMonitors?api_key="
const loadMonitorsFromUptimeRobot = async () => {
const monitors: UptimeRobotMonitor[] = []
let monitorCount = 0
let currentOffset = 0
let totalMonitors = 9999999
while (monitorCount < totalMonitors) {
const response = await axios.post(
UPTIME_ROBOT_GET_MONITOR_API_PATH + UPTIME_ROBOT_API_KEY,
`offset=${currentOffset}`
)
const jsonResponse = response.data as UptimeRobotGetMonitorResponse
totalMonitors = jsonResponse.pagination.total
currentOffset += 50
monitorCount += jsonResponse.monitors.length
monitors.push(...jsonResponse.monitors)
}
return monitors
}
const UPTIME_ROBOT_DISABLE_MONITOR_API_PATH = "https://api.uptimerobot.com/v2/editMonitor"
const disableUptimeRobotMonitor = async (monitor: UptimeRobotMonitor) => {
const response = await axios.post(
UPTIME_ROBOT_DISABLE_MONITOR_API_PATH,
new URLSearchParams({
'api_key': UPTIME_ROBOT_API_KEY,
'format': 'json',
'id': `${monitor.id}`,
'status': '0'
}),
{
headers: {
'Cache-Control': 'no-cache'
}
}
);
const responseJson = response.data as UptimeRobotEditMonitorResponse
if (responseJson?.stat === "fail") {
console.error(`Failed to edit monitor '${monitor.friendly_name}' due to '${responseJson.error?.type}'`)
process.exit(1)
} else {
console.log(`Disabled monitor "${monitor.friendly_name}"`)
}
}
const UPTIME_ROBOT_DELETE_MONITOR_API_PATH = "https://api.uptimerobot.com/v2/deleteMonitor"
const deleteUptimeRobotMonitor = async (monitor: UptimeRobotMonitor) => {
const response = await axios.post(
UPTIME_ROBOT_DELETE_MONITOR_API_PATH,
new URLSearchParams({
'api_key': UPTIME_ROBOT_API_KEY,
'format': 'json',
'id': `${monitor.id}`,
}),
{
headers: {
'Cache-Control': 'no-cache'
}
}
)
const responseJson = response.data as UptimeRobotDeleteMonitorResponse
if (responseJson?.stat === "fail") {
console.error(`Failed to delete monitor '${monitor.friendly_name}' due to '${responseJson.error?.type}'`)
process.exit(1)
} else {
console.log(`Deleted monitor "${monitor.friendly_name}"`)
}
}
const startPlaywright = async () => chromium.launch({headless: false})
const ensureLoggedIn = async (page: Page) => {
const loginButton = await page.getByText("Login")
if (loginButton == null) {
console.log('already logged in ...')
return
}
await page.getByPlaceholder('Username').type(KUMA_USERNAME)
await page.getByPlaceholder('Password').type(KUMA_PASSWORD)
await loginButton.click()
await page.waitForLoadState("domcontentloaded")
}
const createMonitor = async (page: Page, monitor: UptimeRobotMonitor) => {
await page.waitForLoadState('domcontentloaded')
await page.waitForLoadState('networkidle')
await page.getByText('Add New Monitor').first().click()
const createButton = await page.$('a:text(" Add New Monitor")')
await createButton?.click()
await page.waitForTimeout(100)
await page.getByLabel('Friendly Name').first().fill(monitor.friendly_name)
switch (monitor.type) {
case 1:
await createMonitorHTTP(page, monitor)
break
case 2:
await createMonitorKeyword(page, monitor)
break
case 4:
await createMonitorPort(page, monitor)
break
default:
console.log(`Monitor type ${monitor.type} of ${monitor.friendly_name} is not supported`)
break
}
await page.getByLabel('Heartbeat Interval (Check every').fill(String(monitor.interval))
const saveButton = await page.$('button#monitor-submit-btn')
await saveButton?.click()
await page.waitForLoadState("domcontentloaded")
await page.waitForLoadState("networkidle")
await page.waitForTimeout(400)
console.log('Created Monitor ', monitor.friendly_name)
}
const createMonitorHTTP = async (page: Page, monitor: UptimeRobotMonitor) => {
await page.getByLabel("Monitor Type").selectOption("HTTP(s)")
await page.getByLabel('URL').fill(monitor.url)
}
const createMonitorKeyword = async (page: Page, monitor: UptimeRobotMonitor) => {
await page.getByLabel("Monitor Type").selectOption("HTTP(s) - Keyword")
await page.getByLabel('URL').fill(monitor.url)
await page.getByLabel('Keyword').fill(monitor.keyword_value)
}
const createMonitorPort = async (page: Page, monitor: UptimeRobotMonitor) => {
await page.getByLabel("Monitor Type").selectOption("TCP Port")
await page.getByLabel('Hostname').fill(monitor.url)
await page.getByLabel('Port').fill(String(monitor.port))
}
const copyMonitors = async () => {
const monitors = await loadMonitorsFromUptimeRobot()
console.log(`Found ${monitors.length} monitors`)
const browser = await startPlaywright()
const page = await browser.newPage()
await page.goto(KUMA_URL)
await ensureLoggedIn(page)
for (let monitor of monitors) {
await createMonitor(page, monitor)
}
await browser.close()
}
const disableUptimeRobot = async () => {
const monitors = await loadMonitorsFromUptimeRobot()
console.log(`Found ${monitors.length} monitors`)
for (let monitor of monitors) {
await disableUptimeRobotMonitor(monitor)
}
}
const deleteUptimeRobotMonitors = async () => {
const monitors = await loadMonitorsFromUptimeRobot()
console.log(`Found ${monitors.length} monitors`)
for (let monitor of monitors) {
await deleteUptimeRobotMonitor(monitor)
}
}
const run = async () => {
const task = process.argv[2]
switch (task) {
case 'copy-monitors':
await copyMonitors()
break
case 'disable-uptime-robot':
await disableUptimeRobot()
break
case 'delete-uptime-robot':
await deleteUptimeRobotMonitors()
break
default:
console.error(`Task '${task} is not supported ...'`)
}
}
run()

43
src/types.ts Normal file
View File

@ -0,0 +1,43 @@
export interface UptimeRobotMonitor {
id: number;
friendly_name: string;
url: string;
type: number;
sub_type: string;
keyword_type: null;
keyword_case_type: number;
keyword_value: string;
port: string;
interval: number;
timeout: number;
status: number;
create_datetime: number;
}
export interface UptimeRobotPagination {
offset: number;
limit: number;
total: number;
}
export interface UptimeRobotGetMonitorResponse {
stat: 'OK' | 'fail';
pagination: UptimeRobotPagination;
monitors: UptimeRobotMonitor[];
}
export interface UptimeRobotEditMonitorResponse {
stat: 'OK' | 'fail';
error?: {
type: 'not_authorized',
message: string
}
}
export interface UptimeRobotDeleteMonitorResponse {
stat: 'OK' | 'fail';
error?: {
type: 'not_authorized',
message: string
}
}

16
tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "NodeNext",
"target": "ESNext",
"lib": ["ESNext", "DOM"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"noImplicitAny": true,
"sourceMap": false,
"outDir": "dist",
},
}

191
yarn.lock Normal file
View File

@ -0,0 +1,191 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@0.3.9":
version "0.3.9"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@tsconfig/node10@^1.0.7":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
"@tsconfig/node12@^1.0.7":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
"@tsconfig/node14@^1.0.0":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
"@tsconfig/node16@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e"
integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==
"@types/node@^18.11.18":
version "18.11.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f"
integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==
acorn-walk@^8.1.1:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@^8.4.1:
version "8.8.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
axios@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.3.tgz#31a3d824c0ebf754a004b585e5f04a5f87e6c4ff"
integrity sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
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"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
dotenv@^16.0.3:
version "16.0.3"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07"
integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
playwright-core@1.29.2:
version "1.29.2"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.29.2.tgz#2e8347e7e8522409f22b244e600e703b64022406"
integrity sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA==
playwright@^1.29.2:
version "1.29.2"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.29.2.tgz#d6a0a3e8e44f023f7956ed19ffa8af915a042769"
integrity sha512-hKBYJUtdmYzcjdhYDkP9WGtORwwZBBKAW8+Lz7sr0ZMxtJr04ASXVzH5eBWtDkdb0c3LLFsehfPBTRfvlfKJOA==
dependencies:
playwright-core "1.29.2"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
ts-node@^10.9.1:
version "10.9.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
dependencies:
"@cspotcode/source-map-support" "^0.8.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
"@tsconfig/node16" "^1.0.2"
acorn "^8.4.1"
acorn-walk "^8.1.1"
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
typescript@^4.9.4:
version "4.9.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78"
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==