Compare commits

...

19 Commits
v1.1.0 ... main

Author SHA1 Message Date
696aeaef13
Merge pull request #5 from cupcakearmy/dependabot/github_actions/docker/metadata-action-5
Bump docker/metadata-action from 4 to 5
2024-12-17 15:05:17 +01:00
d1546f648b
Merge pull request #6 from cupcakearmy/dependabot/github_actions/docker/login-action-3
Bump docker/login-action from 2 to 3
2024-12-17 15:05:08 +01:00
4ce1cca034
Merge pull request #7 from cupcakearmy/dependabot/github_actions/docker/setup-qemu-action-3
Bump docker/setup-qemu-action from 2 to 3
2024-12-17 15:04:52 +01:00
11ea8f8084
Merge pull request #8 from cupcakearmy/dependabot/github_actions/docker/build-push-action-6
Bump docker/build-push-action from 3 to 6
2024-12-17 15:04:42 +01:00
c03e112134
Merge pull request #9 from cupcakearmy/dependabot/github_actions/docker/setup-buildx-action-3
Bump docker/setup-buildx-action from 2 to 3
2024-12-17 15:04:31 +01:00
dependabot[bot]
0cc2a1ff9e
Bump docker/setup-buildx-action from 2 to 3
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 13:59:00 +00:00
dependabot[bot]
9ccf8b2c9a
Bump docker/build-push-action from 3 to 6
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 13:58:58 +00:00
dependabot[bot]
1092dca38a
Bump docker/setup-qemu-action from 2 to 3
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 13:58:55 +00:00
dependabot[bot]
5ab4af8515
Bump docker/login-action from 2 to 3
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 13:58:53 +00:00
dependabot[bot]
deb6ababeb
Bump docker/metadata-action from 4 to 5
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4 to 5.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 13:58:51 +00:00
936dc72804
Create dependabot.yml 2024-12-17 14:58:10 +01:00
2e34bf8ca0 add run_once and warning about tangling repos 2024-12-16 12:24:25 +01:00
544d65c7eb Merge branch 'main' of github.com:cupcakearmy/gitea-sync 2024-12-16 11:52:48 +01:00
74e9748b43 move to deno 2024-12-15 22:55:42 +01:00
309e939a05
Merge pull request #4 from cupcakearmy/move-to-bun
Move to bun
2024-01-27 01:27:50 +01:00
ee25d7db59
move to bun 2024-01-27 01:26:52 +01:00
7bc3c990e0
logging 2024-01-27 01:26:37 +01:00
a96af3a897
Merge pull request #2 from cupcakearmy/dependabot/npm_and_yarn/axios-1.6.0
Bump axios from 1.3.4 to 1.6.0
2023-11-16 20:54:04 +01:00
dependabot[bot]
ece61afb07
Bump axios from 1.3.4 to 1.6.0
Bumps [axios](https://github.com/axios/axios) from 1.3.4 to 1.6.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.3.4...v1.6.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-11 11:37:50 +00:00
19 changed files with 343 additions and 400 deletions

View File

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

11
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@ -9,15 +9,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
with:
install: true
- name: Docker Labels
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: |
cupcakearmy/gitea-sync
@ -30,19 +30,19 @@ jobs:
type=semver,pattern={{major}}
- name: Log in to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
# - name: Log in to the Container registry
# uses: docker/login-action@v2
# uses: docker/login-action@v3
# with:
# registry: ghcr.io
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true

1
.nvmrc
View File

@ -1 +0,0 @@
18.15

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"deno.enable": true
}

View File

@ -1,19 +1,11 @@
FROM node:18.15-alpine as base
RUN npm -g install pnpm@7
FROM denoland/deno AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
FROM base as build
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
FROM base as runner
RUN pnpm install --frozen-lockfile --prod
COPY --from=build /app/dist /app/dist
CMD ["pnpm", "run", "start"]
RUN deno install --frozen
RUN deno compile --allow-net --allow-env --no-prompt --output /app/sync ./src/index.ts
FROM debian:stable-slim
WORKDIR /app
COPY --from=builder /app/sync .
ENTRYPOINT [ "/app/sync" ]

View File

@ -26,13 +26,19 @@ GITHUB_TOKEN=
# Host of the Gitea server
GITEA_HOST=
# Gitea token (scopes: repo)
# Gitea token (scopes: repos: read and write; user: read)
GITEA_TOKEN=
# OPTIONAL
# Cron schedule
CRON="0 */2 * * *"
# Debug level
DEBUG_LEVEL=debug
# Only run one, skip cron and exit
RUN_ONCE=1
```
## Known limitations

8
deno.jsonc Normal file
View File

@ -0,0 +1,8 @@
{
"version": "1.3.0",
"imports": {
"axios": "npm:axios@^1.7.9",
"node-cron": "npm:node-cron@^3.0.3",
"winston": "npm:winston@^3.17.0"
}
}

228
deno.lock generated Normal file
View File

@ -0,0 +1,228 @@
{
"version": "4",
"specifiers": {
"jsr:@std/dotenv@*": "0.225.3",
"npm:axios@*": "1.7.9",
"npm:axios@^1.7.9": "1.7.9",
"npm:node-cron@*": "3.0.3",
"npm:node-cron@^3.0.3": "3.0.3",
"npm:winston@*": "3.17.0",
"npm:winston@^3.17.0": "3.17.0"
},
"jsr": {
"@std/dotenv@0.225.3": {
"integrity": "a95e5b812c27b0854c52acbae215856d9cce9d4bbf774d938c51d212711e8d4a"
}
},
"npm": {
"@colors/colors@1.6.0": {
"integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="
},
"@dabh/diagnostics@2.0.3": {
"integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
"dependencies": [
"colorspace",
"enabled",
"kuler"
]
},
"@types/triple-beam@1.3.5": {
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="
},
"async@3.2.6": {
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
},
"asynckit@0.4.0": {
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"axios@1.7.9": {
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"dependencies": [
"follow-redirects",
"form-data",
"proxy-from-env"
]
},
"color-convert@1.9.3": {
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": [
"color-name"
]
},
"color-name@1.1.3": {
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"color-string@1.9.1": {
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": [
"color-name",
"simple-swizzle"
]
},
"color@3.2.1": {
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
"dependencies": [
"color-convert",
"color-string"
]
},
"colorspace@1.1.4": {
"integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
"dependencies": [
"color",
"text-hex"
]
},
"combined-stream@1.0.8": {
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": [
"delayed-stream"
]
},
"delayed-stream@1.0.0": {
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"enabled@2.0.0": {
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"fecha@4.2.3": {
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
},
"fn.name@1.1.0": {
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"follow-redirects@1.15.9": {
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="
},
"form-data@4.0.1": {
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"dependencies": [
"asynckit",
"combined-stream",
"mime-types"
]
},
"inherits@2.0.4": {
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"is-arrayish@0.3.2": {
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"is-stream@2.0.1": {
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
},
"kuler@2.0.0": {
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
},
"logform@2.7.0": {
"integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
"dependencies": [
"@colors/colors",
"@types/triple-beam",
"fecha",
"ms",
"safe-stable-stringify",
"triple-beam"
]
},
"mime-db@1.52.0": {
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types@2.1.35": {
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": [
"mime-db"
]
},
"ms@2.1.3": {
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node-cron@3.0.3": {
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
"dependencies": [
"uuid"
]
},
"one-time@1.0.0": {
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"dependencies": [
"fn.name"
]
},
"proxy-from-env@1.1.0": {
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"readable-stream@3.6.2": {
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dependencies": [
"inherits",
"string_decoder",
"util-deprecate"
]
},
"safe-buffer@5.2.1": {
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safe-stable-stringify@2.5.0": {
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="
},
"simple-swizzle@0.2.2": {
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": [
"is-arrayish"
]
},
"stack-trace@0.0.10": {
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="
},
"string_decoder@1.3.0": {
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": [
"safe-buffer"
]
},
"text-hex@1.0.0": {
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
},
"triple-beam@1.4.1": {
"integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="
},
"util-deprecate@1.0.2": {
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"uuid@8.3.2": {
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"winston-transport@4.9.0": {
"integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
"dependencies": [
"logform",
"readable-stream",
"triple-beam"
]
},
"winston@3.17.0": {
"integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
"dependencies": [
"@colors/colors",
"@dabh/diagnostics",
"async",
"is-stream",
"logform",
"one-time",
"readable-stream",
"safe-stable-stringify",
"stack-trace",
"triple-beam",
"winston-transport"
]
}
},
"workspace": {
"dependencies": [
"npm:axios@^1.7.9",
"npm:node-cron@^3.0.3",
"npm:winston@^3.17.0"
]
}
}

View File

@ -1,10 +1,10 @@
# FOR DEVELOPMENT ONLY
# See README.md for an example
version: "3.8"
version: '3.8'
services:
job:
sync:
image: cupcakearmy/gitea-sync
build: .
env_file: .env
@ -18,4 +18,4 @@ services:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- '3000:3000'

View File

@ -1,21 +0,0 @@
{
"version": "1.1.0",
"type": "module",
"scripts": {
"start": "node .",
"build": "tsc",
"dev": "tsc -w"
},
"main": "./dist/index.js",
"dependencies": {
"axios": "^1.3.4",
"dotenv": "^16.0.3",
"node-cron": "^3.0.2",
"winston": "^3.8.2"
},
"devDependencies": {
"@types/node": "18",
"@types/node-cron": "^3.0.7",
"typescript": "^4.9.5"
}
}

293
pnpm-lock.yaml generated
View File

@ -1,293 +0,0 @@
lockfileVersion: 5.4
specifiers:
'@types/node': '18'
'@types/node-cron': ^3.0.7
axios: ^1.3.4
dotenv: ^16.0.3
node-cron: ^3.0.2
typescript: ^4.9.5
winston: ^3.8.2
dependencies:
axios: 1.3.4
dotenv: 16.0.3
node-cron: 3.0.2
winston: 3.8.2
devDependencies:
'@types/node': 18.14.6
'@types/node-cron': 3.0.7
typescript: 4.9.5
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.7:
resolution: {integrity: sha512-9PuLtBboc/+JJ7FshmJWv769gDonTpItN0Ol5TMwclpSQNjVyB2SRxSKBcTtbSysSL5R7Oea06kTTFNciCoYwA==}
dev: true
/@types/node/18.14.6:
resolution: {integrity: sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==}
dev: true
/@types/triple-beam/1.3.2:
resolution: {integrity: sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==}
dev: false
/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.3.4:
resolution: {integrity: sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==}
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.5.1:
resolution: {integrity: sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==}
dependencies:
'@colors/colors': 1.5.0
'@types/triple-beam': 1.3.2
fecha: 4.2.3
ms: 2.1.3
safe-stable-stringify: 2.4.2
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.1:
resolution: {integrity: sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==}
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.2:
resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==}
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.9.5:
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
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.5.1
readable-stream: 3.6.1
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.5.1
one-time: 1.0.0
readable-stream: 3.6.1
safe-stable-stringify: 2.4.2
stack-trace: 0.0.10
triple-beam: 1.3.0
winston-transport: 4.5.0
dev: false

View File

@ -1,9 +1,9 @@
import axios, { AxiosError } from 'axios'
import { Config } from '../config.js'
import { logger } from '../logger.js'
import { ListRepositoriesResponse } from './gitea.types.js'
import { Repository } from './github.types.js'
import { Config } from '../config.ts'
import { logger } from '../logger.ts'
import { ListRepositoriesResponse } from './gitea.types.ts'
import { Repository } from './github.types.ts'
const Base = axios.create({
baseURL: new URL('/api/v1', Config.gitea.host).href,
@ -12,6 +12,8 @@ const Base = axios.create({
},
})
const l = logger.child({ api: 'gitea' })
export type MirrorOptions = {
private: boolean
auth_token: string
@ -20,7 +22,7 @@ export type MirrorOptions = {
}
export async function mirror(options: MirrorOptions) {
try {
logger.debug('Mirroring repository', options)
l.debug('mirroring repository', options)
const response = await Base({
url: '/repos/migrate',
method: 'POST',
@ -41,18 +43,19 @@ export async function mirror(options: MirrorOptions) {
releases: true,
},
})
logger.debug('Mirrored repository', { data: response.data })
l.debug('mirrored repository', { data: response.data })
return response.data
} catch (e) {
if (e instanceof AxiosError) {
logger.error('Error mirroring repository', e.response?.data)
l.error('Error mirroring repository', e.response?.data)
} else {
logger.error('Unknown error', e)
l.error('Unknown error', e)
}
}
}
export async function listRepositories(page: number, limit: number) {
l.debug(`listing repos`, { page, limit })
const response = await Base<ListRepositoriesResponse>({
url: '/user/repos',
method: 'GET',
@ -65,22 +68,23 @@ export async function listRepositories(page: number, limit: number) {
}
export async function listAllRepositories() {
logger.debug('Listing all repositories in Gitea')
l.debug('listing all repositories')
const limit = 50
const repos: ListRepositoriesResponse = []
let page = 1
while (true) {
l.debug('listing page', { page })
const response = await listRepositories(page, limit)
repos.push(...response)
if (response.length < limit) break
page++
}
logger.debug('Listed all repositories in Gitea', { data: repos })
l.debug('listed all repositories', { repos: repos.map((repo) => repo.name) })
return repos
}
export async function updateRepository(owner: string, repo: string, body: Partial<Repository>) {
logger.debug('Updating repository', { owner, repo, body })
l.debug('updating repository', { owner, repo, body })
await Base({
url: `/repos/${owner}/${repo}`,
method: 'PATCH',

View File

@ -1,7 +1,8 @@
import axios from 'axios'
import { Config } from '../config.js'
import type { ListRepositoriesResponse } from './github.types.js'
import { Config } from '../config.ts'
import type { ListRepositoriesResponse } from './github.types.ts'
import { logger } from '../logger.ts'
const Base = axios.create({
baseURL: 'https://api.github.com',
@ -12,7 +13,10 @@ const Base = axios.create({
},
})
const l = logger.child({ api: 'github' })
async function listRepos(page: number, limit: number) {
l.debug('listing repos', { page, limit })
const response = await Base<ListRepositoriesResponse>({
url: `user/repos`,
method: 'GET',
@ -26,14 +30,17 @@ async function listRepos(page: number, limit: number) {
}
export async function listAllRepositories() {
l.debug('listing all repos')
const limit = 100
const repos: ListRepositoriesResponse = []
let page = 1
while (true) {
l.debug('listing page', { page })
const response = await listRepos(page, limit)
repos.push(...response)
if (response.length < limit) break
page++
}
l.debug('listed all repos')
return repos
}

View File

@ -1,7 +1,3 @@
import { config } from 'dotenv'
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>(
@ -10,21 +6,15 @@ function getEnv<T>(
parse?: (value: string) => T,
validator?: (s: string | T) => boolean
): T | string {
const value = process.env[key]
const value = Deno.env.get(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)
Deno.exit(1)
}
return parsed
}
function parseBoolean(value: string): boolean {
value = value.toLowerCase()
const truthy = ['true', 'yes', '1', 'y']
return truthy.includes(value)
}
function isPresent(s: string): boolean {
return s.length > 0
}
@ -45,5 +35,6 @@ export const Config = {
token: simple('GITEA_TOKEN'),
},
cron: getEnv('CRON', '0 */2 * * *'),
runOnce: getEnv('RUN_ONCE', false, Boolean),
version: getEnv('npm_package_version', 'unknown'),
}

View File

@ -1,34 +1,44 @@
import { listAllRepositories as giteaRepos, mirror, MirrorOptions, updateRepository } from './api/gitea.js'
import { listAllRepositories as githubRepos } from './api/github.js'
import { Config } from './config.js'
import { logger } from './logger.js'
import { listAllRepositories as giteaRepos, mirror, MirrorOptions, updateRepository } from './api/gitea.ts'
import { listAllRepositories as githubRepos } from './api/github.ts'
import { Config } from './config.ts'
import { logger } from './logger.ts'
let running = false
const l = logger.child({ context: 'runner' })
export async function sync() {
if (running) {
logger.info('Already running, skipping')
l.info('already running, skipping')
return
}
try {
logger.info('Starting sync')
l.info('starting sync')
const syncedRepos = await giteaRepos()
const toSync = await githubRepos()
logger.debug('Loaded repos', { remote: toSync.length, local: syncedRepos.length })
l.debug('loaded repos', { remote: toSync.length, local: syncedRepos.length })
// List of all the repos in gitea, that are not on github
const notInSource = new Set(syncedRepos.map((r) => r.name))
for (const repo of toSync) {
const lr = l.child({ repo: repo.name })
const sameName = syncedRepos.find((r) => r.name === repo.name || r.original_url === repo.clone_url)
if (sameName) {
notInSource.delete(sameName.name)
if (sameName.original_url === repo.clone_url) {
if (sameName.private === repo.private) logger.info('Already synced, skipping', { name: repo.name })
else {
logger.info('Visibility changed, updating', { name: repo.name })
lr.info('visibility changed, updating')
const [owner, repository] = sameName.full_name.split('/')
if (!owner || !repository) {
lr.error('invalid repository name', { full_name: sameName.full_name })
continue
}
await updateRepository(owner, repository, { private: repo.private })
}
} else {
logger.error('Repo with same name but different url', {
name: repo.name,
lr.error('repo with same name but different url', {
url: repo.clone_url,
original_url: sameName.original_url,
})
@ -42,14 +52,17 @@ export async function sync() {
private: repo.private,
auth_token: Config.github.token,
}
logger.info('Mirroring repository', options)
lr.info('mirroring repository', options)
await mirror(options)
logger.info('Mirrored repository', { name: repo.name })
lr.info('mirrored repository')
}
logger.info('Finished sync')
if (notInSource.size) {
l.info(`Found ${notInSource.size} surplus repositories in gitea`, { repos: [...notInSource] })
}
l.info('Finished sync')
} catch (error) {
logger.debug(error)
logger.error('Failed to sync', { error: error instanceof Error ? error.message : 'Unknown error' })
l.debug(error)
l.error('Failed to sync', { error: error instanceof Error ? error.message : 'Unknown error' })
} finally {
running = false
}

View File

@ -1,10 +1,18 @@
import cron from 'node-cron'
import { Config } from './config.js'
import { sync } from './core.js'
import { logger } from './logger.js'
import { Config } from './config.ts'
import { sync } from './core.ts'
import { logger } from './logger.ts'
logger.info(`Mirror manager - ${Config.version}`, { version: Config.version })
Deno.addSignalListener('SIGINT', () => {
console.log('exiting...')
Deno.exit()
})
// Run on startup once, then delegate to cron
await sync()
cron.schedule(Config.cron, sync)
if (!Config.runOnce) {
cron.schedule(Config.cron, sync)
}

View File

@ -1,6 +1,6 @@
import winston from 'winston'
import { Config } from './config.js'
import { Config } from './config.ts'
export const logger = winston.createLogger({
level: Config.logging.level,

View File

@ -1,12 +0,0 @@
{
"compilerOptions": {
"target": "es2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"module": "ES2022" /* Specify what module code is generated. */,
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}