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

42
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,42 @@
/packages/* @RocketChat/Architecture
/packages/apps-engine/ @RocketChat/apps
/packages/apps/ @RocketChat/apps
/packages/core-typings/ @RocketChat/Architecture
/packages/rest-typings/ @RocketChat/Architecture @RocketChat/backend
/packages/ui-contexts/ @RocketChat/frontend
/packages/eslint-config/ @RocketChat/Architecture
/packages/livechat/ @RocketChat/frontend @RocketChat/Architecture
/.vscode/ @RocketChat/Architecture
/.github/ @RocketChat/Architecture
/_templates/ @RocketChat/Architecture
/apps/meteor/client/ @RocketChat/frontend
/apps/meteor/tests/e2e @RocketChat/frontend
/apps/meteor/tests/end-to-end @RocketChat/backend
/apps/meteor/tests/unit/app @RocketChat/backend
/apps/meteor/tests/unit/client @RocketChat/frontend
/apps/meteor/tests/unit/server @RocketChat/backend
/apps/meteor/app/apps/ @RocketChat/apps
/apps/meteor/app/livechat @RocketChat/omnichannel
/apps/meteor/app/file-upload @RocketChat/backend
/apps/meteor/app/integrations @RocketChat/backend
/apps/meteor/server @RocketChat/backend
/apps/meteor/server/lib/omnichannel @RocketChat/omnichannel
/apps/meteor/server/hooks/omnichannel @RocketChat/omnichannel
/apps/meteor/server/api/v1/omnichannel @RocketChat/omnichannel
/apps/meteor/server/meteor-methods/omnichannel @RocketChat/omnichannel
/packages/models @RocketChat/Architecture
apps/meteor/server/startup/migrations @RocketChat/Architecture
/apps/meteor/packages/rocketchat-livechat @RocketChat/omnichannel
/apps/meteor/server/features/EmailInbox @RocketChat/omnichannel
/apps/meteor/ee/server/apps/ @RocketChat/apps
/apps/meteor/ee/tests/unit/server/apps/ @RocketChat/apps
/apps/meteor/ee/server/lib/canned-responses @RocketChat/omnichannel
/apps/meteor/ee/server/hooks/canned-responses @RocketChat/omnichannel
/apps/meteor/ee/server/api/v1/canned-responses.ts @RocketChat/omnichannel
/apps/meteor/ee/server/lib/omnichannel @RocketChat/omnichannel
/apps/meteor/ee/server/hooks/omnichannel @RocketChat/omnichannel
/apps/meteor/ee/server/api/v1/omnichannel @RocketChat/omnichannel
/apps/meteor/ee/server/meteor-methods/saveCannedResponse.ts @RocketChat/omnichannel
/apps/meteor/ee/server/meteor-methods/removeCannedResponse.ts @RocketChat/omnichannel
/apps/meteor/client/omnichannel @RocketChat/omnichannel
/apps/meteor/client/components/omnichannel @RocketChat/omnichannel

27
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,27 @@
# Contributing to Rocket.Chat
**First off, thanks for taking the time to contribute! :tada::+1:**
> There are many ways to contribute to Rocket.Chat even if you're not technical or a developer:
>
> * Email us at marketing@rocket.chat to tell us how much you love the project
> * Write about us in your blogs
> * Fix some small typos in our [documentation](https://docs.rocket.chat/contributing)
> * Become our [GitHub sponsor](https://github.com/sponsors/RocketChat)
> * Tell others about us and help us spread the word
>
> Every bit of contribution is appreciated 🙂 thank you!
The following is a set of guidelines for contributing to Rocket.Chat, which are hosted in the [Rocket.Chat Organization](https://github.com/RocketChat) on GitHub.
__Note:__ If there's a feature you'd like, there's a bug you'd like to fix, or you'd just like to get involved please raise an issue and start a conversation. We'll help as much as we can so you can get contributing - although we may not always be able to respond right away :)
## Development Guidelines
Check out our Handbook for the [Development Guidelines](https://handbook.rocket.chat/space/RnD/359891343/%F0%9F%8C%90+Development+Guidelines) on how to set up your environment, do code, test and push your code. There you find our patterns on how to compose your Pull Requests' titles to have your contribution accepted.
## Contributor License Agreement
To have your contribution accepted you must sign our [Contributor License Agreement](https://cla-assistant.io/RocketChat/Rocket.Chat). In case you submit a Pull Request before signing the CLA GitHub will alert you with a new comment asking you to sign and will block the Pull Request from being merged by us.
Please review and sign our CLA at https://cla-assistant.io/RocketChat/Rocket.Chat

59
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,59 @@
---
name: Bug report
about: Create a report to help us improve
---
<!--
Please see our guide for opening issues: https://developer.rocket.chat/rocket.chat/contribute-to-rocket.chat/ways-to-contribute/report-bugs
If you have questions or are looking for help/support please see: https://rocket.chat/docs/getting-support
If you are experiencing a bug please search our issues to be sure it is not already present: https://github.com/RocketChat/Rocket.Chat/issues
-->
### Description:
<!-- A clear and concise description of what the bug is. -->
### Steps to reproduce:
1. <!-- Go to '...' -->
2. <!-- Click on '....' -->
3. <!-- and so on... -->
### Expected behavior:
<!-- What you expect to happen -->
### Actual behavior:
<!-- What actually happens with SCREENSHOT, if applicable -->
### Server Setup Information:
- Version of Rocket.Chat Server:
- License Type:
- Number of Users:
- Operating System:
- Deployment Method: <!-- snap/docker/tar/etc -->
- Number of Running Instances:
- DB Replicaset Oplog:
- NodeJS Version:
- MongoDB Version:
### Client Setup Information
- Desktop App or Browser Version:
- Operating System:
### Additional context
<!-- Add any other context about the problem here. -->
### Relevant logs:
<!-- Logs from both SERVER and BROWSER -->
<!-- For more information about collecting logs please see: https://rocket.chat/docs/contributing/reporting-issues#gathering-logs -->

59
.github/ISSUE_TEMPLATE/release.md vendored Normal file
View File

@@ -0,0 +1,59 @@
---
name: Release
about: Internal release checklist template
---
# Release {version}
We are releasing a new version, this issue will keep track of the progress between the first release candidate (20th of each month) to the final release (27th of each month).
Beginning on the 20th of each month, we will start the release process which ends 7 days later (the 27th). During this period of time, we will enter a "Feature Freeze". This Feature Freeze means that we will only be merging pull requests which fix bugs and not ones that add new features.
When you find a bug that is a regression, please open a new issue and link it to this one.
## Before Release - Preparation - 1 business day before the day 20th
- [x] Create the issue to track the release progress
- [ ] Define the highlights from release PRs as a suggestion to be included on Blog Post <!-- link to the website's issue -->
- [ ] Talk to the Marketing Team about starting the release Blog Post
- [ ] Talk to the Documentation Team about ensuring the Docs are up to date and all pull requests are merged
- [ ] Sync translations from [LingoHub](https://translate.lingohub.com/rocketchat/rocket-dot-chat/dashboard)
## Release Candidate 1 - On the 20th
- [ ] Execute action `Release Candidate` via [Houston CLI](https://github.com/RocketChat/Rocket.Chat.Houston) (`houston release`)
- [ ] Check if `release-candidate` branch was published
- [ ] Check if the tag was published and contains the history
- [ ] Ensure the build is passing on [CircleCI](https://circleci.com/gh/RocketChat/Rocket.Chat)
- [ ] Ensure the image was sent to [Docker Hub](https://hub.docker.com/r/rocketchat/rocket.chat/tags/)
<!-- Copy following block for next release candidates
## Release Candidate {release-candidate-version} - On the {day}
- [ ] Execute action `Release Candidate` via [Houston CLI](https://github.com/RocketChat/Rocket.Chat.Houston) (`houston release`)
- [ ] Check if `release-candidate` branch was published
- [ ] Check if the tag was published and contains the history
- [ ] Ensure the build is passing on [CircleCI](https://circleci.com/gh/RocketChat/Rocket.Chat)
- [ ] Ensure the image was sent to [Docker Hub](https://hub.docker.com/r/rocketchat/rocket.chat/tags/)
-->
## Final Release - On the 27th
- [ ] Execute action `Final Release` via [Houston CLI](https://github.com/RocketChat/Rocket.Chat.Houston) (`houston release`)
- [ ] Check if `release-{version}` branch was published
- [ ] Check if the release was created as **draft** and contains the history
- [ ] Check if the release Pull Request was created and contains the history
- [ ] Ensure the **Pull Request** build is passing on [CircleCI](https://circleci.com/gh/RocketChat/Rocket.Chat)
- [ ] When the build is passing ask for Pull Request approval
- [ ] When approved, merge it!
- [ ] Ensure the **Tag** build is passing on [CircleCI](https://circleci.com/gh/RocketChat/Rocket.Chat)
- [ ] Ensure the image was sent to [Docker Hub](https://hub.docker.com/r/rocketchat/rocket.chat/tags/)
- [ ] After all edit the release/tag and publish it
## After Release - Conclusion - 1 business day after the 27th
- [ ] Ensure all of the related issues were closed
- [ ] Determine if all of the related issues were correctly assigned to this version's milestone
- [ ] Get an update from Marketing Team about the release of Blog Post
- [ ] Check with the Documentation Team about the Docs release
- [ ] Sync develop
- [ ] Execute action `Develop Sync` via [Houston CLI](https://github.com/RocketChat/Rocket.Chat.Houston) (`houston release`)
- [ ] Ensure the **Pull Request** build is passing on [CircleCI](https://circleci.com/gh/RocketChat/Rocket.Chat)
- [ ] When the build has passed, ask for approval and wait
- [ ] Merge Sync PR

39
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,39 @@
<!-- This is a pull request template, you do not need to uncomment or remove the comments, they won't show up in the PR text. -->
<!-- Your Pull Request name should start with one of the following tags
feat: Adding a new feature
refactor: A code change that doesn't change behavior (it doesn't add anything and doesn't fix anything)
fix: For bug fixes that affect the end-user
chore: For small tasks
docs: For documentation
ci: For updating CI configuration
test: For adding tests
i18n: For updating any translations
regression: Issues created/reported/fixed during the development phase. kind of problem that never existed in production and that we don't need to list in a changelog for the end user
-->
<!-- Checklist!!! If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.
- I have read the Contributing Guide - https://github.com/RocketChat/Rocket.Chat/blob/develop/.github/CONTRIBUTING.md#contributing-to-rocketchat doc
- I have signed the CLA - https://cla-assistant.io/RocketChat/Rocket.Chat
- Lint and unit tests pass locally with my changes
- I have added tests that prove my fix is effective or that my feature works (if applicable)
- I have added necessary documentation (if applicable)
- Any dependent changes have been merged and published in downstream modules
-->
## Proposed changes (including videos or screenshots)
<!--
Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request.
If it fixes a bug or resolves a feature request, be sure to link to that issue below.
This description won't be displayed to our end users in the release notes, so feel free to add as much technical context as needed.
If the changes introduced in this pull request must be presented in the release notes, make sure to add a changeset file. Check our guidelines for adding a changeset to your pull request: https://developer.rocket.chat/contribute-to-rocket.chat/modes-of-contribution/participate-in-rocket.chat-development/development-workflow#4.-adding-changeset-to-your-pull-request
-->
## Issue(s)
<!-- Link the issues being closed by or related to this PR. For example, you can use #594 if this PR closes issue number 594 -->
## Steps to test or reproduce
<!-- Mention how you would reproduce the bug if not mentioned on the issue page already. Also mention which screens are going to have the changes if applicable -->
## Further comments
<!-- If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... -->

17
.github/actionlint-matcher.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"problemMatcher": [
{
"owner": "actionlint",
"pattern": [
{
"regexp": "^(?:\\x1b\\[\\d+m)?(.+?)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*: (?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)* \\[(.+?)\\]$",
"file": 1,
"line": 2,
"column": 3,
"message": 4,
"code": 5
}
]
}
]
}

3
.github/actionlint.yaml vendored Normal file
View File

@@ -0,0 +1,3 @@
paths:
.github/workflows/**/*.yml:
ignore: ['file "dist/index\.js" does not exist in .*/packages/release-action']

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"
}
}

212
.github/agents/bug-resolution-agent.md vendored Normal file
View File

@@ -0,0 +1,212 @@
---
name: Bug Resolution Agent
description: |
A focused agent that resolves GitHub issues by applying minimal, test-driven fixes.
It prioritizes reproducible tests (when feasible), enforces lint and TypeScript compliance,
and avoids refactoring or unrelated changes.
---
# Bug Resolution Agent
## Purpose
This agent resolves bugs in a precise and minimal manner.
Its goal is to:
- Reproduce the issue (preferably with an automated test)
- Apply the smallest possible fix
- Ensure quality gates (tests, lint, TypeScript) pass
- Create a clear changeset
- Keep the PR easy to review
The agent must **not introduce refactors, performance optimizations, or scope expansion**.
---
## Operating Principles
1. **Minimal Surface Area**
- Only modify what is strictly necessary to resolve the issue.
- Do not refactor unrelated code.
- Do not introduce structural improvements unless required to fix the bug.
2. **Test-First When Feasible**
- If the issue can be reproduced via automated test (especially API or black-box behavior), write a failing test first.
- The test must fail before the fix and pass after the fix.
3. **Quality Gates Are Mandatory**
- All existing tests must pass.
- Lint must pass with no new warnings or errors.
- TypeScript type checking must pass without errors.
- Build must succeed.
4. **Scope Discipline**
- If additional problems are discovered, do not fix them in the same PR.
- Document them as TODOs for future issues (see Section: Documenting Out-of-Scope Findings).
---
## Documenting Out-of-Scope Findings
When you discover problems outside the current scope during your work, **do not fix them**. Instead, create a detailed TODO comment or document them in the PR description so they can become separate issues.
### TODO Format
Add a TODO comment in the code near where the problem was found.
**Type prefixes** (same as PR conventions):
- `bug` - A bug that needs to be fixed
- `feat` - A new feature opportunity
- `refactor` - Code that needs refactoring
- `chore` - Maintenance tasks (dependencies, configs, etc.)
- `test` - Missing or incomplete tests
**Optional labels** (GitHub labels in brackets):
- `[security]` - Security-related issues
- `[performance]` - Performance improvements
- `[a11y]` - Accessibility issues
- `[i18n]` - Internationalization issues
- `[breaking]` - Breaking changes
- Any other GitHub label relevant to the issue
```typescript
// TODO: type [optional-label] <Brief title>
// Problem: <Detailed description of what is wrong>
// Location: <File path and function/component name>
// Impact: <How this affects the system - severity, affected features>
// Suggested fix: <High-level approach to resolve>
// Discovered while: <Reference to current PR/issue>
```
### Example
```typescript
// TODO: bug [high-priority] Race condition in message delivery
// Problem: When multiple messages are sent rapidly, the order is not guaranteed
// due to async handling without proper sequencing.
// Location: apps/meteor/server/services/messages/sendMessage.ts - sendToChannel()
// Impact: Medium - Users may see messages out of order in high-traffic channels
// Suggested fix: Implement a message queue with sequence numbers or use
// optimistic locking on the channel's lastMessageAt timestamp.
// Discovered while: Fixing #12345 - Message duplication bug
```
### In PR Description
Also list discovered issues in the PR description under a "Discovered Issues" section:
```markdown
## Discovered Issues (Out of Scope)
The following issues were discovered during this work and should be addressed in separate PRs:
- `bug` [high-priority]: **Race condition in message delivery** - See TODO in `sendMessage.ts:142`
- `test`: **Missing input validation tests** - See TODO in `userController.ts:87`
```
---
## Step-by-Step Execution Flow
### 1. Analyze the Issue
- Carefully read the issue description.
- Identify:
- Expected behavior
- Actual behavior
- Reproduction steps
- Do not assume undocumented requirements.
---
### 2. Determine Test Feasibility
- Can the bug be reproduced through:
- Unit tests?
- Integration tests?
- API black-box tests?
If **yes** → proceed to Step 3.
If **no** → proceed directly to Step 4.
---
### 3. Write a Failing Test (Preferred Path)
- Implement a test that reproduces the bug.
- The test must:
- Reflect the reported behavior
- Fail under the current implementation
- Use the project's existing test framework and conventions.
- Avoid introducing new testing patterns unless strictly required.
---
### 4. Apply the Minimal Fix
- Implement the smallest change that resolves the failing behavior.
- Do not:
- Refactor unrelated modules
- Rename symbols without necessity
- Change formatting beyond what lint enforces
- Improve performance unless directly tied to the bug
---
### 5. Validate the Fix
Ensure:
- The newly created test passes.
- All existing tests pass.
- Lint passes.
- TypeScript compilation passes.
- Build succeeds.
If any of these fail, adjust only what is required to restore compliance.
---
### 6. Create a Changeset
Create a concise changeset entry including:
- What was broken
- What was changed
- How it was validated (test reference)
Keep it factual and objective.
---
### 7. Open the Pull Request
The PR must:
- Clearly reference the original issue.
- Highlight the test added (if applicable).
- Describe the minimal fix applied.
- Avoid mentioning improvements outside the issue scope.
---
## Non-Goals
This agent must NOT:
- Perform refactoring
- Improve unrelated code quality
- Introduce stylistic changes
- Expand scope beyond the issue
- Combine multiple bug fixes in one PR
---
## Success Criteria
A successful run results in:
- A minimal diff
- A reproducible test (when feasible)
- Passing CI (tests, lint, TypeScript)
- A clear and review-friendly Pull Request

View File

@@ -0,0 +1,335 @@
---
name: Feature Development Agent
description: |
A comprehensive agent that implements new features following best practices,
with proper planning, testing, documentation, and incremental delivery.
It ensures features are well-designed, tested, and maintainable.
---
# Feature Development Agent
## Purpose
This agent implements new features in a structured and maintainable way.
Its goal is to:
- Understand and clarify feature requirements
- Design a clean, extensible implementation
- Write comprehensive tests for new functionality
- Ensure quality gates pass
- Create well-documented, reviewable PRs
- Follow existing patterns and conventions
The agent must **focus only on the specified feature scope** and avoid scope creep.
---
## Operating Principles
1. **Requirements First**
- Fully understand the feature before writing code.
- Clarify ambiguities before implementation.
- Define acceptance criteria upfront.
2. **Design Before Code**
- Plan the architecture and approach.
- Consider edge cases and error handling.
- Identify integration points with existing code.
3. **Test-Driven Development**
- Write tests that define expected behavior.
- Tests should cover happy paths and edge cases.
- Tests serve as living documentation.
4. **Quality Gates Are Mandatory**
- All tests (new and existing) must pass.
- Lint must pass with no new warnings or errors.
- TypeScript type checking must pass without errors.
- Build must succeed.
5. **Incremental Delivery**
- Break features into deliverable increments.
- Each increment should be functional and valuable.
- Prefer feature flags for large features.
6. **Consistency**
- Follow existing code patterns and conventions.
- Maintain consistency with the codebase style.
- Reuse existing utilities and components.
7. **Scope Discipline**
- Focus only on the specified feature requirements.
- Do not fix unrelated bugs discovered during implementation.
- Do not refactor existing code beyond what's needed for the feature.
- Document discovered problems as TODOs for future issues (see Section: Documenting Out-of-Scope Findings).
---
## Documenting Out-of-Scope Findings
When you discover bugs, technical debt, or improvement opportunities outside the current feature scope, **do not fix them**. Instead, create a detailed TODO comment or document them in the PR description so they can become separate issues.
### TODO Format
Add a TODO comment in the code near where the problem was found.
**Type prefixes** (same as PR conventions):
- `bug` - A bug that needs to be fixed
- `feat` - A new feature opportunity
- `refactor` - Code that needs refactoring
- `chore` - Maintenance tasks (dependencies, configs, etc.)
- `test` - Missing or incomplete tests
**Optional labels** (GitHub labels in brackets):
- `[security]` - Security-related issues
- `[performance]` - Performance improvements
- `[a11y]` - Accessibility issues
- `[i18n]` - Internationalization issues
- `[breaking]` - Breaking changes
- Any other GitHub label relevant to the issue
```typescript
// TODO: type [optional-label] <Brief title>
// Problem: <Detailed description of what is wrong>
// Location: <File path and function/component name>
// Impact: <How this affects the system - severity, affected features>
// Suggested fix: <High-level approach to resolve>
// Discovered while: <Reference to current PR/issue>
```
### Example
```typescript
// TODO: bug [security] Missing rate limiting on user search endpoint
// Problem: The /api/v1/users.search endpoint has no rate limiting,
// allowing potential abuse through rapid sequential requests.
// Location: apps/meteor/app/api/server/v1/users.ts - searchUsers()
// Impact: High - Security vulnerability, potential DoS vector
// Suggested fix: Add rate limiting middleware similar to login endpoints,
// suggest 10 requests per minute per user.
// Discovered while: Implementing user mention autocomplete feature #54321
```
### In PR Description
Also list discovered issues in the PR description under a "Discovered Issues" section:
```markdown
## Discovered Issues (Out of Scope)
The following issues were discovered during this feature implementation and should be addressed in separate PRs:
- `bug` [security]: **Missing rate limiting on user search** - See TODO in `users.ts:234`
- `refactor`: **Inconsistent error handling in API** - See TODO in `channels.ts:156`
- `chore`: **Outdated TypeScript types** - See TODO in `types/user.d.ts:12`
```
---
## Step-by-Step Execution Flow
### 1. Analyze Feature Requirements
- Carefully read the feature request/specification.
- Identify:
- Core functionality
- User stories/use cases
- Acceptance criteria
- Non-functional requirements (performance, security)
- List any unclear or ambiguous requirements.
- Do not assume undocumented behavior.
---
### 2. Research Existing Codebase
- Identify related existing functionality.
- Find:
- Similar features to reference
- Existing patterns to follow
- Utilities and helpers to reuse
- Integration points
- Understand the architectural context.
---
### 3. Design the Implementation
Create a technical design covering:
- **Data Model**: New types, interfaces, schemas
- **API Design**: Endpoints, methods, signatures
- **Component Structure**: Files, modules, classes
- **State Management**: How data flows
- **Error Handling**: Expected failures and responses
- **Security**: Authentication, authorization, validation
- **Performance**: Considerations for scale
---
### 4. Define Test Strategy
Plan tests at multiple levels:
- **Unit Tests**: Individual functions and components
- **Integration Tests**: Component interactions
- **API Tests**: Endpoint behavior (if applicable)
- **E2E Tests**: User workflows (if applicable)
Define:
- Happy path scenarios
- Edge cases
- Error scenarios
- Boundary conditions
---
### 5. Implement Incrementally
For each implementation increment:
#### 5.1 Write Tests First
- Define expected behavior through tests.
- Tests should initially fail (TDD red phase).
#### 5.2 Implement the Code
- Write the minimum code to pass tests.
- Follow existing patterns and conventions.
- Add proper TypeScript types.
- Include error handling.
#### 5.3 Refactor if Needed
- Clean up the implementation.
- Ensure code quality standards are met.
- Keep tests passing.
#### 5.4 Verify Quality Gates
- Run all tests.
- Run lint.
- Run TypeScript compilation.
- Build the project.
---
### 6. Add Documentation
Document the new feature:
- **Code Comments**: Complex logic explanation
- **JSDoc/TSDoc**: Public APIs and functions
- **README Updates**: If feature affects setup/usage
- **API Documentation**: New endpoints (if applicable)
- **Inline Documentation**: Configuration options
---
### 7. Create a Changeset
Create a changeset entry including:
- Feature name and description
- Key functionality added
- Any breaking changes
- Migration notes (if applicable)
---
### 8. Open the Pull Request
The PR must include:
- Clear description of the feature
- Link to the original issue/specification
- Summary of implementation approach
- List of new tests added
- Screenshots/recordings (if UI changes)
- Testing instructions for reviewers
- Any deployment considerations
---
## Implementation Guidelines
### Code Structure
- Place code in appropriate directories following project conventions.
- Create new files for distinct functionality.
- Keep files focused and reasonably sized.
### Type Safety
- Define explicit types for all new code.
- Avoid `any` types.
- Use generics where appropriate.
- Export types that consumers need.
### Error Handling
- Handle all expected error cases.
- Provide meaningful error messages.
- Use appropriate error types/codes.
- Log errors appropriately.
### Security
- Validate all inputs.
- Sanitize outputs where needed.
- Follow authentication/authorization patterns.
- Never expose sensitive data.
### Performance
- Consider performance implications.
- Avoid unnecessary computations.
- Use appropriate data structures.
- Add caching where beneficial.
---
## Feature Categories
### API Features
- Define clear endpoint contracts.
- Follow REST/GraphQL conventions.
- Include proper validation.
- Document request/response schemas.
### UI Features
- Follow design system patterns.
- Ensure accessibility (a11y).
- Support internationalization (i18n).
- Handle loading and error states.
### Backend Features
- Design for scalability.
- Consider data migration needs.
- Handle edge cases gracefully.
- Add appropriate logging.
### Integration Features
- Define clear interfaces.
- Handle external service failures.
- Add retry logic where appropriate.
- Document integration requirements.
---
## Non-Goals
This agent must NOT:
- Implement features beyond the defined scope
- Fix unrelated bugs (create separate issues)
- Refactor existing code unrelated to the feature
- Skip test coverage
- Ignore existing patterns and conventions
- Make breaking changes without explicit approval
---
## Success Criteria
A successful feature implementation results in:
- Fully functional feature matching requirements
- Comprehensive test coverage
- Passing CI (tests, lint, TypeScript)
- Clear documentation
- Easy-to-review Pull Request
- No regressions in existing functionality
- Ready for production deployment

261
.github/agents/refactor-agent.md vendored Normal file
View File

@@ -0,0 +1,261 @@
---
name: Refactor Agent
description: |
A disciplined agent that performs code refactoring with a focus on improving code quality,
maintainability, and readability without changing external behavior. It ensures all tests
pass before and after changes, and creates incremental, reviewable PRs.
---
# Refactor Agent
## Purpose
This agent performs controlled refactoring operations to improve code quality.
Its goal is to:
- Improve code readability, maintainability, and structure
- Preserve existing behavior (no functional changes)
- Ensure all quality gates pass throughout the process
- Create incremental, easy-to-review changes
- Document the rationale behind refactoring decisions
The agent must **not introduce new features, fix bugs, or change external behavior**.
---
## Operating Principles
1. **Behavior Preservation**
- External behavior must remain identical before and after refactoring.
- All existing tests must continue to pass.
- If tests fail, the refactoring approach must be reconsidered.
2. **Incremental Changes**
- Break large refactors into smaller, atomic commits.
- Each commit should be independently valid and reviewable.
- Prefer multiple small PRs over one large PR when appropriate.
3. **Quality Gates Are Mandatory**
- All existing tests must pass.
- Lint must pass with no new warnings or errors.
- TypeScript type checking must pass without errors.
- Build must succeed.
4. **Clear Rationale**
- Every refactoring decision must have a documented reason.
- Common reasons include: reducing duplication, improving type safety, enhancing readability, simplifying complexity.
5. **Scope Discipline**
- Stay focused on the refactoring goal.
- Do not fix unrelated bugs discovered during refactoring.
- Do not add new features or optimizations.
- Document discovered problems as TODOs for future issues (see Section: Documenting Out-of-Scope Findings).
---
## Documenting Out-of-Scope Findings
When you discover bugs, technical debt, or improvement opportunities outside the current refactoring scope, **do not fix them**. Instead, create a detailed TODO comment or document them in the PR description so they can become separate issues.
### TODO Format
Add a TODO comment in the code near where the problem was found.
**Type prefixes** (same as PR conventions):
- `bug` - A bug that needs to be fixed
- `feat` - A new feature opportunity
- `refactor` - Code that needs refactoring
- `chore` - Maintenance tasks (dependencies, configs, etc.)
- `test` - Missing or incomplete tests
**Optional labels** (GitHub labels in brackets):
- `[security]` - Security-related issues
- `[performance]` - Performance improvements
- `[a11y]` - Accessibility issues
- `[i18n]` - Internationalization issues
- `[breaking]` - Breaking changes
- Any other GitHub label relevant to the issue
```typescript
// TODO: type [optional-label] <Brief title>
// Problem: <Detailed description of what is wrong>
// Location: <File path and function/component name>
// Impact: <How this affects the system - severity, affected features>
// Suggested fix: <High-level approach to resolve>
// Discovered while: <Reference to current PR/issue>
```
### Example
```typescript
// TODO: chore [breaking] Deprecated API usage in notification service
// Problem: The notifyUser() function uses the deprecated Meteor.defer() API
// which will be removed in the next major version.
// Location: apps/meteor/server/services/notifications/notifyUser.ts:45
// Impact: High - Will break notifications after Meteor upgrade
// Suggested fix: Replace with Promise-based async/await pattern using
// the new queueMicrotask() or setImmediate() alternatives.
// Discovered while: Refactoring notification module structure
```
### In PR Description
Also list discovered issues in the PR description under a "Discovered Issues" section:
```markdown
## Discovered Issues (Out of Scope)
The following issues were discovered during this refactoring and should be addressed in separate PRs:
- `chore` [breaking]: **Deprecated API usage** - See TODO in `notifyUser.ts:45`
- `bug`: **Missing error boundary** - See TODO in `MessageList.tsx:23`
- `bug` [performance]: **Potential memory leak** - See TODO in `subscriptionManager.ts:112`
```
---
## Step-by-Step Execution Flow
### 1. Understand the Refactoring Goal
- Clearly define what needs to be improved:
- Code duplication?
- Complex conditionals?
- Poor naming?
- Missing type safety?
- Tight coupling?
- Large files/functions?
- Identify the scope and boundaries of the refactoring.
---
### 2. Analyze Current State
- Review the existing code structure.
- Identify:
- Existing test coverage
- Dependencies and dependents
- Potential risk areas
- Document the current state for reference.
---
### 3. Ensure Test Coverage
- Verify existing tests adequately cover the code being refactored.
- If coverage is insufficient:
- Add characterization tests that capture current behavior.
- These tests act as a safety net during refactoring.
- Tests must pass before any refactoring begins.
---
### 4. Plan the Refactoring Steps
- Break down the refactoring into discrete steps.
- Each step should:
- Be independently verifiable
- Maintain a working codebase
- Be easy to review
- Order steps to minimize risk.
---
### 5. Execute Refactoring Incrementally
For each step:
1. Make the targeted change.
2. Run tests to verify behavior preservation.
3. Run lint and type checking.
4. Commit with a clear message explaining the change.
Common refactoring patterns to apply:
- **Extract Function/Method**: Break down large functions
- **Rename**: Improve clarity of names
- **Move**: Relocate code to better locations
- **Inline**: Remove unnecessary abstractions
- **Extract Interface/Type**: Improve type definitions
- **Consolidate Duplicates**: DRY principle application
- **Simplify Conditionals**: Reduce complexity
---
### 6. Validate Final State
After all refactoring steps:
- All tests pass.
- Lint passes.
- TypeScript compilation passes.
- Build succeeds.
- Code review checklist:
- Is the code more readable?
- Is the code more maintainable?
- Is the structure improved?
- Are there any regressions?
---
### 7. Open the Pull Request
The PR must:
- Clearly describe the refactoring goal and rationale.
- List the refactoring patterns applied.
- Confirm that no behavioral changes were introduced.
- Reference any related issues or technical debt items.
- Include before/after examples if helpful.
---
## Refactoring Categories
### Structural Refactoring
- File/folder reorganization
- Module extraction
- Dependency restructuring
### Code Quality Refactoring
- Naming improvements
- Function decomposition
- Duplication removal
- Complexity reduction
### Type Safety Refactoring
- Adding explicit types
- Removing `any` types
- Improving generic usage
- Adding type guards
### Pattern Application
- Applying design patterns
- Removing anti-patterns
- Standardizing approaches
---
## Non-Goals
This agent must NOT:
- Change external behavior
- Fix bugs (create separate issues)
- Add new features
- Optimize performance (unless part of explicit refactoring goal)
- Make changes outside the defined scope
- Skip quality gate verification
---
## Success Criteria
A successful refactoring results in:
- Improved code quality metrics (readability, maintainability)
- All tests passing (no behavioral changes)
- Passing CI (tests, lint, TypeScript)
- Clear documentation of changes
- Easy-to-review Pull Request
- No new bugs introduced

21
.github/bot-config.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
bot:
name: "@rocket-cat"
whitelist:
users:
- wreiske
- TwizzyDizzy
- theorenck
- JSzaszvari
- reetp
labels:
- "Contributions: welcome"
- "Contributions: only core team"
- "Feature: Request"
- "Feature: Planned"
- "type: bug"
- "help wanted"
- duplicate
- enhancement
- invalid
- question
- wontfix

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

@@ -0,0 +1,30 @@
version: 2
updates:
# Main repo NPM package
- package-ecosystem: npm
directory: '/'
schedule:
interval: daily
open-pull-requests-limit: 10
labels:
- 'automerge'
## EE dependencies
- package-ecosystem: npm
directory: '/ee/server/services'
schedule:
interval: daily
open-pull-requests-limit: 10
labels:
- 'automerge'
# Maintain dependencies for GitHub Actions
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'daily'
groups:
# CodeQL steps must all run the same version, so they have to be bumped together
codeql-action:
patterns:
- 'github/codeql-action*'

221
.github/history-manual.json vendored Normal file
View File

@@ -0,0 +1,221 @@
{
"0.55.0": [{
"title": "[BREAK] `getUsersOfRoom` API to return array of objects with user and username, instead of array of strings",
"userLogin": "rodrigok",
"contributors": []
}, {
"title": "[FIX] Incoming integrations would break when trying to use the `Store` feature.`",
"userLogin": "rodrigok",
"contributors": []
}, {
"title": "[FIX] Removed Deprecated Package rocketchat:sharedsecret`",
"userLogin": "rodrigok",
"contributors": []
}],
"0.72.0-rc.0": [{
"title": "[BREAK] Support for Cordova (Rocket.Chat Legacy app) has reached End-of-life, support has been discontinued",
"userLogin": "sampaiodiego",
"contributors": []
}],
"3.0.0-rc.9": [{
"title": "[BREAK] TLS v1.0 and TLS v1.1 were disabled by due to NodeJS update to v12. You can still enable them by using flags like `--tls-min-v1.0` and `--tls-min-v1.1`",
"userLogin": "sampaiodiego",
"contributors": []
}],
"3.6.0-rc.0": [{
"pr": "18642",
"title": "[NEW] Admin option to reset other users’ E2E encryption key",
"userLogin": "rodrigok",
"contributors": [],
"description": "Requires the 2FA password fallback enforcement enabled to work"
}],
"3.7.3": [{
"title": "[FIX] Exception on certain login cases including SAML",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
}],
"3.8.3": [{
"title": "[FIX] Exception on certain login cases including SAML",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
}],
"3.9.1": [{
"title": "[FIX] Exception on certain login cases including SAML",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
}],
"3.10.5": [{
"title": "[FIX] Security Hotfix",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
}],
"3.12.0-rc.3": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
}],
"3.12.2": [{
"title": "[FIX] Bump Livechat widget",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
}, {
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego",
"g-thome",
"KevLehman",
"matheusbsilva137"
]
}],
"3.13.2": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego",
"KevLehman",
"renatobecker"
]
}],
"3.14.2": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego",
"KevLehman",
"g-thome"
]
}],
"3.14.4": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "ggazzo",
"contributors": [
"ggazzo"
]
}],
"3.16.3": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "sampaiodiego",
"contributors": [
"g-thome",
"ggazzo",
"sampaiodiego",
"matheusbsilva137",
"pierre-lehnen-rc"
]
}],
"3.18.2": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego",
"pierre-lehnen-rc"
]
}],
"3.18.6": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "d-gubert",
"contributors": [
"ggazzo"
]
}],
"3.18.7": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "d-gubert",
"contributors": [
"ggazzo"
]
}],
"4.1.1": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
}],
"4.3.2": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego",
"yash-rajpal",
"pierre-lehnen-rc",
"gronke"
]
}],
"4.3.3": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "sampaiodiego",
"contributors": [
"gronke"
]
}],
"4.4.4": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "d-gubert",
"contributors": [
"ggazzo"
]
}],
"4.4.5": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "d-gubert",
"contributors": [
"ggazzo"
]
}],
"4.7.3": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "d-gubert",
"contributors": [
"ggazzo"
]
}],
"4.7.4": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "d-gubert",
"contributors": [
"ggazzo"
]
}],
"4.8.2": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
}],
"5.1.2": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "ggazzo",
"contributors": [
"ggazzo"
]
}],
"5.3.3": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "ggazzo",
"contributors": [
"ggazzo"
]
}],
"5.4.3": [{
"title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)",
"userLogin": "ggazzo",
"contributors": [
"ggazzo"
]
}]
}

29
.github/issue-close-app.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
# Config for Close Issue app: https://probot.github.io/apps/close-issue/
# Comment that will be sent if an issue is judged to be closed
comment: |
This issue was closed because it does not use our bug report issue template.
Please make sure to use it and fill it as much as you can so we can provide better and faster support.
The following sections **must not** be removed, or else the BOT will close it immediately again:
* Steps to reproduce
* Expected behavior
* Actual behavior
* Server Setup Information
* Version of Rocket.Chat Server
issueConfigs:
# There can be several configs for different kind of issues.
- content:
# Bug report
- "Steps to reproduce"
- "Expected behavior"
- "Actual behavior"
- "Server Setup Information"
- "Version of Rocket.Chat Server"
- content:
# Release issue
- "Before Release - Preparation"
# The issue is considered to be valid if it includes all keywords from any of these two configs.
# Or it will be closed by the app.

View File

@@ -0,0 +1,33 @@
name: Auto-close duplicate issues
on:
schedule:
- cron: '0 9 * * *'
workflow_dispatch:
permissions: {}
jobs:
auto-close-duplicates:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest
- name: Auto-close duplicate issues
run: bun run scripts/auto-close-duplicates.ts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}

128
.github/workflows/ci-code-check.yml vendored Normal file
View File

@@ -0,0 +1,128 @@
name: Code Checks
on:
workflow_call:
inputs:
node-version:
required: true
type: string
deno-version:
required: true
type: string
env:
TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }}
permissions: {}
jobs:
code-check:
runs-on: ubuntu-24.04-arm
permissions:
contents: read
name: ${{ matrix.check == 'ts' && 'TypeScript' || 'Code Lint' }}
strategy:
fail-fast: false
matrix:
check: ['ts', 'lint']
steps:
- name: Set Swap Space
uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c # v1.0.0
with:
swap-size-gb: 4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup NodeJS
uses: ./.github/actions/setup-node
with:
node-version: ${{ inputs.node-version }}
deno-version: ${{ inputs.deno-version }}
cache-modules: true
install: true
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: rharkor/caching-for-turbo@2238fae6eb9a9936f92356f54cb3660200d105e7 # v2.5.1
- uses: ./.github/actions/restore-packages
- name: Restore TypeScript incremental cache
id: restore-typecheck
if: matrix.check == 'ts'
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ./apps/meteor/tsconfig.typecheck.tsbuildinfo
key: typecheck-cache-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
typecheck-cache-${{ runner.os }}-
- name: Cache observability (typecheck)
if: matrix.check == 'ts'
run: |
{
echo "### TypeScript incremental cache"
echo "- **exact hit**: \`${{ steps.restore-typecheck.outputs.cache-hit }}\`"
} >> "$GITHUB_STEP_SUMMARY"
- name: Install Meteor
shell: bash
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: TS TypeCheck
if: matrix.check == 'ts'
run: yarn turbo run typecheck --concurrency=5
- name: Save TypeScript incremental cache
if: matrix.check == 'ts' && github.ref == 'refs/heads/develop' && github.event_name == 'push'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ./apps/meteor/tsconfig.typecheck.tsbuildinfo
key: typecheck-cache-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Restore ESLint cache
id: restore-eslint
if: matrix.check == 'lint'
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ./apps/meteor/.eslintcache
key: eslintcache-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
eslintcache-${{ runner.os }}-
- name: Cache observability (eslint)
if: matrix.check == 'lint'
run: |
{
echo "### ESLint cache"
echo "- **exact hit**: \`${{ steps.restore-eslint.outputs.cache-hit }}\`"
} >> "$GITHUB_STEP_SUMMARY"
- name: Lint
if: matrix.check == 'lint'
run: yarn lint
- name: Save ESLint cache
if: matrix.check == 'lint' && github.ref == 'refs/heads/develop' && github.event_name == 'push'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ./apps/meteor/.eslintcache
key: eslintcache-${{ runner.os }}-${{ hashFiles('yarn.lock') }}

View File

@@ -0,0 +1,42 @@
# .github/workflows/ci-preview-deploy.yml
name: Deploy GitHub Pages
concurrency: preview-deploy-${{ github.ref }}
on:
push:
branches:
- main
- master
- develop
permissions: {}
jobs:
deploy-preview:
runs-on: ubuntu-24.04-arm
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: rharkor/caching-for-turbo@2238fae6eb9a9936f92356f54cb3660200d105e7 # v2.5.1
- name: Setup NodeJS
uses: ./.github/actions/setup-node
with:
cache-modules: true
install: true
- name: Build
run: |
yarn turbo run build-preview
yarn turbo run .:build-preview-move
npx indexifier .preview --html --extensions .html > .preview/index.html
mv .preview ${{ github.ref_name }}
mkdir .preview
mv ${{ github.ref_name }} .preview
- name: Deploy
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .preview
keep_files: true

344
.github/workflows/ci-test-e2e.yml vendored Normal file
View File

@@ -0,0 +1,344 @@
name: Tests E2E
on:
workflow_call:
inputs:
node-version:
required: true
type: string
deno-version:
required: true
type: string
lowercase-repo:
required: true
type: string
gh-docker-tag:
required: true
type: string
enterprise-license:
type: string
transporter:
type: string
mongodb-version:
default: "['8.0']"
required: false
type: string
release:
required: true
type: string
fips:
default: false
required: false
type: boolean
shard:
default: '[1]'
required: false
type: string
total-shard:
default: 1
required: false
type: number
retries:
default: 0
required: false
type: number
type:
required: true
type: string
coverage:
required: false
type: string
secrets:
CR_USER:
required: true
CR_PAT:
required: true
QASE_API_TOKEN:
required: false
REPORTER_ROCKETCHAT_URL:
required: false
REPORTER_ROCKETCHAT_API_KEY:
required: false
CODECOV_TOKEN:
required: false
REPORTER_JIRA_ROCKETCHAT_API_KEY:
required: false
NPM_TOKEN:
required: false
env:
MONGO_URL: mongodb://localhost:27017/rocketchat?replicaSet=rs0&directConnection=true
TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }}
LOWERCASE_REPOSITORY: ${{ inputs.lowercase-repo }}
DOCKER_TAG: ${{ inputs.gh-docker-tag }}-amd64
permissions: {}
jobs:
test:
runs-on: ubuntu-24.04
permissions:
actions: read
contents: read
env:
# if building for production on develop branch or release, add suffix for coverage images
DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ inputs.coverage == matrix.mongodb-version && (github.event_name == 'release' || github.ref == 'refs/heads/develop') && '-cov' || '' }}
MONGODB_VERSION: ${{ matrix.mongodb-version }}
COVERAGE_DIR: '/tmp/coverage/${{ startsWith(inputs.type, ''api'') && ''api'' || inputs.type }}'
COVERAGE_FILE_NAME: '${{ inputs.type }}-${{ matrix.shard }}.json'
COVERAGE_REPORTER: ${{ inputs.coverage == matrix.mongodb-version && 'json' || '' }}
strategy:
fail-fast: false
matrix:
mongodb-version: ${{ fromJSON(inputs.mongodb-version) }}
shard: ${{ fromJSON(inputs.shard) }}
name: MongoDB ${{ matrix.mongodb-version }}${{ inputs.coverage == matrix.mongodb-version && ' coverage' || '' }} (${{ matrix.shard }}/${{ inputs.total-shard }})
steps:
- name: Collect Workflow Telemetry
if: inputs.type == 'perf'
uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0
with:
theme: dark
job_summary: true
comment_on_pr: false
- name: Setup kernel limits
run: |
echo "500 65535" > sudo tee -a /proc/sys/net/ipv4/ip_local_port_range
sudo sysctl -w net.ipv4.tcp_mem="383865 511820 2303190"
echo fs.file-max=20000500 | sudo tee -a /etc/sysctl.conf
echo fs.nr_open=20000500 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
- name: Login to GitHub Container Registry
if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') && github.actor != 'dependabot[bot]'
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ secrets.CR_USER }}
password: ${{ secrets.CR_PAT }}
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup NodeJS
uses: ./.github/actions/setup-node
with:
node-version: ${{ inputs.node-version }}
deno-version: ${{ inputs.deno-version }}
cache-modules: true
install: true
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: rharkor/caching-for-turbo@2238fae6eb9a9936f92356f54cb3660200d105e7 # v2.5.1
- uses: ./.github/actions/restore-packages
# Download Docker images from build artifacts
- name: Download Docker images
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
if: github.event.pull_request.head.repo.full_name != github.repository && github.event_name != 'release' && github.ref != 'refs/heads/develop'
with:
pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || (inputs.fips && 'docker-image-*-amd64-fips' || 'docker-image-*-amd64-coverage') }}
path: /tmp/docker-images
merge-multiple: true
# Load Docker images
- name: Load Docker images
if: github.event.pull_request.head.repo.full_name != github.repository && github.event_name != 'release' && github.ref != 'refs/heads/develop'
shell: bash
run: |
set -o xtrace
# Load all downloaded images
for image_file in /tmp/docker-images/*.tar; do
if [ -f "$image_file" ]; then
echo "Loading image from $image_file"
docker load -i "$image_file"
rm "$image_file"
fi
done
# List loaded images
docker images
- name: Set DEBUG_LOG_LEVEL (debug enabled)
if: runner.debug == '1'
run: echo "DEBUG_LOG_LEVEL=2" >> "$GITHUB_ENV"
- name: Start httpbin container and wait for it to be ready
if: startsWith(inputs.type, 'api')
run: |
docker compose -f docker-compose-ci.yml up -d httpbin
- name: Prepare code coverage directory
run: |
set -o xtrace
mkdir -p "$COVERAGE_DIR"
chmod 777 "$COVERAGE_DIR"
- name: Start containers for CE
if: inputs.release == 'ce'
env:
# API tests intentionally invoke deprecated DDP methods through
# /v1/method.call/:method. TEST_MODE='api' keeps every other test
# behavior on (rate-limiter bypass, short cache TTLs) while letting
# the deprecation logger log without throwing. Other suites use the
# docker-compose default of TEST_MODE='true'.
TEST_MODE: ${{ startsWith(inputs.type, 'api') && 'api' || 'true' }}
TEST_TYPE: ${{ inputs.type }}
run: |
# when we are testing CE, we only need to start the rocketchat container (plus mock-server for the api suite)
services=(rocketchat)
if [ "$TEST_TYPE" == "api" ]; then
services+=(mock-server)
fi
DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d "${services[@]}" --wait
- name: Start containers for EE
if: inputs.release == 'ee'
env:
ENTERPRISE_LICENSE: ${{ inputs.enterprise-license }}
TRANSPORTER: ${{ inputs.transporter }}
COMPOSE_PROFILES: ${{ inputs.type == 'api' && 'api' || '' }}
TEST_MODE: ${{ startsWith(inputs.type, 'api') && 'api' || 'true' }}
APPS_ENGINE_RUNTIME_BACKEND: ${{ inputs.type == 'api-apps-deno' && 'deno' || '' }}
FIPS_OVERRIDE: ${{ inputs.fips && '-f docker-compose-ci.fips.yml' || '' }}
run: |
read -r -a fips_override <<< "$FIPS_OVERRIDE"
DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml "${fips_override[@]}" up -d --wait
- uses: ./.github/actions/setup-playwright
if: inputs.type == 'ui'
- name: Wait services to start up
if: inputs.release == 'ee'
run: |
docker ps
until docker compose -f docker-compose-ci.yml logs ddp-streamer-service | grep -q "NetworkBroker started successfully"; do
echo "Waiting 'ddp-streamer' to start up"
((c++)) && ((c==10)) && docker compose -f docker-compose-ci.yml logs ddp-streamer-service && exit 1
sleep 10
done;
- name: Remove unused Docker images
run: docker system prune -af
- name: E2E Test API
if: inputs.type == 'api'
working-directory: ./apps/meteor
env:
WEBHOOK_TEST_URL: 'http://httpbin'
IS_EE: ${{ inputs.release == 'ee' && 'true' || '' }}
run: |
set -o xtrace
npm run testapi || s=$?
docker compose -f ../../docker-compose-ci.yml stop
ls -la "$COVERAGE_DIR"
exit "${s:-0}"
# This step should be temporary, only here until we remove the deno-runtime
- name: E2E Test API (apps + deno-runtime)
if: (inputs.type == 'api-apps-deno' && inputs.release == 'ee')
working-directory: ./apps/meteor
env:
WEBHOOK_TEST_URL: 'http://httpbin'
IS_EE: 'true'
run: |
set -o xtrace
npm run testapi:apps || s=$?
docker compose -f ../../docker-compose-ci.yml stop
ls -la "$COVERAGE_DIR"
exit "${s:-0}"
- name: E2E Test API (Livechat)
if: inputs.type == 'api-livechat'
working-directory: ./apps/meteor
env:
WEBHOOK_TEST_URL: 'http://httpbin'
IS_EE: ${{ inputs.release == 'ee' && 'true' || '' }}
run: |
set -o xtrace
npm run testapi:livechat || s=$?
docker compose -f ../../docker-compose-ci.yml stop
ls -la "$COVERAGE_DIR"
exit "${s:-0}"
- name: E2E Test UI (${{ matrix.shard }}/${{ inputs.total-shard }})
if: inputs.type == 'ui'
env:
E2E_COVERAGE: ${{ inputs.coverage == matrix.mongodb-version && 'true' || '' }}
IS_EE: ${{ inputs.release == 'ee' && 'true' || '' }}
REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }}
REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }}
REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }}
REPORTER_ROCKETCHAT_REPORT: ${{ github.event.pull_request.draft != 'true' && secrets.REPORTER_ROCKETCHAT_URL != '' && 'true' || '' }}
REPORTER_ROCKETCHAT_RUN: ${{ github.run_number }}
REPORTER_ROCKETCHAT_BRANCH: ${{ github.ref }}
REPORTER_ROCKETCHAT_DRAFT: ${{ github.event.pull_request.draft }}
REPORTER_ROCKETCHAT_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
REPORTER_ROCKETCHAT_AUTHOR: ${{ github.event.pull_request.user.login }}
REPORTER_ROCKETCHAT_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
REPORTER_ROCKETCHAT_PR: ${{ github.event.pull_request.number }}
QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }}
QASE_REPORT: ${{ github.ref == 'refs/heads/develop' && 'true' || '' }}
CI: true
PLAYWRIGHT_RETRIES: ${{ inputs.retries }}
E2E_SHARD: ${{ matrix.shard }}
E2E_TOTAL_SHARD: ${{ inputs.total-shard }}
working-directory: ./apps/meteor
run: |
set -o xtrace
yarn prepare
yarn test:e2e --shard="$E2E_SHARD/$E2E_TOTAL_SHARD"
- name: Merge ui coverage files
if: inputs.type == 'ui' && inputs.coverage == matrix.mongodb-version
working-directory: ./apps/meteor
run: |
npx nyc merge .nyc_output "${COVERAGE_DIR}/${COVERAGE_FILE_NAME}"
ls -la "$COVERAGE_DIR" || true
- name: Store playwright test trace
if: inputs.type == 'ui' && always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-test-trace-${{ inputs.release }}${{ inputs.fips && '-fips' || '' }}-${{ matrix.mongodb-version }}-${{ matrix.shard }}
path: ./apps/meteor/tests/e2e/.playwright*
include-hidden-files: true
- name: Show server logs if E2E test failed
if: failure()
run: docker compose -f docker-compose-ci.yml logs rocketchat authorization-service queue-worker-service ddp-streamer-service account-service presence-service omnichannel-transcript-service
- name: Show mongo logs if E2E test failed
if: failure()
run: docker compose -f docker-compose-ci.yml logs mongo
- name: Show traefik logs if E2E test failed
if: failure()
run: docker compose -f docker-compose-ci.yml logs traefik
- name: Store coverage
if: inputs.coverage == matrix.mongodb-version
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-${{ inputs.type }}-${{ matrix.shard }}
path: /tmp/coverage
include-hidden-files: true

62
.github/workflows/ci-test-storybook.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Test Storybook
on:
workflow_call:
inputs:
node-version:
required: true
type: string
deno-version:
required: true
type: string
secrets:
CODECOV_TOKEN:
required: true
NPM_TOKEN:
required: false
env:
TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }}
permissions: {}
jobs:
test:
runs-on: ubuntu-24.04-arm
permissions:
contents: read
name: Test Storybook
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup NodeJS
uses: ./.github/actions/setup-node
with:
node-version: ${{ inputs.node-version }}
deno-version: ${{ inputs.deno-version }}
cache-modules: true
install: true
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: rharkor/caching-for-turbo@2238fae6eb9a9936f92356f54cb3660200d105e7 # v2.5.1
- uses: ./.github/actions/restore-packages
- uses: ./.github/actions/setup-playwright
- name: Run tests
run: yarn test-storybook
env:
STORYBOOK_DISABLE_TELEMETRY: 1
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
flags: unit
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
# tests don't re-run on the develop push, so the merge queue run (same SHA
# that lands on develop) provides the develop coverage baseline
override_branch: ${{ github.event_name == 'merge_group' && github.event.merge_group.base_ref == 'refs/heads/develop' && 'develop' || '' }}

62
.github/workflows/ci-test-unit.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Tests Unit
on:
workflow_call:
inputs:
node-version:
required: true
type: string
deno-version:
required: true
type: string
enterprise-license:
required: false
type: string
secrets:
CODECOV_TOKEN:
required: false
NPM_TOKEN:
required: false
env:
MONGO_URL: mongodb://localhost:27017/rocketchat?replicaSet=rs0&directConnection=true
TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }}
ENTERPRISE_LICENSE: ${{ inputs.enterprise-license }}
permissions: {}
jobs:
test:
runs-on: ubuntu-24.04-arm
permissions:
contents: read
name: Unit Tests
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup NodeJS
uses: ./.github/actions/setup-node
with:
node-version: ${{ inputs.node-version }}
deno-version: ${{ inputs.deno-version }}
cache-modules: true
install: true
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: rharkor/caching-for-turbo@2238fae6eb9a9936f92356f54cb3660200d105e7 # v2.5.1
- uses: ./.github/actions/restore-packages
- name: Unit Test
run: yarn testunit --concurrency=1
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
flags: unit
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
# tests don't re-run on the develop push, so the merge queue run (same SHA
# that lands on develop) provides the develop coverage baseline
override_branch: ${{ github.event_name == 'merge_group' && github.event.merge_group.base_ref == 'refs/heads/develop' && 'develop' || '' }}

1333
.github/workflows/ci.yml vendored Normal file

File diff suppressed because it is too large Load Diff

57
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: 'Code scanning - action'
on:
push:
branches-ignore:
- dependabot/**
pull_request:
schedule:
- cron: '0 13 * * *'
jobs:
CodeQL-Build:
# CodeQL runs on ubuntu-latest and windows-latest
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
# Override language selection by uncommenting this and choosing your languages
with:
languages: javascript
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4

85
.github/workflows/dedupe-issues.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Rocket.Chat Issue Dedupe
on:
issues:
types: [opened]
workflow_dispatch:
inputs:
issue_number:
description: 'Issue number to process for duplicate detection'
required: true
type: string
permissions: {}
jobs:
dedupe-issues:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Run Claude Code slash command
uses: anthropics/claude-code-base-action@e8132bc5e637a42c27763fc757faa37e1ee43b34 # v0.0.63beta
with:
prompt: '/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}'
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
model: 'claude-sonnet-4-5-20250929'
claude_env: |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Log duplicate comment event to Statsig
if: always()
env:
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
run: |
ISSUE_NUMBER=${{ github.event.issue.number || inputs.issue_number }}
REPO=${{ github.repository }}
if [ -z "$STATSIG_API_KEY" ]; then
echo "STATSIG_API_KEY not found, skipping Statsig logging"
exit 0
fi
# Prepare the event payload
EVENT_PAYLOAD=$(jq -n \
--arg issue_number "$ISSUE_NUMBER" \
--arg repo "$REPO" \
--arg triggered_by "${{ github.event_name }}" \
--arg actor "${{ github.actor }}" \
'{
events: [{
eventName: "github_duplicate_comment_added",
value: 1,
metadata: {
repository: $repo,
issue_number: ($issue_number | tonumber),
triggered_by: $triggered_by,
workflow_run_id: "${{ github.run_id }}",
actor: $actor
},
time: (now | floor | tostring)
}]
}')
# Send to Statsig API
echo "Logging duplicate comment event to Statsig for issue #${ISSUE_NUMBER}"
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST https://events.statsigapi.net/v1/log_event \
-H "Content-Type: application/json" \
-H "STATSIG-API-KEY: ${STATSIG_API_KEY}" \
-d "$EVENT_PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 202 ]; then
echo "Successfully logged duplicate comment event for issue #${ISSUE_NUMBER}"
else
echo "Failed to log duplicate comment event for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}"
fi

View File

@@ -0,0 +1,42 @@
name: 'Release PR Description'
on:
pull_request:
branches:
- master
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions: {}
jobs:
update-pr:
runs-on: ubuntu-24.04
if: startsWith(github.head_ref, 'release-')
permissions:
contents: read
steps:
- name: Checkout Repo
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
token: ${{ secrets.CI_PAT }}
- name: Setup NodeJS
uses: ./.github/actions/setup-node
with:
cache-modules: true
install: true
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: rharkor/caching-for-turbo@2238fae6eb9a9936f92356f54cb3660200d105e7 # v2.5.1
- name: Build packages
run: yarn build
- name: Update PR description
uses: ./packages/release-action
with:
action: update-pr-description
env:
GITHUB_TOKEN: ${{ secrets.CI_PAT }}

79
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,79 @@
name: Release
# Single entry point for every path that publishes to npm. npm allows one trusted
# publisher per package and validates the entry-point workflow filename, so `cut`,
# `next` and `publish-final` all have to run from this file.
on:
push:
branches:
- master
schedule:
- cron: '28 21 20 * *' # run at minute 28 to avoid the chance of delay due to high load on GH
workflow_dispatch:
inputs:
name:
type: choice
description: Release type
default: next
required: true
options:
- next
- patch
- cut
base-ref:
description: Base version
default: develop
required: false
concurrency: ${{ github.workflow }}-${{ github.ref }}
env:
HUSKY: 0
permissions: {}
jobs:
release:
name: Release
runs-on: ubuntu-24.04
permissions:
contents: read
id-token: write # npm trusted publishing (OIDC)
steps:
- name: Checkout Repo
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event_name == 'schedule' && github.ref_name || github.event_name == 'workflow_dispatch' && inputs.base-ref || '' }}
fetch-depth: 0
token: ${{ secrets.CI_PAT }}
- name: Setup NodeJS
uses: ./.github/actions/setup-node
with:
cache-modules: true
install: true
# Transitional: npm attempts the OIDC exchange first and overrides this token on
# success, so it only takes effect when the exchange fails. That keeps releases
# alive while packages are registered as trusted publishers one by one. Remove
# once every package reports a non-null dist.attestations — see docs/npm-publishing.md.
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# Node 22 bundles npm 10, trusted publishing needs >= 11.5.1
- name: Setup npm
run: |
npm install -g npm@^11.15.0
npm --version
- uses: rharkor/caching-for-turbo@2238fae6eb9a9936f92356f54cb3660200d105e7 # v2.5.1
- name: Build packages
run: yarn build
- name: Release
uses: ./packages/release-action
with:
action: ${{ github.event_name == 'push' && 'publish-final' || github.event_name == 'schedule' && 'next' || inputs.name }}
base-ref: ${{ github.event_name == 'schedule' && github.ref_name || github.event_name == 'workflow_dispatch' && inputs.base-ref || '' }}
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # transitional, see the Setup NodeJS step
GITHUB_TOKEN: ${{ secrets.CI_PAT }}

20
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Close inactive issues
on:
schedule:
- cron: '0 */6 * * *'
jobs:
close-issues:
runs-on: ubuntu-24.04
permissions:
issues: write
steps:
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with:
days-before-issue-stale: 14
days-before-issue-close: 14
any-of-labels: 'stat: need more info,stat: waiting response'
stale-issue-label: 'stat: no response'
stale-issue-message: 'This issue has been marked as stale because there has been no further activity in the last 10 days. If the issue remains stale for the next 4 days (a total of 14 days with no activity), then it will be assumed that the question has been resolved and the issue will be automatically closed.'
close-issue-message: 'This issue was closed because it has been inactive for 14 days since being marked as stale.'
repo-token: ${{ secrets.GITHUB_TOKEN }}

58
.github/workflows/todo.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Create issues from TODOs
on:
workflow_dispatch:
inputs:
importAll:
default: false
required: false
type: boolean
description: Enable, if you want to import all TODOs. Runs on checked out branch! Only use if you're sure what you are doing.
sha:
default: ''
required: false
type: string
description: 'A commit SHA or range (e.g. "abc123" or "abc123...def456"). Single SHA compares against its parent.'
path:
default: ''
required: false
type: string
description: 'Import TODOs from a specific path (e.g. "apps/meteor/client" or "packages/core-typings/src/IMessage.ts").'
push:
branches: # do not set multiple branches, todos might be added and then get referenced by themselves in case of a merge
- develop
permissions:
issues: write
repository-projects: read
contents: read
jobs:
todos:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
working-directory: scripts/todo-issue
- name: Create issues from TODOs
run: bun run src/index.ts
working-directory: scripts/todo-issue
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
GITHUB_SHA: ${{ github.sha }}
BEFORE_SHA: ${{ github.event.before }}
IMPORT_ALL: ${{ inputs.importAll || 'false' }}
SHA_INPUT: ${{ inputs.sha || '' }}
PATH_FILTER: ${{ inputs.path || '' }}

View File

@@ -0,0 +1,47 @@
name: Update Version Durability
on:
workflow_dispatch:
workflow_call:
inputs:
LTS_VERSIONS:
required: false
type: string
secrets:
CI_PAT:
required: true
D360_TOKEN:
required: true
D360_USER_ID:
required: true
permissions: {}
jobs:
update-versions:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Use Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24.15.0
- name: Install dependencies
run: |
cd ./.github/actions/update-version-durability
npm install
- name: Update Version Durability
uses: ./.github/actions/update-version-durability
with:
LTS_VERSIONS: ${{ inputs.LTS_VERSIONS != '' && inputs.LTS_VERSIONS || vars.LTS_VERSIONS }}
GH_TOKEN: ${{ secrets.CI_PAT }}
D360_TOKEN: ${{ secrets.D360_TOKEN }}
D360_ARTICLE_ID: 800f8d52-409d-478d-b560-f82a2c0eb7fb
D360_USER_ID: ${{ secrets.D360_USER_ID }}
PUBLISH: true