11 Commits

Author SHA1 Message Date
a1af82501c 1.3.0 (#6) 2022-10-20 15:18:57 +02:00
fb7416dbec Merge pull request #5 from cupcakearmy/1.2.1
1.2.1
2022-05-14 15:59:53 +02:00
54eb94135b changelog, update deps & adapt pr 2022-05-14 15:58:38 +02:00
47c0adbfb6 Merge pull request #4 from borisbm/master
Added proxied option
2022-05-14 15:52:54 +02:00
borisbm
d25c33d015 Update README.md 2022-05-13 19:42:05 +10:00
borisbm
c007fbb848 Update .sample.env 2022-05-13 19:37:57 +10:00
borisbm
f6d825b843 Update index.ts 2022-05-13 19:37:27 +10:00
10b4d5473b badges 2022-04-07 12:05:47 +02:00
157ce6b7b6 1.2.0 2022-02-07 13:14:23 +01:00
8904ffcbdd update dependencies 2022-02-07 12:41:51 +01:00
832bc1993a pnpm & typescript 2021-10-22 17:47:49 +02:00
21 changed files with 805 additions and 322 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
*
!src
!tsconfig.json
!package.json
!pnpm-lock.yaml

26
.github/workflows/build.yml vendored Normal file
View File

@@ -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

View File

@@ -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: Log in to Docker Hub
uses: docker/login-action@v1
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 }}

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.env
ip.log
dist
node_modules
.vscode

View File

@@ -1,8 +1,8 @@
# Required
EMAIL=my@mail.com
KEY=my_api_key
TOKEn=myapitoken
ZONE=example.org
DNS_RECORD=some.example.org
PROXIED=false
# Optional
#CRON=* * * * *

View File

@@ -5,13 +5,61 @@ 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
- Support for `proxied` parameter thanks to @borisbm.
### Security
- Updated dependencies.
## [1.2.0] - 2022-02-07
### Added
- Sigterm and Sigkill hooks for graceful shutdown
### Changed
- Multistage steps to reduce image size
## [1.1.1] - 2022-02-07
### Security
- Updated dependencies.
## [1.1.0] - 2021-10-22
### Changed
- Added typescript for type safety.
- Switched to pnpm.
## [1.0.0] - 2021-05-05
### Added
- automated build & tagging
- arm images
- Automated build & tagging.
- Arm images.
### Security
- updated dependencies
- Updated dependencies.

View File

@@ -1,9 +1,19 @@
FROM node:14-alpine
FROM node:18-alpine as base
WORKDIR /app
RUN npm -g i pnpm
ADD ./package.json yarn.lock ./
RUN yarn
ADD ./script.js ./
CMD ["node", "script.js"]
FROM base as builder
ADD ./package.json ./pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
ADD . .
RUN pnpm run build
FROM base
ADD ./package.json ./pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
COPY --from=builder /app/dist/ /app/dist/
STOPSIGNAL SIGTERM
CMD ["pnpm", "start"]

View File

@@ -1,7 +1,8 @@
# Docker DDNS Cloudflare
![Docker Size](https://img.shields.io/docker/image-size/cupcakearmy/ddns-cloudflare)
![Docker Downloads](https://img.shields.io/docker/pulls/cupcakearmy/ddns-cloudflare)
![Docker Pulls](https://img.shields.io/docker/pulls/cupcakearmy/ddns-cloudflare?style=flat-square)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/cupcakearmy/ddns-cloudflare/latest?style=flat-square)
![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/cupcakearmy/ddns-cloudflare/latest?style=flat-square)
## Features 🌈
@@ -11,19 +12,18 @@
## Quickstart 🚀
1. Get your api token [here](https://dash.cloudflare.com/profile/api-tokens) (Top right -> My Profile -> API Tokens)
1. Get your API Token [here](https://dash.cloudflare.com/profile/api-tokens) (Top right -> My Profile -> API Tokens)
Click create token. You can then use the Edit DNS Zone template. Give it a name.
![Settings](https://i.imgur.com/dLs8PHs.png)
2. Create an `.env` file:
```bash
EMAIL=my@mail.com
KEY=my_api_key
TOKEN=mytoken
ZONE=example.org
DNS_RECORD=some.example.org
PROXIED=false
```
3. Run the container
@@ -32,12 +32,6 @@ DNS_RECORD=some.example.org
docker run -d --name ddns --restart always --env-file .env cupcakearmy/ddns-cloudflare
```
To check logs:
```bash
docker logs ddns
```
### Docker-Copmose
With docker-compose:
@@ -49,6 +43,18 @@ cp .sample.env .env
docker-compose up -d
```
## ENV Reference
| 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
### Custom CRON

View File

@@ -1,4 +1,4 @@
version: '3.7'
version: '3.8'
services:
ddns:

View File

@@ -1,14 +1,22 @@
{
"version": "1.3.0",
"license": "MIT",
"type": "module",
"main": "dist",
"scripts": {
"docker:build": "docker build -t cupcakearmy/ddns-cloudflare:lastest .",
"docker:push": "docker push cupcakearmy/ddns-cloudflare:lastest",
"docker:publish": "yarn run docker:build && yarn run docker:push"
"dev": "tsc -w",
"build": "tsc",
"start": "node ."
},
"dependencies": {
"axios": "^0.21.1",
"cloudflare": "^2.7.0",
"cron": "^1.8.2",
"dotenv": "^8.2.0"
"axios": "^1.1.3",
"dotenv": "^16.0.3",
"node-cron": "^3.0.2",
"winston": "^3.8.2"
},
"devDependencies": {
"@types/node": "18",
"@types/node-cron": "^3.0.4",
"typescript": "^4.8.4"
}
}

288
pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,288 @@
lockfileVersion: 5.4
specifiers:
'@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: 1.1.3
dotenv: 16.0.3
node-cron: 3.0.2
winston: 3.8.2
devDependencies:
'@types/node': 18.11.2
'@types/node-cron': 3.0.4
typescript: 4.8.4
packages:
/@colors/colors/1.5.0:
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
dev: false
/@dabh/diagnostics/2.0.3:
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
dependencies:
colorspace: 1.1.4
enabled: 2.0.0
kuler: 2.0.0
dev: false
/@types/node-cron/3.0.4:
resolution: {integrity: sha512-A2H+uz5ry4hohYjRe5mQSE/8Dx/HGw4WZ728JxhKUZ7z8CMvRuG2tpbzGHRGQCuQzz5aCNB1iXzPZYHd4BPHvw==}
dev: true
/@types/node/18.11.2:
resolution: {integrity: sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==}
dev: true
/async/3.2.4:
resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
dev: false
/asynckit/0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
/axios/1.1.3:
resolution: {integrity: sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==}
dependencies:
follow-redirects: 1.15.2
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: false
/color-convert/1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
color-name: 1.1.3
dev: false
/color-name/1.1.3:
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
dev: false
/color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: false
/color-string/1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
dev: false
/color/3.2.1:
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
dependencies:
color-convert: 1.9.3
color-string: 1.9.1
dev: false
/colorspace/1.1.4:
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
dependencies:
color: 3.2.1
text-hex: 1.0.0
dev: false
/combined-stream/1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: false
/delayed-stream/1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: false
/dotenv/16.0.3:
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
engines: {node: '>=12'}
dev: false
/enabled/2.0.0:
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
dev: false
/fecha/4.2.3:
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
dev: false
/fn.name/1.1.0:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
dev: false
/follow-redirects/1.15.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: false
/form-data/4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: false
/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-stream/2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
dev: false
/kuler/2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
dev: false
/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.4.1
triple-beam: 1.3.0
dev: false
/mime-db/1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types/2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: false
/ms/2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: false
/node-cron/3.0.2:
resolution: {integrity: sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==}
engines: {node: '>=6.0.0'}
dependencies:
uuid: 8.3.2
dev: false
/one-time/1.0.0:
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
dependencies:
fn.name: 1.1.0
dev: false
/proxy-from-env/1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: false
/readable-stream/3.6.0:
resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: false
/safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
/safe-stable-stringify/2.4.1:
resolution: {integrity: sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==}
engines: {node: '>=10'}
dev: false
/simple-swizzle/0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
dependencies:
is-arrayish: 0.3.2
dev: false
/stack-trace/0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
dev: false
/string_decoder/1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies:
safe-buffer: 5.2.1
dev: false
/text-hex/1.0.0:
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
dev: false
/triple-beam/1.3.0:
resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==}
dev: false
/typescript/4.8.4:
resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/util-deprecate/1.0.2:
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.2
readable-stream: 3.6.0
triple-beam: 1.3.0
dev: false
/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.4
is-stream: 2.0.1
logform: 2.4.2
one-time: 1.0.0
readable-stream: 3.6.0
safe-stable-stringify: 2.4.1
stack-trace: 0.0.10
triple-beam: 1.3.0
winston-transport: 4.5.0
dev: false

View File

@@ -1,69 +0,0 @@
const { readFileSync, writeFileSync, existsSync } = require('fs')
const Cloudflare = require('cloudflare')
const Axios = require('axios')
const { CronJob } = require('cron')
require('dotenv').config()
const { EMAIL, KEY, ZONE, DNS_RECORD, CRON, RESOLVER } = process.env
const cf = Cloudflare({
email: EMAIL,
key: KEY,
})
function log(message) {
const timestamp = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')
console.log(timestamp + '\t' + message)
}
async function getCurrentIp() {
const { data } = await Axios({
url: RESOLVER || 'https://api.ipify.org/',
method: 'GET',
})
return data
}
async function checkIfUpdateIsRequired() {
const LOG = './ip.log'
const current = await getCurrentIp()
const saved = existsSync(LOG) ? readFileSync(LOG, 'utf-8') : null
if (current === saved) return false
else {
writeFileSync(LOG, current, { encoding: 'utf-8' })
return current
}
}
async function update(newIP) {
const { result: zones } = await cf.zones.browse({ name: ZONE })
if (!zones.length) throw new Error(`No ZONE "${ZONE}" found`)
const zoneId = zones[0].id
const { result: records } = await cf.dnsRecords.browse(zoneId, { name: DNS_RECORD, type: 'A' })
const recordAlreadyExists = records.length
if (recordAlreadyExists) {
const { id: recordId, ...rest } = records[0]
await cf.dnsRecords.edit(zoneId, recordId, { ...rest, content: newIP })
log(`Updated:\t${DNS_RECORD}${newIP} `)
} else {
await cf.dnsRecords.add(zoneId, {
name: DNS_RECORD,
type: 'A',
content: newIP,
})
log(`Created:\t${DNS_RECORD}${newIP} `)
}
}
async function main() {
const changed = await checkIfUpdateIsRequired()
log(`Running. Update required: ${!!changed}`)
if (changed) await update(changed).catch((e) => console.error(e.message))
}
new CronJob(CRON || '*/5 * * * *', main, null, true, null, null, true)
log('Started service.')

1
src/cache.ts Normal file
View File

@@ -0,0 +1 @@
export const Cache = new Map<'ip' | 'zone', string>()

126
src/cloudflare.ts Normal file
View File

@@ -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<DNSRecord, 'name' | 'type' | 'ttl' | 'proxied' | 'content'>
type DNSRecordPatch = Partial<DNSRecordCreate>
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<string | null> {
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<void> {
await Base({
url: `/zones/${zoneId}/dns_records`,
method: 'POST',
data: zone,
})
},
async remove(zoneId: string, recordId: string): Promise<void> {
await Base({
url: `/zones/${zoneId}/dns_records/${recordId}`,
method: 'DELETE',
})
},
async patch(zoneId: string, recordId: string, data: DNSRecordPatch): Promise<void> {
await Base({
url: `/zones/${zoneId}/dns_records/${recordId}`,
method: 'PATCH',
data,
})
},
async find(zoneId: string): Promise<DNSRecord[]> {
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 })
}

50
src/config.ts Normal file
View File

@@ -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<T>(key: string, fallback: T, parse: (value: string) => T, validator?: (T: string) => boolean): T
function getEnv<T>(
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'),
},
}

22
src/index.ts Normal file
View File

@@ -0,0 +1,22 @@
import { schedule } from 'node-cron'
import process from 'process'
import { Config } from './config.js'
import { logger } from './logger.js'
import { loop } from './runner.js'
async function main() {
const cron = schedule(Config.runner.cron, loop)
logger.info('Started service.', { version: Config.version })
logger.debug('Config', Config)
function terminate() {
logger.info('Stopping service.')
cron.stop()
process.exit(0)
}
process.on('SIGINT', terminate)
process.on('SIGTERM', terminate)
}
main()

22
src/ip.ts Normal file
View File

@@ -0,0 +1,22 @@
import axios from 'axios'
import { Cache } from './cache.js'
import { Config } from './config.js'
export async function getCurrentIp(): Promise<string> {
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
}

12
src/logger.ts Normal file
View File

@@ -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()),
}),
],
})

18
src/runner.ts Normal file
View File

@@ -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')
}
}
}

103
tsconfig.json Normal file
View File

@@ -0,0 +1,103 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "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": "./.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": "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'. */
// "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. */
// "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": "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. */,
// "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'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "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 '<reference>'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. */
// "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'. */
/* 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. */
"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. */
// "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. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "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. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "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. */,
// "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'. */,
"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. */,
"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 /* Default catch clause variables as 'unknown' instead of 'any'. */,
"alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
"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 /* 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. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

202
yarn.lock
View File

@@ -1,202 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
agent-base@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
dependencies:
es6-promisify "^5.0.0"
autocreate@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/autocreate/-/autocreate-1.2.0.tgz#522167992c4172c15479e5f88f3486a452a40cba"
integrity sha1-UiFnmSxBcsFUeeX4jzSGpFKkDLo=
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
capture-stack-trace@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==
cloudflare@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/cloudflare/-/cloudflare-2.7.0.tgz#ff9249a63ff9b662bd863da21e965c8f82f76012"
integrity sha512-yhroBpn2VBczFwiRLpyUc431XiWE+xNs8YvtjAsj1vEA1pVwhpje6BzgLW5iZbulmCuPX48lvX8HizeMWk713g==
dependencies:
autocreate "^1.1.0"
es-class "^2.1.1"
got "^6.3.0"
https-proxy-agent "^2.1.1"
object-assign "^4.1.0"
should-proxy "^1.0.4"
url-pattern "^1.0.3"
create-error-class@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=
dependencies:
capture-stack-trace "^1.0.0"
cron@^1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/cron/-/cron-1.8.2.tgz#4ac5e3c55ba8c163d84f3407bde94632da8370ce"
integrity sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==
dependencies:
moment-timezone "^0.5.x"
debug@^3.1.0:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
dependencies:
ms "^2.1.1"
dotenv@^8.2.0:
version "8.5.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.5.1.tgz#e3a4c7862daba51b92bce0da5c349f11faa28663"
integrity sha512-qC1FbhCH7UH7B+BcRNUDhAk04d/n+tnGGB1ctwndZkVFeehYJOn39pRWWzmdzpFqImyX1KB8tO0DCHLf8yRaYQ==
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
es-class@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/es-class/-/es-class-2.1.1.tgz#6ec2243b5a1e3581c0b7eecee0130c9c0d6fb2b7"
integrity sha1-bsIkO1oeNYHAt+7O4BMMnA1vsrc=
es6-promise@^4.0.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
es6-promisify@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
dependencies:
es6-promise "^4.0.3"
follow-redirects@^1.10.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.0.tgz#f5d260f95c5f8c105894491feee5dc8993b402fe"
integrity sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg==
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
got@^6.3.0:
version "6.7.1"
resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=
dependencies:
create-error-class "^3.0.0"
duplexer3 "^0.1.4"
get-stream "^3.0.0"
is-redirect "^1.0.0"
is-retry-allowed "^1.0.0"
is-stream "^1.0.0"
lowercase-keys "^1.0.0"
safe-buffer "^5.0.1"
timed-out "^4.0.0"
unzip-response "^2.0.1"
url-parse-lax "^1.0.0"
https-proxy-agent@^2.1.1:
version "2.2.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
dependencies:
agent-base "^4.3.0"
debug "^3.1.0"
is-redirect@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=
is-retry-allowed@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
is-stream@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
lowercase-keys@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
moment-timezone@^0.5.x:
version "0.5.33"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c"
integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0":
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
object-assign@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
prepend-http@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
safe-buffer@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
should-proxy@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/should-proxy/-/should-proxy-1.0.4.tgz#c805a501abf69539600634809e62fbf238ba35e4"
integrity sha1-yAWlAav2lTlgBjSAnmL78ji6NeQ=
timed-out@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
unzip-response@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
url-parse-lax@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=
dependencies:
prepend-http "^1.0.1"
url-pattern@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/url-pattern/-/url-pattern-1.0.3.tgz#0409292471b24f23c50d65a47931793d2b5acfc1"
integrity sha1-BAkpJHGyTyPFDWWkeTF5PStaz8E=