From a1af82501c960c3ba859d38188280ba6d6cd77fa Mon Sep 17 00:00:00 2001 From: Nicco Date: Thu, 20 Oct 2022 15:18:57 +0200 Subject: [PATCH] 1.3.0 (#6) --- .github/workflows/build.yml | 26 ++ .github/workflows/docker.yml | 42 ++- CHANGELOG.md | 15 + Dockerfile | 20 +- README.md | 22 +- package.json | 23 +- pnpm-lock.yaml | 631 ++++------------------------------- src/cache.ts | 1 + src/cloudflare.ts | 126 +++++++ src/config.ts | 50 +++ src/index.ts | 122 +------ src/ip.ts | 22 ++ src/logger.ts | 12 + src/runner.ts | 18 + tsconfig.json | 67 ++-- 15 files changed, 429 insertions(+), 768 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 src/cache.ts create mode 100644 src/cloudflare.ts create mode 100644 src/config.ts create mode 100644 src/ip.ts create mode 100644 src/logger.ts create mode 100644 src/runner.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..2a7a8b6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: Build Docker image + +on: + pull_request: + push: + +concurrency: + group: build + cancel-in-progress: true + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + install: true + + - name: Build and push + uses: docker/build-push-action@v3 + with: + platforms: linux/amd64,linux/arm64 + push: false diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index aa3f909..67b35e6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,39 +1,47 @@ -name: ci +name: Publish Docker image on: - workflow_dispatch: - push: - tags: - - "v*.*.*" + release: + types: [published] jobs: docker: runs-on: ubuntu-latest steps: - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 + with: + install: true + - name: Docker Labels id: meta - uses: crazy-max/ghaction-docker-meta@v2 + uses: docker/metadata-action@v4 with: - images: cupcakearmy/ddns-cloudflare + images: | + cupcakearmy/ddns-cloudflare + ghcr.io/${{ github.repository }} tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - - name: Login to DockerHub - uses: docker/login-action@v1 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/CHANGELOG.md b/CHANGELOG.md index f78a533..fcf76c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.0] - 2022-10-20 + +### Removed + +- Login with username and password as it's considered deprecated. + +### Added + +- `LOG_LEVEL` env variable. +- Config parsing and validation. + +### Changed + +- Removed Cloudflare SDK due to outdated and bloated package. + ## [1.2.1] - 2022-05-14 ### Added diff --git a/Dockerfile b/Dockerfile index 9edbf69..75c0047 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,19 @@ -FROM node:16-alpine as builder - +FROM node:18-alpine as base WORKDIR /app +RUN npm -g i pnpm + +FROM base as builder ADD ./package.json ./pnpm-lock.yaml ./ -RUN npm exec pnpm install --frozen-lockfile - +RUN pnpm install --frozen-lockfile ADD . . -RUN npm exec pnpm run build - -FROM node:16-alpine - -WORKDIR /app +RUN pnpm run build +FROM base ADD ./package.json ./pnpm-lock.yaml ./ -RUN npm exec pnpm install --frozen-lockfile --prod +RUN pnpm install --frozen-lockfile --prod COPY --from=builder /app/dist/ /app/dist/ STOPSIGNAL SIGTERM -CMD ["node", "."] +CMD ["pnpm", "start"] diff --git a/README.md b/README.md index db0a0d3..77f1d65 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,6 @@ Click create token. You can then use the Edit DNS Zone template. Give it a name. ```bash TOKEN=mytoken -# Or (Legacy mode) -EMAIL=my@mail.com -KEY=Global_API_Key ZONE=example.org DNS_RECORD=some.example.org @@ -48,16 +45,15 @@ docker-compose up -d ## ENV Reference -| Env | Description | Default | -| ------------ | ------------------------------------------------------ | ---------------------- | -| `EMAIL` | [Legacy] E-Mail of the Cloudflare account. | | -| `KEY` | [Legacy] Key for authentication. | | -| `TOKEN` | API Token that can be used instead of `EMAIL` & `KEY`. | | -| `ZONE` | Cloudflare zone where your domain is. | | -| `DNS_RECORD` | The actual DNS record that should be updated. | | -| `PROXIED` | Whether the record is proxied by CloudFlare or not. | | -| `CRON` | Frequency of updates. | \*/5 \* \* \* \* | -| `RESOLVER` | The endpoint used to determine your public ip. | https://api.ipify.org/ | +| Env | Default | Description | +| ------------ | ------------------------ | ----------------------------------------------------------------------------------------------------------- | +| `TOKEN` | | API Token. | +| `ZONE` | | Cloudflare zone where your domain is. | +| `DNS_RECORD` | | The actual DNS record that should be updated. | +| `PROXIED` | `true` | Whether the record is proxied by CloudFlare or not. | +| `CRON` | `*/5 * * * *` | Frequency of updates. | +| `RESOLVER` | `https://api.ipify.org/` | The endpoint used to determine your public ip. | +| `LOG_LEVEL` | `info` | Log level to run at. See [winston](https://github.com/winstonjs/winston#logging-levels) for possible values | ## Customize diff --git a/package.json b/package.json index 7ab2e10..c4c8be1 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,22 @@ { + "version": "1.3.0", "license": "MIT", + "type": "module", "main": "dist", "scripts": { - "dev": "tsnd src/index.ts", - "build": "tsc" + "dev": "tsc -w", + "build": "tsc", + "start": "node ." }, "dependencies": { - "axios": "^0.27.2", - "cloudflare": "^2.9.1", - "cron": "^1.8.2", - "dotenv": "^16.0.1", - "winston": "^3.7.2" + "axios": "^1.1.3", + "dotenv": "^16.0.3", + "node-cron": "^3.0.2", + "winston": "^3.8.2" }, "devDependencies": { - "@types/cloudflare": "^2.7.8", - "@types/cron": "^1.7.3", - "ts-node-dev": "^1.1.8", - "typescript": "^4.6.4" + "@types/node": "18", + "@types/node-cron": "^3.0.4", + "typescript": "^4.8.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b4acfc..6089d42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,28 +1,24 @@ -lockfileVersion: 5.3 +lockfileVersion: 5.4 specifiers: - '@types/cloudflare': ^2.7.8 - '@types/cron': ^1.7.3 - axios: ^0.27.2 - cloudflare: ^2.9.1 - cron: ^1.8.2 - dotenv: ^16.0.1 - ts-node-dev: ^1.1.8 - typescript: ^4.6.4 - winston: ^3.7.2 + '@types/node': '18' + '@types/node-cron': ^3.0.4 + axios: ^1.1.3 + dotenv: ^16.0.3 + node-cron: ^3.0.2 + typescript: ^4.8.4 + winston: ^3.8.2 dependencies: - axios: 0.27.2 - cloudflare: 2.9.1 - cron: 1.8.2 - dotenv: 16.0.1 - winston: 3.7.2 + axios: 1.1.3 + dotenv: 16.0.3 + node-cron: 3.0.2 + winston: 3.8.2 devDependencies: - '@types/cloudflare': 2.7.8 - '@types/cron': 1.7.3 - ts-node-dev: 1.1.8_typescript@4.6.4 - typescript: 4.6.4 + '@types/node': 18.11.2 + '@types/node-cron': 3.0.4 + typescript: 4.8.4 packages: @@ -39,143 +35,32 @@ packages: kuler: 2.0.0 dev: false - /@types/cloudflare/2.7.8: - resolution: {integrity: sha512-Ww0+DV8Um7Rtr8DI4ZTwHWylJ73HyOSBdiqXz6JWt1jcXotL/3vWEHmrtx8qBWQOS5Epusp7J86ocYhaSur6ag==} + /@types/node-cron/3.0.4: + resolution: {integrity: sha512-A2H+uz5ry4hohYjRe5mQSE/8Dx/HGw4WZ728JxhKUZ7z8CMvRuG2tpbzGHRGQCuQzz5aCNB1iXzPZYHd4BPHvw==} dev: true - /@types/cron/1.7.3: - resolution: {integrity: sha512-iPmUXyIJG1Js+ldPYhOQcYU3kCAQ2FWrSkm1FJPoii2eYSn6wEW6onPukNTT0bfiflexNSRPl6KWmAIqS+36YA==} - dependencies: - '@types/node': 17.0.33 - moment: 2.29.3 + /@types/node/18.11.2: + resolution: {integrity: sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==} dev: true - /@types/keyv/3.1.4: - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - dependencies: - '@types/node': 17.0.33 - dev: false - - /@types/node/17.0.33: - resolution: {integrity: sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ==} - - /@types/responselike/1.0.0: - resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} - dependencies: - '@types/node': 17.0.33 - dev: false - - /@types/strip-bom/3.0.0: - resolution: {integrity: sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=} - dev: true - - /@types/strip-json-comments/0.0.30: - resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} - dev: true - - /agent-base/6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - - /anymatch/3.1.2: - resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} - engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: true - - /arg/4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true - - /async/3.2.3: - resolution: {integrity: sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==} + /async/3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: false /asynckit/0.4.0: - resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: false - /autocreate/1.2.0: - resolution: {integrity: sha1-UiFnmSxBcsFUeeX4jzSGpFKkDLo=} - dev: false - - /axios/0.27.2: - resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + /axios/1.1.3: + resolution: {integrity: sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==} dependencies: - follow-redirects: 1.15.0 + follow-redirects: 1.15.2 form-data: 4.0.0 + proxy-from-env: 1.1.0 transitivePeerDependencies: - debug dev: false - /balanced-match/1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - - /binary-extensions/2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} - dev: true - - /brace-expansion/1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - - /braces/3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - - /buffer-from/1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true - - /capture-stack-trace/1.0.1: - resolution: {integrity: sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==} - engines: {node: '>=0.10.0'} - dev: false - - /chokidar/3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.2 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /cloudflare/2.9.1: - resolution: {integrity: sha512-x8yXPPoloy7xQ9GCKnsvQ3U1nwvcLndA2B3nxwSjIWxgLTUJOyakeEDsrqxZO8Dr6FkGdaXwy554fQVMpOabiw==} - dependencies: - autocreate: 1.2.0 - es-class: 2.1.1 - got: 6.7.1 - https-proxy-agent: 5.0.1 - object-assign: 4.1.1 - should-proxy: 1.0.4 - url-pattern: 1.0.3 - transitivePeerDependencies: - - supports-color - dev: false - /color-convert/1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -183,7 +68,7 @@ packages: dev: false /color-name/1.1.3: - resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} dev: false /color-name/1.1.4: @@ -218,89 +103,30 @@ packages: delayed-stream: 1.0.0 dev: false - /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} - dev: true - - /create-error-class/3.0.2: - resolution: {integrity: sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=} - engines: {node: '>=0.10.0'} - dependencies: - capture-stack-trace: 1.0.1 - dev: false - - /create-require/1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true - - /cron/1.8.2: - resolution: {integrity: sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==} - dependencies: - moment-timezone: 0.5.34 - dev: false - - /debug/4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: false - /delayed-stream/1.0.0: - resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} dev: false - /diff/4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dev: true - - /dotenv/16.0.1: - resolution: {integrity: sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==} + /dotenv/16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} dev: false - /duplexer3/0.1.4: - resolution: {integrity: sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=} - dev: false - - /dynamic-dedupe/0.3.0: - resolution: {integrity: sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=} - dependencies: - xtend: 4.0.2 - dev: true - /enabled/2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} dev: false - /es-class/2.1.1: - resolution: {integrity: sha1-bsIkO1oeNYHAt+7O4BMMnA1vsrc=} - dev: false - /fecha/4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} dev: false - /fill-range/7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - /fn.name/1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} dev: false - /follow-redirects/1.15.0: - resolution: {integrity: sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==} + /follow-redirects/1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -318,140 +144,14 @@ packages: mime-types: 2.1.35 dev: false - /fs.realpath/1.0.0: - resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} - dev: true - - /fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /function-bind/1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: true - - /get-stream/3.0.0: - resolution: {integrity: sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=} - engines: {node: '>=4'} - dev: false - - /glob-parent/5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob/7.2.2: - resolution: {integrity: sha512-NzDgHDiJwKYByLrL5lONmQFpK/2G78SMMfo+E9CuGlX4IkvfKDsiQSNPwAYxEy+e6p7ZQ3uslSLlwlJcqezBmQ==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /got/6.7.1: - resolution: {integrity: sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=} - engines: {node: '>=4'} - dependencies: - '@types/keyv': 3.1.4 - '@types/responselike': 1.0.0 - create-error-class: 3.0.2 - duplexer3: 0.1.4 - get-stream: 3.0.0 - is-redirect: 1.0.0 - is-retry-allowed: 1.2.0 - is-stream: 1.1.0 - lowercase-keys: 1.0.1 - safe-buffer: 5.2.1 - timed-out: 4.0.1 - unzip-response: 2.0.1 - url-parse-lax: 1.0.0 - dev: false - - /has/1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - dev: true - - /https-proxy-agent/5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - - /inflight/1.0.6: - resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true - /inherits/2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: false /is-arrayish/0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} dev: false - /is-binary-path/2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - dev: true - - /is-core-module/2.9.0: - resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==} - dependencies: - has: 1.0.3 - dev: true - - /is-extglob/2.1.1: - resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} - engines: {node: '>=0.10.0'} - dev: true - - /is-glob/4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - - /is-number/7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true - - /is-redirect/1.0.0: - resolution: {integrity: sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=} - engines: {node: '>=0.10.0'} - dev: false - - /is-retry-allowed/1.2.0: - resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} - engines: {node: '>=0.10.0'} - dev: false - - /is-stream/1.1.0: - resolution: {integrity: sha1-EtSj3U5o4Lec6428hBc66A2RykQ=} - engines: {node: '>=0.10.0'} - dev: false - /is-stream/2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -461,25 +161,16 @@ packages: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} dev: false - /logform/2.4.0: - resolution: {integrity: sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==} + /logform/2.4.2: + resolution: {integrity: sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==} dependencies: '@colors/colors': 1.5.0 fecha: 4.2.3 ms: 2.1.3 - safe-stable-stringify: 2.3.1 + safe-stable-stringify: 2.4.1 triple-beam: 1.3.0 dev: false - /lowercase-keys/1.0.1: - resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} - engines: {node: '>=0.10.0'} - dev: false - - /make-error/1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true - /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -492,54 +183,16 @@ packages: mime-db: 1.52.0 dev: false - /minimatch/3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: true - - /minimist/1.2.6: - resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} - dev: true - - /mkdirp/1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - dev: true - - /moment-timezone/0.5.34: - resolution: {integrity: sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==} - dependencies: - moment: 2.29.3 - dev: false - - /moment/2.29.3: - resolution: {integrity: sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==} - - /ms/2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: false - /ms/2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: false - /normalize-path/3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: true - - /object-assign/4.1.1: - resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} - engines: {node: '>=0.10.0'} - dev: false - - /once/1.4.0: - resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} + /node-cron/3.0.2: + resolution: {integrity: sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==} + engines: {node: '>=6.0.0'} dependencies: - wrappy: 1.0.2 - dev: true + uuid: 8.3.2 + dev: false /one-time/1.0.0: resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} @@ -547,23 +200,8 @@ packages: fn.name: 1.1.0 dev: false - /path-is-absolute/1.0.1: - resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} - engines: {node: '>=0.10.0'} - dev: true - - /path-parse/1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true - - /picomatch/2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: true - - /prepend-http/1.0.4: - resolution: {integrity: sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=} - engines: {node: '>=0.10.0'} + /proxy-from-env/1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false /readable-stream/3.6.0: @@ -575,62 +213,23 @@ packages: util-deprecate: 1.0.2 dev: false - /readdirp/3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - dev: true - - /resolve/1.22.0: - resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} - hasBin: true - dependencies: - is-core-module: 2.9.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - - /rimraf/2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - hasBin: true - dependencies: - glob: 7.2.2 - dev: true - /safe-buffer/5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: false - /safe-stable-stringify/2.3.1: - resolution: {integrity: sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==} + /safe-stable-stringify/2.4.1: + resolution: {integrity: sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==} engines: {node: '>=10'} dev: false - /should-proxy/1.0.4: - resolution: {integrity: sha1-yAWlAav2lTlgBjSAnmL78ji6NeQ=} - dev: false - /simple-swizzle/0.2.2: - resolution: {integrity: sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=} + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} dependencies: is-arrayish: 0.3.2 dev: false - /source-map-support/0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - dev: true - - /source-map/0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - dev: true - /stack-trace/0.0.10: - resolution: {integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=} + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} dev: false /string_decoder/1.3.0: @@ -639,157 +238,51 @@ packages: safe-buffer: 5.2.1 dev: false - /strip-bom/3.0.0: - resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=} - engines: {node: '>=4'} - dev: true - - /strip-json-comments/2.0.1: - resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=} - engines: {node: '>=0.10.0'} - dev: true - - /supports-preserve-symlinks-flag/1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: true - /text-hex/1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} dev: false - /timed-out/4.0.1: - resolution: {integrity: sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=} - engines: {node: '>=0.10.0'} - dev: false - - /to-regex-range/5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: true - - /tree-kill/1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - dev: true - /triple-beam/1.3.0: resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==} dev: false - /ts-node-dev/1.1.8_typescript@4.6.4: - resolution: {integrity: sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==} - engines: {node: '>=0.8.0'} - hasBin: true - peerDependencies: - node-notifier: '*' - typescript: '*' - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - chokidar: 3.5.3 - dynamic-dedupe: 0.3.0 - minimist: 1.2.6 - mkdirp: 1.0.4 - resolve: 1.22.0 - rimraf: 2.7.1 - source-map-support: 0.5.21 - tree-kill: 1.2.2 - ts-node: 9.1.1_typescript@4.6.4 - tsconfig: 7.0.0 - typescript: 4.6.4 - dev: true - - /ts-node/9.1.1_typescript@4.6.4: - resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==} - engines: {node: '>=10.0.0'} - hasBin: true - peerDependencies: - typescript: '>=2.7' - dependencies: - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - source-map-support: 0.5.21 - typescript: 4.6.4 - yn: 3.1.1 - dev: true - - /tsconfig/7.0.0: - resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} - dependencies: - '@types/strip-bom': 3.0.0 - '@types/strip-json-comments': 0.0.30 - strip-bom: 3.0.0 - strip-json-comments: 2.0.1 - dev: true - - /typescript/4.6.4: - resolution: {integrity: sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==} + /typescript/4.8.4: + resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} engines: {node: '>=4.2.0'} hasBin: true dev: true - /unzip-response/2.0.1: - resolution: {integrity: sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=} - engines: {node: '>=4'} - dev: false - - /url-parse-lax/1.0.0: - resolution: {integrity: sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=} - engines: {node: '>=0.10.0'} - dependencies: - prepend-http: 1.0.4 - dev: false - - /url-pattern/1.0.3: - resolution: {integrity: sha1-BAkpJHGyTyPFDWWkeTF5PStaz8E=} - engines: {node: '>=0.12.0'} - dev: false - /util-deprecate/1.0.2: - resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + + /uuid/8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true dev: false /winston-transport/4.5.0: resolution: {integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==} engines: {node: '>= 6.4.0'} dependencies: - logform: 2.4.0 + logform: 2.4.2 readable-stream: 3.6.0 triple-beam: 1.3.0 dev: false - /winston/3.7.2: - resolution: {integrity: sha512-QziIqtojHBoyzUOdQvQiar1DH0Xp9nF1A1y7NVy2DGEsz82SBDtOalS0ulTRGVT14xPX3WRWkCsdcJKqNflKng==} + /winston/3.8.2: + resolution: {integrity: sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==} engines: {node: '>= 12.0.0'} dependencies: + '@colors/colors': 1.5.0 '@dabh/diagnostics': 2.0.3 - async: 3.2.3 + async: 3.2.4 is-stream: 2.0.1 - logform: 2.4.0 + logform: 2.4.2 one-time: 1.0.0 readable-stream: 3.6.0 - safe-stable-stringify: 2.3.1 + safe-stable-stringify: 2.4.1 stack-trace: 0.0.10 triple-beam: 1.3.0 winston-transport: 4.5.0 dev: false - - /wrappy/1.0.2: - resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} - dev: true - - /xtend/4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - dev: true - - /yn/3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - dev: true diff --git a/src/cache.ts b/src/cache.ts new file mode 100644 index 0000000..cd2ab3b --- /dev/null +++ b/src/cache.ts @@ -0,0 +1 @@ +export const Cache = new Map<'ip' | 'zone', string>() diff --git a/src/cloudflare.ts b/src/cloudflare.ts new file mode 100644 index 0000000..24463d4 --- /dev/null +++ b/src/cloudflare.ts @@ -0,0 +1,126 @@ +import axios from 'axios' + +import { Cache } from './cache.js' +import { Config } from './config.js' +import { logger } from './logger.js' + +type DNSRecord = { + id: string + zone_id: string + zone_name: string + name: string + type: string + content: string + proxiable: boolean + proxied: boolean + ttl: number // 1 means automatic + locked: boolean +} + +type DNSRecordCreate = Pick +type DNSRecordPatch = Partial + +const Base = axios.create({ + baseURL: 'https://api.cloudflare.com/client/v4', + headers: { + Authorization: `Bearer ${Config.auth.token}`, + }, +}) + +export const API = { + zones: { + async findByName(name: string): Promise { + try { + const { data } = await Base({ + url: '/zones', + params: { + name, + }, + }) + return data.result[0].id + } catch { + return null + } + }, + }, + records: { + async create(zoneId: string, zone: DNSRecordCreate): Promise { + await Base({ + url: `/zones/${zoneId}/dns_records`, + method: 'POST', + data: zone, + }) + }, + async remove(zoneId: string, recordId: string): Promise { + await Base({ + url: `/zones/${zoneId}/dns_records/${recordId}`, + method: 'DELETE', + }) + }, + async patch(zoneId: string, recordId: string, data: DNSRecordPatch): Promise { + await Base({ + url: `/zones/${zoneId}/dns_records/${recordId}`, + method: 'PATCH', + data, + }) + }, + async find(zoneId: string): Promise { + const { data } = await Base({ + url: `/zones/${zoneId}/dns_records`, + params: { + type: 'A', + name: Config.dns.record, + }, + }) + return data.result as DNSRecord[] + }, + }, +} + +export async function update(ip: string) { + // Find zone + if (!Cache.has('zone')) { + logger.debug('Fetching zone') + const zone = await API.zones.findByName(Config.dns.zone) + if (!zone) { + logger.error(`Zone "${Config.dns.zone}"" not found`) + process.exit(1) + } + Cache.set('zone', zone) + } + + const zoneId = Cache.get('zone')! + logger.debug(`Zone ID: ${zoneId}`) + + // Set record + const records = await API.records.find(zoneId) + + logger.debug('Updating record', ip) + + switch (records.length) { + case 0: + // Create DNS Record + logger.debug('Creating DNS record') + await API.records.create(zoneId, { + content: ip, + name: Config.dns.record, + proxied: Config.dns.proxied, + ttl: 1, + type: 'A', + }) + return + case 1: + // Only one record, thats fine + break + default: + // More than one record, delete all but the first + logger.debug('Deleting other DNS records') + for (const record of records.slice(1)) { + await API.records.remove(zoneId, record.id) + } + break + } + + // Update the remaining record + await API.records.patch(zoneId, records[0]!.id, { content: ip, proxied: Config.dns.proxied }) +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..73ef83b --- /dev/null +++ b/src/config.ts @@ -0,0 +1,50 @@ +import { config } from 'dotenv' +import { validate } from 'node-cron' + +config() + +function getEnv(key: string, fallback: string, parse?: undefined, validator?: (s: string) => boolean): string +function getEnv(key: string, fallback: T, parse: (value: string) => T, validator?: (T: string) => boolean): T +function getEnv( + key: string, + fallback: T, + parse?: (value: string) => T, + validator?: (s: string | T) => boolean +): T | string { + const value = process.env[key] + const parsed = value === undefined ? fallback : parse ? parse(value) : value + if (validator && !validator(parsed)) { + console.error(`Invalid or missing value for ${key}: ${value}`) + process.exit(1) + } + return parsed +} + +function parseBoolean(value: string): boolean { + value = value.toLowerCase() + const truthy = ['true', 'yes', '1'] + return truthy.includes(value) +} + +function isPresent(s: string): boolean { + return s.length > 0 +} + +export const Config = { + version: getEnv('npm_package_version', 'unknown'), + logging: { + level: getEnv('LOG_LEVEL', 'info'), + }, + auth: { + token: getEnv('TOKEN', '', undefined, isPresent), + }, + dns: { + zone: getEnv('ZONE', '', undefined, isPresent), + record: getEnv('DNS_RECORD', '', undefined, isPresent), + proxied: getEnv('PROXIED', false, parseBoolean), + }, + runner: { + cron: getEnv('CRON', '*/5 * * * *', undefined, (s) => validate(s)), + resolver: getEnv('RESOLVER', 'https://api.ipify.org'), + }, +} diff --git a/src/index.ts b/src/index.ts index e1ff3c0..6b6b950 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,122 +1,14 @@ -import Cloudflare from 'cloudflare' -import Axios from 'axios' -import { CronJob } from 'cron' -import { config } from 'dotenv' -import winston from 'winston' +import { schedule } from 'node-cron' import process from 'process' -const logger = winston.createLogger({ - level: 'info', - transports: [ - new winston.transports.Console({ - format: winston.format.combine(winston.format.timestamp(), winston.format.colorize(), winston.format.simple()), - }), - ], -}) - -const Cache = new Map() - -async function getCurrentIp(resolver?: string): Promise { - const { data } = await Axios({ - url: resolver || 'https://api.ipify.org/', - method: 'GET', - }) - return data as string -} - -function checkIfUpdateIsRequired(newIP: string): boolean { - // Check if IP has changed. - const current = Cache.get('ip') - if (newIP !== current) { - Cache.set('ip', newIP) - return true - } - return false -} - -type DNSRecord = { - zone: string - record: string - ip: string - proxied: boolean -} - -type DNSBrowseResult = { result: { id: string; type: string; name: string; proxied: boolean; ttl: number }[] } -async function update(cf: Cloudflare, options: DNSRecord) { - // Find zone - if (!Cache.has('zone')) { - logger.debug('Fetching zone') - const zones: { result: { id: string; name: string }[] } = (await cf.zones.browse()) as any - const zone = zones.result.find((z) => z.name === options.zone) - if (!zone) { - logger.error(`Zone "${options.zone}"" not found`) - process.exit(1) - } - Cache.set('zone', zone.id) - } - - const zoneId = Cache.get('zone')! - logger.debug(`Zone ID: ${zoneId}`) - - // Set record - - const records: DNSBrowseResult = (await cf.dnsRecords.browse(zoneId)) as any - const relevant = records.result.filter((r) => r.name === options.record && r.type === 'A') - if (relevant.length === 0) { - // Create DNS Record - logger.debug('Creating DNS record') - await cf.dnsRecords.add(zoneId, { - type: 'A', - name: options.record, - content: options.ip, - proxied: options.proxied, - ttl: 1, - }) - } else { - if (relevant.length > 1) { - // Delete other records as they cannot all point to the same IP - logger.debug('Deleting other DNS records') - for (const record of relevant.slice(1)) { - await cf.dnsRecords.del(zoneId, record.id) - } - } - // Update DNS Record - logger.debug('Updating DNS record') - const record = relevant[0]! - await cf.dnsRecords.edit(zoneId, record.id, { - type: 'A', - name: options.record, - content: options.ip, - proxied: options.proxied, - ttl: record.ttl, - }) - logger.info(`Updated DNS record ${record.name}`) - } -} +import { Config } from './config.js' +import { logger } from './logger.js' +import { loop } from './runner.js' async function main() { - config() - const { EMAIL, KEY, TOKEN, ZONE, DNS_RECORD, PROXIED, CRON, RESOLVER } = process.env - if (!ZONE || !DNS_RECORD) { - logger.error('Missing environment variables') - process.exit(1) - } - - // Initialize Cloudflare - const cf = new Cloudflare(TOKEN ? { token: TOKEN } : { email: EMAIL, key: KEY }) - - async function fn() { - const ip = await getCurrentIp(RESOLVER) - const changed = checkIfUpdateIsRequired(ip) - logger.info(`Running. Update required: ${!!changed}`) - if (changed) - await update(cf, { ip, record: DNS_RECORD!, zone: ZONE!, proxied: Boolean(PROXIED) }).catch((e) => - logger.error(e.message) - ) - } - - const cron = new CronJob(CRON || '*/5 * * * *', fn, null, true, undefined, null, true) - logger.info('Started service.') + const cron = schedule(Config.runner.cron, loop) + logger.info('Started service.', { version: Config.version }) + logger.debug('Config', Config) function terminate() { logger.info('Stopping service.') diff --git a/src/ip.ts b/src/ip.ts new file mode 100644 index 0000000..def1e9b --- /dev/null +++ b/src/ip.ts @@ -0,0 +1,22 @@ +import axios from 'axios' + +import { Cache } from './cache.js' +import { Config } from './config.js' + +export async function getCurrentIp(): Promise { + const { data } = await axios({ + url: Config.runner.resolver, + method: 'GET', + }) + return data as string +} + +export function checkIfUpdateIsRequired(newIP: string): boolean { + // Check if IP has changed. + const current = Cache.get('ip') + if (newIP !== current) { + Cache.set('ip', newIP) + return true + } + return false +} diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..5e4b3b2 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,12 @@ +import winston from 'winston' + +import { Config } from './config.js' + +export const logger = winston.createLogger({ + level: Config.logging.level, + transports: [ + new winston.transports.Console({ + format: winston.format.combine(winston.format.timestamp(), winston.format.colorize(), winston.format.simple()), + }), + ], +}) diff --git a/src/runner.ts b/src/runner.ts new file mode 100644 index 0000000..26cf358 --- /dev/null +++ b/src/runner.ts @@ -0,0 +1,18 @@ +import { update } from './cloudflare.js' +import { checkIfUpdateIsRequired, getCurrentIp } from './ip.js' +import { logger } from './logger.js' + +export async function loop() { + const ip = await getCurrentIp() + const changed = checkIfUpdateIsRequired(ip) + logger.info(`Running. Update required: ${!!changed}`) + if (changed) { + try { + await update(ip) + logger.info('Successfully updated DNS record') + } catch (e) { + logger.error(e) + logger.error('Failed to update DNS record') + } + } +} diff --git a/tsconfig.json b/tsconfig.json index e36e77a..501c4e5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,57 +1,59 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ + /* Visit https://aka.ms/tsconfig to read more about this file */ /* Projects */ - // "incremental": true, /* Enable incremental compilation */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ - // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "commonjs" /* Specify what module code is generated. */, + "module": "ES2022" /* Specify what module code is generated. */, "rootDir": "./src" /* Specify the root folder within your source files. */, - // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "resolveJsonModule": true, /* Enable importing .json files */ - // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ // "declarationMap": true, /* Create sourcemaps for d.ts files. */ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ "outDir": "./dist" /* Specify an output folder for all emitted files. */, // "removeComments": true, /* Disable emitting comments. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ @@ -59,39 +61,40 @@ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ /* Interop Constraints */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ "strict": true /* Enable all strict type-checking options. */, - "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied `any` type.. */, - "strictNullChecks": true /* When type checking, take into account `null` and `undefined`. */, + "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, + "strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */, "strictFunctionTypes": true /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */, - "strictBindCallApply": true /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */, + "strictBindCallApply": true /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */, "strictPropertyInitialization": true /* Check for class properties that are declared but not set in the constructor. */, - "noImplicitThis": true /* Enable error reporting when `this` is given the type `any`. */, - "useUnknownInCatchVariables": true /* Type catch clause variables as 'unknown' instead of 'any'. */, + "noImplicitThis": true /* Enable error reporting when 'this' is given the type 'any'. */, + "useUnknownInCatchVariables": true /* Default catch clause variables as 'unknown' instead of 'any'. */, "alwaysStrict": true /* Ensure 'use strict' is always emitted. */, - // "noUnusedLocals": true /* Enable error reporting when a local variables aren't read. */, - // "noUnusedParameters": true /* Raise an error when a function parameter isn't read */, + "noUnusedLocals": true /* Enable error reporting when local variables aren't read. */, + "noUnusedParameters": true /* Raise an error when a function parameter isn't read. */, "exactOptionalPropertyTypes": true /* Interpret optional property types as written, rather than adding 'undefined'. */, "noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */, "noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */, - "noUncheckedIndexedAccess": true /* Include 'undefined' in index signature results */, + "noUncheckedIndexedAccess": true /* Add 'undefined' to a type when accessed using an index. */, "noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */, - "noPropertyAccessFromIndexSignature": true /* Enforces using indexed accessors for keys declared using an indexed type */, - "allowUnusedLabels": true /* Disable error reporting for unused labels. */, - "allowUnreachableCode": true /* Disable error reporting for unreachable code. */, + "noPropertyAccessFromIndexSignature": true /* Enforces using indexed accessors for keys declared using an indexed type. */, + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */