snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

246
.github/actions/build-docker/action.yml vendored Normal file
View File

@@ -0,0 +1,246 @@
name: 'Build Docker'
description: 'Build Docker images for Rocket.Chat and related services'
inputs:
CR_USER:
required: true
description: 'GitHub Container Registry username'
CR_PAT:
required: true
description: 'GitHub Container Registry Personal Access Token'
DOCKER_USER:
required: false
description: 'DockerHub username (required for FIPS builds to pull private base image)'
DOCKER_PASS:
required: false
description: 'DockerHub password (required for FIPS builds to pull private base image)'
deno-version:
required: true
description: 'Deno version'
arch:
required: false
description: 'Architecture'
default: 'arm64'
service:
required: false
description: 'Container to build'
publish-image:
required: false
description: 'Publish image'
default: 'true'
setup-docker:
required: false
description: 'Setup Docker'
default: 'true'
type:
required: false
description: 'production, coverage, or fips'
default: 'coverage'
runs:
using: composite
steps:
- name: Login to GitHub Container Registry
if: inputs.publish-image == 'true' && github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop')
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ inputs.CR_USER }}
password: ${{ inputs.CR_PAT }}
- name: Login to DockerHub
# FIPS base image (rocketchat/dhi-node) lives in a private DockerHub repo and requires auth to pull.
if: inputs.type == 'fips' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.event_name == 'merge_group' || github.ref == 'refs/heads/develop')
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
username: ${{ inputs.DOCKER_USER }}
password: ${{ inputs.DOCKER_PASS }}
- name: Restore meteor build
if: inputs.service == 'rocketchat'
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: build-${{ inputs.type == 'fips' && ((github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production') || inputs.type }}
path: /tmp/build
- name: Unpack meteor build
if: inputs.service == 'rocketchat'
shell: bash
run: |
cd /tmp/build
tar xzf Rocket.Chat.tar.gz
rm Rocket.Chat.tar.gz
- name: Set up Docker
if: inputs.setup-docker == 'true'
uses: docker/setup-docker-action@e43656e248c0bd0647d3f5c195d116aacf6fcaf4 # v4.7.0
with:
daemon-config: |
{
"debug": false,
"features": {
"containerd-snapshotter": true
}
}
# Microservice images build from a pruned, self-contained context (out/<service>)
# produced by `turbo prune`, instead of a hand-maintained list of COPY lines.
# The rocketchat (Meteor) image is a prebuilt bundle and is staged separately above.
- name: Stage service build context
if: inputs.service != 'rocketchat'
shell: bash
env:
INPUT_SERVICE: ${{ inputs.service }}
run: |
set -o xtrace
# The Docker SERVICE arg (folder under ee/apps) lives in the compose build args;
# the compose service name (INPUT_SERVICE) can differ (e.g. ddp-streamer-service).
SERVICE_DIR=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].build.args.SERVICE')
PKG_NAME=$(node -p "require('./ee/apps/${SERVICE_DIR}/package.json').name")
TURBO_VERSION=$(node -p "require('./package.json').devDependencies.turbo")
rm -rf "out/${SERVICE_DIR}"
# `turbo prune` derives the exact workspace dependency closure for the service — no
# hand-maintained list. --use-gitignore=false also captures the gitignored build output
# we need (dist/, generated definition/), at the cost of dragging in node_modules/.turbo,
# which we strip below; `yarn workspaces focus --production` rebuilds node_modules in-image.
npx -y "turbo@${TURBO_VERSION}" prune "$PKG_NAME" --skip-infer --use-gitignore=false --out-dir "out/${SERVICE_DIR}"
find "out/${SERVICE_DIR}" \( -name node_modules -o -name .turbo \) -type d -prune -exec rm -rf {} +
- name: Build Docker images
shell: bash
env:
DENO_VERSION: ${{ inputs.deno-version }}
INPUT_ARCH: ${{ inputs.arch }}
INPUT_SERVICE: ${{ inputs.service }}
INPUT_PUBLISH_IMAGE: ${{ inputs.publish-image }}
INPUT_TYPE: ${{ inputs.type }}
GITHUB_RUN_ID: ${{ github.run_id }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
GITHUB_REF: ${{ github.ref }}
run: |
set -o xtrace
compose_fips_override=''
if [[ "$INPUT_TYPE" == 'fips' ]]; then
compose_fips_override='-f docker-compose-ci.fips.yml'
fi
# Removes unnecessary swc cores and sharp binaries to reduce image size
swc_arch='x64'
if [[ "$INPUT_SERVICE" == 'rocketchat' ]]; then
if [[ "$INPUT_ARCH" == 'arm64' ]]; then
swc_arch='arm64'
fi
find /tmp/build/bundle/programs/server/npm/node_modules/@img -type d -name 'sharp-*' -not -name "*-linuxmusl-${swc_arch}" -exec rm -rf {} +
find /tmp/build/bundle/programs/server/npm/node_modules/@napi-rs -type d -name 'pinyin-linux-*' -not -name "*-linux-${swc_arch}-*" -exec rm -rf {} +
find /tmp/build/bundle/programs/server/npm/node_modules/@esbuild -type d -name 'linux-*' -not -name "*-${swc_arch}" -exec rm -rf {} +
find /tmp/build/bundle/programs/server/npm/node_modules/@rocket.chat/apps/node_modules/@esbuild -type d -name 'linux-*' -not -name "*-${swc_arch}" -exec rm -rf {} +
fi
if [[ "$INPUT_PUBLISH_IMAGE" == 'true' ]]; then
LOAD_OR_PUSH="--push"
else
LOAD_OR_PUSH="--load"
fi
# Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat)
IMAGE=$(docker compose -f docker-compose-ci.yml $compose_fips_override config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image')
docker buildx bake \
-f docker-compose-ci.yml $compose_fips_override \
${LOAD_OR_PUSH} \
--allow=fs.read=/tmp/build \
--set "*.tags+=${IMAGE}-gha-run-${GITHUB_RUN_ID}" \
--set "*.labels.org.opencontainers.image.description=Build run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
--set "*.labels.org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \
--set "*.platform=linux/${INPUT_ARCH}" \
--set *.cache-from=type=gha \
--set *.cache-to=type=gha,mode=max \
--provenance=false \
--sbom=false \
--metadata-file "/tmp/meta.json" \
"$INPUT_SERVICE"
echo "Contents of /tmp/meta.json:"
cat /tmp/meta.json
if [[ "$INPUT_PUBLISH_IMAGE" == 'true' ]]; then
SERVICE_SUFFIX=''
if [[ "$INPUT_SERVICE" == 'rocketchat' && "$INPUT_TYPE" == 'coverage' ]] && [[ "$GITHUB_EVENT_NAME" == 'release' || "$GITHUB_REF" == 'refs/heads/develop' ]]; then
SERVICE_SUFFIX='-cov'
elif [[ "$INPUT_TYPE" == 'fips' ]]; then
SERVICE_SUFFIX='-fips'
fi
mkdir -p "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}"
# Get digest and image info
DIGEST=$(jq -r --arg service "$INPUT_SERVICE" '.[$service].["containerimage.digest"]' "/tmp/meta.json")
IMAGE_NO_TAG=$(echo "$IMAGE" | sed 's/:.*$//')
FULL_IMAGE="${IMAGE_NO_TAG}@${DIGEST}"
echo "Inspecting image: $FULL_IMAGE"
# Inspect the image and save complete manifest with sizes (using -v for verbose)
docker manifest inspect -v "$FULL_IMAGE" > "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}/manifest.json"
echo "Saved manifest to /tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}/manifest.json"
cat "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}/manifest.json" | jq '.'
fi
- name: Save Docker image as artifact
if: inputs.publish-image == 'false' && inputs.arch == 'amd64'
shell: bash
env:
SERVICE: ${{ inputs.service }}
ARCH: ${{ inputs.arch }}
TYPE: ${{ inputs.type }}
run: |
set -o xtrace
compose_fips_override=''
if [[ "$TYPE" == 'fips' ]]; then
compose_fips_override='-f docker-compose-ci.fips.yml'
fi
# Get image name from docker-compose-ci.yml
IMAGE=$(docker compose -f docker-compose-ci.yml $compose_fips_override config --format json 2>/dev/null | jq -r --arg s "$SERVICE" '.services[$s].image')
# Create directory for image archives
mkdir -p /tmp/docker-images
# Save the image to a tar file
docker save "${IMAGE}" -o "/tmp/docker-images/${SERVICE}-${ARCH}-${TYPE}.tar"
echo "Saved image to /tmp/docker-images/${SERVICE}-${ARCH}-${TYPE}.tar"
ls -lh /tmp/docker-images/
- name: Upload Docker image artifact
if: inputs.publish-image == 'false' && inputs.arch == 'amd64'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: docker-image-${{ inputs.service }}-${{ inputs.arch }}-${{ inputs.type }}
path: /tmp/docker-images/${{ inputs.service }}-${{ inputs.arch }}-${{ inputs.type }}.tar
retention-days: 1
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: inputs.publish-image == 'true'
with:
name: manifests-${{ inputs.service }}-${{ inputs.arch }}-${{ inputs.type }}
path: /tmp/manifests
retention-days: 5
- name: Clean up temporary files
if: inputs.service == 'rocketchat'
shell: bash
run: |
sudo rm -rf /tmp/build

View File

@@ -0,0 +1,581 @@
name: 'Docker Image Size Tracker'
description: 'Track and report Docker image sizes in Pull Requests'
author: 'Rocket.Chat'
inputs:
github-token:
description: 'GitHub token for commenting on PRs'
required: true
ci-pat:
description: 'GitHub token for committing to history branch'
required: true
registry:
description: 'Container registry (e.g., ghcr.io)'
required: false
default: 'ghcr.io'
repository:
description: 'Repository name (e.g., rocketchat)'
required: true
tag:
description: 'Image tag to measure'
required: true
baseline-tag:
description: 'Baseline tag to compare against'
required: false
default: 'develop'
platform:
description: 'Platform architecture to compare (amd64 or arm64)'
required: false
default: 'amd64'
size-thresholds:
description: 'Optional JSON: per-image trigger thresholds. Only comment when an image increase exceeds its threshold. Example: {"rocketchat":{"mb":50,"percent":5},"omnichannel":{"mb":10,"percent":2}}'
required: false
default: ''
fail-thresholds:
description: 'Optional JSON: per-image fail thresholds. Task fails when an image increase exceeds its threshold (mb and/or percent). Example: {"rocketchat":{"mb":100,"percent":15}}'
required: false
default: ''
outputs:
total-size:
description: 'Total size in bytes'
value: ${{ steps.measure.outputs.total-size }}
size-diff:
description: 'Size difference in bytes'
value: ${{ steps.compare.outputs.size-diff }}
size-diff-percent:
description: 'Size difference percentage'
value: ${{ steps.compare.outputs.size-diff-percent }}
comment-triggered:
description: 'Whether to post PR comment (only when size is bigger and thresholds met)'
value: ${{ steps.compare.outputs.comment-triggered }}
failed:
description: 'True if image size exceeded fail-thresholds'
value: ${{ steps.compare.outputs.failed }}
runs:
using: 'composite'
steps:
- name: Download manifests
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
pattern: manifests-*
path: /tmp/manifests
merge-multiple: true
- name: Measure image sizes from artifacts
id: measure
shell: bash
env:
PLATFORM: ${{ inputs.platform }}
TAG: ${{ inputs.tag }}
run: |
echo "Reading image sizes from build artifacts for platform: $PLATFORM"
declare -A sizes
declare -a services_list
total=0
# Loop through service directories (same as publish workflow)
shopt -s nullglob
for service_dir in /tmp/manifests/*; do
[[ -d "$service_dir" ]] || continue
service="$(basename "$service_dir")"
echo "Processing service: $service"
services_list+=("$service")
size=0
# Read only the specified platform architecture
manifest_file="$service_dir/$PLATFORM/manifest.json"
if [[ -f "$manifest_file" ]]; then
# docker manifest inspect -v puts the manifest under SchemaV2Manifest
# (Docker schema2 images) or OCIManifest (OCI images, which buildx
# produces by default). Coalesce so both formats are read.
config_size=$(jq -r '(.SchemaV2Manifest // .OCIManifest).config.size // 0' "$manifest_file")
layers_size=$(jq '[(.SchemaV2Manifest // .OCIManifest).layers[]?.size // 0] | add // 0' "$manifest_file")
size=$((config_size + layers_size))
echo " → Found $manifest_file: $size bytes (config: $config_size, layers: $layers_size)"
else
echo " ⚠ Manifest not found for platform $PLATFORM: $manifest_file"
fi
sizes[$service]=$size
total=$((total + size))
done
echo "Total size (all services, $PLATFORM only): $total bytes"
echo "total-size=$total" >> $GITHUB_OUTPUT
# Save to JSON
echo "{" > current-sizes.json
echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"," >> current-sizes.json
echo " \"tag\": \"$TAG\"," >> current-sizes.json
echo " \"total\": $total," >> current-sizes.json
echo " \"services\": {" >> current-sizes.json
first=true
for service in "${services_list[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo "," >> current-sizes.json
fi
echo " \"$service\": ${sizes[$service]}" >> current-sizes.json
done
echo " }" >> current-sizes.json
echo "}" >> current-sizes.json
echo "Current sizes saved:"
cat current-sizes.json
# Save services list for baseline measurement
printf '%s\n' "${services_list[@]}" > /tmp/services-list.txt
- name: Measure baseline
id: baseline
shell: bash
continue-on-error: true
env:
REGISTRY: ${{ inputs.registry }}
ORG: ${{ inputs.repository }}
TAG: ${{ inputs.baseline-tag }}
PLATFORM: ${{ inputs.platform }}
run: |
echo "Measuring baseline: $REGISTRY/$ORG/*:$TAG (platform: $PLATFORM)"
declare -A sizes
declare -a services_list
total=0
# Read services list from current measurement
while IFS= read -r service; do
services_list+=("$service")
# Map service name to image name (handle rocketchat -> rocket.chat)
if [[ "$service" == "rocketchat" ]] || [[ "$service" == "rocketchat-cov" ]]; then
image_name="rocket.chat"
[[ "$service" == "rocketchat-cov" ]] && image_name="rocket.chat-cov"
else
image_name="$service"
fi
image="$REGISTRY/$ORG/$image_name:$TAG"
echo " → Inspecting $image"
size=0
if manifest=$(docker manifest inspect "$image" 2>/dev/null); then
# Check if it's a manifest list (multi-arch)
if echo "$manifest" | jq -e '.manifests' > /dev/null 2>&1; then
# Manifest list - find the specified platform
echo " → Multi-arch manifest detected, filtering for $PLATFORM"
platform_digest=$(echo "$manifest" | jq -r --arg arch "$PLATFORM" '.manifests[] | select(.platform.architecture == $arch) | .digest' | head -1)
if [[ -n "$platform_digest" ]]; then
echo " → Inspecting $PLATFORM platform: $platform_digest"
if platform_manifest=$(docker manifest inspect "$REGISTRY/$ORG/$image_name@$platform_digest" 2>/dev/null); then
config_size=$(echo "$platform_manifest" | jq -r '.config.size // 0')
layers_size=$(echo "$platform_manifest" | jq '[.layers[]?.size // 0] | add // 0')
size=$((config_size + layers_size))
echo " → Size: $size bytes"
fi
else
echo " ⚠ Platform $PLATFORM not found in manifest"
fi
else
# Single arch manifest
config_size=$(echo "$manifest" | jq -r '.config.size // 0')
layers_size=$(echo "$manifest" | jq '[.layers[]?.size // 0] | add // 0')
size=$((config_size + layers_size))
fi
fi
sizes[$service]=$size
total=$((total + size))
done < /tmp/services-list.txt
echo "baseline-total=$total" >> $GITHUB_OUTPUT
echo "{" > baseline-sizes.json
echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"," >> baseline-sizes.json
echo " \"tag\": \"$TAG\"," >> baseline-sizes.json
echo " \"total\": $total," >> baseline-sizes.json
echo " \"services\": {" >> baseline-sizes.json
first=true
for service in "${services_list[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo "," >> baseline-sizes.json
fi
echo " \"$service\": ${sizes[$service]}" >> baseline-sizes.json
done
echo " }" >> baseline-sizes.json
echo "}" >> baseline-sizes.json
- name: Setup history storage
id: history
shell: bash
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
# Create a separate worktree for history branch
mkdir -p /tmp/history-worktree
# Try to fetch history branch
if git ls-remote --heads origin image-size-history | grep -q image-size-history; then
git fetch origin image-size-history
git worktree add /tmp/history-worktree image-size-history
else
# Create orphan branch for history in worktree
git worktree add --detach /tmp/history-worktree
cd /tmp/history-worktree
git checkout --orphan image-size-history
git rm -rf . 2>/dev/null || true
mkdir -p history
echo "# Image Size History" > README.md
echo "This branch stores historical image size data for tracking" >> README.md
git add README.md
git commit -m "Initialize image size history"
git push origin image-size-history
cd -
fi
mkdir -p /tmp/history-worktree/history
- name: Load historical data
shell: bash
run: |
# Load last 30 measurements and group by day (keep only last entry per day)
echo "[]" > history-data.json
if [[ -d /tmp/history-worktree/history ]]; then
jq -s '.' /tmp/history-worktree/history/*.json 2>/dev/null | jq '
sort_by(.timestamp) |
group_by(.timestamp[0:10]) |
map(.[-1]) |
.[-30:]
' > history-data.json || echo "[]" > history-data.json
fi
count=$(jq 'length' history-data.json)
echo "Loaded $count historical measurements (one per day)"
- name: Save current measurement to history
if: github.ref == 'refs/heads/develop'
shell: bash
env:
CI_PAT: ${{ inputs.ci-pat }}
run: |
timestamp=$(date -u +%Y%m%d-%H%M%S)
commit_sha="${{ github.sha }}"
# Add commit info to current measurement
jq --arg sha "$commit_sha" '. + {commit: $sha}' current-sizes.json > "/tmp/history-worktree/history/${timestamp}.json"
cd /tmp/history-worktree
git add "history/${timestamp}.json"
git commit -m "Add measurement for ${timestamp} (${commit_sha:0:7})"
git config --global user.email "ci@rocket.chat"
git config --global user.name "rocketchat-ci[bot]"
git config --global url.https://$CI_PAT@github.com/.insteadOf https://github.com/
git push origin image-size-history
cd -
echo "Saved measurement to history"
- name: Compare and generate report
id: compare
shell: bash
env:
SIZE_THRESHOLDS: ${{ inputs.size-thresholds }}
FAIL_THRESHOLDS: ${{ inputs.fail-thresholds }}
TAG: ${{ inputs.tag }}
BASELINE_TAG: ${{ inputs.baseline-tag }}
run: |
current_total=$(jq -r '.total' current-sizes.json)
if [[ ! -f baseline-sizes.json ]]; then
echo "No baseline available"
echo "size-diff=0" >> $GITHUB_OUTPUT
echo "size-diff-percent=0" >> $GITHUB_OUTPUT
echo "comment-triggered=false" >> $GITHUB_OUTPUT
cat > report.md << 'EOF'
# 📦 Docker Image Size Report
**Status:** First measurement - no baseline for comparison
**Total Size:** $(numfmt --to=iec-i --suffix=B $current_total)
EOF
exit 0
fi
baseline_total=$(jq -r '.total' baseline-sizes.json)
diff=$((current_total - baseline_total))
if [[ $baseline_total -gt 0 ]]; then
percent=$(awk "BEGIN {printf \"%.2f\", ($diff / $baseline_total) * 100}")
else
percent=0
fi
echo "size-diff=$diff" >> $GITHUB_OUTPUT
echo "size-diff-percent=$percent" >> $GITHUB_OUTPUT
# Only comment when size is bigger than baseline; optionally require per-image thresholds
THRESHOLDS="$SIZE_THRESHOLDS"
FAIL_THRESHOLDS="$FAIL_THRESHOLDS"
comment_triggered=false
fail_triggered=false
if [[ $diff -gt 0 ]]; then
if [[ -z "$THRESHOLDS" ]] || [[ "$THRESHOLDS" == "{}" ]]; then
comment_triggered=true
fi
fi
color="gray"
if (( $(awk "BEGIN {print ($percent > 0.01)}") )); then
color="red"
elif (( $(awk "BEGIN {print ($percent < -0.01)}") )); then
color="green"
fi
# Generate report
if [[ $diff -gt 0 ]]; then
emoji="📈"
badge="![](https://img.shields.io/badge/size-+${percent}%25-${color})"
sign="+"
elif [[ $diff -lt 0 ]]; then
emoji="📉"
badge="![](https://img.shields.io/badge/size--${percent}%25-${color})"
sign="-"
else
emoji="➡️"
badge="![](https://img.shields.io/badge/size-unchanged-${color})"
sign=""
fi
cat > report.md << EOF
# 📦 Docker Image Size Report
## $emoji Changes $badge
| Service | Current | Baseline | Change | Percent |
|---------|---------|----------|--------|---------|
| **sum of all images** | **$(numfmt --to=iec-i --suffix=B $current_total)** | **$(numfmt --to=iec-i --suffix=B $baseline_total)** | **${sign}$(numfmt --to=iec-i --suffix=B ${diff#-})** | $badge |
EOF
# Get services dynamically from current-sizes.json, sorted by size (largest first)
for service in $(jq -r '.services | to_entries | sort_by(-.value) | .[].key' current-sizes.json); do
current=$(jq -r ".services.\"$service\"" current-sizes.json)
baseline=$(jq -r ".services.\"$service\" // 0" baseline-sizes.json)
service_diff=$((current - baseline))
if [[ $baseline -gt 0 ]]; then
service_percent=$(awk "BEGIN {printf \"%.2f\", ($service_diff / $baseline) * 100}")
else
service_percent=0
fi
# Check per-image thresholds for comment trigger (only when size increased)
if [[ $diff -gt 0 ]] && [[ -n "$THRESHOLDS" ]] && [[ "$THRESHOLDS" != "{}" ]]; then
threshold_mb=$(echo "$THRESHOLDS" | jq -r ".\"$service\".mb // empty")
threshold_pct=$(echo "$THRESHOLDS" | jq -r ".\"$service\".percent // empty")
if [[ -n "$threshold_mb" ]] || [[ -n "$threshold_pct" ]]; then
exceeded=false
if [[ -n "$threshold_mb" ]] && [[ $service_diff -ge $(awk "BEGIN {printf \"%.0f\", $threshold_mb * 1048576}") ]]; then
exceeded=true
fi
if [[ -n "$threshold_pct" ]] && [[ $service_percent != "0.00" ]] && (( $(awk "BEGIN {print ($service_percent >= $threshold_pct)}") )); then
exceeded=true
fi
[[ "$exceeded" == "true" ]] && comment_triggered=true
fi
fi
# Check per-image fail thresholds (task fails when exceeded)
if [[ $diff -gt 0 ]] && [[ -n "$FAIL_THRESHOLDS" ]] && [[ "$FAIL_THRESHOLDS" != "{}" ]]; then
fail_mb=$(echo "$FAIL_THRESHOLDS" | jq -r ".\"$service\".mb // empty")
fail_pct=$(echo "$FAIL_THRESHOLDS" | jq -r ".\"$service\".percent // empty")
if [[ -n "$fail_mb" ]] || [[ -n "$fail_pct" ]]; then
exceeded=false
if [[ -n "$fail_mb" ]] && [[ $service_diff -ge $(awk "BEGIN {printf \"%.0f\", $fail_mb * 1048576}") ]]; then
exceeded=true
fi
if [[ -n "$fail_pct" ]] && [[ $service_percent != "0.00" ]] && (( $(awk "BEGIN {print ($service_percent >= $fail_pct)}") )); then
exceeded=true
fi
[[ "$exceeded" == "true" ]] && fail_triggered=true
fi
fi
color="gray"
if (( $(awk "BEGIN {print ($service_percent > 0.01)}") )); then
color="red"
elif (( $(awk "BEGIN {print ($service_percent < -0.01)}") )); then
color="green"
fi
if [[ $service_diff -gt 0 ]]; then
badge="![](https://img.shields.io/badge/size-+${service_percent}%25-${color})"
sign="+"
elif [[ $service_diff -lt 0 ]]; then
badge="![](https://img.shields.io/badge/size--${service_percent}%25-${color})"
sign="-"
else
badge="![](https://img.shields.io/badge/size-unchanged-${color})"
sign=""
fi
echo "| $service | $(numfmt --to=iec-i --suffix=B $current) | $(numfmt --to=iec-i --suffix=B $baseline) | ${sign}$(numfmt --to=iec-i --suffix=B ${service_diff#-}) | $badge |" >> report.md
done
# Generate historical trend chart
echo "" >> report.md
echo "## 📊 Historical Trend" >> report.md
echo "" >> report.md
# Load history and generate mermaid chart
history_count=$(jq 'length' history-data.json)
if [[ $history_count -gt 0 ]]; then
# Get all services from current build
all_services=$(jq -r '.services | keys | .[]' current-sizes.json | sort)
# Generate Mermaid chart data
x_labels=""
declare -A service_data
# Initialize service data arrays
for service in $all_services; do
service_data[$service]=""
done
# Process historical data
while IFS= read -r line; do
timestamp=$(echo "$line" | jq -r '.timestamp')
date_label=$(date -d "$timestamp" +"%m/%d %H:%M")
if [[ -z "$x_labels" ]]; then
x_labels="\"$date_label\""
else
x_labels="$x_labels, \"$date_label\""
fi
# Add data point for each service
for service in $all_services; do
size=$(echo "$line" | jq -r ".services.\"$service\" // 0")
size_gb=$(awk "BEGIN {printf \"%.2f\", $size / 1073741824}")
if [[ -z "${service_data[$service]}" ]]; then
service_data[$service]="$size_gb"
else
service_data[$service]="${service_data[$service]}, $size_gb"
fi
done
done < <(jq -c '.[]' history-data.json)
# Add current PR as last point
current_date=$(date -u +"%m/%d %H:%M")
x_labels="$x_labels, \"$current_date (PR)\""
for service in $all_services; do
size=$(jq -r ".services.\"$service\" // 0" current-sizes.json)
size_gb=$(awk "BEGIN {printf \"%.2f\", $size / 1073741824}")
service_data[$service]="${service_data[$service]}, $size_gb"
done
# Generate mermaid chart with multiple lines
cat >> report.md << EOF
\`\`\`mermaid
---
config:
theme: "dark"
xyChart:
width: 900
height: 400
---
xychart
title "Image Size Evolution by Service (Last 30 Days + This PR)"
x-axis [$x_labels]
y-axis "Size (GB)" 0 --> 0.5
EOF
# Add a line for each service
for service in $all_services; do
echo " line \"$service\" [${service_data[$service]}]" >> report.md
done
cat >> report.md << 'EOF'
```
EOF
# Add summary stats
min_size=$(jq '[.[].total] | min' history-data.json)
max_size=$(jq '[.[].total] | max' history-data.json)
avg_size=$(jq '[.[].total] | add / length' history-data.json)
cat >> report.md << EOF
**Statistics (last $history_count days):**
- 📊 Average: $(numfmt --to=iec-i --suffix=B ${avg_size%.*})
- ⬇️ Minimum: $(numfmt --to=iec-i --suffix=B $min_size)
- ⬆️ Maximum: $(numfmt --to=iec-i --suffix=B $max_size)
- 🎯 Current PR: $(numfmt --to=iec-i --suffix=B $current_total)
EOF
else
cat >> report.md << 'EOF'
*No historical data available yet. History tracking starts after merging to develop.*
EOF
fi
cat >> report.md << EOF
<details>
<summary>ℹ️ About this report</summary>
This report compares Docker image sizes from this build against the \`$BASELINE_TAG\` baseline.
- **Tag:** \`$TAG\`
- **Baseline:** \`$BASELINE_TAG\`
- **Timestamp:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
- **Historical data points:** $history_count
</details>
EOF
echo "comment-triggered=$comment_triggered" >> $GITHUB_OUTPUT
echo "failed=$fail_triggered" >> $GITHUB_OUTPUT
if [[ "$fail_triggered" == "true" ]]; then
echo "::error::Docker image size exceeded fail-thresholds (mb/percent). Check the report for details."
exit 1
fi
- name: Add report to Job Summary
if: always()
shell: bash
run: |
if [[ -f report.md ]]; then
cat report.md >> $GITHUB_STEP_SUMMARY
fi
- name: Cleanup worktree
if: always()
shell: bash
run: |
if [[ -d /tmp/history-worktree ]]; then
git worktree remove /tmp/history-worktree --force 2>/dev/null || true
fi
branding:
icon: 'package'
color: 'blue'

218
.github/actions/meteor-build/action.yml vendored Normal file
View File

@@ -0,0 +1,218 @@
name: 'Meteor Build'
description: 'Build Meteor bundle for production or coverage'
inputs:
type:
required: false
description: 'production or coverage'
default: 'production'
reset-meteor:
required: false
description: 'Reset Meteor'
node-version:
required: true
description: 'Node version'
NPM_TOKEN:
required: false
description: 'NPM token'
deno-version:
required: true
description: 'Deno version'
source-hash:
required: true
description: 'RC cache id (e.g. packages-build-cache-key + meteor apps/meteor archive hash from CI)'
runs:
using: composite
steps:
- name: Cache build
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: cache-build
with:
path: /tmp/Rocket.Chat.tar.gz
key: ${{ runner.arch }}-${{ runner.os }}-${{ inputs.type }}-rc-build-${{ inputs.source-hash }}
- name: Set Swap Space
uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c # v1.0
if: steps.cache-build.outputs.cache-hit != 'true'
with:
swap-size-gb: 4
- name: Merge dependencies for build
shell: bash
if: steps.cache-build.outputs.cache-hit != 'true'
run: |
# Merge dependencies and devDependencies into a new 'dependencies' field
cd apps/meteor
cp package.json package.json.bak
jq '.dependencies = (.dependencies + .devDependencies) | del(.devDependencies)' package.json > package.json.tmp && mv package.json.tmp package.json
- name: Setup NodeJS
uses: ./.github/actions/setup-node
if: steps.cache-build.outputs.cache-hit != 'true'
with:
node-version: ${{ inputs.node-version }}
deno-version: ${{ inputs.deno-version }}
cache-modules: true
install: true
type: 'production'
NPM_TOKEN: ${{ inputs.NPM_TOKEN }}
# - name: Free disk space
# run: |
# sudo apt clean
# docker rmi $(docker image ls -aq)
# df -h
- name: Cache vite
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: steps.cache-build.outputs.cache-hit != 'true'
with:
path: ./node_modules/.vite
key: vite-local-cache-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('package.json') }}
restore-keys: |
vite-local-cache-${{ runner.arch }}-${{ runner.os }}-
- name: Cache meteor local
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: steps.cache-build.outputs.cache-hit != 'true'
with:
path: ./apps/meteor/.meteor/local
key: meteor-local-cache-${{ runner.arch }}-${{ runner.os }}-${{ inputs.type }}-${{ hashFiles('apps/meteor/.meteor/versions') }}
restore-keys: |
meteor-local-cache-${{ runner.arch }}-${{ runner.os }}-${{ inputs.type }}-
- name: Cache meteor
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: steps.cache-build.outputs.cache-hit != 'true'
with:
path: ~/.meteor
key: meteor-cache-${{ runner.arch }}-${{ runner.os }}-${{ inputs.type }}-${{ hashFiles('apps/meteor/.meteor/release') }}
restore-keys: |
meteor-cache-${{ runner.arch }}-${{ runner.os }}-${{ inputs.type }}-
- name: Install Meteor
shell: bash
if: steps.cache-build.outputs.cache-hit != 'true'
run: |
# Restore bin from cache
set +e
METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor)
METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET")
set -e
LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor
if [ -e $LAUNCHER ]
then
echo "Cached Meteor bin found, restoring it"
sudo cp "$LAUNCHER" "/usr/local/bin/meteor"
else
echo "No cached Meteor bin found."
fi
# only install meteor if bin isn't found
command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh
- name: Versions
shell: bash
if: steps.cache-build.outputs.cache-hit != 'true'
run: |
npm --versions
yarn -v
node -v
meteor --version
meteor npm --versions
meteor node -v
git version
- uses: rharkor/caching-for-turbo@00a0515f175df9fd2e15c4560144ad5fdbebb0c7 # v2.3.13
- name: Reset Meteor
shell: bash
if: ${{ steps.cache-build.outputs.cache-hit != 'true' && inputs.reset-meteor == 'true' }}
working-directory: ./apps/meteor
run: meteor reset
- name: Restore packages build
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: packages-build
path: /tmp
- name: Unpack packages build
shell: bash
run: |
tar -xzf /tmp/RocketChat-packages-build.tar.gz -C .
- name: Build Rocket.Chat
shell: bash
if: steps.cache-build.outputs.cache-hit != 'true'
env:
METEOR_PROFILE: 1000
BABEL_ENV: ${{ inputs.type }}
run: |
# check if BABEL_ENV is set to coverage
if [[ $BABEL_ENV == "coverage" ]]; then
echo -e "rocketchat:coverage\n" >> ./apps/meteor/.meteor/packages
# Inject swc-plugin-coverage-instrument into .swcrc for istanbul-compatible coverage
node -e "
const fs = require('fs');
const swcrc = JSON.parse(fs.readFileSync('./apps/meteor/.swcrc', 'utf8'));
swcrc.jsc.experimental = swcrc.jsc.experimental || {};
swcrc.jsc.experimental.plugins = swcrc.jsc.experimental.plugins || [];
swcrc.jsc.experimental.plugins.push(['swc-plugin-coverage-instrument', {}]);
fs.writeFileSync('./apps/meteor/.swcrc', JSON.stringify(swcrc, null, 2) + '\n');
"
echo "Coverage enabled (SWC istanbul plugin injected)"
fi
# Restore original package.json so meteor should not copy devDependencies
mv apps/meteor/package.json.bak apps/meteor/package.json
yarn build:ci
declare -a meter_modules_to_remove=(
"meteor/babel-compiler/node_modules/@meteorjs/swc-core/.swc/node_modules/@swc/core-darwin-arm64" # Removes 35M
"meteor/babel-compiler/node_modules/@meteorjs/swc-core/.swc/node_modules/@swc/core-linux-x64-musl" # Removes 58M
"meteor/babel-compiler/node_modules/@meteorjs/swc-core/.swc/node_modules/@swc/core-linux-arm64-musl" # Removes 44M
"meteor/babel-compiler/node_modules/typescript" # Removes 31M
# "meteor/babel-compiler/node_modules/@babel" # Removes 14M
"@rocket.chat/i18n/src" # Removes 16M
"typescript" # Removes 19M
# "@babel" # Removes 34M - Needed by Minimongo
)
du -s /tmp/dist/bundle
for dir_path in "${meter_modules_to_remove[@]}"; do
path=/tmp/dist/bundle/programs/server/npm/node_modules/${dir_path}
if [ -d "$path" ]; then
rm -rf "$path"
echo "Removed directory: $path"
else
echo "Path is not a directory or does not exist: $path"
fi
done
# Remove all .d.ts files from node_modules to reduce size
# Removes 184M
find /tmp/dist/bundle -type f -name "*.d.ts" -delete
du -s /tmp/dist/bundle
- name: Prepare build
shell: bash
if: steps.cache-build.outputs.cache-hit != 'true'
run: |
cd /tmp/dist
tar czf /tmp/Rocket.Chat.tar.gz bundle
- name: Store build
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: build-${{ inputs.type }}
path: /tmp/Rocket.Chat.tar.gz
overwrite: true
include-hidden-files: true

View File

@@ -0,0 +1,16 @@
name: 'Restore Packages Build'
description: 'Downloads and unpacks the packages build artifact'
runs:
using: 'composite'
steps:
- name: Restore packages build
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: packages-build
path: /tmp
- name: Unpack packages build
shell: bash
run: |
tar -xzf /tmp/RocketChat-packages-build.tar.gz -C .

120
.github/actions/setup-node/action.yml vendored Normal file
View File

@@ -0,0 +1,120 @@
name: 'Setup Node'
description: 'Setup NodeJS'
inputs:
node-version:
required: false
description: 'Node version. If not provided, read from package.json volta.node'
cache-modules:
required: false
description: 'Cache node_modules'
install:
required: false
description: 'Install dependencies'
type:
required: false
description: 'development or production'
default: 'development'
deno-version:
required: false
description: 'Deno version. If not provided, read from .tool-versions'
NPM_TOKEN:
required: false
description: 'NPM token'
HARDENED_MODE:
required: false
description: 'Hardened mode'
default: '0'
outputs:
node-version:
description: 'Node version'
value: ${{ steps.node-version.outputs.node-version }}
cache-deno-hit:
description: 'Exact key hit for Deno cache'
value: ${{ steps.cache-deno.outputs.cache-hit }}
runs:
using: composite
steps:
- name: Resolve tool versions
shell: bash
run: |
NODE_VERSION="${{ inputs.node-version }}"
if [ -z "$NODE_VERSION" ]; then
NODE_VERSION=$(node -p "require('./package.json').volta.node")
fi
echo "NODE_VERSION: ${NODE_VERSION}"
echo "NODE_VERSION=${NODE_VERSION}" >> $GITHUB_ENV
DENO_VERSION="${{ inputs.deno-version }}"
if [ -z "$DENO_VERSION" ]; then
DENO_VERSION=$(awk '$1=="deno"{ print $2 }' .tool-versions)
fi
echo "DENO_VERSION: ${DENO_VERSION}"
echo "DENO_VERSION=${DENO_VERSION}" >> $GITHUB_ENV
# Turbo local cache: use rharkor/caching-for-turbo in workflows — not stored here to avoid huge redundant keys.
- name: Cache Deno (apps)
if: inputs.cache-modules
id: cache-deno
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: packages/apps/.deno-cache
key: deno-${{ runner.os }}-${{ runner.arch }}-v${{ env.DENO_VERSION }}-${{ hashFiles('packages/apps/deno-runtime/deno.lock') }}
restore-keys: |
deno-${{ runner.os }}-${{ runner.arch }}-v${{ env.DENO_VERSION }}-
- name: Use Node.js ${{ env.NODE_VERSION }}
id: node-version
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: ${{ inputs.cache-modules && 'yarn' || '' }}
- name: Use Deno ${{ env.DENO_VERSION }}
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
with:
deno-version: ${{ env.DENO_VERSION }}
- name: Cache mongodb-memory-server binary
if: inputs.cache-modules
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ runner.temp }}/mongodb-memory-server
key: mongoms-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Configure mongodb-memory-server cache
shell: bash
run: |
CACHE_DIR="${{ runner.temp }}/mongodb-memory-server"
echo "MONGOMS_DOWNLOAD_DIR=$CACHE_DIR" >> $GITHUB_ENV
echo "MONGOMS_PREFER_GLOBAL_PATH=false" >> $GITHUB_ENV
- name: yarn login
shell: bash
if: inputs.NPM_TOKEN
run: |
echo "//registry.npmjs.org/:_authToken=${{ inputs.NPM_TOKEN }}" > ~/.npmrc
- name: yarn config
if: inputs.install
shell: bash
run: |
yarn config set supportedArchitectures --json '{"os": ["linux"], "cpu": ["arm64", "x64"], "libc": ["glibc", "musl"]}'
- name: yarn install
if: inputs.install && inputs.type == 'development'
shell: bash
run: YARN_ENABLE_HARDENED_MODE=${{ inputs.HARDENED_MODE }} yarn
- name: yarn install production
if: inputs.install && inputs.type == 'production'
shell: bash
run: YARN_ENABLE_HARDENED_MODE=${{ inputs.HARDENED_MODE }} yarn workspaces focus --all --production
- name: restore yarn config file
if: inputs.install
shell: bash
run: git checkout -- .yarnrc.yml

View File

@@ -0,0 +1,21 @@
name: Setup Playwright
description: 'Setup Playwright binaries and dependencies'
runs:
using: 'composite'
steps:
- name: Cache Playwright binaries
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: cache-playwright
with:
path: |
~/.cache/ms-playwright
# This is the version of Playwright that we are using, if you are willing to upgrade, you should update this.
key: playwright-${{ runner.arch }}-${{ runner.os }}-1.52.0
- name: Install Playwright
shell: bash
if: steps.cache-playwright.outputs.cache-hit != 'true'
working-directory: .
run: npx playwright install --with-deps

View File

@@ -0,0 +1,26 @@
name: Update Version Durability
description: Update Version Durability page on Document360
inputs:
LTS_VERSIONS:
required: true
description: Comma separated list of LTS versions
GH_TOKEN:
required: true
description: GitHub API Token
D360_TOKEN:
required: true
description: Document360 API Token
D360_ARTICLE_ID:
required: true
description: Document360 Article ID
D360_USER_ID:
required: true
description: Document360 User ID used to fork and publish the article
PUBLISH:
required: true
description: Publish Draft
runs:
using: node20
main: index.js

View File

@@ -0,0 +1,228 @@
import 'colors';
import axios from 'axios';
import * as Diff from 'diff';
import semver from 'semver';
import crypto from 'crypto';
import fs from 'fs/promises';
import BeautyHtml from 'beauty-html';
import { DOMParser } from '@xmldom/xmldom';
import core from '@actions/core';
import { Octokit } from '@octokit/rest';
const D360_TOKEN = core.getInput('D360_TOKEN');
const D360_ARTICLE_ID = core.getInput('D360_ARTICLE_ID');
const D360_USER_ID = core.getInput('D360_USER_ID');
const PUBLISH = core.getInput('PUBLISH') === 'true';
const LTS_VERSIONS = (core.getInput('LTS_VERSIONS') || '7.10').split(',').map((v) => v.trim());
const octokit = new Octokit({
auth: core.getInput('GH_TOKEN'),
});
async function requestDocument360(method = 'get', api, data = {}) {
return axios.request({
method,
maxBodyLength: Infinity,
url: `https://apihub.us.document360.io/v1/${api}`,
headers: {
'accept': 'application/json',
'api_token': D360_TOKEN,
},
data,
});
}
function md5(text) {
return crypto.createHash('md5').update(text).digest("hex");
}
async function generateTable({ owner, repo } = {}) {
const response = await requestDocument360('get', `Articles/${D360_ARTICLE_ID}/en`);
// console.log(response.data.data);
// const releasesResult = JSON.parse(await fs.readFile('/tmp/releasesResult'));
const releasesResult = await octokit.paginate(octokit.repos.listReleases.endpoint.merge({ owner, repo, per_page: 100 }));
// await fs.writeFile('/tmp/releasesResult', JSON.stringify(releasesResult));
const releases = releasesResult
.filter((release) => !release.tag_name.includes('-rc') && semver.gte(release.tag_name, '1.0.0'))
.sort((a, b) => semver.compare(b.tag_name, a.tag_name));
const releasesMap = {};
for (const release of releases) {
release.releaseDate = new Date(release.published_at);
releasesMap[release.tag_name] = release;
}
let index = 0;
// eslint-disable-next-line no-constant-condition
while (true) {
const release = releases[index];
release.minor_tag = release.tag_name.replace(/\.\d+$/, '');
release.minorRelease = releasesMap[`${release.minor_tag}.0`];
if (!releases[index + 1]) {
break;
}
const currentVersion = semver.parse(release.tag_name);
const previousVersion = semver.parse(releases[index + 1].tag_name);
releases[index + 1].nextRelease = release;
// Remove duplicated due to patches
if (currentVersion.major === previousVersion.major && currentVersion.minor === previousVersion.minor) {
releases.splice(index + 1, 1);
continue;
}
// Mark LTS
if (LTS_VERSIONS.includes(`${currentVersion.major}.${currentVersion.minor}`)) {
release.lts = true;
}
index++;
}
releases[0].last = true;
const releaseData = [];
for (const { tag_name, html_url, lts, last, nextRelease, minorRelease, minor_tag} of releases) {
let supportDate;
let supportDateStart;
let releasedAt = new Date(minorRelease.releaseDate);
releasedAt.setDate(1);
let minorDate = new Date(minorRelease.releaseDate);
minorDate.setDate(1);
supportDateStart = minorDate;
supportDate = new Date(minorDate);
supportDate.setMonth(supportDate.getMonth() + (lts ? 12 : 6));
releaseData.push({
release: {
version: minor_tag,
releasedAt,
extendedSupport: {
start: supportDateStart,
end: supportDate,
},
lts: lts === true,
},
latestPatch: {
version: tag_name,
url: html_url,
}
})
}
function header({data, salt = ''}) {
return [
'<th colspan="1" data-vertical-align="middle" data-horizontal-align="left" rowspan="1" style="vertical-align:middle;text-align:left;">',
`<p data-block-id="${md5(salt+data)}">${data}</p>`,
'</th>',
].join('');
}
function line({data, salt = ''}) {
return [
'<td colspan="1" rowspan="1" data-vertical-align="middle" data-horizontal-align="left" style="vertical-align:middle;text-align:left;">',
`<p data-block-id="${md5(salt+data)}">${data}</p>`,
'</td>',
].join('');
}
const text = [
'<tr>',
header({data: 'Rocket.Chat Release'}),
header({data: 'Released At'}),
header({data: 'End of Life'}),
'</tr>',
];
releaseData.forEach(({release, latestPatch}) => {
const releasedAt = release.releasedAt.toLocaleString('en', { month: 'short', year: "numeric" });
const endOfLife = !release.extendedSupport
? 'TBD'
: release.extendedSupport.end.toLocaleString('en', { month: 'short', year: "numeric" });
const link = `${release.version}${release.lts ? " LTS" : ""} (<a href="${latestPatch.url}" target="_blank" translate="no">${latestPatch.version}</a>)`;
text.push(
'<tr>',
line({data: link}),
line({data: releasedAt, salt: release.version}),
line({data: endOfLife, salt: release.version}),
'</tr>',
);
});
const content = response.data.data.html_content.replace(/<tbody>.+(\n.+)*<\/tbody>/m, `<tbody>${text.join('').replace(/\t|\n/g, '')}</tbody>`)
// console.log(content);
const parser = new BeautyHtml({ parser: DOMParser });
const diff = Diff.diffLines(parser.beautify(response.data.data.html_content), parser.beautify(content), { ignoreWhitespace: true, newlineIsToken: false });
diff.forEach((item) => {
let color = 'green';
if (item.removed) {
color = 'red';
}
if (item.removed || item.added) {
item.value.split('\n').forEach((line) => {
if (line === '') { return };
console.log(`${item.removed ? '-' : '+'} ${line}`[color]);
})
}
});
if (diff.length === 1) {
console.log('No changes found');
return;
}
if (response.data.data.status === 3) {
console.log('forking article', response.data.data.version_number);
const forkResponse = await requestDocument360('put', `Articles/${D360_ARTICLE_ID}/fork`, {
lang_code: "en",
user_id: D360_USER_ID,
version_number: response.data.data.version_number,
});
console.log(forkResponse.data);
if (!forkResponse.data.success) {
throw new Error(`Failed to fork article: ${JSON.stringify(forkResponse.data.errors)}`);
}
}
console.log('Updating article');
const updateResponse = await requestDocument360('put', `Articles/${D360_ARTICLE_ID}/en`, {
content,
});
console.log(updateResponse.data);
if (PUBLISH) {
console.log('publishing article', updateResponse.data.data.version_number);
const forkResponse = await requestDocument360('post', `Articles/${D360_ARTICLE_ID}/en/publish`, {
user_id: D360_USER_ID,
version_number: updateResponse.data.data.version_number,
publish_message: 'Update support versions table via GitHub Action',
});
console.log(forkResponse.data);
}
}
generateTable({ owner: 'RocketChat', repo: 'Rocket.Chat' });

View File

@@ -0,0 +1,717 @@
{
"name": "scripts",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "scripts",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@actions/core": "^1.10.1",
"@octokit/rest": "^21.0.2",
"@xmldom/xmldom": "^0.8.15",
"axios": "^1.18.1",
"beauty-html": "^1.3.1",
"colors": "^1.4.0",
"diff": "^5.1.0",
"semver": "^7.5.4"
}
},
"node_modules/@actions/core": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"license": "MIT",
"dependencies": {
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1"
}
},
"node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"license": "MIT",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/http-client": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.1.tgz",
"integrity": "sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^5.25.4"
}
},
"node_modules/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
"license": "MIT"
},
"node_modules/@octokit/auth-token": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz",
"integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/core": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz",
"integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==",
"license": "MIT",
"dependencies": {
"@octokit/auth-token": "^5.0.0",
"@octokit/graphql": "^8.2.2",
"@octokit/request": "^9.2.3",
"@octokit/request-error": "^6.1.8",
"@octokit/types": "^14.0.0",
"before-after-hook": "^3.0.2",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/core/node_modules/@octokit/openapi-types": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz",
"integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==",
"license": "MIT"
},
"node_modules/@octokit/core/node_modules/@octokit/types": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz",
"integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^25.1.0"
}
},
"node_modules/@octokit/endpoint": {
"version": "10.1.4",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz",
"integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^14.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz",
"integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==",
"license": "MIT"
},
"node_modules/@octokit/endpoint/node_modules/@octokit/types": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz",
"integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^25.1.0"
}
},
"node_modules/@octokit/graphql": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz",
"integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==",
"license": "MIT",
"dependencies": {
"@octokit/request": "^9.2.3",
"@octokit/types": "^14.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz",
"integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==",
"license": "MIT"
},
"node_modules/@octokit/graphql/node_modules/@octokit/types": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz",
"integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^25.1.0"
}
},
"node_modules/@octokit/openapi-types": {
"version": "23.0.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "11.4.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.2.tgz",
"integrity": "sha512-BXJ7XPCTDXFF+wxcg/zscfgw2O/iDPtNSkwwR1W1W5c4Mb3zav/M2XvxQ23nVmKj7jpweB4g8viMeCQdm7LMVA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.7.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-request-log": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz",
"integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "13.5.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz",
"integrity": "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.10.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": {
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
"license": "MIT"
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
"version": "13.10.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^24.2.0"
}
},
"node_modules/@octokit/request": {
"version": "9.2.4",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz",
"integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^10.1.4",
"@octokit/request-error": "^6.1.8",
"@octokit/types": "^14.0.0",
"fast-content-type-parse": "^2.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/request-error": {
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz",
"integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^14.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz",
"integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==",
"license": "MIT"
},
"node_modules/@octokit/request-error/node_modules/@octokit/types": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz",
"integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^25.1.0"
}
},
"node_modules/@octokit/request/node_modules/@octokit/openapi-types": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz",
"integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==",
"license": "MIT"
},
"node_modules/@octokit/request/node_modules/@octokit/types": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz",
"integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^25.1.0"
}
},
"node_modules/@octokit/rest": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz",
"integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==",
"license": "MIT",
"dependencies": {
"@octokit/core": "^6.1.4",
"@octokit/plugin-paginate-rest": "^11.4.2",
"@octokit/plugin-request-log": "^5.3.1",
"@octokit/plugin-rest-endpoint-methods": "^13.3.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^23.0.1"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.15",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz",
"integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
"integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
},
"node_modules/beauty-html": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/beauty-html/-/beauty-html-1.3.1.tgz",
"integrity": "sha512-c0iKWc527T2MQcYhIMMw9OHN8kcXSf/ijadWzURhZWi6e6cnBXxAQ5IlXbYd0YZJE9lFtXRB1fJVQrvJf5DmPQ=="
},
"node_modules/before-after-hook": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz",
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/diff": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz",
"integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/fast-content-type-parse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz",
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT"
},
"node_modules/follow-redirects": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.4",
"mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/proxy-from-env": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/semver": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/undici": {
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
"integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==",
"license": "MIT",
"engines": {
"node": ">=18.17"
}
},
"node_modules/universal-user-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
"license": "ISC"
}
}
}

View File

@@ -0,0 +1,24 @@
{
"name": "scripts",
"version": "1.0.0",
"type": "module",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@actions/core": "^1.10.1",
"@octokit/rest": "^21.0.2",
"axios": "^1.18.1",
"beauty-html": "^1.3.1",
"colors": "^1.4.0",
"diff": "^5.1.0",
"semver": "^7.5.4",
"@xmldom/xmldom": "^0.8.15"
},
"overrides": {
"undici": "6.28.0"
}
}