mirror of
https://github.com/discordjs/discord.js.git
synced 2026-05-23 03:50:09 +00:00
Compare commits
157 Commits
@discordjs
...
@discordjs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a60e4033b | ||
|
|
ac683b9d04 | ||
|
|
437bb94349 | ||
|
|
77f7c7fb15 | ||
|
|
9ce9e66ec1 | ||
|
|
4906aaea4c | ||
|
|
ba5ccab919 | ||
|
|
93cf7721d0 | ||
|
|
e6370ae378 | ||
|
|
f8d7b1c0e0 | ||
|
|
669a23050a | ||
|
|
87f474aea8 | ||
|
|
0903e6d498 | ||
|
|
118e682682 | ||
|
|
53dbc96194 | ||
|
|
49ef3a833e | ||
|
|
bffe8a423e | ||
|
|
5d896f7d4f | ||
|
|
f3a2fb8a68 | ||
|
|
fbc3d57828 | ||
|
|
d6c7b8fe05 | ||
|
|
e9d6046047 | ||
|
|
24e20d27a9 | ||
|
|
8760fde9ff | ||
|
|
90ed51e06e | ||
|
|
641a980b60 | ||
|
|
1f2047ff90 | ||
|
|
23636a9a2f | ||
|
|
6a6bc63973 | ||
|
|
b715b7d653 | ||
|
|
2cb2d81b82 | ||
|
|
0411ce268e | ||
|
|
584bd6f2fc | ||
|
|
c887388db6 | ||
|
|
4059432c78 | ||
|
|
6b34486f3f | ||
|
|
b3f3d54f18 | ||
|
|
ea597aa886 | ||
|
|
5e08ea68d2 | ||
|
|
ec7b20f51d | ||
|
|
74df5c7fa4 | ||
|
|
cec816f9f5 | ||
|
|
3979f0b6e6 | ||
|
|
13dc779029 | ||
|
|
fc0b6f7f8e | ||
|
|
a5afc406b9 | ||
|
|
437437461e | ||
|
|
e2e71b4d09 | ||
|
|
bddf018f26 | ||
|
|
ec9080b883 | ||
|
|
bba0e72e22 | ||
|
|
00accf7470 | ||
|
|
dd795da790 | ||
|
|
b0f8df0f6c | ||
|
|
bf83db9480 | ||
|
|
1b1ae2f0cb | ||
|
|
1f7d1f8094 | ||
|
|
9907ff915e | ||
|
|
9b707f2b83 | ||
|
|
5d92525596 | ||
|
|
45f7e1a2e8 | ||
|
|
69adc6f4b9 | ||
|
|
3d37660107 | ||
|
|
87776bb0e8 | ||
|
|
2d5531f35c | ||
|
|
bbef68d271 | ||
|
|
c63bde9479 | ||
|
|
2ca187bd34 | ||
|
|
8fb400827f | ||
|
|
bb71dc825e | ||
|
|
defb083528 | ||
|
|
a6de2707fc | ||
|
|
432e9b8425 | ||
|
|
54303d085d | ||
|
|
5c90b7f716 | ||
|
|
f623e7a315 | ||
|
|
bb459d95e9 | ||
|
|
48682ad474 | ||
|
|
057fc89c92 | ||
|
|
dc13324ddc | ||
|
|
de94eaf351 | ||
|
|
8f97d2bacf | ||
|
|
5eabec14d4 | ||
|
|
785ec8fd75 | ||
|
|
6b383350a6 | ||
|
|
bf6761a44a | ||
|
|
fcd35ea2e7 | ||
|
|
b2970bb2dd | ||
|
|
efa16a6095 | ||
|
|
be04acd534 | ||
|
|
9461045e5a | ||
|
|
3654efede2 | ||
|
|
d8e94d8f10 | ||
|
|
4f59b740d0 | ||
|
|
093ac924ae | ||
|
|
ab8bf0f4d2 | ||
|
|
9c76bbea17 | ||
|
|
b8397b24e5 | ||
|
|
ba0cb66ff9 | ||
|
|
15021990e8 | ||
|
|
a76b1b60f7 | ||
|
|
9c8784fe51 | ||
|
|
b0e57126dc | ||
|
|
e723230dff | ||
|
|
38c699bc8a | ||
|
|
c5d40d3807 | ||
|
|
02d196474a | ||
|
|
68031210f5 | ||
|
|
3cdddbe31d | ||
|
|
757bed0b1f | ||
|
|
599ad3eab5 | ||
|
|
7f60a8fc5d | ||
|
|
885defbce4 | ||
|
|
4f174c644d | ||
|
|
346d1be72b | ||
|
|
94cc02a258 | ||
|
|
17d4c78fde | ||
|
|
3b5c600b9e | ||
|
|
311aaf2605 | ||
|
|
4ea73bb64e | ||
|
|
aae2faf9e9 | ||
|
|
9b07036d70 | ||
|
|
c1e6890132 | ||
|
|
38a37b5caf | ||
|
|
29a50bb476 | ||
|
|
d22b55fc82 | ||
|
|
a468ae8bb5 | ||
|
|
638b896efa | ||
|
|
27d0659a45 | ||
|
|
a35d760421 | ||
|
|
7f467ed2d1 | ||
|
|
f5dd6879a2 | ||
|
|
f9ba11eba3 | ||
|
|
b36ec98382 | ||
|
|
bb884fc260 | ||
|
|
555961b3b8 | ||
|
|
92c1a511dc | ||
|
|
35207b0b31 | ||
|
|
29fd89f23c | ||
|
|
c2432d5704 | ||
|
|
616208ba77 | ||
|
|
3640fe7bca | ||
|
|
c78af13c1e | ||
|
|
914cc4ba54 | ||
|
|
393ded4ea1 | ||
|
|
20258f94bf | ||
|
|
7816ec2e6b | ||
|
|
5498e18bf4 | ||
|
|
e673b3c129 | ||
|
|
776880d06b | ||
|
|
c05244af61 | ||
|
|
12deea85e5 | ||
|
|
07c12101e5 | ||
|
|
30d79e85fb | ||
|
|
f2794e1221 | ||
|
|
0474a43751 | ||
|
|
c91d03c535 |
@@ -5,8 +5,9 @@
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
["chore", "build", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test", "types", "typings"]
|
||||
["chore", "build", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test", "types"]
|
||||
],
|
||||
"scope-case": [0]
|
||||
"scope-case": [0],
|
||||
"subject-exclamation-mark": [0]
|
||||
}
|
||||
}
|
||||
|
||||
3
.github/COMMIT_CONVENTION.md
vendored
3
.github/COMMIT_CONVENTION.md
vendored
@@ -7,7 +7,7 @@
|
||||
Messages must be matched by the following regex:
|
||||
|
||||
```js
|
||||
/^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,72}/;
|
||||
/^(revert: )?(feat|fix|docs|style|refactor|perf|test|build|ci|chore|types)(\(.+\))?!?: .{1,72}/;
|
||||
```
|
||||
|
||||
#### Examples
|
||||
@@ -55,6 +55,7 @@ A commit message consists of a **header**, **body** and **footer**. The header h
|
||||
```
|
||||
|
||||
The **header** is mandatory and the **scope** of the header is optional.
|
||||
If the commit contains **Breaking Changes**, a `!` can be added before the `:` as an indicator.
|
||||
|
||||
### Revert
|
||||
|
||||
|
||||
120
.github/labeler.yml
vendored
120
.github/labeler.yml
vendored
@@ -1,60 +1,100 @@
|
||||
apps:guide:
|
||||
- apps/guide/*
|
||||
- apps/guide/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- apps/guide/*
|
||||
- apps/guide/**/*
|
||||
apps:website:
|
||||
- apps/website/*
|
||||
- apps/website/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- apps/website/*
|
||||
- apps/website/**/*
|
||||
packages:api-extractor:
|
||||
- packages/api-extractor/*
|
||||
- packages/api-extractor/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/api-extractor/*
|
||||
- packages/api-extractor/**/*
|
||||
packages:api-extractor-model:
|
||||
- packages/api-extractor-model/*
|
||||
- packages/api-extractor-model/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/api-extractor-model/*
|
||||
- packages/api-extractor-model/**/*
|
||||
packages:brokers:
|
||||
- packages/brokers/*
|
||||
- packages/brokers/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/brokers/*
|
||||
- packages/brokers/**/*
|
||||
packages:builders:
|
||||
- packages/builders/*
|
||||
- packages/builders/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/builders/*
|
||||
- packages/builders/**/*
|
||||
packages:collection:
|
||||
- packages/collection/*
|
||||
- packages/collection/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/collection/*
|
||||
- packages/collection/**/*
|
||||
packages:core:
|
||||
- packages/core/*
|
||||
- packages/core/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/core/*
|
||||
- packages/core/**/*
|
||||
packages:create-discord-bot:
|
||||
- packages/create-discord-bot/*
|
||||
- packages/create-discord-bot/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/create-discord-bot/*
|
||||
- packages/create-discord-bot/**/*
|
||||
packages:discord.js:
|
||||
- packages/discord.js/*
|
||||
- packages/discord.js/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/discord.js/*
|
||||
- packages/discord.js/**/*
|
||||
packages:docgen:
|
||||
- packages/docgen/*
|
||||
- packages/docgen/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/docgen/*
|
||||
- packages/docgen/**/*
|
||||
packages:formatters:
|
||||
- packages/formatters/*
|
||||
- packages/formatters/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/formatters/*
|
||||
- packages/formatters/**/*
|
||||
packages:next:
|
||||
- packages/next/*
|
||||
- packages/next/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/next/*
|
||||
- packages/next/**/*
|
||||
packages:proxy:
|
||||
- packages/proxy/*
|
||||
- packages/proxy/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/proxy/*
|
||||
- packages/proxy/**/*
|
||||
packages:proxy-container:
|
||||
- packages/proxy-container/*
|
||||
- packages/proxy-container/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/proxy-container/*
|
||||
- packages/proxy-container/**/*
|
||||
packages:rest:
|
||||
- packages/rest/*
|
||||
- packages/rest/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/rest/*
|
||||
- packages/rest/**/*
|
||||
packages:ui:
|
||||
- packages/ui/*
|
||||
- packages/ui/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/ui/*
|
||||
- packages/ui/**/*
|
||||
packages:util:
|
||||
- packages/util/*
|
||||
- packages/util/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/util/*
|
||||
- packages/util/**/*
|
||||
packages:voice:
|
||||
- packages/voice/*
|
||||
- packages/voice/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/voice/*
|
||||
- packages/voice/**/*
|
||||
packages:ws:
|
||||
- packages/ws/*
|
||||
- packages/ws/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/ws/*
|
||||
- packages/ws/**/*
|
||||
|
||||
2
.github/workflows/cleanup-cache.yml
vendored
2
.github/workflows/cleanup-cache.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cleanup caches
|
||||
run: |
|
||||
|
||||
8
.github/workflows/deploy-website.yml
vendored
8
.github/workflows/deploy-website.yml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js v20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
|
||||
8
.github/workflows/deprecate-version.yml
vendored
8
.github/workflows/deprecate-version.yml
vendored
@@ -34,12 +34,12 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js v20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
|
||||
22
.github/workflows/documentation.yml
vendored
22
.github/workflows/documentation.yml
vendored
@@ -36,14 +36,14 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.ref || '' }}
|
||||
|
||||
- name: Install node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js v20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Checkout main repository
|
||||
if: ${{ inputs.ref && inputs.ref != 'main' }}
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: 'main'
|
||||
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
- name: Apply tag to api-extractor config
|
||||
if: ${{ env.REF_TYPE == 'tag' && !inputs.ref }}
|
||||
run: sed -i 's!https://github.com/discordjs/discord.js/tree/main!https://github.com/discordjs/discord.js/tree/${{ steps.extract-tag.outputs.semver }}!' "packages/${{ steps.extract-tag.outputs.package}}/"
|
||||
run: sed -i 's!https://github.com/discordjs/discord.js/tree/main!https://github.com/discordjs/discord.js/tree/${{ github.ref_name }}!' "packages/${{ steps.extract-tag.outputs.package}}/api-extractor.json"
|
||||
|
||||
- name: Build docs
|
||||
run: pnpm run docs
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Checkout docs repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'discordjs/docs'
|
||||
token: ${{ secrets.DJS_DOCS }}
|
||||
@@ -211,12 +211,12 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js v20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
|
||||
2
.github/workflows/issue-triage.yml
vendored
2
.github/workflows/issue-triage.yml
vendored
@@ -6,7 +6,7 @@ jobs:
|
||||
issue-triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: github/issue-labeler@v3.2
|
||||
- uses: github/issue-labeler@v3.4
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
configuration-path: .github/issue-labeler.yml
|
||||
|
||||
4
.github/workflows/label-sync.yml
vendored
4
.github/workflows/label-sync.yml
vendored
@@ -15,9 +15,9 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Label sync
|
||||
uses: crazy-max/ghaction-github-labeler@v4
|
||||
uses: crazy-max/ghaction-github-labeler@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-inactive-days: 365
|
||||
|
||||
30
.github/workflows/pr-triage.yml
vendored
30
.github/workflows/pr-triage.yml
vendored
@@ -1,13 +1,35 @@
|
||||
name: 'PR Triage'
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
jobs:
|
||||
pr-triage:
|
||||
name: PR Triage
|
||||
label:
|
||||
name: Label
|
||||
if: github.event.action != 'edited'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Automatically label PR
|
||||
uses: actions/labeler@v4
|
||||
- name: Label pull request
|
||||
uses: actions/labeler@v5
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
sync-labels: true
|
||||
validate-title:
|
||||
name: Validate title
|
||||
if: github.event.action != 'synchronize'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate pull request title
|
||||
env:
|
||||
TITLE: ${{ github.event.pull_request.title }}
|
||||
run: |
|
||||
REGEX="^(revert: )?(feat|fix|docs|style|refactor|perf|test|build|ci|chore|types)(\\(.+\\))?!?: .{1,72}$"
|
||||
|
||||
echo "Title: \"$TITLE\""
|
||||
|
||||
if [[ ! "$TITLE" =~ $REGEX ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
10
.github/workflows/publish-dev-docker.yml
vendored
10
.github/workflows/publish-dev-docker.yml
vendored
@@ -10,18 +10,18 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js v20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
run: echo ${{ secrets.DOCKER_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
|
||||
10
.github/workflows/publish-dev.yml
vendored
10
.github/workflows/publish-dev.yml
vendored
@@ -43,14 +43,14 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js v20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: Check the current development version
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: Publish package
|
||||
if: steps.release-check.outputs.release == '1'
|
||||
run: |
|
||||
pnpm --filter=${{ matrix.package }} run release --preid "dev.$(date +%s)-$(git rev-parse --short HEAD)"
|
||||
pnpm --filter=${{ matrix.package }} run release --preid "dev.$(date +%s)-$(git rev-parse --short HEAD)" --skip-changelog
|
||||
pnpm --filter=${{ matrix.package }} publish --provenance --no-git-checks --tag dev || true
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
|
||||
10
.github/workflows/publish-docker.yml
vendored
10
.github/workflows/publish-docker.yml
vendored
@@ -7,18 +7,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js v20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
run: echo ${{ secrets.DOCKER_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
|
||||
8
.github/workflows/publish-release.yml
vendored
8
.github/workflows/publish-release.yml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js v20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
10
.github/workflows/tests.yml
vendored
10
.github/workflows/tests.yml
vendored
@@ -15,14 +15,14 @@ jobs:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install node.js v18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js v20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
@@ -62,3 +62,5 @@ jobs:
|
||||
- name: Upload Coverage
|
||||
if: github.repository_owner == 'discordjs'
|
||||
uses: ./packages/actions/src/uploadCoverage
|
||||
with:
|
||||
codecov_token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
// import bundleAnalyzer from '@next/bundle-analyzer';
|
||||
// import { withContentlayer } from 'next-contentlayer';
|
||||
const bundleAnalyzer = require('@next/bundle-analyzer');
|
||||
const { withContentlayer } = require('next-contentlayer');
|
||||
|
||||
const withBundleAnalyzer = bundleAnalyzer({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
module.exports = withContentlayer({
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
typedRoutes: true,
|
||||
},
|
||||
images: {
|
||||
dangerouslyAllowSVG: true,
|
||||
contentDispositionType: 'attachment',
|
||||
contentSecurityPolicy: "default-src 'self'; frame-src 'none'; sandbox;",
|
||||
},
|
||||
poweredByHeader: false,
|
||||
});
|
||||
|
||||
module.exports = withBundleAnalyzer(
|
||||
withContentlayer({
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
typedRoutes: true,
|
||||
},
|
||||
images: {
|
||||
dangerouslyAllowSVG: true,
|
||||
contentDispositionType: 'attachment',
|
||||
contentSecurityPolicy: "default-src 'self'; frame-src 'none'; sandbox;",
|
||||
},
|
||||
poweredByHeader: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -48,51 +48,50 @@
|
||||
"@code-hike/mdx": "^0.9.0",
|
||||
"@discordjs/ui": "workspace:^",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@vercel/analytics": "^1.2.2",
|
||||
"@vercel/edge-config": "^1.1.0",
|
||||
"@vercel/analytics": "^1.3.1",
|
||||
"@vercel/edge-config": "^1.1.1",
|
||||
"@vercel/og": "^0.6.2",
|
||||
"ariakit": "2.0.0-next.44",
|
||||
"cmdk": "^1.0.0",
|
||||
"contentlayer": "^0.3.4",
|
||||
"next": "14.2.1",
|
||||
"next": "^14.2.3",
|
||||
"next-contentlayer": "^0.3.4",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-custom-scrollbars-2": "^4.5.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"rehype-autolink-headings": "^6.1.1",
|
||||
"rehype-slug": "^5.1.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.33.3"
|
||||
"sharp": "^0.33.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "14.2.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
"@testing-library/react": "^15.0.7",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/html-escaper": "^3.0.2",
|
||||
"@types/node": "18.18.8",
|
||||
"@types/react": "^18.2.79",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@unocss/eslint-plugin": "^0.59.3",
|
||||
"@unocss/postcss": "^0.58.5",
|
||||
"@unocss/reset": "^0.59.3",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@vitest/coverage-v8": "^1.5.0",
|
||||
"@types/node": "^18.19.45",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@unocss/eslint-plugin": "^0.60.4",
|
||||
"@unocss/postcss": "^0.60.4",
|
||||
"@unocss/reset": "^0.60.4",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-neon": "^0.1.62",
|
||||
"eslint-formatter-pretty": "^6.0.1",
|
||||
"happy-dom": "^14.7.1",
|
||||
"happy-dom": "^14.12.0",
|
||||
"hast-util-to-string": "^2.0.0",
|
||||
"hastscript": "^8.0.0",
|
||||
"html-escaper": "^3.0.3",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.2.5",
|
||||
"turbo": "^1.13.2",
|
||||
"typescript": "^5.4.5",
|
||||
"unocss": "^0.59.3",
|
||||
"vercel": "^34.0.0",
|
||||
"vitest": "^1.5.0"
|
||||
"prettier": "^3.3.3",
|
||||
"turbo": "^2.0.14",
|
||||
"typescript": "~5.5.4",
|
||||
"unocss": "^0.60.4",
|
||||
"vercel": "^37.0.0",
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -134,8 +134,8 @@ collector.on('end', (collected) => {
|
||||
|
||||
### Await reactions
|
||||
|
||||
<DocsLink type="class" parent="Message" symbol="awaitReactions" brackets /> works almost the same as a reaction collector,
|
||||
except it is Promise-based. The same differences apply as with channel collectors.
|
||||
<DocsLink type="class" parent="Message" symbol="awaitReactions" brackets /> works almost the same as a reaction
|
||||
collector, except it is Promise-based. The same differences apply as with channel collectors.
|
||||
|
||||
```js
|
||||
const collectorFilter = (reaction, user) => {
|
||||
|
||||
@@ -158,21 +158,25 @@ Various _`create()`_ and _`edit()`_ methods on managers and objects have had the
|
||||
- <DocsLink type="class" parent="Role" symbol="edit" brackets /> now takes _`reason`_ in the _`data`_ parameter
|
||||
- <DocsLink type="class" parent="Sticker" symbol="edit" brackets /> now takes _`reason`_ in the _`data`_ parameter
|
||||
- <DocsLink type="class" parent="ThreadChannel" symbol="edit" brackets /> now takes _`reason`_ in the _`data`_ parameter
|
||||
- <DocsLink type="class" parent="GuildChannelManager" symbol="create" brackets /> now takes _`name`_ in the _`options`_ parameter
|
||||
- <DocsLink type="class" parent="GuildChannelManager" symbol="create" brackets /> now takes _`name`_ in the _`options`_
|
||||
parameter
|
||||
- <DocsLink type="class" parent="GuildChannelManager" symbol="createWebhook" brackets /> (and other text-based channels)
|
||||
now takes _`channel`_ and _`name`_ in the _`options`_ parameter
|
||||
- <DocsLink type="class" parent="GuildChannelManager" symbol="edit" brackets /> now takes _`reason`_ as a part of _`data`_
|
||||
- <DocsLink type="class" parent="GuildChannelManager" symbol="edit" brackets /> now takes _`reason`_ as a part of
|
||||
_`data`_
|
||||
- <DocsLink type="class" parent="GuildEmojiManager" symbol="edit" brackets /> now takes _`reason`_ as a part of _`data`_
|
||||
- <DocsLink type="class" parent="GuildManager" symbol="create" brackets /> now takes _`name`_ as a part of _`options`_
|
||||
- <DocsLink type="class" parent="GuildMemberManager" symbol="edit" brackets /> now takes _`reason`_ as a part of _`data`_
|
||||
- <DocsLink type="class" parent="GuildMemberManager" symbol="edit" brackets /> now takes _`reason`_ as a part of
|
||||
_`data`_
|
||||
- <DocsLink type="class" parent="GuildMember" symbol="edit" brackets /> now takes _`reason`_ as a part of _`data`_
|
||||
- <DocsLink type="class" parent="GuildStickerManager" symbol="edit" brackets /> now takes _`reason`_ as a part of _`data`_
|
||||
- <DocsLink type="class" parent="GuildStickerManager" symbol="edit" brackets /> now takes _`reason`_ as a part of
|
||||
_`data`_
|
||||
- <DocsLink type="class" parent="RoleManager" symbol="edit" brackets /> now takes _`reason`_ as a part of _`options`_
|
||||
- <DocsLink type="class" parent="Webhook" symbol="edit" brackets /> now takes _`reason`_ as a part of _`options`_
|
||||
- <DocsLink type="class" parent="GuildEmojiManager" symbol="create" brackets /> now takes _`attachment`_ and _`name`_ as
|
||||
a part of _`options`_
|
||||
- <DocsLink type="class" parent="GuildStickerManager" symbol="create" brackets /> now takes _`file`_, _`name`_, and _`tags`_
|
||||
as a part of _`options`_
|
||||
- <DocsLink type="class" parent="GuildStickerManager" symbol="create" brackets /> now takes _`file`_, _`name`_, and
|
||||
_`tags`_ as a part of _`options`_
|
||||
|
||||
### Activity
|
||||
|
||||
@@ -236,9 +240,10 @@ Dynamic URLs use <DocsLink package="rest" type="Interface" parent="ImageURLOptio
|
||||
|
||||
### CategoryChannel
|
||||
|
||||
<DocsLink type="class" parent="CategoryChannel" symbol="children" /> is no longer a _`Collection`_ of channels the category
|
||||
contains. It is now a <DocsLink type="class" parent="CategoryChannelChildManager" />. This also means
|
||||
_`CategoryChannel#createChannel()`_ has been moved to the <DocsLink type="class" parent="CategoryChannelChildManager" />.
|
||||
<DocsLink type="class" parent="CategoryChannel" symbol="children" /> is no longer a _`Collection`_ of channels the
|
||||
category contains. It is now a <DocsLink type="class" parent="CategoryChannelChildManager" />. This also means
|
||||
_`CategoryChannel#createChannel()`_ has been moved to the <DocsLink type="class" parent="CategoryChannelChildManager" />
|
||||
.
|
||||
|
||||
### Channel
|
||||
|
||||
@@ -262,8 +267,8 @@ The _`restWsBridgeTimeout`_ client option has been removed.
|
||||
|
||||
### CommandInteractionOptionResolver
|
||||
|
||||
<DocsLink type="class" parent="CommandInteractionOptionResolver" symbol="getMember" brackets /> no longer has a parameter
|
||||
for _`required`_.[^1]
|
||||
<DocsLink type="class" parent="CommandInteractionOptionResolver" symbol="getMember" brackets /> no longer has a
|
||||
parameter for _`required`_.[^1]
|
||||
|
||||
### Constants
|
||||
|
||||
@@ -357,7 +362,8 @@ The following properties & methods have been moved to the <DocsLink type="class"
|
||||
|
||||
### GuildMember
|
||||
|
||||
<DocsLink type="class" parent="GuildMember" symbol="pending" /> is now nullable to account for partial guild members.[^4]
|
||||
<DocsLink type="class" parent="GuildMember" symbol="pending" /> is now nullable to account for partial guild
|
||||
members.[^4]
|
||||
|
||||
### IntegrationApplication
|
||||
|
||||
@@ -582,8 +588,8 @@ _`Role.comparePositions()`_ has been removed. Use <DocsLink type="class" parent=
|
||||
|
||||
### Sticker
|
||||
|
||||
<DocsLink type="class" parent="Sticker" symbol="tags" /> is now a nullable string (_`string | null`_). Previously, it was
|
||||
a nullable array of strings (_`string[] | null`_).[^5]
|
||||
<DocsLink type="class" parent="Sticker" symbol="tags" /> is now a nullable string (_`string | null`_). Previously, it
|
||||
was a nullable array of strings (_`string[] | null`_).[^5]
|
||||
|
||||
### ThreadChannel
|
||||
|
||||
@@ -668,8 +674,8 @@ Added support for <DocsLink type="class" parent="BaseChannel" symbol="flags" />.
|
||||
|
||||
Store channels have been removed as they are no longer part of the API.
|
||||
|
||||
<DocsLink type="class" parent="BaseChannel" symbol="url" /> has been added which is a link to a channel, just like in the
|
||||
client.
|
||||
<DocsLink type="class" parent="BaseChannel" symbol="url" /> has been added which is a link to a channel, just like in
|
||||
the client.
|
||||
|
||||
Additionally, new typeguards have been added:
|
||||
|
||||
@@ -713,13 +719,13 @@ Component collector options now use the <DiscordAPITypesLink type="enum" parent=
|
||||
|
||||
### CommandInteraction
|
||||
|
||||
<DocsLink type="class" parent="CommandInteraction" symbol="commandGuildId" /> has been added which is the id of the guild
|
||||
the invoked application command is registered to.
|
||||
<DocsLink type="class" parent="CommandInteraction" symbol="commandGuildId" /> has been added which is the id of the
|
||||
guild the invoked application command is registered to.
|
||||
|
||||
### CommandInteractionOptionResolver
|
||||
|
||||
<DocsLink type="class" parent="CommandInteractionOptionResolver" symbol="getChannel" brackets /> now has a third parameter
|
||||
which narrows the channel type.
|
||||
<DocsLink type="class" parent="CommandInteractionOptionResolver" symbol="getChannel" brackets /> now has a third
|
||||
parameter which narrows the channel type.
|
||||
|
||||
### Events
|
||||
|
||||
@@ -814,9 +820,15 @@ Added the _`threadName`_ property in <DocsLink type="typedef" parent="WebhookMes
|
||||
discord.js uses <DocsLink package="ws" /> internally.
|
||||
|
||||
[^1]: https://github.com/discordjs/discord.js/pull/7188
|
||||
|
||||
[^2]: https://github.com/discordjs/discord.js/pull/6492
|
||||
|
||||
[^3]: https://github.com/discordjs/discord.js/pull/7669
|
||||
|
||||
[^4]: https://github.com/discordjs/discord.js/issues/6546
|
||||
|
||||
[^5]: https://github.com/discordjs/discord.js/pull/8010
|
||||
|
||||
[^6]: https://github.com/discordjs/discord.js/issues/7091
|
||||
|
||||
[^7]: https://github.com/discord/discord-api-docs/pull/6017
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import bundleAnalyzer from '@next/bundle-analyzer';
|
||||
import localesPlugin from '@react-aria/optimize-locales-plugin';
|
||||
|
||||
const withBundleAnalyzer = bundleAnalyzer({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
});
|
||||
|
||||
export default withBundleAnalyzer({
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
export default {
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
dangerouslyAllowSVG: true,
|
||||
@@ -18,14 +14,8 @@ export default withBundleAnalyzer({
|
||||
},
|
||||
},
|
||||
experimental: {
|
||||
ppr: false,
|
||||
},
|
||||
webpack(config, { isServer }) {
|
||||
if (!isServer) {
|
||||
config.plugins.push(localesPlugin.webpack({ locales: ['en-US'] }));
|
||||
}
|
||||
|
||||
return config;
|
||||
ppr: true,
|
||||
reactCompiler: true,
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
@@ -41,4 +31,4 @@ export default withBundleAnalyzer({
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -49,58 +49,56 @@
|
||||
"dependencies": {
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@vercel/analytics": "^1.2.2",
|
||||
"@vercel/blob": "^0.22.3",
|
||||
"@vercel/edge-config": "^1.1.0",
|
||||
"@vercel/analytics": "^1.3.1",
|
||||
"@vercel/edge-config": "^1.1.1",
|
||||
"@vercel/og": "^0.6.2",
|
||||
"@vercel/postgres": "^0.8.0",
|
||||
"@vercel/postgres": "^0.9.0",
|
||||
"cmdk": "^1.0.0",
|
||||
"geist": "^1.3.0",
|
||||
"jotai": "^2.8.0",
|
||||
"lucide-react": "^0.368.0",
|
||||
"meilisearch": "^0.38.0",
|
||||
"next": "14.2.1",
|
||||
"next-mdx-remote": "^4.4.1",
|
||||
"jotai": "^2.8.2",
|
||||
"lucide-react": "^0.379.0",
|
||||
"meilisearch": "^0.40.0",
|
||||
"next": "^15.0.0-rc.0",
|
||||
"next-mdx-remote-client": "^1.0.3",
|
||||
"next-themes": "^0.3.0",
|
||||
"overlayscrollbars": "^2.6.0",
|
||||
"overlayscrollbars": "^2.8.3",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"react": "^18.2.0",
|
||||
"react-aria-components": "^1.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"sharp": "^0.33.3",
|
||||
"react": "19.0.0-rc-f994737d14-20240522",
|
||||
"react-aria-components": "^1.2.1",
|
||||
"react-dom": "19.0.0-rc-f994737d14-20240522",
|
||||
"sharp": "^0.33.4",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"vaul": "^0.9.0"
|
||||
"vaul": "^0.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "14.2.1",
|
||||
"@react-aria/optimize-locales-plugin": "^1.0.2",
|
||||
"@shikijs/rehype": "1.1.7",
|
||||
"@tailwindcss/typography": "^0.5.12",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
"@shikijs/rehype": "^1.6.2",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@testing-library/react": "^15.0.7",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/node": "18.18.8",
|
||||
"@types/react": "^18.2.79",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@vitest/coverage-v8": "^1.5.0",
|
||||
"@types/node": "^18.19.45",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"babel-plugin-react-compiler": "0.0.0-experimental-592953e-20240517",
|
||||
"cpy-cli": "^5.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-neon": "^0.1.62",
|
||||
"eslint-formatter-pretty": "^6.0.1",
|
||||
"happy-dom": "^14.7.1",
|
||||
"happy-dom": "^14.12.0",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-rehype": "^11.1.0",
|
||||
"shiki": "1.3.0",
|
||||
"shiki": "^1.6.2",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"turbo": "^1.13.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vercel": "^34.0.0",
|
||||
"vitest": "^1.5.0"
|
||||
"turbo": "^2.0.14",
|
||||
"typescript": "~5.5.4",
|
||||
"vercel": "^37.0.0",
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable react/no-unknown-property */
|
||||
|
||||
import { ImageResponse } from 'next/og';
|
||||
import { resolveKind } from '~/util/resolveNodeKind';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { DocItem } from '~/components/DocItem';
|
||||
import { fetchNode } from '~/util/fetchNode';
|
||||
|
||||
@@ -25,6 +26,10 @@ export default async function Page({
|
||||
}) {
|
||||
const node = await fetchNode({ item: params.item, packageName: params.packageName, version: params.version });
|
||||
|
||||
if (!node) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex w-full flex-col gap-8 pb-12 md:pb-0">
|
||||
<DocItem node={node} packageName={params.packageName} version={params.version} />
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Navigation } from '~/components/Navigation';
|
||||
import { OverlayScrollbarsComponent } from '~/components/OverlayScrollbars';
|
||||
import { Drawer } from '~/components/ui/Drawer';
|
||||
import { Footer } from '~/components/ui/Footer';
|
||||
import { ENV } from '~/util/env';
|
||||
import { fetchDependencies } from '~/util/fetchDependencies';
|
||||
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
@@ -33,11 +32,9 @@ export default async function Layout({
|
||||
return (
|
||||
// eslint-disable-next-line react/no-unknown-property
|
||||
<div vaul-drawer-wrapper="" className="mx-auto flex max-w-screen-2xl flex-col gap-12 p-6 md:flex-row">
|
||||
<div
|
||||
className={`sticky hidden flex-shrink-0 self-start md:block ${ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'top-[64px]' : 'top-6'}`}
|
||||
>
|
||||
<div className="sticky top-6 hidden flex-shrink-0 self-start md:block">
|
||||
<OverlayScrollbarsComponent
|
||||
className={`${ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'max-h-[calc(100dvh-48px-40px)]' : 'max-h-[calc(100dvh-48px)]'}`}
|
||||
className="max-h-[calc(100dvh-48px)]"
|
||||
defer
|
||||
options={{
|
||||
overflow: { x: 'hidden' },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import rehypeShikiFromHighlighter from '@shikijs/rehype/core';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
import { MDXRemote } from 'next-mdx-remote-client/rsc';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { getHighlighterCore } from 'shiki/core';
|
||||
import getWasm from 'shiki/wasm';
|
||||
@@ -30,7 +30,7 @@ export default async function Page({ params }: { readonly params: { readonly pac
|
||||
remarkPlugins: [remarkGfm],
|
||||
rehypePlugins: [
|
||||
[
|
||||
rehypeShikiFromHighlighter as any,
|
||||
rehypeShikiFromHighlighter,
|
||||
highlighter,
|
||||
{
|
||||
themes: {
|
||||
|
||||
@@ -20,11 +20,7 @@ export const viewport: Viewport = {
|
||||
};
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(
|
||||
process.env.NEXT_PUBLIC_LOCAL_DEV === 'true'
|
||||
? `http://localhost:${process.env.PORT ?? 3_000}`
|
||||
: 'https://discord.js.org',
|
||||
),
|
||||
metadataBase: new URL(ENV.IS_LOCAL_DEV ? `http://localhost:${ENV.PORT}` : 'https://discord.js.org'),
|
||||
title: {
|
||||
template: '%s | discord.js',
|
||||
default: 'discord.js',
|
||||
|
||||
@@ -18,8 +18,9 @@ export async function Badges({ node }: { readonly node: any }) {
|
||||
const isAbstract = node.isAbstract;
|
||||
const isReadonly = node.isReadonly;
|
||||
const isOptional = node.isOptional;
|
||||
const isExternal = node.isExternal;
|
||||
|
||||
const isAny = isDeprecated || isProtected || isStatic || isAbstract || isReadonly || isOptional;
|
||||
const isAny = isDeprecated || isProtected || isStatic || isAbstract || isReadonly || isOptional || isExternal;
|
||||
|
||||
return isAny ? (
|
||||
<div className="mb-1 flex gap-3">
|
||||
@@ -33,6 +34,7 @@ export async function Badges({ node }: { readonly node: any }) {
|
||||
{isAbstract ? <Badge className="bg-cyan-500/20 text-cyan-500">abstract</Badge> : null}
|
||||
{isReadonly ? <Badge className="bg-purple-500/20 text-purple-500">readonly</Badge> : null}
|
||||
{isOptional ? <Badge className="bg-cyan-500/20 text-cyan-500">optional</Badge> : null}
|
||||
{isExternal ? <Badge className="bg-purple-500/20 text-purple-500">external</Badge> : null}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
import { BuiltinDocumentationLinks } from '~/util/builtinDocumentationLinks';
|
||||
import { OverlayScrollbarsComponent } from './OverlayScrollbars';
|
||||
import { SyntaxHighlighter } from './SyntaxHighlighter';
|
||||
|
||||
@@ -28,6 +29,21 @@ export async function DocNode({ node, version }: { readonly node?: any; readonly
|
||||
href={node.uri}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{`${node.text}${node.members ?? ''}`}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (node.text in BuiltinDocumentationLinks) {
|
||||
const href = BuiltinDocumentationLinks[node.text as keyof typeof BuiltinDocumentationLinks];
|
||||
return (
|
||||
<a
|
||||
key={`${node.text}-${idx}`}
|
||||
className="text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
||||
href={href}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{node.text}
|
||||
</a>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { BuiltinDocumentationLinks } from '~/util/builtinDocumentationLinks';
|
||||
|
||||
export async function ExcerptNode({ node, version }: { readonly node?: any; readonly version: string }) {
|
||||
const createExcerpt = (excerpts: any) => {
|
||||
const excerpt = Array.isArray(excerpts) ? excerpts : excerpts.excerpts ?? [excerpts];
|
||||
const excerpt = Array.isArray(excerpts) ? excerpts : (excerpts.excerpts ?? [excerpts]);
|
||||
|
||||
return (
|
||||
<span
|
||||
|
||||
@@ -2,6 +2,7 @@ import { VscGithubInverted } from '@react-icons/all-files/vsc/VscGithubInverted'
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { fetchSitemap } from '~/util/fetchSitemap';
|
||||
import { fetchVersions } from '~/util/fetchVersions';
|
||||
import { resolveNodeKind } from './DocKind';
|
||||
@@ -28,6 +29,11 @@ export async function Navigation({
|
||||
readonly version: string;
|
||||
}) {
|
||||
const node = await fetchSitemap({ packageName, version });
|
||||
|
||||
if (!node) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const versions = await fetchVersions(packageName);
|
||||
|
||||
const groupedNodes = node.reduce((acc: any, node: any) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useAtom, useSetAtom } from 'jotai';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import MeiliSearch from 'meilisearch';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDebounceValue } from 'usehooks-ts';
|
||||
import { isCmdKOpenAtom } from '~/stores/cmdk';
|
||||
import { isDrawerOpenAtom } from '~/stores/drawer';
|
||||
@@ -25,32 +25,29 @@ export function CmdK({ dependencies }: { readonly dependencies: string[] }) {
|
||||
const [search, setSearch] = useDebounceValue('', 250);
|
||||
const [searchResults, setSearchResults] = useState<any[]>([]);
|
||||
|
||||
const packageName = useMemo(() => pathname?.split('/').slice(3, 4)[0], [pathname]);
|
||||
const branchName = useMemo(() => pathname?.split('/').slice(4, 5)[0], [pathname]);
|
||||
const packageName = pathname?.split('/').slice(3, 4)[0];
|
||||
const branchName = pathname?.split('/').slice(4, 5)[0];
|
||||
|
||||
const searchResultItems = useMemo(
|
||||
() =>
|
||||
searchResults?.map((item, idx) => (
|
||||
<Command.Item
|
||||
key={`${item.id}-${idx}`}
|
||||
className="flex cursor-pointer place-items-center gap-2 rounded-md p-2 data-[selected]:bg-neutral-200 dark:data-[selected]:bg-neutral-800"
|
||||
onSelect={() => {
|
||||
router.push(item.path);
|
||||
setOpen(false);
|
||||
}}
|
||||
value={item.id}
|
||||
>
|
||||
{resolveKind(item.kind)}
|
||||
<div className="flex flex-grow flex-col">
|
||||
<span className="font-semibold">{item.name}</span>
|
||||
<span className="line-clamp-1 text-sm">{item.summary}</span>
|
||||
<span className="truncate text-xs">{item.path}</span>
|
||||
</div>
|
||||
<ArrowRight aria-hidden className="flex-shrink-0" />
|
||||
</Command.Item>
|
||||
)) ?? [],
|
||||
[router, searchResults, setOpen],
|
||||
);
|
||||
const searchResultItems =
|
||||
searchResults?.map((item, idx) => (
|
||||
<Command.Item
|
||||
key={`${item.id}-${idx}`}
|
||||
className="flex cursor-pointer place-items-center gap-2 rounded-md p-2 data-[selected='true']:bg-neutral-200 dark:data-[selected='true']:bg-neutral-800"
|
||||
onSelect={() => {
|
||||
router.push(item.path);
|
||||
setOpen(false);
|
||||
}}
|
||||
value={item.id}
|
||||
>
|
||||
{resolveKind(item.kind)}
|
||||
<div className="flex flex-grow flex-col">
|
||||
<span className="font-semibold">{item.name}</span>
|
||||
<span className="line-clamp-1 text-sm">{item.summary}</span>
|
||||
<span className="truncate text-xs">{item.path}</span>
|
||||
</div>
|
||||
<ArrowRight aria-hidden className="flex-shrink-0" />
|
||||
</Command.Item>
|
||||
)) ?? [];
|
||||
|
||||
// Toggle the menu when ⌘K is pressed
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const ENV = {
|
||||
IS_LOCAL_DEV: process.env.VERCEL_ENV === 'development' || process.env.NEXT_PUBLIC_LOCAL_DEV === 'true',
|
||||
IS_PREVIEW: process.env.VERCEL_ENV === 'preview',
|
||||
PORT: process.env.PORT ?? 3_000,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { ENV } from './env';
|
||||
|
||||
export async function fetchNode({
|
||||
@@ -15,30 +14,26 @@ export async function fetchNode({
|
||||
const normalizeItem = item.split(encodeURIComponent(':')).join('.').toLowerCase();
|
||||
|
||||
if (ENV.IS_LOCAL_DEV) {
|
||||
try {
|
||||
const fileContent = await readFile(
|
||||
join(
|
||||
process.cwd(),
|
||||
`../../packages/${packageName}/docs/${packageName}/split/${version}.${normalizeItem}.api.json`,
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
return JSON.parse(fileContent);
|
||||
} catch {
|
||||
notFound();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const isMainVersion = version === 'main';
|
||||
const fileContent = await fetch(
|
||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.${normalizeItem}.api.json`,
|
||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
||||
const fileContent = await readFile(
|
||||
join(
|
||||
process.cwd(),
|
||||
`../../packages/${packageName}/docs/${packageName}/split/${version}.${normalizeItem}.api.json`,
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
return await fileContent.json();
|
||||
} catch {
|
||||
notFound();
|
||||
return JSON.parse(fileContent);
|
||||
}
|
||||
|
||||
const isMainVersion = version === 'main';
|
||||
const fileContent = await fetch(
|
||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.${normalizeItem}.api.json`,
|
||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
||||
);
|
||||
|
||||
if (!fileContent.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fileContent.json();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { ENV } from './env';
|
||||
|
||||
export async function fetchSitemap({
|
||||
@@ -11,27 +10,19 @@ export async function fetchSitemap({
|
||||
readonly version: string;
|
||||
}) {
|
||||
if (ENV.IS_LOCAL_DEV) {
|
||||
try {
|
||||
const fileContent = await readFile(
|
||||
join(process.cwd(), `../../packages/${packageName}/docs/${packageName}/split/${version}.sitemap.api.json`),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
return JSON.parse(fileContent);
|
||||
} catch {
|
||||
notFound();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const isMainVersion = version === 'main';
|
||||
const fileContent = await fetch(
|
||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.sitemap.api.json`,
|
||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
||||
const fileContent = await readFile(
|
||||
join(process.cwd(), `../../packages/${packageName}/docs/${packageName}/split/${version}.sitemap.api.json`),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
return await fileContent.json();
|
||||
} catch {
|
||||
notFound();
|
||||
return JSON.parse(fileContent);
|
||||
}
|
||||
|
||||
const isMainVersion = version === 'main';
|
||||
const fileContent = await fetch(
|
||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.sitemap.api.json`,
|
||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
||||
);
|
||||
|
||||
return fileContent.json();
|
||||
}
|
||||
|
||||
50
package.json
50
package.json
@@ -6,19 +6,19 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "turbo run build --concurrency=4",
|
||||
"build:affected": "turbo run build --filter='...[origin/main]' --concurrency=4",
|
||||
"build:apps": "turbo run build:local --filter='...{apps/*}' --concurrency=4",
|
||||
"build:apps:affected": "turbo run build:local --filter='...{apps/*}[origin/main]' --concurrency=4",
|
||||
"build:affected": "turbo run build --filter=...[origin/main] --concurrency=4",
|
||||
"build:apps": "turbo run build:local --filter=...{apps/*} --concurrency=4",
|
||||
"build:apps:affected": "turbo run build:local --filter=...{apps/*}[origin/main] --concurrency=4",
|
||||
"test": "turbo run test --concurrency=4",
|
||||
"test:affected": "turbo run test --filter='...[origin/main]' --concurrency=4",
|
||||
"test:affected": "turbo run test --filter=...[origin/main] --concurrency=4",
|
||||
"lint": "turbo run lint --concurrency=4",
|
||||
"lint:affected": "turbo run lint --filter='...[origin/main]' --concurrency=4",
|
||||
"lint:affected": "turbo run lint --filter=...[origin/main] --concurrency=4",
|
||||
"format": "turbo run format --concurrency=4",
|
||||
"format:affected": "turbo run format --filter='...[origin/main]' --concurrency=4",
|
||||
"format:affected": "turbo run format --filter=...[origin/main] --concurrency=4",
|
||||
"fmt": "turbo run format --concurrency=4",
|
||||
"fmt:affected": "turbo run format --filter='...[origin/main]' --concurrency=4",
|
||||
"fmt:affected": "turbo run format --filter=...[origin/main] --concurrency=4",
|
||||
"docs": "turbo run docs --concurrency=4",
|
||||
"docs:affected": "turbo run docs --filter='...[origin/main]' --concurrency=4",
|
||||
"docs:affected": "turbo run docs --filter=...[origin/main] --concurrency=4",
|
||||
"prepare": "is-ci || husky",
|
||||
"update": "pnpm --recursive update --interactive",
|
||||
"update:latest": "pnpm --recursive update --interactive --latest",
|
||||
@@ -28,7 +28,7 @@
|
||||
"contributors": [
|
||||
"Crawl <icrawltogo@gmail.com>",
|
||||
"Amish Shah <amishshah.2k@gmail.com>",
|
||||
"Vlad Frangu <kingdgrizzle@gmail.com>",
|
||||
"Vlad Frangu <me@vladfrangu.dev>",
|
||||
"SpaceEEC <spaceeec@yahoo.com>",
|
||||
"Aura Román <kyradiscord@gmail.com>"
|
||||
],
|
||||
@@ -50,28 +50,28 @@
|
||||
"homepage": "https://discord.js.org",
|
||||
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.2.2",
|
||||
"@commitlint/config-angular": "^19.2.2",
|
||||
"@favware/cliff-jumper": "^3.0.2",
|
||||
"@commitlint/cli": "^19.4.0",
|
||||
"@commitlint/config-angular": "^19.3.0",
|
||||
"@favware/cliff-jumper": "^4.1.0",
|
||||
"@favware/npm-deprecate": "^1.0.7",
|
||||
"@types/lodash.merge": "^4.6.9",
|
||||
"@unocss/eslint-plugin": "^0.59.3",
|
||||
"@vitest/coverage-v8": "^1.5.0",
|
||||
"@unocss/eslint-plugin": "^0.59.4",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-neon": "^0.1.62",
|
||||
"husky": "^9.0.11",
|
||||
"husky": "^9.1.5",
|
||||
"is-ci": "^3.0.1",
|
||||
"lint-staged": "^15.2.2",
|
||||
"lint-staged": "^15.2.9",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"prettier": "^3.2.5",
|
||||
"tsup": "^8.0.2",
|
||||
"turbo": "^1.13.2",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^7.7.0",
|
||||
"unocss": "^0.59.3",
|
||||
"vercel": "^34.0.0",
|
||||
"vitest": "^1.5.0"
|
||||
"prettier": "^3.3.3",
|
||||
"tsup": "^8.2.4",
|
||||
"turbo": "^2.0.14",
|
||||
"typescript": "~5.5.4",
|
||||
"typescript-eslint": "^8.2.0",
|
||||
"unocss": "^0.60.4",
|
||||
"vercel": "^37.0.0",
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
@@ -97,5 +97,5 @@
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.7+sha256.50783dd0fa303852de2dd1557cd4b9f07cb5b018154a6e76d0f40635d6cee019"
|
||||
"packageManager": "pnpm@9.8.0"
|
||||
}
|
||||
|
||||
@@ -42,27 +42,27 @@
|
||||
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/glob": "^0.4.0",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@discordjs/scripts": "workspace:^",
|
||||
"@vercel/blob": "^0.22.3",
|
||||
"@vercel/postgres": "^0.8.0",
|
||||
"@vercel/blob": "^0.23.4",
|
||||
"@vercel/postgres": "^0.9.0",
|
||||
"meilisearch": "^0.38.0",
|
||||
"p-limit": "^5.0.0",
|
||||
"tslib": "^2.6.2",
|
||||
"undici": "6.13.0"
|
||||
"p-limit": "^6.1.0",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "6.21.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.18.8",
|
||||
"@vitest/coverage-v8": "^1.5.0",
|
||||
"@types/node": "^18.19.45",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-neon": "^0.1.62",
|
||||
"eslint-formatter-pretty": "^6.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
"tsup": "^8.0.2",
|
||||
"turbo": "^1.13.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^1.5.0"
|
||||
"prettier": "^3.3.3",
|
||||
"tsup": "^8.2.4",
|
||||
"turbo": "^2.0.14",
|
||||
"typescript": "~5.5.4",
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -4,7 +4,7 @@ export function formatTag(tag: string) {
|
||||
|
||||
if (parsed?.groups) {
|
||||
const isSubpackage = typeof parsed.groups.package === 'string';
|
||||
const pkg = isSubpackage ? parsed.groups.package : parsedPackage?.groups?.package ?? 'discord.js';
|
||||
const pkg = isSubpackage ? parsed.groups.package : (parsedPackage?.groups?.package ?? 'discord.js');
|
||||
const semver = parsed.groups.semver;
|
||||
|
||||
return {
|
||||
|
||||
@@ -9,7 +9,7 @@ runs:
|
||||
with:
|
||||
swap-size-gb: 10
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
- uses: pnpm/action-setup@v4.0.0
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
@@ -26,7 +26,7 @@ runs:
|
||||
run: |
|
||||
echo "YEAR_MONTH=$(/bin/date -u "+%Y%m")" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-config.outputs.STORE_PATH }}
|
||||
|
||||
@@ -1,88 +1,134 @@
|
||||
name: 'Upload Coverage'
|
||||
description: 'Uploads code coverage reports to codecov with separate flags for separate packages'
|
||||
inputs:
|
||||
codecov_token:
|
||||
description: 'Codecov token.'
|
||||
required: true
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Upload Guide Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('apps/guide/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./apps/guide/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: guide
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Website Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('apps/website/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./apps/website/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: website
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Brokers Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/brokers/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/brokers/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: brokers
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Builders Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/builders/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/builders/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: builders
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Collection Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/collection/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/collection/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: collection
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Discord.js Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/discord.js/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/discord.js/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: discord.js
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Formatters Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/formatters/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/formatters/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: formatters
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Next Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/next/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/next/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: next
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Proxy Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/proxy/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/proxy/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: proxy
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Rest Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/rest/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/rest/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: rest
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Voice Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/voice/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/voice/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: voice
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload WS Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/ws/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/ws/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: ws
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Util Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/util/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/util/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: util
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload Utilities Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
if: ${{ hashFiles('packages/actions/coverage/cobertura-coverage.xml') != '' || hashFiles('packages/scripts/coverage/cobertura-coverage.xml') != '' }}
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./packages/actions/coverage/cobertura-coverage.xml, ./packages/scripts/coverage/cobertura-coverage.xml
|
||||
disable_search: true
|
||||
flags: utilities
|
||||
token: ${{ inputs.CODECOV_TOKEN }}
|
||||
|
||||
@@ -53,7 +53,6 @@ try {
|
||||
console.log('Uploading indices...');
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
promises = indices.map(async (index) =>
|
||||
limit(async () => {
|
||||
console.log(`Uploading ${index.index}...`);
|
||||
|
||||
@@ -21,7 +21,7 @@ runs:
|
||||
echo "NPM_GLOBAL_CACHE_FOLDER=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Restore yarn cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
id: yarn-download-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
|
||||
@@ -31,7 +31,7 @@ runs:
|
||||
|
||||
- name: Restore global npm cache folder
|
||||
id: npm-global-cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.yarn-config.outputs.NPM_GLOBAL_CACHE_FOLDER }}
|
||||
key: npm-global-cache-default-${{ runner.os }}-${{ steps.yarn-config.outputs.CURRENT_NODE_VERSION }}-${{ hashFiles(yarn.lock, .yarnrc.yml) }}
|
||||
|
||||
@@ -37,14 +37,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^18.19.22",
|
||||
"@types/node": "^18.19.45",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-neon": "^0.1.62",
|
||||
"eslint-formatter-pretty": "^6.0.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.2.5",
|
||||
"tsup": "^8.0.2",
|
||||
"turbo": "^1.13.2"
|
||||
"prettier": "^3.3.3",
|
||||
"tsup": "^8.2.4",
|
||||
"turbo": "^2.0.14",
|
||||
"typescript": "~5.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ export class ApiPackage extends ApiItemContainerMixin(ApiNameMixin(ApiDocumented
|
||||
toolPackage: ioptions.toolPackage ?? packageJson.name,
|
||||
// In test mode, we don't write the real version, since that would cause spurious diffs whenever
|
||||
// the version is bumped. Instead we write a placeholder string.
|
||||
toolVersion: ioptions.testMode ? '[test mode]' : ioptions.toolVersion ?? packageJson.version,
|
||||
toolVersion: ioptions.testMode ? '[test mode]' : (ioptions.toolVersion ?? packageJson.version),
|
||||
schemaVersion: ApiJsonSchemaVersion.LATEST,
|
||||
oldestForwardsCompatibleVersion: ApiJsonSchemaVersion.OLDEST_FORWARDS_COMPATIBLE,
|
||||
tsdocConfig,
|
||||
|
||||
@@ -182,7 +182,9 @@ export interface DocgenJson {
|
||||
}
|
||||
|
||||
function formatVarType(type: DocgenVarTypeJson): string {
|
||||
return (Array.isArray(type) ? type : type.types ?? []).map((t1) => t1.map((t2) => t2.join('')).join('')).join(' | ');
|
||||
return (Array.isArray(type) ? type : (type.types ?? []))
|
||||
.map((t1) => t1.map((t2) => t2.join('')).join(''))
|
||||
.join(' | ');
|
||||
}
|
||||
|
||||
function getFirstType(type: DocgenVarTypeJson): string {
|
||||
@@ -192,7 +194,7 @@ function getFirstType(type: DocgenVarTypeJson): string {
|
||||
// function mapEvent(_event: DocgenEventJson, _package: string, _parent: DocgenClassJson): void {}
|
||||
|
||||
function mapVarType(type: DocgenVarTypeJson, _package: string): IExcerptToken[] {
|
||||
const mapper = Array.isArray(type) ? type : type.types ?? [];
|
||||
const mapper = Array.isArray(type) ? type : (type.types ?? []);
|
||||
return mapper.flatMap((typ) =>
|
||||
typ.reduce<IExcerptToken[]>(
|
||||
(arr, [_class, symbol]) => [
|
||||
|
||||
@@ -6,6 +6,7 @@ import { type ApiItem, ApiItemKind } from '../items/ApiItem.js';
|
||||
import { ApiItemContainerMixin } from '../mixins/ApiItemContainerMixin.js';
|
||||
import { ApiParameterListMixin } from '../mixins/ApiParameterListMixin.js';
|
||||
import type { ApiEntryPoint } from './ApiEntryPoint.js';
|
||||
import type { ApiMethod } from './ApiMethod.js';
|
||||
import type { ApiModel } from './ApiModel.js';
|
||||
import type { ApiPackage } from './ApiPackage.js';
|
||||
|
||||
@@ -114,11 +115,21 @@ export class ModelReferenceResolver {
|
||||
if (memberSelector === undefined) {
|
||||
if (foundMembers.length > 1) {
|
||||
const foundClass: ApiItem | undefined = foundMembers.find((member) => member.kind === ApiItemKind.Class);
|
||||
const foundEvent: ApiItem | undefined = foundMembers.find((member) => member.kind === ApiItemKind.Event);
|
||||
if (
|
||||
foundClass &&
|
||||
foundMembers.filter((member) => member.kind === ApiItemKind.Interface).length === foundMembers.length - 1
|
||||
) {
|
||||
currentItem = foundClass;
|
||||
} else if (
|
||||
foundMembers.every((member) => member.kind === ApiItemKind.Method && (member as ApiMethod).overloadIndex)
|
||||
) {
|
||||
currentItem = foundMembers.find((member) => (member as ApiMethod).overloadIndex === 1)!;
|
||||
} else if (
|
||||
foundEvent &&
|
||||
foundMembers.filter((member) => member.kind === ApiItemKind.Method).length === foundMembers.length - 1
|
||||
) {
|
||||
currentItem = foundEvent;
|
||||
} else {
|
||||
result.errorMessage = `The member reference ${JSON.stringify(identifier)} was ambiguous`;
|
||||
return result;
|
||||
|
||||
@@ -50,15 +50,15 @@
|
||||
"@microsoft/tsdoc": "0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.18.8",
|
||||
"@types/node": "^18.19.45",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-neon": "^0.1.62",
|
||||
"eslint-formatter-pretty": "^6.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
"tsup": "^8.0.2",
|
||||
"turbo": "^1.13.2",
|
||||
"typescript": "^5.4.5"
|
||||
"prettier": "^3.3.3",
|
||||
"tsup": "^8.2.4",
|
||||
"turbo": "^2.0.14",
|
||||
"typescript": "~5.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -199,7 +199,7 @@ export function genToken(model: ApiModel, token: ExcerptToken, version: string)
|
||||
}
|
||||
|
||||
const item = token.canonicalReference
|
||||
? model.resolveDeclarationReference(token.canonicalReference, undefined).resolvedApiItem ?? null
|
||||
? (model.resolveDeclarationReference(token.canonicalReference, undefined).resolvedApiItem ?? null)
|
||||
: null;
|
||||
|
||||
return {
|
||||
|
||||
@@ -61,12 +61,12 @@
|
||||
"resolve": "~1.22.1",
|
||||
"semver": "~7.5.4",
|
||||
"source-map": "0.6.1",
|
||||
"typescript": "^5.4.5"
|
||||
"typescript": "~5.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.17.0",
|
||||
"@types/node": "^18.19.22",
|
||||
"@types/lodash": "^4.17.4",
|
||||
"@types/node": "^18.19.45",
|
||||
"@types/resolve": "^1.20.6",
|
||||
"@types/semver": "^7.5.8",
|
||||
"cpy-cli": "^5.0.0",
|
||||
@@ -75,8 +75,8 @@
|
||||
"eslint-config-neon": "^0.1.62",
|
||||
"eslint-formatter-pretty": "^6.0.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.2.5",
|
||||
"tsup": "^8.0.2",
|
||||
"turbo": "^1.13.2"
|
||||
"prettier": "^3.3.3",
|
||||
"tsup": "^8.2.4",
|
||||
"turbo": "^2.0.14"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ interface DocgenEventJson {
|
||||
}
|
||||
|
||||
interface DocgenParamJson {
|
||||
default?: string;
|
||||
default?: boolean | number | string;
|
||||
description: string;
|
||||
name: string;
|
||||
nullable?: boolean;
|
||||
@@ -155,7 +155,7 @@ interface DocgenMethodJson {
|
||||
interface DocgenPropertyJson {
|
||||
abstract?: boolean;
|
||||
access?: DocgenAccess;
|
||||
default?: string;
|
||||
default?: boolean | number | string;
|
||||
deprecated?: DocgenDeprecated;
|
||||
description: string;
|
||||
meta: DocgenMetaJson;
|
||||
@@ -1264,7 +1264,7 @@ export class ApiModelGenerator {
|
||||
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
|
||||
const docComment: tsdoc.DocComment | undefined = jsDoc
|
||||
? this._tsDocParser.parseString(
|
||||
`/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${
|
||||
`/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}${jsDoc.default ? ` (default: ${this._escapeSpecialChars(jsDoc.default)})` : ''}\n${
|
||||
'see' in jsDoc ? jsDoc.see.map((see) => ` * @see ${see}\n`).join('') : ''
|
||||
}${'readonly' in jsDoc && jsDoc.readonly ? ' * @readonly\n' : ''}${
|
||||
'deprecated' in jsDoc && jsDoc.deprecated
|
||||
@@ -1342,7 +1342,7 @@ export class ApiModelGenerator {
|
||||
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
|
||||
const docComment: tsdoc.DocComment | undefined = jsDoc
|
||||
? this._tsDocParser.parseString(
|
||||
`/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${
|
||||
`/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}${jsDoc.default ? ` (default: ${this._escapeSpecialChars(jsDoc.default)})` : ''}\n${
|
||||
'see' in jsDoc ? jsDoc.see.map((see) => ` * @see ${see}\n`).join('') : ''
|
||||
}${'readonly' in jsDoc && jsDoc.readonly ? ' * @readonly\n' : ''}${
|
||||
'deprecated' in jsDoc && jsDoc.deprecated
|
||||
@@ -1423,7 +1423,9 @@ export class ApiModelGenerator {
|
||||
}${
|
||||
'returns' in jsDoc
|
||||
? jsDoc.returns
|
||||
.map((ret) => ` * @returns ${Array.isArray(ret) ? '' : this._fixLinkTags(ret.description) ?? ''}\n`)
|
||||
.map(
|
||||
(ret) => ` * @returns ${Array.isArray(ret) ? '' : (this._fixLinkTags(ret.description) ?? '')}\n`,
|
||||
)
|
||||
.join('')
|
||||
: ''
|
||||
} */`,
|
||||
@@ -1744,6 +1746,14 @@ export class ApiModelGenerator {
|
||||
return sourceLocation;
|
||||
}
|
||||
|
||||
private _escapeSpecialChars(input: boolean | number | string) {
|
||||
if (typeof input !== 'string') {
|
||||
return input;
|
||||
}
|
||||
|
||||
return input.replaceAll(/(?<char>[{}])/g, '\\$<char>');
|
||||
}
|
||||
|
||||
private _fixLinkTags(input?: string): string | undefined {
|
||||
return input
|
||||
?.replaceAll(linkRegEx, (_match, _p1, _p2, _p3, _p4, _p5, _offset, _string, groups) => {
|
||||
@@ -1764,7 +1774,7 @@ export class ApiModelGenerator {
|
||||
}
|
||||
|
||||
private _mapVarType(typey: DocgenVarTypeJson): IExcerptToken[] {
|
||||
const mapper = Array.isArray(typey) ? typey : typey.types ?? [];
|
||||
const mapper = Array.isArray(typey) ? typey : (typey.types ?? []);
|
||||
const lookup: { [K in ts.SyntaxKind]?: string } = {
|
||||
[ts.SyntaxKind.ClassDeclaration]: 'class',
|
||||
[ts.SyntaxKind.EnumDeclaration]: 'enum',
|
||||
@@ -1788,18 +1798,20 @@ export class ApiModelGenerator {
|
||||
{
|
||||
kind: type?.includes("'") ? ExcerptTokenKind.Content : ExcerptTokenKind.Reference,
|
||||
text: fixPrimitiveTypes(type ?? 'unknown', symbol),
|
||||
canonicalReference: type?.includes("'")
|
||||
? undefined
|
||||
: DeclarationReference.package(pkg)
|
||||
.addNavigationStep(
|
||||
Navigation.Members as any,
|
||||
DeclarationReference.parseComponent(type ?? 'unknown'),
|
||||
)
|
||||
.withMeaning(
|
||||
(lookup[astSymbol?.astDeclarations.at(-1)?.declaration.kind ?? ts.SyntaxKind.ClassDeclaration] ??
|
||||
'class') as Meaning,
|
||||
)
|
||||
.toString(),
|
||||
canonicalReference:
|
||||
type?.includes("'") || !astEntity
|
||||
? undefined
|
||||
: DeclarationReference.package(pkg)
|
||||
.addNavigationStep(
|
||||
Navigation.Members as any,
|
||||
DeclarationReference.parseComponent(type ?? 'unknown'),
|
||||
)
|
||||
.withMeaning(
|
||||
(lookup[
|
||||
astSymbol?.astDeclarations.at(-1)?.declaration.kind ?? ts.SyntaxKind.ClassDeclaration
|
||||
] ?? 'class') as Meaning,
|
||||
)
|
||||
.toString(),
|
||||
},
|
||||
{ kind: ExcerptTokenKind.Content, text: symbol ?? '' },
|
||||
];
|
||||
@@ -1819,7 +1831,7 @@ export class ApiModelGenerator {
|
||||
isOptional: Boolean(prop.nullable),
|
||||
isReadonly: Boolean(prop.readonly),
|
||||
docComment: this._tsDocParser.parseString(
|
||||
`/**\n * ${this._fixLinkTags(prop.description) ?? ''}\n${
|
||||
`/**\n * ${this._fixLinkTags(prop.description) ?? ''}${prop.default ? ` (default: ${this._escapeSpecialChars(prop.default)})` : ''}\n${
|
||||
prop.see?.map((see) => ` * @see ${see}\n`).join('') ?? ''
|
||||
}${prop.readonly ? ' * @readonly\n' : ''} */`,
|
||||
).docComment,
|
||||
@@ -1867,7 +1879,7 @@ export class ApiModelGenerator {
|
||||
: `${method.access ? `${method.access} ` : ''}${method.scope === 'static' ? 'static ' : ''}${method.name}(`
|
||||
}${
|
||||
method.params?.length
|
||||
? `${method.params[0]!.name}${method.params[0]!.nullable || method.params[0]!.optional ? '?' : ''}`
|
||||
? `${method.params[0]!.name}${method.params[0]!.nullable || method.params[0]!.optional ? '?' : ''}: `
|
||||
: '): '
|
||||
}`,
|
||||
});
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
# [@discordjs/brokers@1.0.0](https://github.com/discordjs/discord.js/compare/@discordjs/brokers@0.3.0...@discordjs/brokers@1.0.0) - (2024-09-01)
|
||||
|
||||
## Refactor
|
||||
|
||||
- **brokers:** Re-design API to make groups a constructor option (#10297) ([38a37b5](https://github.com/discordjs/discord.js/commit/38a37b5caf06913131c6dc2dc5cc258aecfe2266))
|
||||
- **brokers:** Make option props more correct (#10242) ([393ded4](https://github.com/discordjs/discord.js/commit/393ded4ea14e73b2bb42226f57896130329f88ca))
|
||||
- **BREAKING CHANGE:** Classes now take redis client as standalone parameter, various props from the base option interface moved to redis options
|
||||
|
||||
# [@discordjs/brokers@0.3.0](https://github.com/discordjs/discord.js/compare/@discordjs/brokers@0.2.3...@discordjs/brokers@0.3.0) - (2024-05-04)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
@@ -40,7 +40,7 @@ pnpm add @discordjs/brokers
|
||||
import { PubSubRedisBroker } from '@discordjs/brokers';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const broker = new PubSubRedisBroker({ redisClient: new Redis() });
|
||||
const broker = new PubSubRedisBroker(new Redis());
|
||||
|
||||
await broker.publish('test', 'Hello World!');
|
||||
await broker.destroy();
|
||||
@@ -49,7 +49,7 @@ await broker.destroy();
|
||||
import { PubSubRedisBroker } from '@discordjs/brokers';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const broker = new PubSubRedisBroker({ redisClient: new Redis() });
|
||||
const broker = new PubSubRedisBroker(new Redis());
|
||||
broker.on('test', ({ data, ack }) => {
|
||||
console.log(data);
|
||||
void ack();
|
||||
@@ -65,7 +65,7 @@ await broker.subscribe('subscribers', ['test']);
|
||||
import { RPCRedisBroker } from '@discordjs/brokers';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const broker = new RPCRedisBroker({ redisClient: new Redis() });
|
||||
const broker = new RPCRedisBroker(new Redis());
|
||||
|
||||
console.log(await broker.call('testcall', 'Hello World!'));
|
||||
await broker.destroy();
|
||||
@@ -74,7 +74,7 @@ await broker.destroy();
|
||||
import { RPCRedisBroker } from '@discordjs/brokers';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const broker = new RPCRedisBroker({ redisClient: new Redis() });
|
||||
const broker = new RPCRedisBroker(new Redis());
|
||||
broker.on('testcall', ({ data, ack, reply }) => {
|
||||
console.log('responder', data);
|
||||
void ack();
|
||||
|
||||
@@ -17,7 +17,7 @@ const mockRedisClient = {
|
||||
test('pubsub with custom encoding', async () => {
|
||||
const encode = vi.fn((data) => data);
|
||||
|
||||
const broker = new PubSubRedisBroker({ redisClient: mockRedisClient, encode });
|
||||
const broker = new PubSubRedisBroker(mockRedisClient, { encode });
|
||||
await broker.publish('test', 'test');
|
||||
expect(encode).toHaveBeenCalledWith('test');
|
||||
});
|
||||
|
||||
@@ -5,13 +5,16 @@ header = """
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
body = """
|
||||
{%- macro remote_url() -%}
|
||||
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
||||
{%- endmacro -%}
|
||||
{% if version %}\
|
||||
# [{{ version | trim_start_matches(pat="v") }}]\
|
||||
{% if previous %}\
|
||||
{% if previous.version %}\
|
||||
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
|
||||
({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }})\
|
||||
{% else %}\
|
||||
(https://github.com/discordjs/discord.js/tree/{{ version }})\
|
||||
({{ self::remote_url() }}/tree/{{ version }})\
|
||||
{% endif %}\
|
||||
{% endif %} \
|
||||
- ({{ timestamp | date(format="%Y-%m-%d") }})
|
||||
@@ -24,14 +27,21 @@ body = """
|
||||
- {% if commit.scope %}\
|
||||
**{{commit.scope}}:** \
|
||||
{% endif %}\
|
||||
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
|
||||
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
|
||||
{% if commit.github.username %} by @{{ commit.github.username }}{%- endif %}\
|
||||
{% if commit.breaking %}\
|
||||
{% for breakingChange in commit.footers %}\
|
||||
\n{% raw %} {% endraw %}- **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\
|
||||
{% endfor %}\
|
||||
{% endif %}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
{% endfor %}\
|
||||
{% if github.contributors | filter(attribute="is_first_time", value=true) | length %}\
|
||||
\n### New Contributors\n
|
||||
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}\
|
||||
* @{{ contributor.username }} made their first contribution in #{{ contributor.pr_number }}
|
||||
{% endfor %}\
|
||||
{% endif %}\n
|
||||
"""
|
||||
trim = true
|
||||
footer = ""
|
||||
@@ -59,5 +69,9 @@ commit_parsers = [
|
||||
filter_commits = true
|
||||
tag_pattern = "@discordjs/brokers@[0-9]*"
|
||||
ignore_tags = ""
|
||||
topo_order = true
|
||||
topo_order = false
|
||||
sort_commits = "newest"
|
||||
|
||||
[remote.github]
|
||||
owner = "discordjs"
|
||||
repo = "discord.js"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@discordjs/brokers",
|
||||
"version": "0.3.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Powerful set of message brokers",
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
@@ -42,7 +42,7 @@
|
||||
"Crawl <icrawltogo@gmail.com>",
|
||||
"Amish Shah <amishshah.2k@gmail.com>",
|
||||
"SpaceEEC <spaceeec@yahoo.com>",
|
||||
"Vlad Frangu <kingdgrizzle@gmail.com>",
|
||||
"Vlad Frangu <me@vladfrangu.dev>",
|
||||
"Aura Roman <kyradiscord@gmail.com>",
|
||||
"DD <didinele.dev@gmail.com>"
|
||||
],
|
||||
@@ -68,25 +68,25 @@
|
||||
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^3.0.0-beta2",
|
||||
"@vladfrangu/async_event_emitter": "^2.2.4",
|
||||
"ioredis": "^5.3.2"
|
||||
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||
"ioredis": "^5.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@discordjs/api-extractor": "workspace:^",
|
||||
"@discordjs/scripts": "workspace:^",
|
||||
"@favware/cliff-jumper": "^3.0.2",
|
||||
"@types/node": "18.18.8",
|
||||
"@vitest/coverage-v8": "^1.5.0",
|
||||
"@favware/cliff-jumper": "^4.1.0",
|
||||
"@types/node": "^18.19.45",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"esbuild-plugin-version-injector": "^1.2.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-neon": "^0.1.62",
|
||||
"eslint-formatter-pretty": "^6.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
"tsup": "^8.0.2",
|
||||
"turbo": "^1.13.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^1.5.0"
|
||||
"prettier": "^3.3.3",
|
||||
"tsup": "^8.2.4",
|
||||
"turbo": "^2.0.14",
|
||||
"typescript": "~5.5.4",
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Buffer } from 'node:buffer';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { encode, decode } from '@msgpack/msgpack';
|
||||
import type { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
|
||||
|
||||
@@ -7,10 +6,6 @@ import type { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
|
||||
* Base options for a broker implementation
|
||||
*/
|
||||
export interface BaseBrokerOptions {
|
||||
/**
|
||||
* How long to block for messages when polling
|
||||
*/
|
||||
blockTimeout?: number;
|
||||
/**
|
||||
* Function to use for decoding messages
|
||||
*/
|
||||
@@ -21,25 +16,12 @@ export interface BaseBrokerOptions {
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/method-signature-style
|
||||
encode?: (data: unknown) => Buffer;
|
||||
/**
|
||||
* Max number of messages to poll at once
|
||||
*/
|
||||
maxChunk?: number;
|
||||
/**
|
||||
* Unique consumer name.
|
||||
*
|
||||
* @see {@link https://redis.io/commands/xreadgroup/}
|
||||
*/
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default broker options
|
||||
*/
|
||||
export const DefaultBrokerOptions = {
|
||||
name: randomBytes(20).toString('hex'),
|
||||
maxChunk: 10,
|
||||
blockTimeout: 5_000,
|
||||
encode: (data): Buffer => {
|
||||
const encoded = encode(data);
|
||||
return Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength);
|
||||
@@ -48,28 +30,28 @@ export const DefaultBrokerOptions = {
|
||||
} as const satisfies Required<BaseBrokerOptions>;
|
||||
|
||||
export type ToEventMap<
|
||||
TRecord extends Record<string, any>,
|
||||
TRecord extends Record<string, any[]>,
|
||||
TResponses extends Record<keyof TRecord, any> | undefined = undefined,
|
||||
> = {
|
||||
[TKey in keyof TRecord]: [
|
||||
event: TResponses extends Record<keyof TRecord, any>
|
||||
? { ack(): Promise<void>; reply(data: TResponses[TKey]): Promise<void> }
|
||||
: { ack(): Promise<void> } & { data: TRecord[TKey] },
|
||||
: { ack(): Promise<void>; data: TRecord[TKey] },
|
||||
];
|
||||
} & { [K: string]: any };
|
||||
};
|
||||
|
||||
export interface IBaseBroker<TEvents extends Record<string, any>> {
|
||||
export interface IBaseBroker<TEvents extends {}> {
|
||||
/**
|
||||
* Subscribes to the given events, grouping them by the given group name
|
||||
* Subscribes to the given events
|
||||
*/
|
||||
subscribe(group: string, events: (keyof TEvents)[]): Promise<void>;
|
||||
subscribe(events: (keyof TEvents)[]): Promise<void>;
|
||||
/**
|
||||
* Unsubscribes from the given events - it's required to pass the same group name as when subscribing for proper cleanup
|
||||
* Unsubscribes from the given events
|
||||
*/
|
||||
unsubscribe(group: string, events: (keyof TEvents)[]): Promise<void>;
|
||||
unsubscribe(events: (keyof TEvents)[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IPubSubBroker<TEvents extends Record<string, any>>
|
||||
export interface IPubSubBroker<TEvents extends {}>
|
||||
extends IBaseBroker<TEvents>,
|
||||
AsyncEventEmitter<ToEventMap<TEvents>> {
|
||||
/**
|
||||
@@ -78,7 +60,7 @@ export interface IPubSubBroker<TEvents extends Record<string, any>>
|
||||
publish<Event extends keyof TEvents>(event: Event, data: TEvents[Event]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IRPCBroker<TEvents extends Record<string, any>, TResponses extends Record<keyof TEvents, any>>
|
||||
export interface IRPCBroker<TEvents extends Record<string, any[]>, TResponses extends Record<keyof TEvents, any>>
|
||||
extends IBaseBroker<TEvents>,
|
||||
AsyncEventEmitter<ToEventMap<TEvents, TResponses>> {
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Buffer } from 'node:buffer';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
|
||||
@@ -19,16 +20,48 @@ declare module 'ioredis' {
|
||||
*/
|
||||
export interface RedisBrokerOptions extends BaseBrokerOptions {
|
||||
/**
|
||||
* The Redis client to use
|
||||
* How long to block for messages when polling
|
||||
*/
|
||||
redisClient: Redis;
|
||||
blockTimeout?: number;
|
||||
|
||||
/**
|
||||
* Consumer group name to use for this broker
|
||||
*
|
||||
* @see {@link https://redis.io/commands/xreadgroup/}
|
||||
*/
|
||||
group: string;
|
||||
|
||||
/**
|
||||
* Max number of messages to poll at once
|
||||
*/
|
||||
maxChunk?: number;
|
||||
|
||||
/**
|
||||
* Unique consumer name.
|
||||
*
|
||||
* @see {@link https://redis.io/commands/xreadgroup/}
|
||||
*/
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default broker options for redis
|
||||
*/
|
||||
export const DefaultRedisBrokerOptions = {
|
||||
...DefaultBrokerOptions,
|
||||
name: randomBytes(20).toString('hex'),
|
||||
maxChunk: 10,
|
||||
blockTimeout: 5_000,
|
||||
} as const satisfies Required<Omit<RedisBrokerOptions, 'group'>>;
|
||||
|
||||
/**
|
||||
* Helper class with shared Redis logic
|
||||
*/
|
||||
export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||
extends AsyncEventEmitter<ToEventMap<TEvents>>
|
||||
export abstract class BaseRedisBroker<
|
||||
TEvents extends Record<string, any[]>,
|
||||
TResponses extends Record<keyof TEvents, any> | undefined = undefined,
|
||||
>
|
||||
extends AsyncEventEmitter<ToEventMap<TEvents, TResponses>>
|
||||
implements IBaseBroker<TEvents>
|
||||
{
|
||||
/**
|
||||
@@ -56,26 +89,29 @@ export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||
*/
|
||||
protected listening = false;
|
||||
|
||||
public constructor(options: RedisBrokerOptions) {
|
||||
public constructor(
|
||||
protected readonly redisClient: Redis,
|
||||
options: RedisBrokerOptions,
|
||||
) {
|
||||
super();
|
||||
this.options = { ...DefaultBrokerOptions, ...options };
|
||||
options.redisClient.defineCommand('xcleangroup', {
|
||||
this.options = { ...DefaultRedisBrokerOptions, ...options };
|
||||
redisClient.defineCommand('xcleangroup', {
|
||||
numberOfKeys: 1,
|
||||
lua: readFileSync(resolve(__dirname, '..', 'scripts', 'xcleangroup.lua'), 'utf8'),
|
||||
});
|
||||
this.streamReadClient = options.redisClient.duplicate();
|
||||
this.streamReadClient = redisClient.duplicate();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc IBaseBroker.subscribe}
|
||||
*/
|
||||
public async subscribe(group: string, events: (keyof TEvents)[]): Promise<void> {
|
||||
public async subscribe(events: (keyof TEvents)[]): Promise<void> {
|
||||
await Promise.all(
|
||||
// @ts-expect-error: Intended
|
||||
events.map(async (event) => {
|
||||
this.subscribedEvents.add(event as string);
|
||||
try {
|
||||
return await this.options.redisClient.xgroup('CREATE', event as string, group, 0, 'MKSTREAM');
|
||||
return await this.redisClient.xgroup('CREATE', event as string, this.options.group, 0, 'MKSTREAM');
|
||||
} catch (error) {
|
||||
if (!(error instanceof ReplyError)) {
|
||||
throw error;
|
||||
@@ -83,21 +119,21 @@ export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||
}
|
||||
}),
|
||||
);
|
||||
void this.listen(group);
|
||||
void this.listen();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc IBaseBroker.unsubscribe}
|
||||
*/
|
||||
public async unsubscribe(group: string, events: (keyof TEvents)[]): Promise<void> {
|
||||
public async unsubscribe(events: (keyof TEvents)[]): Promise<void> {
|
||||
const commands: unknown[][] = Array.from({ length: events.length * 2 });
|
||||
for (let idx = 0; idx < commands.length; idx += 2) {
|
||||
const event = events[idx / 2];
|
||||
commands[idx] = ['xgroup', 'delconsumer', event as string, group, this.options.name];
|
||||
commands[idx + 1] = ['xcleangroup', event as string, group];
|
||||
commands[idx] = ['xgroup', 'delconsumer', event as string, this.options.group, this.options.name];
|
||||
commands[idx + 1] = ['xcleangroup', event as string, this.options.group];
|
||||
}
|
||||
|
||||
await this.options.redisClient.pipeline(commands).exec();
|
||||
await this.redisClient.pipeline(commands).exec();
|
||||
|
||||
for (const event of events) {
|
||||
this.subscribedEvents.delete(event as string);
|
||||
@@ -107,18 +143,18 @@ export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||
/**
|
||||
* Begins polling for events, firing them to {@link BaseRedisBroker.listen}
|
||||
*/
|
||||
protected async listen(group: string): Promise<void> {
|
||||
protected async listen(): Promise<void> {
|
||||
if (this.listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listening = true;
|
||||
|
||||
while (true) {
|
||||
while (this.subscribedEvents.size > 0) {
|
||||
try {
|
||||
const data = await this.streamReadClient.xreadgroupBuffer(
|
||||
'GROUP',
|
||||
group,
|
||||
this.options.group,
|
||||
this.options.name,
|
||||
'COUNT',
|
||||
String(this.options.maxChunk),
|
||||
@@ -145,10 +181,11 @@ export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||
continue;
|
||||
}
|
||||
|
||||
this.emitEvent(id, group, event.toString('utf8'), this.options.decode(data));
|
||||
this.emitEvent(id, this.options.group, event.toString('utf8'), this.options.decode(data));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// @ts-expect-error: Intended
|
||||
this.emit('error', error);
|
||||
break;
|
||||
}
|
||||
@@ -161,8 +198,9 @@ export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||
* Destroys the broker, closing all connections
|
||||
*/
|
||||
public async destroy() {
|
||||
await this.unsubscribe([...this.subscribedEvents]);
|
||||
this.streamReadClient.disconnect();
|
||||
this.options.redisClient.disconnect();
|
||||
this.redisClient.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ import { BaseRedisBroker } from './BaseRedis.js';
|
||||
* import { PubSubRedisBroker } from '@discordjs/brokers';
|
||||
* import Redis from 'ioredis';
|
||||
*
|
||||
* const broker = new PubSubRedisBroker({ redisClient: new Redis() });
|
||||
* const broker = new PubSubRedisBroker(new Redis());
|
||||
*
|
||||
* await broker.publish('test', 'Hello World!');
|
||||
* await broker.destroy();
|
||||
@@ -20,7 +20,7 @@ import { BaseRedisBroker } from './BaseRedis.js';
|
||||
* import { PubSubRedisBroker } from '@discordjs/brokers';
|
||||
* import Redis from 'ioredis';
|
||||
*
|
||||
* const broker = new PubSubRedisBroker({ redisClient: new Redis() });
|
||||
* const broker = new PubSubRedisBroker(new Redis());
|
||||
* broker.on('test', ({ data, ack }) => {
|
||||
* console.log(data);
|
||||
* void ack();
|
||||
@@ -37,22 +37,18 @@ export class PubSubRedisBroker<TEvents extends Record<string, any>>
|
||||
* {@inheritDoc IPubSubBroker.publish}
|
||||
*/
|
||||
public async publish<Event extends keyof TEvents>(event: Event, data: TEvents[Event]): Promise<void> {
|
||||
await this.options.redisClient.xadd(
|
||||
event as string,
|
||||
'*',
|
||||
BaseRedisBroker.STREAM_DATA_KEY,
|
||||
this.options.encode(data),
|
||||
);
|
||||
await this.redisClient.xadd(event as string, '*', BaseRedisBroker.STREAM_DATA_KEY, this.options.encode(data));
|
||||
}
|
||||
|
||||
protected emitEvent(id: Buffer, group: string, event: string, data: unknown) {
|
||||
const payload: { ack(): Promise<void>; data: unknown } = {
|
||||
data,
|
||||
ack: async () => {
|
||||
await this.options.redisClient.xack(event, group, id);
|
||||
await this.redisClient.xack(event, group, id);
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-expect-error: Intended
|
||||
this.emit(event, payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Buffer } from 'node:buffer';
|
||||
import { clearTimeout, setTimeout } from 'node:timers';
|
||||
import type Redis from 'ioredis/built/Redis.js';
|
||||
import type { IRPCBroker } from '../Broker.js';
|
||||
import { DefaultBrokerOptions } from '../Broker.js';
|
||||
import type { RedisBrokerOptions } from './BaseRedis.js';
|
||||
import { BaseRedisBroker } from './BaseRedis.js';
|
||||
import { BaseRedisBroker, DefaultRedisBrokerOptions } from './BaseRedis.js';
|
||||
|
||||
interface InternalPromise {
|
||||
reject(error: any): void;
|
||||
@@ -22,9 +22,9 @@ export interface RPCRedisBrokerOptions extends RedisBrokerOptions {
|
||||
* Default values used for the {@link RPCRedisBrokerOptions}
|
||||
*/
|
||||
export const DefaultRPCRedisBrokerOptions = {
|
||||
...DefaultBrokerOptions,
|
||||
...DefaultRedisBrokerOptions,
|
||||
timeout: 5_000,
|
||||
} as const satisfies Required<Omit<RPCRedisBrokerOptions, 'redisClient'>>;
|
||||
} as const satisfies Required<Omit<RPCRedisBrokerOptions, 'group'>>;
|
||||
|
||||
/**
|
||||
* RPC broker powered by Redis
|
||||
@@ -35,7 +35,7 @@ export const DefaultRPCRedisBrokerOptions = {
|
||||
* import { RPCRedisBroker } from '@discordjs/brokers';
|
||||
* import Redis from 'ioredis';
|
||||
*
|
||||
* const broker = new RPCRedisBroker({ redisClient: new Redis() });
|
||||
* const broker = new RPCRedisBroker(new Redis());
|
||||
*
|
||||
* console.log(await broker.call('testcall', 'Hello World!'));
|
||||
* await broker.destroy();
|
||||
@@ -44,7 +44,7 @@ export const DefaultRPCRedisBrokerOptions = {
|
||||
* import { RPCRedisBroker } from '@discordjs/brokers';
|
||||
* import Redis from 'ioredis';
|
||||
*
|
||||
* const broker = new RPCRedisBroker({ redisClient: new Redis() });
|
||||
* const broker = new RPCRedisBroker(new Redis());
|
||||
* broker.on('testcall', ({ data, ack, reply }) => {
|
||||
* console.log('responder', data);
|
||||
* void ack();
|
||||
@@ -54,8 +54,8 @@ export const DefaultRPCRedisBrokerOptions = {
|
||||
* await broker.subscribe('responders', ['testcall']);
|
||||
* ```
|
||||
*/
|
||||
export class RPCRedisBroker<TEvents extends Record<string, any>, TResponses extends Record<keyof TEvents, any>>
|
||||
extends BaseRedisBroker<TEvents>
|
||||
export class RPCRedisBroker<TEvents extends Record<string, any[]>, TResponses extends Record<keyof TEvents, any>>
|
||||
extends BaseRedisBroker<TEvents, TResponses>
|
||||
implements IRPCBroker<TEvents, TResponses>
|
||||
{
|
||||
/**
|
||||
@@ -65,8 +65,8 @@ export class RPCRedisBroker<TEvents extends Record<string, any>, TResponses exte
|
||||
|
||||
protected readonly promises = new Map<string, InternalPromise>();
|
||||
|
||||
public constructor(options: RPCRedisBrokerOptions) {
|
||||
super(options);
|
||||
public constructor(redisClient: Redis, options: RPCRedisBrokerOptions) {
|
||||
super(redisClient, options);
|
||||
this.options = { ...DefaultRPCRedisBrokerOptions, ...options };
|
||||
|
||||
this.streamReadClient.on('messageBuffer', (channel: Buffer, message: Buffer) => {
|
||||
@@ -88,7 +88,7 @@ export class RPCRedisBroker<TEvents extends Record<string, any>, TResponses exte
|
||||
data: TEvents[Event],
|
||||
timeoutDuration: number = this.options.timeout,
|
||||
): Promise<TResponses[Event]> {
|
||||
const id = await this.options.redisClient.xadd(
|
||||
const id = await this.redisClient.xadd(
|
||||
event as string,
|
||||
'*',
|
||||
BaseRedisBroker.STREAM_DATA_KEY,
|
||||
@@ -114,17 +114,18 @@ export class RPCRedisBroker<TEvents extends Record<string, any>, TResponses exte
|
||||
});
|
||||
}
|
||||
|
||||
protected emitEvent(id: Buffer, group: string, event: string, data: unknown) {
|
||||
protected emitEvent(id: Buffer, event: string, data: unknown) {
|
||||
const payload: { ack(): Promise<void>; data: unknown; reply(data: unknown): Promise<void> } = {
|
||||
data,
|
||||
ack: async () => {
|
||||
await this.options.redisClient.xack(event, group, id);
|
||||
await this.redisClient.xack(event, this.options.group, id);
|
||||
},
|
||||
reply: async (data) => {
|
||||
await this.options.redisClient.publish(`${event}:${id.toString()}`, this.options.encode(data));
|
||||
await this.redisClient.publish(`${event}:${id.toString()}`, this.options.encode(data));
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-expect-error: Intended
|
||||
this.emit(event, payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,81 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
# [@discordjs/builders@1.12.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.11.3...@discordjs/builders@1.12.0) - (2025-10-08)
|
||||
|
||||
## Features
|
||||
|
||||
- **builders:** Modal select menus in builders v1 (#11138) ([ac683b9](https://github.com/discordjs/discord.js/commit/ac683b9d040635de8514c80a9d433d9c6d63701b))
|
||||
|
||||
# [@discordjs/builders@1.11.3](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.11.2...@discordjs/builders@1.11.3) - (2025-08-10)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **contextMenuCommands:** Remove regular expression validation (#10996) ([4906aae](https://github.com/discordjs/discord.js/commit/4906aaea4c0e6e868fa658d3359026eb662fbcb8))
|
||||
|
||||
# [@discordjs/builders@1.11.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.10.1...@discordjs/builders@1.11.0) - (2025-04-25)
|
||||
|
||||
## Features
|
||||
|
||||
- Components v2 in builders v1 (#10787) ([118e682](https://github.com/discordjs/discord.js/commit/118e6826821b3b90f5923e40f167747e0658cfd1))
|
||||
|
||||
# [@discordjs/builders@1.10.1](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.10.0...@discordjs/builders@1.10.1) - (2025-02-10)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **EmbedBuilder:** Allow empty `name` and `value` on fields (#10747) ([49ef3a8](https://github.com/discordjs/discord.js/commit/49ef3a833eab23d426d5c667e28aa493ddc9cb6c))
|
||||
|
||||
# [@discordjs/builders@1.9.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.8.2...@discordjs/builders@1.9.0) - (2024-09-01)
|
||||
|
||||
## Features
|
||||
|
||||
- User-installable apps (#10227) ([fc0b6f7](https://github.com/discordjs/discord.js/commit/fc0b6f7f8ebd94a4a05fac0c76e49b23752a8e65))
|
||||
- **builders:** Update to @sapphire/shapeshift v4 (#10291) ([2d5531f](https://github.com/discordjs/discord.js/commit/2d5531f35c6b4d70f83e46b99c284030108dcf5c))
|
||||
- **SlashCommandBuilder:** Add explicit command type when building (#10395) ([b2970bb](https://github.com/discordjs/discord.js/commit/b2970bb2dddf70d2d918fda825059315f35d23f3))
|
||||
- Premium buttons (#10353) ([4f59b74](https://github.com/discordjs/discord.js/commit/4f59b740d01b9ff2213949708a36e17da32b89c3))
|
||||
- Add user-installable apps support (#10348) ([9c76bbe](https://github.com/discordjs/discord.js/commit/9c76bbea172d49320f7fdac19ec1a43a49d05116))
|
||||
|
||||
# [@discordjs/builders@1.8.2](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.8.1...@discordjs/builders@1.8.2) - (2024-06-02)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **SlashCommandBuilder:** Add missing shared properties (#10255) ([29fd89f](https://github.com/discordjs/discord.js/commit/29fd89f23c22ac5b4ce0a3ed34f5d27e28b1a0b8))
|
||||
|
||||
## Documentation
|
||||
|
||||
- **SelectMenuBuilder:** Correct grammatical errors (#10309) ([aae2faf](https://github.com/discordjs/discord.js/commit/aae2faf9e923a268f84c8b7fb3283aea09dca586))
|
||||
- **TextInputBuilder:** Correct constructor documentation (#10308) ([c1e6890](https://github.com/discordjs/discord.js/commit/c1e6890132d5597a6ebd9d79383ec572582c0601))
|
||||
- **MappedComponentTypes:** Fix "inpiut" typo (#10306) ([29a50bb](https://github.com/discordjs/discord.js/commit/29a50bb476e8e84896dbaec96c6009589afaafbf))
|
||||
|
||||
# [@discordjs/builders@1.8.1](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.8.0...@discordjs/builders@1.8.1) - (2024-05-05)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Slashcommand builder type split (#10253) ([07c1210](https://github.com/discordjs/discord.js/commit/07c12101e534fdce836a94bc571b53f75979ea86))
|
||||
- Don't mutate user provided array (#10014) ([7ea3638](https://github.com/discordjs/discord.js/commit/7ea3638dbcf38926596fb5da8b85040e70f1b98b))
|
||||
- Minify mainlib docs json (#9963) ([4b88306](https://github.com/discordjs/discord.js/commit/4b88306dcb2b16b840ec61e9e33047af3a31c45d))
|
||||
|
||||
## Documentation
|
||||
|
||||
- Split docs.api.json into multiple json files ([597340f](https://github.com/discordjs/discord.js/commit/597340f288437c35da8c703d9b621274de60d880))
|
||||
|
||||
## Features
|
||||
|
||||
- **api-extractor:** Support `export * as ___` syntax (#10173) ([1c5de21](https://github.com/discordjs/discord.js/commit/1c5de21a2905fe21b54dea805013f089ed9000d0))
|
||||
- Allow RestOrArray for command option builders (#10175) ([a1a3a95](https://github.com/discordjs/discord.js/commit/a1a3a95c94194a8ab789d567a778b376e13ea973))
|
||||
- Local and preview detection ([79fbda3](https://github.com/discordjs/discord.js/commit/79fbda3aac6d4f0f8bfb193e797d09cbe331d315))
|
||||
|
||||
## Refactor
|
||||
|
||||
- Docs (#10126) ([18cce83](https://github.com/discordjs/discord.js/commit/18cce83d80598c430218775c53441b6b2ecdc776))
|
||||
- Make builders types great again (#10026) ([a0c83a2](https://github.com/discordjs/discord.js/commit/a0c83a254c21dad5ac14b649a95ded57d6678d95))
|
||||
|
||||
# [@discordjs/builders@1.8.1](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.8.0...@discordjs/builders@1.8.1) - (2024-05-05)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Slashcommand builder type split (#10253) ([07c1210](https://github.com/discordjs/discord.js/commit/07c12101e534fdce836a94bc571b53f75979ea86))
|
||||
|
||||
# [@discordjs/builders@1.8.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.7.0...@discordjs/builders@1.8.0) - (2024-05-04)
|
||||
|
||||
## Bug Fixes
|
||||
@@ -229,261 +304,6 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
- Cleanup tests and tsup configs ([6b8ef20](https://github.com/discordjs/discord.js/commit/6b8ef20cb3af5b5cfd176dd0aa0a1a1e98551629))
|
||||
|
||||
# [@discordjs/builders@1.6.5](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.6.4...@discordjs/builders@1.6.5) - (2023-08-17)
|
||||
|
||||
## Documentation
|
||||
|
||||
- Update Node.js requirement to 16.11.0 (#9764) ([188877c](https://github.com/discordjs/discord.js/commit/188877c50af70f0d5cffb246620fa277435c6ce6))
|
||||
|
||||
# [@discordjs/builders@1.6.3](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.6.2...@discordjs/builders@1.6.3) - (2023-05-01)
|
||||
|
||||
## Refactor
|
||||
|
||||
- Remove `@discordjs/util` re-export (#9488) ([54ceedf](https://github.com/discordjs/discord.js/commit/54ceedf6c535d4641643d4106b6286cbef09de4a))
|
||||
|
||||
# [@discordjs/builders@1.6.2](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.6.1...@discordjs/builders@1.6.2) - (2023-05-01)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **BaseSelectMenuBuilder:** Modify class to be `abstract` (#9358) ([ca4de2d](https://github.com/discordjs/discord.js/commit/ca4de2d9c6bc204e85d1b7eae7eabd23dbeb4475))
|
||||
- Correct `@link` tags that involve parents (#9351) ([fbbce3e](https://github.com/discordjs/discord.js/commit/fbbce3eb4ba20bc0c4806ca2259d1f86001594be))
|
||||
- Fix external links (#9313) ([a7425c2](https://github.com/discordjs/discord.js/commit/a7425c29c4f23f1b31f4c6a463107ca9eb7fd7e2))
|
||||
|
||||
## Documentation
|
||||
|
||||
- Reference package names properly (#9426) ([d6bca9b](https://github.com/discordjs/discord.js/commit/d6bca9bb4d976dc069a5039250db7d5b3e9142ef))
|
||||
- Generate static imports for types with api-extractor ([98a76db](https://github.com/discordjs/discord.js/commit/98a76db482879f79d6bb2fb2e5fc65ac2c34e2d9))
|
||||
- **builders:** Add some basic documentation (#9359) ([8073561](https://github.com/discordjs/discord.js/commit/8073561824f911d1a18d0b4f1de39f452bc69fa9))
|
||||
- Use `@link` in `@see` (#9348) ([d66d113](https://github.com/discordjs/discord.js/commit/d66d1133331b81563588db4500c63a18c3c3dfae))
|
||||
|
||||
# [@discordjs/builders@1.6.3](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.6.2...@discordjs/builders@1.6.3) - (2023-05-01)
|
||||
|
||||
## Refactor
|
||||
|
||||
- Remove `@discordjs/util` re-export (#9488) ([54ceedf](https://github.com/discordjs/discord.js/commit/54ceedf6c535d4641643d4106b6286cbef09de4a))
|
||||
|
||||
# [@discordjs/builders@1.6.2](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.6.1...@discordjs/builders@1.6.2) - (2023-05-01)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **BaseSelectMenuBuilder:** Modify class to be `abstract` (#9358) ([ca4de2d](https://github.com/discordjs/discord.js/commit/ca4de2d9c6bc204e85d1b7eae7eabd23dbeb4475))
|
||||
- Correct `@link` tags that involve parents (#9351) ([fbbce3e](https://github.com/discordjs/discord.js/commit/fbbce3eb4ba20bc0c4806ca2259d1f86001594be))
|
||||
- Fix external links (#9313) ([a7425c2](https://github.com/discordjs/discord.js/commit/a7425c29c4f23f1b31f4c6a463107ca9eb7fd7e2))
|
||||
|
||||
## Documentation
|
||||
|
||||
- Reference package names properly (#9426) ([d6bca9b](https://github.com/discordjs/discord.js/commit/d6bca9bb4d976dc069a5039250db7d5b3e9142ef))
|
||||
- Generate static imports for types with api-extractor ([98a76db](https://github.com/discordjs/discord.js/commit/98a76db482879f79d6bb2fb2e5fc65ac2c34e2d9))
|
||||
- **builders:** Add some basic documentation (#9359) ([8073561](https://github.com/discordjs/discord.js/commit/8073561824f911d1a18d0b4f1de39f452bc69fa9))
|
||||
- Use `@link` in `@see` (#9348) ([d66d113](https://github.com/discordjs/discord.js/commit/d66d1133331b81563588db4500c63a18c3c3dfae))
|
||||
|
||||
# [@discordjs/builders@1.6.3](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.6.2...@discordjs/builders@1.6.3) - (2023-05-01)
|
||||
|
||||
## Refactor
|
||||
|
||||
- Remove `@discordjs/util` re-export (#9488) ([54ceedf](https://github.com/discordjs/discord.js/commit/54ceedf6c535d4641643d4106b6286cbef09de4a))
|
||||
|
||||
# [@discordjs/builders@1.6.2](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.6.1...@discordjs/builders@1.6.2) - (2023-05-01)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **BaseSelectMenuBuilder:** Modify class to be `abstract` (#9358) ([ca4de2d](https://github.com/discordjs/discord.js/commit/ca4de2d9c6bc204e85d1b7eae7eabd23dbeb4475))
|
||||
- Correct `@link` tags that involve parents (#9351) ([fbbce3e](https://github.com/discordjs/discord.js/commit/fbbce3eb4ba20bc0c4806ca2259d1f86001594be))
|
||||
- Fix external links (#9313) ([a7425c2](https://github.com/discordjs/discord.js/commit/a7425c29c4f23f1b31f4c6a463107ca9eb7fd7e2))
|
||||
|
||||
## Documentation
|
||||
|
||||
- Reference package names properly (#9426) ([d6bca9b](https://github.com/discordjs/discord.js/commit/d6bca9bb4d976dc069a5039250db7d5b3e9142ef))
|
||||
- Generate static imports for types with api-extractor ([98a76db](https://github.com/discordjs/discord.js/commit/98a76db482879f79d6bb2fb2e5fc65ac2c34e2d9))
|
||||
- **builders:** Add some basic documentation (#9359) ([8073561](https://github.com/discordjs/discord.js/commit/8073561824f911d1a18d0b4f1de39f452bc69fa9))
|
||||
- Use `@link` in `@see` (#9348) ([d66d113](https://github.com/discordjs/discord.js/commit/d66d1133331b81563588db4500c63a18c3c3dfae))
|
||||
|
||||
# [@discordjs/builders@1.6.2](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.6.1...@discordjs/builders@1.6.2) - (2023-05-01)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **BaseSelectMenuBuilder:** Modify class to be `abstract` (#9358) ([ca4de2d](https://github.com/discordjs/discord.js/commit/ca4de2d9c6bc204e85d1b7eae7eabd23dbeb4475))
|
||||
- Correct `@link` tags that involve parents (#9351) ([fbbce3e](https://github.com/discordjs/discord.js/commit/fbbce3eb4ba20bc0c4806ca2259d1f86001594be))
|
||||
- Fix external links (#9313) ([a7425c2](https://github.com/discordjs/discord.js/commit/a7425c29c4f23f1b31f4c6a463107ca9eb7fd7e2))
|
||||
|
||||
## Documentation
|
||||
|
||||
- Reference package names properly (#9426) ([d6bca9b](https://github.com/discordjs/discord.js/commit/d6bca9bb4d976dc069a5039250db7d5b3e9142ef))
|
||||
- Generate static imports for types with api-extractor ([98a76db](https://github.com/discordjs/discord.js/commit/98a76db482879f79d6bb2fb2e5fc65ac2c34e2d9))
|
||||
- **builders:** Add some basic documentation (#9359) ([8073561](https://github.com/discordjs/discord.js/commit/8073561824f911d1a18d0b4f1de39f452bc69fa9))
|
||||
- Use `@link` in `@see` (#9348) ([d66d113](https://github.com/discordjs/discord.js/commit/d66d1133331b81563588db4500c63a18c3c3dfae))
|
||||
|
||||
# [@discordjs/builders@1.6.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.5.0...@discordjs/builders@1.6.0) - (2023-04-01)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **scripts:** Accessing tsComment ([d8d5f31](https://github.com/discordjs/discord.js/commit/d8d5f31d3927fd1de62f1fa3a1a6e454243ad87b))
|
||||
|
||||
## Features
|
||||
|
||||
- **website:** Render syntax and mdx on the server (#9086) ([ee5169e](https://github.com/discordjs/discord.js/commit/ee5169e0aadd7bbfcd752aae614ec0f69602b68b))
|
||||
|
||||
# [@discordjs/builders@1.5.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.4.0...@discordjs/builders@1.5.0) - (2023-03-12)
|
||||
|
||||
## Documentation
|
||||
|
||||
- **EmbedBuilder#spliceFields:** Fix a typo (#9159) ([4367ab9](https://github.com/discordjs/discord.js/commit/4367ab930227048868db3ed8437f6c4507ff32e1))
|
||||
- Fix version export (#9049) ([8b70f49](https://github.com/discordjs/discord.js/commit/8b70f497a1207e30edebdecd12b926c981c13d28))
|
||||
|
||||
## Features
|
||||
|
||||
- **website:** Add support for source file links (#9048) ([f6506e9](https://github.com/discordjs/discord.js/commit/f6506e99c496683ee0ab67db0726b105b929af38))
|
||||
- **StringSelectMenu:** Add `spliceOptions()` (#8937) ([a6941d5](https://github.com/discordjs/discord.js/commit/a6941d536ce24ed2b5446a154cbc886b2b97c63a))
|
||||
- Add support for nsfw commands (#7976) ([7a51344](https://github.com/discordjs/discord.js/commit/7a5134459c5f06864bf74631d83b96d9c21b72d8))
|
||||
- Add `@discordjs/formatters` (#8889) ([3fca638](https://github.com/discordjs/discord.js/commit/3fca638a8470dcea2f79ddb9f18526dbc0017c88))
|
||||
|
||||
## Styling
|
||||
|
||||
- Run prettier (#9041) ([2798ba1](https://github.com/discordjs/discord.js/commit/2798ba1eb3d734f0cf2eeccd2e16cfba6804873b))
|
||||
|
||||
# [@discordjs/builders@1.4.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.3.0...@discordjs/builders@1.4.0) - (2022-11-28)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Pin @types/node version ([9d8179c](https://github.com/discordjs/discord.js/commit/9d8179c6a78e1c7f9976f852804055964d5385d4))
|
||||
|
||||
## Features
|
||||
|
||||
- New select menus (#8793) ([5152abf](https://github.com/discordjs/discord.js/commit/5152abf7285581abf7689e9050fdc56c4abb1e2b))
|
||||
- Allow punctuation characters in context menus (#8783) ([b521366](https://github.com/discordjs/discord.js/commit/b5213664fa66746daab1673ebe2adf2db3d1522c))
|
||||
|
||||
## Typings
|
||||
|
||||
- **Formatters:** Allow boolean in `formatEmoji` (#8823) ([ec37f13](https://github.com/discordjs/discord.js/commit/ec37f137fd4fca0fdbdb8a5c83abf32362a8f285))
|
||||
|
||||
# [@discordjs/builders@1.3.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.2.0...@discordjs/builders@1.3.0) - (2022-10-08)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Allow adding forums to `channelTypes` (#8658) ([b1e190c](https://github.com/discordjs/discord.js/commit/b1e190c4f0773a1a739625f5b41026f593515370))
|
||||
- **SlashCommandBuilder:** Missing methods in subcommand builder (#8583) ([1c5b78f](https://github.com/discordjs/discord.js/commit/1c5b78fd2130f09c951459cf4c2d637f46c3c2c9))
|
||||
- Footer / sidebar / deprecation alert ([ba3e0ed](https://github.com/discordjs/discord.js/commit/ba3e0ed348258fe8e51eefb4aa7379a1230616a9))
|
||||
|
||||
## Documentation
|
||||
|
||||
- **builders/components:** Document constructors (#8636) ([8444576](https://github.com/discordjs/discord.js/commit/8444576f45da5fdddbf8ba2d91b4cb31a3b51c04))
|
||||
- Change name (#8604) ([dd5a089](https://github.com/discordjs/discord.js/commit/dd5a08944c258a847fc4377f1d5e953264ab47d0))
|
||||
- Use remarks instead of `Note` in descriptions (#8597) ([f3ce4a7](https://github.com/discordjs/discord.js/commit/f3ce4a75d0c4eafc89a1f0ce9f4964bcbcdae6da))
|
||||
|
||||
## Features
|
||||
|
||||
- Web-components (#8715) ([0ac3e76](https://github.com/discordjs/discord.js/commit/0ac3e766bd9dbdeb106483fa4bb085d74de346a2))
|
||||
- Add `@discordjs/util` (#8591) ([b2ec865](https://github.com/discordjs/discord.js/commit/b2ec865765bf94181473864a627fb63ea8173fd3))
|
||||
- Add `chatInputApplicationCommandMention` formatter (#8546) ([d08a57c](https://github.com/discordjs/discord.js/commit/d08a57cadd9d69a734077cc1902d931ab10336db))
|
||||
|
||||
## Refactor
|
||||
|
||||
- Replace usage of deprecated `ChannelType`s (#8625) ([669c3cd](https://github.com/discordjs/discord.js/commit/669c3cd2566eac68ef38ab522dd6378ba761e8b3))
|
||||
- Website components (#8600) ([c334157](https://github.com/discordjs/discord.js/commit/c3341570d983aea9ecc419979d5a01de658c9d67))
|
||||
- Use `eslint-config-neon` for packages. (#8579) ([edadb9f](https://github.com/discordjs/discord.js/commit/edadb9fe5dfd9ff51a3cfc9b25cb242d3f9f5241))
|
||||
|
||||
## Testing
|
||||
|
||||
- Rename incorrect test (#8596) ([ce991dd](https://github.com/discordjs/discord.js/commit/ce991dd1d883f6785b5f4b4b3ac80ef21cb304e7))
|
||||
|
||||
## Typings
|
||||
|
||||
- **interactions:** Fix `{Slash,ContextMenu}CommandBuilder#toJSON` (#8568) ([b7eb96d](https://github.com/discordjs/discord.js/commit/b7eb96d45670616521fbcca28a657793d91605c7))
|
||||
|
||||
# [@discordjs/builders@1.2.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.1.0...@discordjs/builders@1.2.0) - (2022-08-22)
|
||||
|
||||
## Features
|
||||
|
||||
- **website:** Show `constructor` information (#8540) ([e42fd16](https://github.com/discordjs/discord.js/commit/e42fd1636973b10dd7ed6fb4280ee1a4a8f82007))
|
||||
- **website:** Show descriptions for `@typeParam` blocks (#8523) ([e475b63](https://github.com/discordjs/discord.js/commit/e475b63f257f6261d73cb89fee9ecbcdd84e2a6b))
|
||||
- **website:** Show parameter descriptions (#8519) ([7f415a2](https://github.com/discordjs/discord.js/commit/7f415a2502bf7ce2025dbcfed9017b0635a19966))
|
||||
- **WebSocketShard:** Support new resume url (#8480) ([bc06cc6](https://github.com/discordjs/discord.js/commit/bc06cc638d2f57ab5c600e8cdb6afc8eb2180166))
|
||||
|
||||
## Refactor
|
||||
|
||||
- Docs design (#8487) ([4ab1d09](https://github.com/discordjs/discord.js/commit/4ab1d09997a18879a9eb9bda39df6f15aa22557e))
|
||||
|
||||
# [@discordjs/builders@1.1.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.16.0...@discordjs/builders@1.1.0) - (2022-07-29)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Use proper format for `@link` text (#8384) ([2655639](https://github.com/discordjs/discord.js/commit/26556390a3800e954974a00c1328ff47d3e67e9a))
|
||||
- **Formatters:** Add newline in `codeBlock` (#8369) ([5d8bd03](https://github.com/discordjs/discord.js/commit/5d8bd030d60ef364de3ef5f9963da8bda5c4efd4))
|
||||
- **selectMenu:** Allow json to be used for select menu options (#8322) ([6a2d0d8](https://github.com/discordjs/discord.js/commit/6a2d0d8e96d157d5b85cee7f17bffdfff4240074))
|
||||
|
||||
## Documentation
|
||||
|
||||
- Use link tags (#8382) ([5494791](https://github.com/discordjs/discord.js/commit/549479131318c659f86f0eb18578d597e22522d3))
|
||||
|
||||
## Features
|
||||
|
||||
- Add channel & message URL formatters (#8371) ([a7deb8f](https://github.com/discordjs/discord.js/commit/a7deb8f89830ead6185c5fb46a49688b6d209ed1))
|
||||
|
||||
## Testing
|
||||
|
||||
- **builders:** Improve coverage (#8274) ([b7e6238](https://github.com/discordjs/discord.js/commit/b7e62380f2e6b9324d6bba9b9eaa5315080bf66a))
|
||||
|
||||
# [@discordjs/builders@0.16.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.15.0...@discordjs/builders@0.16.0) - (2022-07-17)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Slash command name regex (#8265) ([32f9056](https://github.com/discordjs/discord.js/commit/32f9056b15edede3bab07de96afb4b56d3a9ecca))
|
||||
- **TextInputBuilder:** Parse `custom_id`, `label`, and `style` (#8216) ([2d9dfa3](https://github.com/discordjs/discord.js/commit/2d9dfa3c6ea4bb972da2f7e088d148b798c866d9))
|
||||
|
||||
## Documentation
|
||||
|
||||
- Add codecov coverage badge to readmes (#8226) ([f6db285](https://github.com/discordjs/discord.js/commit/f6db285c073898a749fe4591cbd4463d1896daf5))
|
||||
|
||||
## Features
|
||||
|
||||
- **builder:** Add max min length in string option (#8214) ([96c8d21](https://github.com/discordjs/discord.js/commit/96c8d21f95eb366c46ae23505ba9054f44821b25))
|
||||
- Codecov (#8219) ([f10f4cd](https://github.com/discordjs/discord.js/commit/f10f4cdcd88ca6be7ec735ed3a415ba13da83db0))
|
||||
- **docgen:** Update typedoc ([b3346f4](https://github.com/discordjs/discord.js/commit/b3346f4b9b3d4f96443506643d4631dc1c6d7b21))
|
||||
- Website (#8043) ([127931d](https://github.com/discordjs/discord.js/commit/127931d1df7a2a5c27923c2f2151dbf3824e50cc))
|
||||
- **docgen:** Typescript support ([3279b40](https://github.com/discordjs/discord.js/commit/3279b40912e6aa61507bedb7db15a2b8668de44b))
|
||||
- Docgen package (#8029) ([8b979c0](https://github.com/discordjs/discord.js/commit/8b979c0245c42fd824d8e98745ee869f5360fc86))
|
||||
|
||||
## Refactor
|
||||
|
||||
- **builder:** Remove `unsafe*Builder`s (#8074) ([a4d1862](https://github.com/discordjs/discord.js/commit/a4d18629828234f43f03d1bd4851d4b727c6903b))
|
||||
- Remove @sindresorhus/is as it's now esm only (#8133) ([c6f285b](https://github.com/discordjs/discord.js/commit/c6f285b7b089b004776fbeb444fe973a68d158d8))
|
||||
- Move all the config files to root (#8033) ([769ea0b](https://github.com/discordjs/discord.js/commit/769ea0bfe78c4f1d413c6b397c604ffe91e39c6a))
|
||||
|
||||
## Typings
|
||||
|
||||
- Remove expect error (#8242) ([7e6dbaa](https://github.com/discordjs/discord.js/commit/7e6dbaaed900c07d1a04e23bbbf9cd0d1b0501c5))
|
||||
- **builder:** Remove casting (#8241) ([8198da5](https://github.com/discordjs/discord.js/commit/8198da5cd0898e06954615a2287853321e7ebbd4))
|
||||
|
||||
# [@discordjs/builders@0.15.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.14.0...@discordjs/builders@0.15.0) - (2022-06-06)
|
||||
|
||||
## Features
|
||||
|
||||
- Allow builders to accept rest params and arrays (#7874) ([ad75be9](https://github.com/discordjs/discord.js/commit/ad75be9a9cf90c8624495df99b75177e6c24022f))
|
||||
- Use vitest instead of jest for more speed ([8d8e6c0](https://github.com/discordjs/discord.js/commit/8d8e6c03decd7352a2aa180f6e5bc1a13602539b))
|
||||
- Add scripts package for locally used scripts ([f2ae1f9](https://github.com/discordjs/discord.js/commit/f2ae1f9348bfd893332a9060f71a8a5f272a1b8b))
|
||||
|
||||
# [@discordjs/builders@0.14.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.13.0...@discordjs/builders@0.14.0) - (2022-06-04)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **builders:** Leftover invalid null type ([8a7cd10](https://github.com/discordjs/discord.js/commit/8a7cd10554a2a71cd2fe7f6a177b5f4f43464348))
|
||||
- **SlashCommandBuilder:** Import `Permissions` correctly (#7921) ([7ce641d](https://github.com/discordjs/discord.js/commit/7ce641d33a4af6586d5e7beffbe7d38619dcf1a2))
|
||||
- Add localizations for subcommand builders and option choices (#7862) ([c1b5e73](https://github.com/discordjs/discord.js/commit/c1b5e731daa9cbbfca03a046e47cb1221ee1ed7c))
|
||||
|
||||
## Features
|
||||
|
||||
- Export types from `interactions/slashCommands/mixins` (#7942) ([68d5169](https://github.com/discordjs/discord.js/commit/68d5169f66c96f8fe5be17a1c01cdd5155607ab2))
|
||||
- **builders:** Add new command permissions v2 (#7861) ([de3f157](https://github.com/discordjs/discord.js/commit/de3f1573f07dda294cc0fbb1ca4b659eb2388a12))
|
||||
- **builders:** Improve embed errors and predicates (#7795) ([ec8d87f](https://github.com/discordjs/discord.js/commit/ec8d87f93272cc9987f9613735c0361680c4ed1e))
|
||||
|
||||
## Refactor
|
||||
|
||||
- Use arrays instead of rest parameters for builders (#7759) ([29293d7](https://github.com/discordjs/discord.js/commit/29293d7bbb5ed463e52e5a5853817e5a09cf265b))
|
||||
|
||||
## Styling
|
||||
|
||||
- Cleanup tests and tsup configs ([6b8ef20](https://github.com/discordjs/discord.js/commit/6b8ef20cb3af5b5cfd176dd0aa0a1a1e98551629))
|
||||
|
||||
# [@discordjs/builders@0.13.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.12.0...@discordjs/builders@0.13.0) - (2022-04-17)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
type APIActionRowComponent,
|
||||
type APIMessageActionRowComponent,
|
||||
type APIComponentInMessageActionRow,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
StringSelectMenuOptionBuilder,
|
||||
} from '../../src/index.js';
|
||||
|
||||
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
const rowWithButtonData: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
@@ -25,7 +25,7 @@ const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
],
|
||||
};
|
||||
|
||||
const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
const rowWithSelectMenuData: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
@@ -57,7 +57,7 @@ describe('Action Row Components', () => {
|
||||
});
|
||||
|
||||
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||
const actionRowData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
const actionRowData: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
@@ -92,7 +92,7 @@ describe('Action Row Components', () => {
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
const rowWithButtonData: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
@@ -104,7 +104,7 @@ describe('Action Row Components', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
const rowWithSelectMenuData: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
|
||||
@@ -50,6 +50,11 @@ describe('Button Components', () => {
|
||||
button.toJSON();
|
||||
}).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent().setSKUId('123456789012345678').setStyle(ButtonStyle.Premium);
|
||||
button.toJSON();
|
||||
}).not.toThrowError();
|
||||
|
||||
expect(() => buttonComponent().setURL('https://google.com')).not.toThrowError();
|
||||
});
|
||||
|
||||
@@ -101,6 +106,47 @@ describe('Button Components', () => {
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent().setStyle(ButtonStyle.Primary).setSKUId('123456789012345678');
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent()
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setLabel('button')
|
||||
.setSKUId('123456789012345678');
|
||||
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent()
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setEmoji({ name: '😇' })
|
||||
.setSKUId('123456789012345678');
|
||||
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent()
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setCustomId('test')
|
||||
.setSKUId('123456789012345678');
|
||||
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent()
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL('https://google.com')
|
||||
.setSKUId('123456789012345678');
|
||||
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid style
|
||||
expect(() => buttonComponent().setStyle(24)).toThrowError();
|
||||
expect(() => buttonComponent().setLabel(longStr)).toThrowError();
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
ComponentType,
|
||||
TextInputStyle,
|
||||
type APIButtonComponent,
|
||||
type APIMessageActionRowComponent,
|
||||
type APIComponentInMessageActionRow,
|
||||
type APISelectMenuComponent,
|
||||
type APITextInputComponent,
|
||||
type APIActionRowComponent,
|
||||
@@ -27,7 +27,7 @@ describe('createComponentBuilder', () => {
|
||||
);
|
||||
|
||||
test('GIVEN an action row component THEN returns a ActionRowBuilder', () => {
|
||||
const actionRow: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
const actionRow: APIActionRowComponent<APIComponentInMessageActionRow> = {
|
||||
components: [],
|
||||
type: ComponentType.ActionRow,
|
||||
};
|
||||
|
||||
@@ -100,7 +100,7 @@ describe('Text Input Components', () => {
|
||||
.setPlaceholder('hello')
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.toJSON();
|
||||
}).toThrowError();
|
||||
}).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid input THEN valid JSON outputs are given', () => {
|
||||
|
||||
248
packages/builders/__tests__/components/v2/container.test.ts
Normal file
248
packages/builders/__tests__/components/v2/container.test.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import { type APIContainerComponent, ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { ActionRowBuilder } from '../../../src/components/ActionRow.js';
|
||||
import { createComponentBuilder } from '../../../src/components/Components.js';
|
||||
import { ButtonBuilder } from '../../../src/components/button/Button.js';
|
||||
import { ContainerBuilder } from '../../../src/components/v2/Container.js';
|
||||
import { FileBuilder } from '../../../src/components/v2/File.js';
|
||||
import { MediaGalleryBuilder } from '../../../src/components/v2/MediaGallery.js';
|
||||
import { SectionBuilder } from '../../../src/components/v2/Section.js';
|
||||
import { SeparatorBuilder } from '../../../src/components/v2/Separator.js';
|
||||
import { TextDisplayBuilder } from '../../../src/components/v2/TextDisplay.js';
|
||||
|
||||
const containerWithTextDisplay: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
id: 123,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const containerWithSeparatorData: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Separator,
|
||||
id: 1_234,
|
||||
spacing: SeparatorSpacingSize.Small,
|
||||
divider: false,
|
||||
},
|
||||
],
|
||||
accent_color: 0x00ff00,
|
||||
};
|
||||
|
||||
const containerWithSeparatorDataNoColor: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Separator,
|
||||
id: 1_234,
|
||||
spacing: SeparatorSpacingSize.Small,
|
||||
divider: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('Container Components', () => {
|
||||
describe('Assertion Tests', () => {
|
||||
test('GIVEN valid components THEN do not throw', () => {
|
||||
expect(() =>
|
||||
new ContainerBuilder().addActionRowComponents(
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()),
|
||||
),
|
||||
).not.toThrowError();
|
||||
expect(() => new ContainerBuilder().addFileComponents(new FileBuilder())).not.toThrowError();
|
||||
expect(() => new ContainerBuilder().addMediaGalleryComponents(new MediaGalleryBuilder())).not.toThrowError();
|
||||
expect(() => new ContainerBuilder().addSectionComponents(new SectionBuilder())).not.toThrowError();
|
||||
expect(() => new ContainerBuilder().addSeparatorComponents(new SeparatorBuilder())).not.toThrowError();
|
||||
expect(() => new ContainerBuilder().addTextDisplayComponents(new TextDisplayBuilder())).not.toThrowError();
|
||||
expect(() => new ContainerBuilder().spliceComponents(0, 0, new SeparatorBuilder())).not.toThrowError();
|
||||
expect(() => new ContainerBuilder().addSeparatorComponents([new SeparatorBuilder()])).not.toThrowError();
|
||||
expect(() =>
|
||||
new ContainerBuilder().spliceComponents(0, 0, [{ type: ComponentType.Separator }]),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||
const containerData: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
type: ComponentType.Separator,
|
||||
spacing: SeparatorSpacingSize.Large,
|
||||
divider: true,
|
||||
id: 4,
|
||||
},
|
||||
{
|
||||
type: ComponentType.File,
|
||||
file: {
|
||||
url: 'attachment://file.png',
|
||||
},
|
||||
spoiler: false,
|
||||
},
|
||||
],
|
||||
accent_color: 0xff00ff,
|
||||
spoiler: true,
|
||||
};
|
||||
|
||||
expect(new ContainerBuilder(containerData).toJSON()).toEqual(containerData);
|
||||
expect(() => createComponentBuilder({ type: ComponentType.Container, components: [] })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||
const containerWithTextDisplay: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
id: 123,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const containerWithSeparatorData: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Separator,
|
||||
id: 1_234,
|
||||
spacing: SeparatorSpacingSize.Small,
|
||||
divider: false,
|
||||
},
|
||||
],
|
||||
accent_color: 0x00ff00,
|
||||
};
|
||||
|
||||
expect(new ContainerBuilder(containerWithTextDisplay).toJSON()).toEqual(containerWithTextDisplay);
|
||||
expect(new ContainerBuilder(containerWithSeparatorData).toJSON()).toEqual(containerWithSeparatorData);
|
||||
expect(() => createComponentBuilder({ type: ComponentType.Container, components: [] })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
|
||||
const textDisplay = new TextDisplayBuilder().setContent('test').setId(123);
|
||||
const separator = new SeparatorBuilder().setId(1_234).setSpacing(SeparatorSpacingSize.Small).setDivider(false);
|
||||
|
||||
expect(new ContainerBuilder().addTextDisplayComponents(textDisplay).toJSON()).toEqual(containerWithTextDisplay);
|
||||
expect(new ContainerBuilder().addSeparatorComponents(separator).toJSON()).toEqual(
|
||||
containerWithSeparatorDataNoColor,
|
||||
);
|
||||
expect(new ContainerBuilder().addTextDisplayComponents([textDisplay]).toJSON()).toEqual(containerWithTextDisplay);
|
||||
expect(new ContainerBuilder().addSeparatorComponents([separator]).toJSON()).toEqual(
|
||||
containerWithSeparatorDataNoColor,
|
||||
);
|
||||
});
|
||||
|
||||
test('GIVEN valid accent color THEN valid JSON output is given', () => {
|
||||
expect(
|
||||
new ContainerBuilder({
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
})
|
||||
.setAccentColor([255, 0, 255])
|
||||
.toJSON(),
|
||||
).toEqual({
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
accent_color: 0xff00ff,
|
||||
});
|
||||
expect(
|
||||
new ContainerBuilder({
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
})
|
||||
.setAccentColor(0xff00ff)
|
||||
.toJSON(),
|
||||
).toEqual({
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
accent_color: 0xff00ff,
|
||||
});
|
||||
expect(
|
||||
new ContainerBuilder({
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
})
|
||||
.setAccentColor([255, 0, 255])
|
||||
.clearAccentColor()
|
||||
.toJSON(),
|
||||
).toEqual({
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(new ContainerBuilder(containerWithSeparatorData).clearAccentColor().toJSON()).toEqual(
|
||||
containerWithSeparatorDataNoColor,
|
||||
);
|
||||
});
|
||||
|
||||
test('GIVEN valid method parameters THEN valid JSON is given', () => {
|
||||
expect(
|
||||
new ContainerBuilder()
|
||||
.addTextDisplayComponents(new TextDisplayBuilder().setId(3).clearId().setContent('test'))
|
||||
.setSpoiler()
|
||||
.toJSON(),
|
||||
).toEqual({
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
spoiler: true,
|
||||
});
|
||||
expect(
|
||||
new ContainerBuilder()
|
||||
.addTextDisplayComponents({ type: ComponentType.TextDisplay, content: 'test' })
|
||||
.setSpoiler(false)
|
||||
.setId(5)
|
||||
.toJSON(),
|
||||
).toEqual({
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
spoiler: false,
|
||||
id: 5,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
44
packages/builders/__tests__/components/v2/file.test.ts
Normal file
44
packages/builders/__tests__/components/v2/file.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { FileBuilder } from '../../../src/components/v2/File';
|
||||
|
||||
const dummy = {
|
||||
type: ComponentType.File as const,
|
||||
file: { url: 'attachment://owo.png' },
|
||||
};
|
||||
|
||||
describe('File', () => {
|
||||
describe('File url', () => {
|
||||
test('GIVEN a file with a pre-defined url THEN return valid toJSON data', () => {
|
||||
const file = new FileBuilder({ file: { url: 'attachment://owo.png' } });
|
||||
expect(file.toJSON()).toEqual({ ...dummy, file: { url: 'attachment://owo.png' } });
|
||||
});
|
||||
|
||||
test('GIVEN a file using File#setURL THEN return valid toJSON data', () => {
|
||||
const file = new FileBuilder();
|
||||
file.setURL('attachment://uwu.png');
|
||||
|
||||
expect(file.toJSON()).toEqual({ ...dummy, file: { url: 'attachment://uwu.png' } });
|
||||
});
|
||||
|
||||
test('GIVEN a file with an invalid url THEN throws error', () => {
|
||||
const file = new FileBuilder();
|
||||
|
||||
expect(() => file.setURL('https://google.com')).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('File spoiler', () => {
|
||||
test('GIVEN a file with a pre-defined spoiler status THEN return valid toJSON data', () => {
|
||||
const file = new FileBuilder({ ...dummy, spoiler: true });
|
||||
expect(file.toJSON()).toEqual({ ...dummy, spoiler: true });
|
||||
});
|
||||
|
||||
test('GIVEN a file using File#setSpoiler THEN return valid toJSON data', () => {
|
||||
const file = new FileBuilder({ ...dummy });
|
||||
file.setSpoiler(false);
|
||||
|
||||
expect(file.toJSON()).toEqual({ ...dummy, spoiler: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
150
packages/builders/__tests__/components/v2/mediagallery.test.ts
Normal file
150
packages/builders/__tests__/components/v2/mediagallery.test.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { type APIMediaGalleryItem, type APIMediaGalleryComponent, ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { createComponentBuilder } from '../../../src/components/Components.js';
|
||||
import { MediaGalleryBuilder } from '../../../src/components/v2/MediaGallery.js';
|
||||
import { MediaGalleryItemBuilder } from '../../../src/components/v2/MediaGalleryItem.js';
|
||||
|
||||
const galleryHttpsDisplay: APIMediaGalleryComponent = {
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{
|
||||
description: 'test',
|
||||
spoiler: false,
|
||||
media: { url: 'https://discord.com/logo.png' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const galleryAttachmentData: APIMediaGalleryComponent = {
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{
|
||||
media: { url: 'attachment://file.png' },
|
||||
},
|
||||
],
|
||||
id: 123,
|
||||
};
|
||||
|
||||
describe('Media Gallery Components', () => {
|
||||
describe('Assertion Tests', () => {
|
||||
test('GIVEN an empty media gallery THEN throws error', () => {
|
||||
const gallery = new MediaGalleryBuilder();
|
||||
expect(() => gallery.toJSON()).toThrow();
|
||||
});
|
||||
|
||||
test('GIVEN valid items THEN do not throw', () => {
|
||||
expect(() => new MediaGalleryBuilder().addItems(new MediaGalleryItemBuilder())).not.toThrowError();
|
||||
expect(() => new MediaGalleryBuilder().spliceItems(0, 0, new MediaGalleryItemBuilder())).not.toThrowError();
|
||||
expect(() => new MediaGalleryBuilder().addItems([new MediaGalleryItemBuilder()])).not.toThrowError();
|
||||
expect(() => new MediaGalleryBuilder().spliceItems(0, 0, [new MediaGalleryItemBuilder()])).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||
const mediaGalleryData: APIMediaGalleryComponent = {
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{
|
||||
media: { url: 'attachment://file.png' },
|
||||
description: 'test',
|
||||
spoiler: false,
|
||||
},
|
||||
{
|
||||
media: { url: 'https://discord.js.org/logo.jpg' },
|
||||
spoiler: true,
|
||||
},
|
||||
],
|
||||
id: 1_234,
|
||||
};
|
||||
|
||||
expect(new MediaGalleryBuilder(mediaGalleryData).toJSON()).toEqual(mediaGalleryData);
|
||||
expect(() => createComponentBuilder({ type: ComponentType.MediaGallery, items: [] })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||
const galleryHttpsDisplay: APIMediaGalleryComponent = {
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{
|
||||
description: 'test',
|
||||
spoiler: false,
|
||||
media: { url: 'https://discord.com/logo.png' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const galleryAttachmentData: APIMediaGalleryComponent = {
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{
|
||||
media: { url: 'attachment://file.png' },
|
||||
},
|
||||
],
|
||||
id: 123,
|
||||
};
|
||||
|
||||
expect(new MediaGalleryBuilder(galleryHttpsDisplay).toJSON()).toEqual(galleryHttpsDisplay);
|
||||
expect(new MediaGalleryBuilder(galleryAttachmentData).toJSON()).toEqual(galleryAttachmentData);
|
||||
expect(() => createComponentBuilder({ type: ComponentType.MediaGallery, items: [] })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
|
||||
const item1 = new MediaGalleryItemBuilder()
|
||||
.setDescription('test')
|
||||
.setSpoiler(false)
|
||||
.setURL('https://discord.com/logo.png');
|
||||
const item2 = new MediaGalleryItemBuilder().setURL('attachment://file.png');
|
||||
|
||||
expect(new MediaGalleryBuilder().addItems(item1).toJSON()).toEqual(galleryHttpsDisplay);
|
||||
expect(new MediaGalleryBuilder().addItems(item2).setId(123).toJSON()).toEqual(galleryAttachmentData);
|
||||
expect(new MediaGalleryBuilder().addItems([item1]).toJSON()).toEqual(galleryHttpsDisplay);
|
||||
expect(new MediaGalleryBuilder().addItems([item2]).setId(123).toJSON()).toEqual(galleryAttachmentData);
|
||||
});
|
||||
|
||||
test('GIVEN valid JSON options THEN valid JSON output is given 2', () => {
|
||||
const item1: APIMediaGalleryItem = {
|
||||
description: 'test',
|
||||
spoiler: false,
|
||||
media: { url: 'https://discord.com/logo.png' },
|
||||
};
|
||||
const item2 = {
|
||||
media: { url: 'attachment://file.png' },
|
||||
};
|
||||
|
||||
expect(new MediaGalleryBuilder().addItems(item1).toJSON()).toEqual(galleryHttpsDisplay);
|
||||
expect(new MediaGalleryBuilder().addItems(item2).setId(123).toJSON()).toEqual(galleryAttachmentData);
|
||||
expect(new MediaGalleryBuilder().addItems([item1]).toJSON()).toEqual(galleryHttpsDisplay);
|
||||
expect(new MediaGalleryBuilder().addItems([item2]).setId(123).toJSON()).toEqual(galleryAttachmentData);
|
||||
});
|
||||
|
||||
test('GIVEN valid builder callback THEN valid JSON output is given', () => {
|
||||
const item1 = new MediaGalleryItemBuilder()
|
||||
.setDescription('test')
|
||||
.setSpoiler(false)
|
||||
.setURL('https://discord.com/logo.png');
|
||||
const item2 = new MediaGalleryItemBuilder().setURL('attachment://file.png');
|
||||
|
||||
expect(
|
||||
new MediaGalleryBuilder()
|
||||
.addItems((item) => item.setDescription('test').setSpoiler(false).setURL('https://discord.com/logo.png'))
|
||||
.toJSON(),
|
||||
).toEqual(galleryHttpsDisplay);
|
||||
expect(
|
||||
new MediaGalleryBuilder()
|
||||
.spliceItems(0, 0, (item) => item.setURL('attachment://file.png'))
|
||||
.setId(123)
|
||||
.toJSON(),
|
||||
).toEqual(galleryAttachmentData);
|
||||
expect(
|
||||
new MediaGalleryBuilder()
|
||||
.addItems([(item) => item.setDescription('test').setSpoiler(false).setURL('https://discord.com/logo.png')])
|
||||
.toJSON(),
|
||||
).toEqual(galleryHttpsDisplay);
|
||||
expect(
|
||||
new MediaGalleryBuilder()
|
||||
.spliceItems(0, 0, [(item) => item.setDescription('test').clearDescription().setURL('attachment://file.png')])
|
||||
.setId(123)
|
||||
.toJSON(),
|
||||
).toEqual(galleryAttachmentData);
|
||||
});
|
||||
});
|
||||
});
|
||||
191
packages/builders/__tests__/components/v2/section.test.ts
Normal file
191
packages/builders/__tests__/components/v2/section.test.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { type APISectionComponent, ButtonStyle, ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { createComponentBuilder } from '../../../src/components/Components.js';
|
||||
import { ButtonBuilder } from '../../../src/components/button/Button.js';
|
||||
import { SectionBuilder } from '../../../src/components/v2/Section.js';
|
||||
import { TextDisplayBuilder } from '../../../src/components/v2/TextDisplay.js';
|
||||
import { ThumbnailBuilder } from '../../../src/components/v2/Thumbnail.js';
|
||||
|
||||
const sectionWithButtonData: APISectionComponent = {
|
||||
type: ComponentType.Section,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
accessory: {
|
||||
type: ComponentType.Button,
|
||||
label: 'test',
|
||||
custom_id: '123',
|
||||
style: ButtonStyle.Primary,
|
||||
},
|
||||
};
|
||||
|
||||
const sectionWithThumbnailData: APISectionComponent = {
|
||||
type: ComponentType.Section,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
accessory: {
|
||||
type: ComponentType.Thumbnail,
|
||||
media: { url: 'attachment://file.png' },
|
||||
spoiler: true,
|
||||
description: 'test',
|
||||
},
|
||||
};
|
||||
|
||||
describe('Section Components', () => {
|
||||
describe('Assertion Tests', () => {
|
||||
test('GIVEN valid components THEN do not throw', () => {
|
||||
expect(() => new SectionBuilder().addTextDisplayComponents(new TextDisplayBuilder())).not.toThrowError();
|
||||
expect(() => new SectionBuilder().spliceTextDisplayComponents(0, 0, new TextDisplayBuilder())).not.toThrowError();
|
||||
expect(() => new SectionBuilder().addTextDisplayComponents([new TextDisplayBuilder()])).not.toThrowError();
|
||||
expect(() =>
|
||||
new SectionBuilder().spliceTextDisplayComponents(0, 0, [new TextDisplayBuilder()]),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||
const sectionData: APISectionComponent = {
|
||||
type: ComponentType.Section,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
id: 123,
|
||||
},
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
accessory: {
|
||||
type: ComponentType.Thumbnail,
|
||||
media: { url: 'attachment://file.png' },
|
||||
},
|
||||
};
|
||||
|
||||
expect(new SectionBuilder(sectionData).toJSON()).toEqual(sectionData);
|
||||
expect(() =>
|
||||
createComponentBuilder({
|
||||
type: ComponentType.Section,
|
||||
components: [],
|
||||
accessory: { type: ComponentType.Thumbnail, media: { url: 'https://discord.com/logo.png' } },
|
||||
}),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||
const sectionWithButtonData: APISectionComponent = {
|
||||
type: ComponentType.Section,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
accessory: {
|
||||
type: ComponentType.Button,
|
||||
label: 'test',
|
||||
custom_id: '123',
|
||||
style: ButtonStyle.Primary,
|
||||
},
|
||||
};
|
||||
|
||||
const sectionWithThumbnailData: APISectionComponent = {
|
||||
type: ComponentType.Section,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
accessory: {
|
||||
type: ComponentType.Thumbnail,
|
||||
media: { url: 'attachment://file.png' },
|
||||
spoiler: true,
|
||||
description: 'test',
|
||||
},
|
||||
};
|
||||
|
||||
expect(new SectionBuilder(sectionWithButtonData).toJSON()).toEqual(sectionWithButtonData);
|
||||
expect(new SectionBuilder(sectionWithThumbnailData).toJSON()).toEqual(sectionWithThumbnailData);
|
||||
expect(() =>
|
||||
createComponentBuilder({
|
||||
type: ComponentType.Section,
|
||||
components: [],
|
||||
accessory: {
|
||||
type: ComponentType.Button,
|
||||
label: 'test',
|
||||
custom_id: '123',
|
||||
style: ButtonStyle.Primary,
|
||||
},
|
||||
}),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
|
||||
const button = new ButtonBuilder().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123');
|
||||
const thumbnail = new ThumbnailBuilder().setDescription('test').setSpoiler().setURL('attachment://file.png');
|
||||
const textDisplay = new TextDisplayBuilder().setContent('test');
|
||||
|
||||
expect(new SectionBuilder().addTextDisplayComponents(textDisplay).setButtonAccessory(button).toJSON()).toEqual(
|
||||
sectionWithButtonData,
|
||||
);
|
||||
expect(
|
||||
new SectionBuilder().addTextDisplayComponents(textDisplay).setThumbnailAccessory(thumbnail).toJSON(),
|
||||
).toEqual(sectionWithThumbnailData);
|
||||
expect(
|
||||
new SectionBuilder()
|
||||
.addTextDisplayComponents([textDisplay])
|
||||
.setButtonAccessory((button) => button.setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123'))
|
||||
.toJSON(),
|
||||
).toEqual(sectionWithButtonData);
|
||||
expect(
|
||||
new SectionBuilder()
|
||||
.addTextDisplayComponents([textDisplay])
|
||||
.setThumbnailAccessory((thumbnail) =>
|
||||
thumbnail.setDescription('test').setSpoiler().setURL('attachment://file.png'),
|
||||
)
|
||||
.toJSON(),
|
||||
).toEqual(sectionWithThumbnailData);
|
||||
});
|
||||
|
||||
test('GIVEN valid builder callback THEN valid JSON output is given', () => {
|
||||
const button = new ButtonBuilder().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123');
|
||||
|
||||
expect(
|
||||
new SectionBuilder()
|
||||
.addTextDisplayComponents((textDisplay) => textDisplay.setContent('test'))
|
||||
.setButtonAccessory(button)
|
||||
.toJSON(),
|
||||
).toEqual(sectionWithButtonData);
|
||||
expect(
|
||||
new SectionBuilder()
|
||||
.spliceTextDisplayComponents(0, 0, (textDisplay) => textDisplay.setContent('test'))
|
||||
.setButtonAccessory(button)
|
||||
.toJSON(),
|
||||
).toEqual(sectionWithButtonData);
|
||||
expect(
|
||||
new SectionBuilder()
|
||||
.addTextDisplayComponents([(textDisplay) => textDisplay.setContent('test')])
|
||||
.setButtonAccessory(button)
|
||||
.toJSON(),
|
||||
).toEqual(sectionWithButtonData);
|
||||
expect(
|
||||
new SectionBuilder()
|
||||
.spliceTextDisplayComponents(0, 0, [(textDisplay) => textDisplay.setContent('test')])
|
||||
.setButtonAccessory(button)
|
||||
.toJSON(),
|
||||
).toEqual(sectionWithButtonData);
|
||||
});
|
||||
});
|
||||
});
|
||||
35
packages/builders/__tests__/components/v2/separator.test.ts
Normal file
35
packages/builders/__tests__/components/v2/separator.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { SeparatorBuilder } from '../../../src/components/v2/Separator';
|
||||
|
||||
describe('Separator', () => {
|
||||
describe('Divider', () => {
|
||||
test('GIVEN a separator with a pre-defined divider THEN return valid toJSON data', () => {
|
||||
const separator = new SeparatorBuilder({ divider: true });
|
||||
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, divider: true });
|
||||
});
|
||||
|
||||
test('GIVEN a separator with a set divider THEN return valid toJSON data', () => {
|
||||
const separator = new SeparatorBuilder().setDivider(false);
|
||||
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, divider: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Spacing', () => {
|
||||
test('GIVEN a separator with a pre-defined spacing THEN return valid toJSON data', () => {
|
||||
const separator = new SeparatorBuilder({ spacing: SeparatorSpacingSize.Small });
|
||||
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, spacing: SeparatorSpacingSize.Small });
|
||||
});
|
||||
|
||||
test('GIVEN a separator with a set spacing THEN return valid toJSON data', () => {
|
||||
const separator = new SeparatorBuilder().setSpacing(SeparatorSpacingSize.Large);
|
||||
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, spacing: SeparatorSpacingSize.Large });
|
||||
});
|
||||
|
||||
test('GIVEN a separator with a set spacing THEN clear spacing THEN return valid toJSON data', () => {
|
||||
const separator = new SeparatorBuilder({ spacing: SeparatorSpacingSize.Small });
|
||||
separator.clearSpacing();
|
||||
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { TextDisplayBuilder } from '../../../src/components/v2/TextDisplay';
|
||||
|
||||
describe('TextDisplay', () => {
|
||||
describe('TextDisplay content', () => {
|
||||
test('GIVEN a text display with a pre-defined content THEN return valid toJSON data', () => {
|
||||
const textDisplay = new TextDisplayBuilder({ content: 'foo' });
|
||||
expect(textDisplay.toJSON()).toEqual({ type: ComponentType.TextDisplay, content: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN a text display with a set content THEN return valid toJSON data', () => {
|
||||
const textDisplay = new TextDisplayBuilder().setContent('foo');
|
||||
expect(textDisplay.toJSON()).toEqual({ type: ComponentType.TextDisplay, content: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN a text display with a pre-defined content THEN overwritten content THEN return valid toJSON data', () => {
|
||||
const textDisplay = new TextDisplayBuilder({ content: 'foo' });
|
||||
textDisplay.setContent('bar');
|
||||
expect(textDisplay.toJSON()).toEqual({ type: ComponentType.TextDisplay, content: 'bar' });
|
||||
});
|
||||
});
|
||||
});
|
||||
69
packages/builders/__tests__/components/v2/thumbnail.test.ts
Normal file
69
packages/builders/__tests__/components/v2/thumbnail.test.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { ThumbnailBuilder } from '../../../src/components/v2/Thumbnail';
|
||||
|
||||
const dummy = {
|
||||
type: ComponentType.Thumbnail as const,
|
||||
media: { url: 'https://google.com' },
|
||||
};
|
||||
|
||||
describe('Thumbnail', () => {
|
||||
describe('Thumbnail url', () => {
|
||||
test('GIVEN a thumbnail with a pre-defined url THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ media: { url: 'https://google.com' } });
|
||||
expect(thumbnail.toJSON()).toEqual({ type: ComponentType.Thumbnail, media: { url: 'https://google.com' } });
|
||||
});
|
||||
|
||||
test('GIVEN a thumbnail with a set url THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder().setURL('https://google.com');
|
||||
expect(thumbnail.toJSON()).toEqual({ type: ComponentType.Thumbnail, media: { url: 'https://google.com' } });
|
||||
});
|
||||
|
||||
test.each(['owo', 'discord://user'])('GIVEN a thumbnail with an invalid URL (%s) THEN throws error', (input) => {
|
||||
const thumbnail = new ThumbnailBuilder();
|
||||
|
||||
expect(() => thumbnail.setURL(input)).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Thumbnail description', () => {
|
||||
test('GIVEN a thumbnail with a pre-defined description THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ ...dummy, description: 'foo' });
|
||||
expect(thumbnail.toJSON()).toEqual({ ...dummy, description: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN a thumbnail with a set description THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ ...dummy });
|
||||
thumbnail.setDescription('foo');
|
||||
|
||||
expect(thumbnail.toJSON()).toEqual({ ...dummy, description: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN a thumbnail with a pre-defined description THEN unset description THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ description: 'foo', ...dummy });
|
||||
thumbnail.clearDescription();
|
||||
|
||||
expect(thumbnail.toJSON()).toEqual({ ...dummy });
|
||||
});
|
||||
|
||||
test('GIVEN a thumbnail with an invalid description THEN throws error', () => {
|
||||
const thumbnail = new ThumbnailBuilder();
|
||||
|
||||
expect(() => thumbnail.setDescription('a'.repeat(1_025))).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Thumbnail spoiler', () => {
|
||||
test('GIVEN a thumbnail with a pre-defined spoiler status THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ ...dummy, spoiler: true });
|
||||
expect(thumbnail.toJSON()).toEqual({ ...dummy, spoiler: true });
|
||||
});
|
||||
|
||||
test('GIVEN a thumbnail with a set spoiler status THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ ...dummy });
|
||||
thumbnail.setSpoiler(false);
|
||||
|
||||
expect(thumbnail.toJSON()).toEqual({ ...dummy, spoiler: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PermissionFlagsBits } from 'discord-api-types/v10';
|
||||
import { ApplicationIntegrationType, InteractionContextType, PermissionFlagsBits } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { ContextMenuCommandAssertions, ContextMenuCommandBuilder } from '../../src/index.js';
|
||||
|
||||
@@ -16,8 +16,8 @@ describe('Context Menu Commands', () => {
|
||||
// Too short of a name
|
||||
expect(() => ContextMenuCommandAssertions.validateName('')).toThrowError();
|
||||
|
||||
// Invalid characters used
|
||||
expect(() => ContextMenuCommandAssertions.validateName('ABC123$%^&')).toThrowError();
|
||||
// This should be fine, even with trailing and leading spaces (API trims it).
|
||||
expect(() => ContextMenuCommandAssertions.validateName(' 🩵 ABC 123 $%^& ')).not.toThrowError();
|
||||
|
||||
// Too long of a name
|
||||
expect(() =>
|
||||
@@ -60,8 +60,6 @@ describe('Context Menu Commands', () => {
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
expect(() => getBuilder().setName('$$$')).toThrowError();
|
||||
|
||||
expect(() => getBuilder().setName(' ')).toThrowError();
|
||||
});
|
||||
|
||||
@@ -144,5 +142,51 @@ describe('Context Menu Commands', () => {
|
||||
expect(() => getBuilder().setDefaultMemberPermissions(1.1)).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('contexts', () => {
|
||||
test('GIVEN a builder with valid contexts THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().setContexts([InteractionContextType.Guild, InteractionContextType.BotDM]),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getBuilder().setContexts(InteractionContextType.Guild, InteractionContextType.BotDM),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid contexts THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid contexts
|
||||
expect(() => getBuilder().setContexts(999)).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid contexts
|
||||
expect(() => getBuilder().setContexts([999, 998])).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration types', () => {
|
||||
test('GIVEN a builder with valid integraton types THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().setIntegrationTypes([
|
||||
ApplicationIntegrationType.GuildInstall,
|
||||
ApplicationIntegrationType.UserInstall,
|
||||
]),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getBuilder().setIntegrationTypes(
|
||||
ApplicationIntegrationType.GuildInstall,
|
||||
ApplicationIntegrationType.UserInstall,
|
||||
),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid integration types THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid integration types
|
||||
expect(() => getBuilder().setIntegrationTypes(999)).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid integration types
|
||||
expect(() => getBuilder().setIntegrationTypes([999, 998])).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { ChannelType, PermissionFlagsBits, type APIApplicationCommandOptionChoice } from 'discord-api-types/v10';
|
||||
import {
|
||||
ApplicationCommandType,
|
||||
ApplicationIntegrationType,
|
||||
ChannelType,
|
||||
InteractionContextType,
|
||||
PermissionFlagsBits,
|
||||
type APIApplicationCommandOptionChoice,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
SlashCommandAssertions,
|
||||
@@ -127,6 +134,10 @@ describe('Slash Commands', () => {
|
||||
});
|
||||
|
||||
describe('Builder with simple options', () => {
|
||||
test('GIVEN valid builder THEN returns type included', () => {
|
||||
expect(getNamedBuilder().toJSON()).includes({ type: ApplicationCommandType.ChatInput });
|
||||
});
|
||||
|
||||
test('GIVEN valid builder with options THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getBuilder()
|
||||
@@ -357,6 +368,10 @@ describe('Slash Commands', () => {
|
||||
getBuilder().addStringOption(getStringOption().setChoices({ name: 'owo', value: 'uwu' })),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder with NSFW, THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('foo').setDescription('foo').setNSFW(true)).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Builder with subcommand (group) options', () => {
|
||||
@@ -519,6 +534,60 @@ describe('Slash Commands', () => {
|
||||
|
||||
expect(() => getBuilder().setDefaultMemberPermissions(1.1)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid permission with options THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addBooleanOption(getBooleanOption()).setDefaultMemberPermissions('1'),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addChannelOption(getChannelOption()).setDMPermission(false)).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('contexts', () => {
|
||||
test('GIVEN a builder with valid contexts THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().setContexts([InteractionContextType.Guild, InteractionContextType.BotDM]),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getBuilder().setContexts(InteractionContextType.Guild, InteractionContextType.BotDM),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid contexts THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid contexts
|
||||
expect(() => getBuilder().setContexts(999)).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid contexts
|
||||
expect(() => getBuilder().setContexts([999, 998])).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration types', () => {
|
||||
test('GIVEN a builder with valid integraton types THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().setIntegrationTypes([
|
||||
ApplicationIntegrationType.GuildInstall,
|
||||
ApplicationIntegrationType.UserInstall,
|
||||
]),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getBuilder().setIntegrationTypes(
|
||||
ApplicationIntegrationType.GuildInstall,
|
||||
ApplicationIntegrationType.UserInstall,
|
||||
),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid integration types THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid integration types
|
||||
expect(() => getBuilder().setIntegrationTypes(999)).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid integration types
|
||||
expect(() => getBuilder().setIntegrationTypes([999, 998])).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -324,12 +324,16 @@ describe('Embed', () => {
|
||||
test('GIVEN an embed using Embed#addFields THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed.addFields({ name: 'foo', value: 'bar' });
|
||||
embed.addFields([{ name: 'foo', value: 'bar' }]);
|
||||
embed.addFields([
|
||||
{ name: 'foo', value: 'bar' },
|
||||
{ name: '', value: '' },
|
||||
]);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
fields: [
|
||||
{ name: 'foo', value: 'bar' },
|
||||
{ name: 'foo', value: 'bar' },
|
||||
{ name: '', value: '' },
|
||||
],
|
||||
});
|
||||
});
|
||||
@@ -381,38 +385,24 @@ describe('Embed', () => {
|
||||
expect(() => embed.setFields(Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })))).toThrowError();
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field amount THEN throws error', () => {
|
||||
test('1', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
test('GIVEN invalid field amount THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() =>
|
||||
embed.addFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).toThrowError();
|
||||
});
|
||||
expect(() =>
|
||||
embed.addFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field name THEN throws error', () => {
|
||||
test('2', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
test('GIVEN invalid field name length THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.addFields({ name: '', value: 'bar' })).toThrowError();
|
||||
});
|
||||
expect(() => embed.addFields({ name: 'a'.repeat(257), value: 'bar' })).toThrowError();
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field name length THEN throws error', () => {
|
||||
test('3', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
test('GIVEN invalid field value length THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.addFields({ name: 'a'.repeat(257), value: 'bar' })).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field value length THEN throws error', () => {
|
||||
test('4', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.addFields({ name: '', value: 'a'.repeat(1_025) })).toThrowError();
|
||||
});
|
||||
expect(() => embed.addFields({ name: '', value: 'a'.repeat(1_025) })).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
17
packages/builders/__tests__/types.test.ts
Normal file
17
packages/builders/__tests__/types.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { expectTypeOf } from 'vitest';
|
||||
import { SlashCommandBuilder, SlashCommandStringOption, SlashCommandSubcommandBuilder } from '../src/index.js';
|
||||
|
||||
const getBuilder = () => new SlashCommandBuilder();
|
||||
const getStringOption = () => new SlashCommandStringOption().setName('owo').setDescription('Testing 123');
|
||||
const getSubcommand = () => new SlashCommandSubcommandBuilder().setName('owo').setDescription('Testing 123');
|
||||
|
||||
type BuilderPropsOnly<Type = SlashCommandBuilder> = Pick<
|
||||
Type,
|
||||
keyof {
|
||||
[Key in keyof Type as Type[Key] extends (...args: any) => any ? never : Key]: any;
|
||||
}
|
||||
>;
|
||||
|
||||
expectTypeOf(getBuilder().addStringOption(getStringOption())).toMatchTypeOf<BuilderPropsOnly>();
|
||||
|
||||
expectTypeOf(getBuilder().addSubcommand(getSubcommand())).toMatchTypeOf<BuilderPropsOnly>();
|
||||
@@ -5,13 +5,16 @@ header = """
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
body = """
|
||||
{%- macro remote_url() -%}
|
||||
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
||||
{%- endmacro -%}
|
||||
{% if version %}\
|
||||
# [{{ version | trim_start_matches(pat="v") }}]\
|
||||
{% if previous %}\
|
||||
{% if previous.version %}\
|
||||
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
|
||||
({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }})\
|
||||
{% else %}\
|
||||
(https://github.com/discordjs/discord.js/tree/{{ version }})\
|
||||
({{ self::remote_url() }}/tree/{{ version }})\
|
||||
{% endif %}\
|
||||
{% endif %} \
|
||||
- ({{ timestamp | date(format="%Y-%m-%d") }})
|
||||
@@ -24,14 +27,21 @@ body = """
|
||||
- {% if commit.scope %}\
|
||||
**{{commit.scope}}:** \
|
||||
{% endif %}\
|
||||
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
|
||||
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
|
||||
{% if commit.github.username %} by @{{ commit.github.username }}{%- endif %}\
|
||||
{% if commit.breaking %}\
|
||||
{% for breakingChange in commit.footers %}\
|
||||
\n{% raw %} {% endraw %}- **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\
|
||||
{% endfor %}\
|
||||
{% endif %}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
{% endfor %}\
|
||||
{% if github.contributors | filter(attribute="is_first_time", value=true) | length %}\
|
||||
\n### New Contributors\n
|
||||
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}\
|
||||
* @{{ contributor.username }} made their first contribution in #{{ contributor.pr_number }}
|
||||
{% endfor %}\
|
||||
{% endif %}\n
|
||||
"""
|
||||
trim = true
|
||||
footer = ""
|
||||
@@ -59,5 +69,9 @@ commit_parsers = [
|
||||
filter_commits = true
|
||||
tag_pattern = "@discordjs/builders@[0-9]*"
|
||||
ignore_tags = ""
|
||||
topo_order = true
|
||||
topo_order = false
|
||||
sort_commits = "newest"
|
||||
|
||||
[remote.github]
|
||||
owner = "discordjs"
|
||||
repo = "discord.js"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@discordjs/builders",
|
||||
"version": "1.8.0",
|
||||
"version": "1.12.0",
|
||||
"description": "A set of builders that you can use when creating your bot",
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
@@ -38,7 +38,7 @@
|
||||
"dist"
|
||||
],
|
||||
"contributors": [
|
||||
"Vlad Frangu <kingdgrizzle@gmail.com>",
|
||||
"Vlad Frangu <me@vladfrangu.dev>",
|
||||
"Crawl <icrawltogo@gmail.com>",
|
||||
"Amish Shah <amishshah.2k@gmail.com>",
|
||||
"SpaceEEC <spaceeec@yahoo.com>",
|
||||
@@ -67,28 +67,28 @@
|
||||
"dependencies": {
|
||||
"@discordjs/formatters": "workspace:^",
|
||||
"@discordjs/util": "workspace:^",
|
||||
"@sapphire/shapeshift": "^3.9.7",
|
||||
"discord-api-types": "0.37.83",
|
||||
"@sapphire/shapeshift": "^4.0.0",
|
||||
"discord-api-types": "^0.38.26",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"ts-mixer": "^6.0.4",
|
||||
"tslib": "^2.6.2"
|
||||
"tslib": "^2.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@discordjs/api-extractor": "workspace:^",
|
||||
"@discordjs/scripts": "workspace:^",
|
||||
"@favware/cliff-jumper": "^3.0.2",
|
||||
"@types/node": "16.18.60",
|
||||
"@vitest/coverage-v8": "^1.5.0",
|
||||
"@favware/cliff-jumper": "^4.1.0",
|
||||
"@types/node": "^16.18.105",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"esbuild-plugin-version-injector": "^1.2.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-neon": "^0.1.62",
|
||||
"eslint-formatter-pretty": "^6.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
"tsup": "^8.0.2",
|
||||
"turbo": "^1.13.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^1.5.0"
|
||||
"prettier": "^3.3.3",
|
||||
"tsup": "^8.2.4",
|
||||
"turbo": "^2.0.14",
|
||||
"typescript": "~5.5.4",
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import {
|
||||
type APIActionRowComponent,
|
||||
ComponentType,
|
||||
type APIMessageActionRowComponent,
|
||||
type APIModalActionRowComponent,
|
||||
type APIActionRowComponentTypes,
|
||||
type APIComponentInMessageActionRow,
|
||||
type APIComponentInModalActionRow,
|
||||
type APIComponentInActionRow,
|
||||
} from 'discord-api-types/v10';
|
||||
import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js';
|
||||
import { ComponentBuilder } from './Component.js';
|
||||
@@ -18,13 +18,6 @@ import type { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js';
|
||||
import type { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
|
||||
import type { TextInputBuilder } from './textInput/TextInput.js';
|
||||
|
||||
/**
|
||||
* The builders that may be used for messages.
|
||||
*/
|
||||
export type MessageComponentBuilder =
|
||||
| ActionRowBuilder<MessageActionRowComponentBuilder>
|
||||
| MessageActionRowComponentBuilder;
|
||||
|
||||
/**
|
||||
* The builders that may be used for modals.
|
||||
*/
|
||||
@@ -57,7 +50,7 @@ export type AnyComponentBuilder = MessageActionRowComponentBuilder | ModalAction
|
||||
* @typeParam ComponentType - The types of components this action row holds
|
||||
*/
|
||||
export class ActionRowBuilder<ComponentType extends AnyComponentBuilder> extends ComponentBuilder<
|
||||
APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent>
|
||||
APIActionRowComponent<APIComponentInMessageActionRow | APIComponentInModalActionRow>
|
||||
> {
|
||||
/**
|
||||
* The components within this action row.
|
||||
@@ -98,7 +91,7 @@ export class ActionRowBuilder<ComponentType extends AnyComponentBuilder> extends
|
||||
* .addComponents(button2, button3);
|
||||
* ```
|
||||
*/
|
||||
public constructor({ components, ...data }: Partial<APIActionRowComponent<APIActionRowComponentTypes>> = {}) {
|
||||
public constructor({ components, ...data }: Partial<APIActionRowComponent<APIComponentInActionRow>> = {}) {
|
||||
super({ type: ComponentType.ActionRow, ...data });
|
||||
this.components = (components?.map((component) => createComponentBuilder(component)) ?? []) as ComponentType[];
|
||||
}
|
||||
|
||||
@@ -3,35 +3,49 @@ import { ButtonStyle, ChannelType, type APIMessageComponentEmoji } from 'discord
|
||||
import { isValidationEnabled } from '../util/validation.js';
|
||||
import { StringSelectMenuOptionBuilder } from './selectMenu/StringSelectMenuOption.js';
|
||||
|
||||
export const customIdValidator = s.string
|
||||
export const idValidator = s
|
||||
.number()
|
||||
.safeInt()
|
||||
.greaterThanOrEqual(1)
|
||||
.lessThan(4_294_967_296) // 2^32 - 1
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const customIdValidator = s
|
||||
.string()
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(100)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const emojiValidator = s
|
||||
.object({
|
||||
id: s.string,
|
||||
name: s.string,
|
||||
animated: s.boolean,
|
||||
id: s.string(),
|
||||
name: s.string(),
|
||||
animated: s.boolean(),
|
||||
})
|
||||
.partial.strict.setValidationEnabled(isValidationEnabled);
|
||||
.partial()
|
||||
.strict()
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const disabledValidator = s.boolean;
|
||||
export const disabledValidator = s.boolean();
|
||||
|
||||
export const buttonLabelValidator = s.string
|
||||
export const buttonLabelValidator = s
|
||||
.string()
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(80)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const buttonStyleValidator = s.nativeEnum(ButtonStyle);
|
||||
|
||||
export const placeholderValidator = s.string.lengthLessThanOrEqual(150).setValidationEnabled(isValidationEnabled);
|
||||
export const minMaxValidator = s.number.int
|
||||
export const placeholderValidator = s.string().lengthLessThanOrEqual(150).setValidationEnabled(isValidationEnabled);
|
||||
export const minMaxValidator = s
|
||||
.number()
|
||||
.int()
|
||||
.greaterThanOrEqual(0)
|
||||
.lessThanOrEqual(25)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const labelValueDescriptionValidator = s.string
|
||||
export const labelValueDescriptionValidator = s
|
||||
.string()
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(100)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
@@ -40,18 +54,21 @@ export const jsonOptionValidator = s
|
||||
.object({
|
||||
label: labelValueDescriptionValidator,
|
||||
value: labelValueDescriptionValidator,
|
||||
description: labelValueDescriptionValidator.optional,
|
||||
emoji: emojiValidator.optional,
|
||||
default: s.boolean.optional,
|
||||
description: labelValueDescriptionValidator.optional(),
|
||||
emoji: emojiValidator.optional(),
|
||||
default: s.boolean().optional(),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const optionValidator = s.instance(StringSelectMenuOptionBuilder).setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const optionsValidator = optionValidator.array
|
||||
export const optionsValidator = optionValidator
|
||||
.array()
|
||||
.lengthGreaterThanOrEqual(0)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
export const optionsLengthValidator = s.number.int
|
||||
export const optionsLengthValidator = s
|
||||
.number()
|
||||
.int()
|
||||
.greaterThanOrEqual(0)
|
||||
.lessThanOrEqual(25)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
@@ -61,16 +78,17 @@ export function validateRequiredSelectMenuParameters(options: StringSelectMenuOp
|
||||
optionsValidator.parse(options);
|
||||
}
|
||||
|
||||
export const defaultValidator = s.boolean;
|
||||
export const defaultValidator = s.boolean();
|
||||
|
||||
export function validateRequiredSelectMenuOptionParameters(label?: string, value?: string) {
|
||||
labelValueDescriptionValidator.parse(label);
|
||||
labelValueDescriptionValidator.parse(value);
|
||||
}
|
||||
|
||||
export const channelTypesValidator = s.nativeEnum(ChannelType).array.setValidationEnabled(isValidationEnabled);
|
||||
export const channelTypesValidator = s.nativeEnum(ChannelType).array().setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const urlValidator = s.string
|
||||
export const urlValidator = s
|
||||
.string()
|
||||
.url({
|
||||
allowedProtocols: ['http:', 'https:', 'discord:'],
|
||||
})
|
||||
@@ -81,21 +99,36 @@ export function validateRequiredButtonParameters(
|
||||
label?: string,
|
||||
emoji?: APIMessageComponentEmoji,
|
||||
customId?: string,
|
||||
skuId?: string,
|
||||
url?: string,
|
||||
) {
|
||||
if (url && customId) {
|
||||
throw new RangeError('URL and custom id are mutually exclusive');
|
||||
}
|
||||
|
||||
if (!label && !emoji) {
|
||||
throw new RangeError('Buttons must have a label and/or an emoji');
|
||||
}
|
||||
|
||||
if (style === ButtonStyle.Link) {
|
||||
if (!url) {
|
||||
throw new RangeError('Link buttons must have a url');
|
||||
if (style === ButtonStyle.Premium) {
|
||||
if (!skuId) {
|
||||
throw new RangeError('Premium buttons must have an SKU id.');
|
||||
}
|
||||
|
||||
if (customId || label || url || emoji) {
|
||||
throw new RangeError('Premium buttons cannot have a custom id, label, URL, or emoji.');
|
||||
}
|
||||
} else {
|
||||
if (skuId) {
|
||||
throw new RangeError('Non-premium buttons must not have an SKU id.');
|
||||
}
|
||||
|
||||
if (url && customId) {
|
||||
throw new RangeError('URL and custom id are mutually exclusive.');
|
||||
}
|
||||
|
||||
if (!label && !emoji) {
|
||||
throw new RangeError('Non-premium buttons must have a label and/or an emoji.');
|
||||
}
|
||||
|
||||
if (style === ButtonStyle.Link) {
|
||||
if (!url) {
|
||||
throw new RangeError('Link buttons must have a URL.');
|
||||
}
|
||||
} else if (url) {
|
||||
throw new RangeError('Non-premium and non-link buttons cannot have a URL.');
|
||||
}
|
||||
} else if (url) {
|
||||
throw new RangeError('Non-link buttons cannot have a url');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import type { JSONEncodable } from '@discordjs/util';
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIActionRowComponentTypes,
|
||||
APIComponentInActionRow,
|
||||
APIBaseComponent,
|
||||
ComponentType,
|
||||
APIMessageComponent,
|
||||
APIModalComponent,
|
||||
} from 'discord-api-types/v10';
|
||||
import { idValidator } from './Assertions';
|
||||
|
||||
/**
|
||||
* Any action row component data represented as an object.
|
||||
*/
|
||||
export type AnyAPIActionRowComponent = APIActionRowComponent<APIActionRowComponentTypes> | APIActionRowComponentTypes;
|
||||
export type AnyAPIActionRowComponent =
|
||||
| APIActionRowComponent<APIComponentInActionRow>
|
||||
| APIComponentInActionRow
|
||||
| APIMessageComponent
|
||||
| APIModalComponent;
|
||||
|
||||
/**
|
||||
* The base component builder that contains common symbols for all sorts of components.
|
||||
@@ -42,4 +49,22 @@ export abstract class ComponentBuilder<
|
||||
public constructor(data: Partial<DataType>) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the id (not the custom id) for this component.
|
||||
*
|
||||
* @param id - The id for this component
|
||||
*/
|
||||
public setId(id: number) {
|
||||
this.data.id = idValidator.parse(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the id of this component, defaulting to a default incremented id.
|
||||
*/
|
||||
public clearId() {
|
||||
this.data.id = undefined;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,41 @@
|
||||
import type { JSONEncodable } from '@discordjs/util';
|
||||
import { ComponentType, type APIMessageComponent, type APIModalComponent } from 'discord-api-types/v10';
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
type MessageActionRowComponentBuilder,
|
||||
type AnyComponentBuilder,
|
||||
type MessageComponentBuilder,
|
||||
type ModalComponentBuilder,
|
||||
} from './ActionRow.js';
|
||||
import { ComponentBuilder } from './Component.js';
|
||||
import { ButtonBuilder } from './button/Button.js';
|
||||
import { LabelBuilder } from './label/Label.js';
|
||||
import { ChannelSelectMenuBuilder } from './selectMenu/ChannelSelectMenu.js';
|
||||
import { MentionableSelectMenuBuilder } from './selectMenu/MentionableSelectMenu.js';
|
||||
import { RoleSelectMenuBuilder } from './selectMenu/RoleSelectMenu.js';
|
||||
import { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js';
|
||||
import { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
|
||||
import { TextInputBuilder } from './textInput/TextInput.js';
|
||||
import { ContainerBuilder } from './v2/Container.js';
|
||||
import { FileBuilder } from './v2/File.js';
|
||||
import { MediaGalleryBuilder } from './v2/MediaGallery.js';
|
||||
import { SectionBuilder } from './v2/Section.js';
|
||||
import { SeparatorBuilder } from './v2/Separator.js';
|
||||
import { TextDisplayBuilder } from './v2/TextDisplay.js';
|
||||
import { ThumbnailBuilder } from './v2/Thumbnail.js';
|
||||
|
||||
/**
|
||||
* The builders that may be used for messages.
|
||||
*/
|
||||
export type MessageComponentBuilder =
|
||||
| ActionRowBuilder<MessageActionRowComponentBuilder>
|
||||
| ContainerBuilder
|
||||
| FileBuilder
|
||||
| MediaGalleryBuilder
|
||||
| MessageActionRowComponentBuilder
|
||||
| SectionBuilder
|
||||
| SeparatorBuilder
|
||||
| TextDisplayBuilder
|
||||
| ThumbnailBuilder;
|
||||
|
||||
/**
|
||||
* Components here are mapped to their respective builder.
|
||||
@@ -23,33 +46,65 @@ export interface MappedComponentTypes {
|
||||
*/
|
||||
[ComponentType.ActionRow]: ActionRowBuilder<AnyComponentBuilder>;
|
||||
/**
|
||||
* The button component type is associated with an {@link ButtonBuilder}.
|
||||
* The button component type is associated with a {@link ButtonBuilder}.
|
||||
*/
|
||||
[ComponentType.Button]: ButtonBuilder;
|
||||
/**
|
||||
* The string select component type is associated with an {@link StringSelectMenuBuilder}.
|
||||
* The string select component type is associated with a {@link StringSelectMenuBuilder}.
|
||||
*/
|
||||
[ComponentType.StringSelect]: StringSelectMenuBuilder;
|
||||
/**
|
||||
* The text inpiut component type is associated with an {@link TextInputBuilder}.
|
||||
* The text input component type is associated with a {@link TextInputBuilder}.
|
||||
*/
|
||||
[ComponentType.TextInput]: TextInputBuilder;
|
||||
/**
|
||||
* The user select component type is associated with an {@link UserSelectMenuBuilder}.
|
||||
* The user select component type is associated with a {@link UserSelectMenuBuilder}.
|
||||
*/
|
||||
[ComponentType.UserSelect]: UserSelectMenuBuilder;
|
||||
/**
|
||||
* The role select component type is associated with an {@link RoleSelectMenuBuilder}.
|
||||
* The role select component type is associated with a {@link RoleSelectMenuBuilder}.
|
||||
*/
|
||||
[ComponentType.RoleSelect]: RoleSelectMenuBuilder;
|
||||
/**
|
||||
* The mentionable select component type is associated with an {@link MentionableSelectMenuBuilder}.
|
||||
* The mentionable select component type is associated with a {@link MentionableSelectMenuBuilder}.
|
||||
*/
|
||||
[ComponentType.MentionableSelect]: MentionableSelectMenuBuilder;
|
||||
/**
|
||||
* The channel select component type is associated with an {@link ChannelSelectMenuBuilder}.
|
||||
* The channel select component type is associated with a {@link ChannelSelectMenuBuilder}.
|
||||
*/
|
||||
[ComponentType.ChannelSelect]: ChannelSelectMenuBuilder;
|
||||
/**
|
||||
* The file component type is associated with a {@link FileBuilder}.
|
||||
*/
|
||||
[ComponentType.File]: FileBuilder;
|
||||
/**
|
||||
* The separator component type is associated with a {@link SeparatorBuilder}.
|
||||
*/
|
||||
[ComponentType.Separator]: SeparatorBuilder;
|
||||
/**
|
||||
* The container component type is associated with a {@link ContainerBuilder}.
|
||||
*/
|
||||
[ComponentType.Container]: ContainerBuilder;
|
||||
/**
|
||||
* The text display component type is associated with a {@link TextDisplayBuilder}.
|
||||
*/
|
||||
[ComponentType.TextDisplay]: TextDisplayBuilder;
|
||||
/**
|
||||
* The thumbnail component type is associated with a {@link ThumbnailBuilder}.
|
||||
*/
|
||||
[ComponentType.Thumbnail]: ThumbnailBuilder;
|
||||
/**
|
||||
* The section component type is associated with a {@link SectionBuilder}.
|
||||
*/
|
||||
[ComponentType.Section]: SectionBuilder;
|
||||
/**
|
||||
* The media gallery component type is associated with a {@link MediaGalleryBuilder}.
|
||||
*/
|
||||
[ComponentType.MediaGallery]: MediaGalleryBuilder;
|
||||
/**
|
||||
* The label component type is associated with a {@link LabelBuilder}.
|
||||
*/
|
||||
[ComponentType.Label]: LabelBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,8 +152,46 @@ export function createComponentBuilder(
|
||||
return new MentionableSelectMenuBuilder(data);
|
||||
case ComponentType.ChannelSelect:
|
||||
return new ChannelSelectMenuBuilder(data);
|
||||
case ComponentType.File:
|
||||
return new FileBuilder(data);
|
||||
case ComponentType.Container:
|
||||
return new ContainerBuilder(data);
|
||||
case ComponentType.Section:
|
||||
return new SectionBuilder(data);
|
||||
case ComponentType.Separator:
|
||||
return new SeparatorBuilder(data);
|
||||
case ComponentType.TextDisplay:
|
||||
return new TextDisplayBuilder(data);
|
||||
case ComponentType.Thumbnail:
|
||||
return new ThumbnailBuilder(data);
|
||||
case ComponentType.MediaGallery:
|
||||
return new MediaGalleryBuilder(data);
|
||||
case ComponentType.Label:
|
||||
return new LabelBuilder(data);
|
||||
default:
|
||||
// @ts-expect-error This case can still occur if we get a newer unsupported component type
|
||||
throw new Error(`Cannot properly serialize component type: ${data.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function isBuilder<Builder extends JSONEncodable<any>>(
|
||||
builder: unknown,
|
||||
Constructor: new () => Builder,
|
||||
): builder is Builder {
|
||||
return builder instanceof Constructor;
|
||||
}
|
||||
|
||||
export function resolveBuilder<ComponentType extends Record<PropertyKey, any>, Builder extends JSONEncodable<any>>(
|
||||
builder: Builder | ComponentType | ((builder: Builder) => Builder),
|
||||
Constructor: new (data?: ComponentType) => Builder,
|
||||
) {
|
||||
if (isBuilder(builder, Constructor)) {
|
||||
return builder;
|
||||
}
|
||||
|
||||
if (typeof builder === 'function') {
|
||||
return builder(new Constructor());
|
||||
}
|
||||
|
||||
return new Constructor(builder);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {
|
||||
ComponentType,
|
||||
type APIMessageComponentEmoji,
|
||||
type APIButtonComponent,
|
||||
type APIButtonComponentWithURL,
|
||||
type APIButtonComponentWithCustomId,
|
||||
type APIButtonComponentWithSKUId,
|
||||
type APIButtonComponentWithURL,
|
||||
type APIMessageComponentEmoji,
|
||||
type ButtonStyle,
|
||||
type Snowflake,
|
||||
} from 'discord-api-types/v10';
|
||||
import {
|
||||
buttonLabelValidator,
|
||||
@@ -88,13 +90,24 @@ export class ButtonBuilder extends ComponentBuilder<APIButtonComponent> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SKU id that represents a purchasable SKU for this button.
|
||||
*
|
||||
* @remarks Only available when using premium-style buttons.
|
||||
* @param skuId - The SKU id to use
|
||||
*/
|
||||
public setSKUId(skuId: Snowflake) {
|
||||
(this.data as APIButtonComponentWithSKUId).sku_id = skuId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the emoji to display on this button.
|
||||
*
|
||||
* @param emoji - The emoji to use
|
||||
*/
|
||||
public setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
this.data.emoji = emojiValidator.parse(emoji);
|
||||
(this.data as Exclude<APIButtonComponent, APIButtonComponentWithSKUId>).emoji = emojiValidator.parse(emoji);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -114,7 +127,7 @@ export class ButtonBuilder extends ComponentBuilder<APIButtonComponent> {
|
||||
* @param label - The label to use
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this.data.label = buttonLabelValidator.parse(label);
|
||||
(this.data as Exclude<APIButtonComponent, APIButtonComponentWithSKUId>).label = buttonLabelValidator.parse(label);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -124,9 +137,10 @@ export class ButtonBuilder extends ComponentBuilder<APIButtonComponent> {
|
||||
public toJSON(): APIButtonComponent {
|
||||
validateRequiredButtonParameters(
|
||||
this.data.style,
|
||||
this.data.label,
|
||||
this.data.emoji,
|
||||
(this.data as Exclude<APIButtonComponent, APIButtonComponentWithSKUId>).label,
|
||||
(this.data as Exclude<APIButtonComponent, APIButtonComponentWithSKUId>).emoji,
|
||||
(this.data as APIButtonComponentWithCustomId).custom_id,
|
||||
(this.data as APIButtonComponentWithSKUId).sku_id,
|
||||
(this.data as APIButtonComponentWithURL).url,
|
||||
);
|
||||
|
||||
|
||||
29
packages/builders/src/components/label/Assertions.ts
Normal file
29
packages/builders/src/components/label/Assertions.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { isValidationEnabled } from '../../util/validation.js';
|
||||
import { idValidator } from '../Assertions.js';
|
||||
import {
|
||||
selectMenuChannelPredicate,
|
||||
selectMenuMentionablePredicate,
|
||||
selectMenuRolePredicate,
|
||||
selectMenuStringPredicate,
|
||||
selectMenuUserPredicate,
|
||||
} from '../selectMenu/Assertions.js';
|
||||
import { textInputPredicate } from '../textInput/Assertions.js';
|
||||
|
||||
export const labelPredicate = s
|
||||
.object({
|
||||
id: idValidator.optional(),
|
||||
type: s.literal(ComponentType.Label),
|
||||
label: s.string().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(45),
|
||||
description: s.string().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100).optional(),
|
||||
component: s.union([
|
||||
textInputPredicate,
|
||||
selectMenuUserPredicate,
|
||||
selectMenuRolePredicate,
|
||||
selectMenuMentionablePredicate,
|
||||
selectMenuChannelPredicate,
|
||||
selectMenuStringPredicate,
|
||||
]),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
198
packages/builders/src/components/label/Label.ts
Normal file
198
packages/builders/src/components/label/Label.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import type {
|
||||
APIChannelSelectComponent,
|
||||
APILabelComponent,
|
||||
APIMentionableSelectComponent,
|
||||
APIRoleSelectComponent,
|
||||
APIStringSelectComponent,
|
||||
APITextInputComponent,
|
||||
APIUserSelectComponent,
|
||||
} from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { ComponentBuilder } from '../Component.js';
|
||||
import { createComponentBuilder, resolveBuilder } from '../Components.js';
|
||||
import { ChannelSelectMenuBuilder } from '../selectMenu/ChannelSelectMenu.js';
|
||||
import { MentionableSelectMenuBuilder } from '../selectMenu/MentionableSelectMenu.js';
|
||||
import { RoleSelectMenuBuilder } from '../selectMenu/RoleSelectMenu.js';
|
||||
import { StringSelectMenuBuilder } from '../selectMenu/StringSelectMenu.js';
|
||||
import { UserSelectMenuBuilder } from '../selectMenu/UserSelectMenu.js';
|
||||
import { TextInputBuilder } from '../textInput/TextInput.js';
|
||||
import { labelPredicate } from './Assertions.js';
|
||||
|
||||
export interface LabelBuilderData extends Partial<Omit<APILabelComponent, 'component'>> {
|
||||
component?:
|
||||
| ChannelSelectMenuBuilder
|
||||
| MentionableSelectMenuBuilder
|
||||
| RoleSelectMenuBuilder
|
||||
| StringSelectMenuBuilder
|
||||
| TextInputBuilder
|
||||
| UserSelectMenuBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder that creates API-compatible JSON data for labels.
|
||||
*/
|
||||
export class LabelBuilder extends ComponentBuilder<LabelBuilderData> {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public override readonly data: LabelBuilderData;
|
||||
|
||||
/**
|
||||
* Creates a new label.
|
||||
*
|
||||
* @param data - The API data to create this label with
|
||||
* @example
|
||||
* Creating a label from an API data object:
|
||||
* ```ts
|
||||
* const label = new LabelBuilder({
|
||||
* label: "label",
|
||||
* component,
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a label using setters and API data:
|
||||
* ```ts
|
||||
* const label = new LabelBuilder({
|
||||
* label: 'label',
|
||||
* component,
|
||||
* }).setLabel('new text');
|
||||
* ```
|
||||
*/
|
||||
public constructor(data: Partial<APILabelComponent> = {}) {
|
||||
super({ type: ComponentType.Label });
|
||||
|
||||
const { component, ...rest } = data;
|
||||
|
||||
this.data = {
|
||||
...rest,
|
||||
component: component ? createComponentBuilder(component) : undefined,
|
||||
type: ComponentType.Label,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label for this label.
|
||||
*
|
||||
* @param label - The label to use
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this.data.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description for this label.
|
||||
*
|
||||
* @param description - The description to use
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
this.data.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the description for this label.
|
||||
*/
|
||||
public clearDescription() {
|
||||
this.data.description = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a string select menu component to this label.
|
||||
*
|
||||
* @param input - A function that returns a component builder or an already built builder
|
||||
*/
|
||||
public setStringSelectMenuComponent(
|
||||
input:
|
||||
| APIStringSelectComponent
|
||||
| StringSelectMenuBuilder
|
||||
| ((builder: StringSelectMenuBuilder) => StringSelectMenuBuilder),
|
||||
): this {
|
||||
this.data.component = resolveBuilder(input, StringSelectMenuBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a user select menu component to this label.
|
||||
*
|
||||
* @param input - A function that returns a component builder or an already built builder
|
||||
*/
|
||||
public setUserSelectMenuComponent(
|
||||
input: APIUserSelectComponent | UserSelectMenuBuilder | ((builder: UserSelectMenuBuilder) => UserSelectMenuBuilder),
|
||||
): this {
|
||||
this.data.component = resolveBuilder(input, UserSelectMenuBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a role select menu component to this label.
|
||||
*
|
||||
* @param input - A function that returns a component builder or an already built builder
|
||||
*/
|
||||
public setRoleSelectMenuComponent(
|
||||
input: APIRoleSelectComponent | RoleSelectMenuBuilder | ((builder: RoleSelectMenuBuilder) => RoleSelectMenuBuilder),
|
||||
): this {
|
||||
this.data.component = resolveBuilder(input, RoleSelectMenuBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a mentionable select menu component to this label.
|
||||
*
|
||||
* @param input - A function that returns a component builder or an already built builder
|
||||
*/
|
||||
public setMentionableSelectMenuComponent(
|
||||
input:
|
||||
| APIMentionableSelectComponent
|
||||
| MentionableSelectMenuBuilder
|
||||
| ((builder: MentionableSelectMenuBuilder) => MentionableSelectMenuBuilder),
|
||||
): this {
|
||||
this.data.component = resolveBuilder(input, MentionableSelectMenuBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a channel select menu component to this label.
|
||||
*
|
||||
* @param input - A function that returns a component builder or an already built builder
|
||||
*/
|
||||
public setChannelSelectMenuComponent(
|
||||
input:
|
||||
| APIChannelSelectComponent
|
||||
| ChannelSelectMenuBuilder
|
||||
| ((builder: ChannelSelectMenuBuilder) => ChannelSelectMenuBuilder),
|
||||
): this {
|
||||
this.data.component = resolveBuilder(input, ChannelSelectMenuBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a text input component to this label.
|
||||
*
|
||||
* @param input - A function that returns a component builder or an already built builder
|
||||
*/
|
||||
public setTextInputComponent(
|
||||
input: APITextInputComponent | TextInputBuilder | ((builder: TextInputBuilder) => TextInputBuilder),
|
||||
): this {
|
||||
this.data.component = resolveBuilder(input, TextInputBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public override toJSON(): APILabelComponent {
|
||||
const { component, ...rest } = this.data;
|
||||
|
||||
const data = {
|
||||
...rest,
|
||||
// The label predicate validates the component.
|
||||
component: component?.toJSON(),
|
||||
};
|
||||
|
||||
labelPredicate.parse(data);
|
||||
|
||||
return data as APILabelComponent;
|
||||
}
|
||||
}
|
||||
92
packages/builders/src/components/selectMenu/Assertions.ts
Normal file
92
packages/builders/src/components/selectMenu/Assertions.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Result, s } from '@sapphire/shapeshift';
|
||||
import { ChannelType, ComponentType, SelectMenuDefaultValueType } from 'discord-api-types/v10';
|
||||
import { isValidationEnabled } from '../../util/validation.js';
|
||||
import { customIdValidator, emojiValidator, idValidator } from '../Assertions.js';
|
||||
import { labelValidator } from '../textInput/Assertions.js';
|
||||
|
||||
const selectMenuBasePredicate = s.object({
|
||||
id: idValidator.optional(),
|
||||
placeholder: s.string().lengthLessThanOrEqual(150).optional(),
|
||||
min_values: s.number().greaterThanOrEqual(0).lessThanOrEqual(25).optional(),
|
||||
max_values: s.number().greaterThanOrEqual(0).lessThanOrEqual(25).optional(),
|
||||
custom_id: customIdValidator,
|
||||
disabled: s.boolean().optional(),
|
||||
});
|
||||
|
||||
export const selectMenuChannelPredicate = selectMenuBasePredicate
|
||||
.extend({
|
||||
type: s.literal(ComponentType.ChannelSelect),
|
||||
channel_types: s.nativeEnum(ChannelType).array().optional(),
|
||||
default_values: s
|
||||
.object({ id: s.string(), type: s.literal(SelectMenuDefaultValueType.Channel) })
|
||||
.array()
|
||||
.lengthLessThanOrEqual(25)
|
||||
.optional(),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const selectMenuMentionablePredicate = selectMenuBasePredicate
|
||||
.extend({
|
||||
type: s.literal(ComponentType.MentionableSelect),
|
||||
default_values: s
|
||||
.object({
|
||||
id: s.string(),
|
||||
type: s.literal([SelectMenuDefaultValueType.Role, SelectMenuDefaultValueType.User]),
|
||||
})
|
||||
.array()
|
||||
.lengthLessThanOrEqual(25)
|
||||
.optional(),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const selectMenuRolePredicate = selectMenuBasePredicate
|
||||
.extend({
|
||||
type: s.literal(ComponentType.RoleSelect),
|
||||
default_values: s
|
||||
.object({ id: s.string(), type: s.literal(SelectMenuDefaultValueType.Role) })
|
||||
.array()
|
||||
.lengthLessThanOrEqual(25)
|
||||
.optional(),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const selectMenuUserPredicate = selectMenuBasePredicate
|
||||
.extend({
|
||||
type: s.literal(ComponentType.UserSelect),
|
||||
default_values: s
|
||||
.object({ id: s.string(), type: s.literal(SelectMenuDefaultValueType.User) })
|
||||
.array()
|
||||
.lengthLessThanOrEqual(25)
|
||||
.optional(),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const selectMenuStringOptionPredicate = s
|
||||
.object({
|
||||
label: labelValidator,
|
||||
value: s.string().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100),
|
||||
description: s.string().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100).optional(),
|
||||
emoji: emojiValidator.optional(),
|
||||
default: s.boolean().optional(),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const selectMenuStringPredicate = selectMenuBasePredicate
|
||||
.extend({
|
||||
type: s.literal(ComponentType.StringSelect),
|
||||
options: selectMenuStringOptionPredicate.array().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(25),
|
||||
})
|
||||
.reshape((value) => {
|
||||
if (value.min_values !== undefined && value.options.length < value.min_values) {
|
||||
return Result.err(new RangeError(`The number of options must be greater than or equal to min_values`));
|
||||
}
|
||||
|
||||
if (value.min_values !== undefined && value.max_values !== undefined && value.min_values > value.max_values) {
|
||||
return Result.err(
|
||||
new RangeError(`The maximum amount of options must be greater than or equal to the minimum amount of options`),
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(value);
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { APISelectMenuComponent } from 'discord-api-types/v10';
|
||||
import { customIdValidator, disabledValidator, minMaxValidator, placeholderValidator } from '../Assertions.js';
|
||||
import { ComponentBuilder } from '../Component.js';
|
||||
import { requiredValidator } from '../textInput/Assertions.js';
|
||||
|
||||
/**
|
||||
* The base select menu builder that contains common symbols for select menu builders.
|
||||
@@ -60,6 +61,17 @@ export abstract class BaseSelectMenuBuilder<
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this select menu is required.
|
||||
*
|
||||
* @remarks Only for use in modals.
|
||||
* @param required - Whether this select menu is required
|
||||
*/
|
||||
public setRequired(required = true) {
|
||||
this.data.required = requiredValidator.parse(required);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
|
||||
@@ -85,7 +85,7 @@ export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder<APIChannelSe
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default channels to this auto populated select menu.
|
||||
* Sets default channels for this auto populated select menu.
|
||||
*
|
||||
* @param channels - The channels to set
|
||||
*/
|
||||
|
||||
@@ -98,7 +98,7 @@ export class MentionableSelectMenuBuilder extends BaseSelectMenuBuilder<APIMenti
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default values to this auto populated select menu.
|
||||
* Sets default values for this auto populated select menu.
|
||||
*
|
||||
* @param values - The values to set
|
||||
*/
|
||||
|
||||
@@ -59,7 +59,7 @@ export class RoleSelectMenuBuilder extends BaseSelectMenuBuilder<APIRoleSelectCo
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default roles to this auto populated select menu.
|
||||
* Sets default roles for this auto populated select menu.
|
||||
*
|
||||
* @param roles - The roles to set
|
||||
*/
|
||||
|
||||
@@ -59,7 +59,7 @@ export class UserSelectMenuBuilder extends BaseSelectMenuBuilder<APIUserSelectCo
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default users to this auto populated select menu.
|
||||
* Sets default users for this auto populated select menu.
|
||||
*
|
||||
* @param users - The users to set
|
||||
*/
|
||||
|
||||
@@ -1,27 +1,45 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { TextInputStyle } from 'discord-api-types/v10';
|
||||
import { ComponentType, TextInputStyle } from 'discord-api-types/v10';
|
||||
import { isValidationEnabled } from '../../util/validation.js';
|
||||
import { customIdValidator } from '../Assertions.js';
|
||||
import { customIdValidator, idValidator } from '../Assertions.js';
|
||||
|
||||
export const textInputStyleValidator = s.nativeEnum(TextInputStyle);
|
||||
export const minLengthValidator = s.number.int
|
||||
export const textInputStyleValidator = s.nativeEnum(TextInputStyle).setValidationEnabled(isValidationEnabled);
|
||||
export const minLengthValidator = s
|
||||
.number()
|
||||
.int()
|
||||
.greaterThanOrEqual(0)
|
||||
.lessThanOrEqual(4_000)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
export const maxLengthValidator = s.number.int
|
||||
export const maxLengthValidator = s
|
||||
.number()
|
||||
.int()
|
||||
.greaterThanOrEqual(1)
|
||||
.lessThanOrEqual(4_000)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
export const requiredValidator = s.boolean;
|
||||
export const valueValidator = s.string.lengthLessThanOrEqual(4_000).setValidationEnabled(isValidationEnabled);
|
||||
export const placeholderValidator = s.string.lengthLessThanOrEqual(100).setValidationEnabled(isValidationEnabled);
|
||||
export const labelValidator = s.string
|
||||
export const requiredValidator = s.boolean().setValidationEnabled(isValidationEnabled);
|
||||
export const valueValidator = s.string().lengthLessThanOrEqual(4_000).setValidationEnabled(isValidationEnabled);
|
||||
export const placeholderValidator = s.string().lengthLessThanOrEqual(100).setValidationEnabled(isValidationEnabled);
|
||||
export const labelValidator = s
|
||||
.string()
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(45)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export function validateRequiredParameters(customId?: string, style?: TextInputStyle, label?: string) {
|
||||
export const textInputPredicate = s
|
||||
.object({
|
||||
type: s.literal(ComponentType.TextInput),
|
||||
custom_id: customIdValidator,
|
||||
style: textInputStyleValidator,
|
||||
id: idValidator.optional(),
|
||||
min_length: minLengthValidator.optional(),
|
||||
max_length: maxLengthValidator.optional(),
|
||||
placeholder: placeholderValidator.optional(),
|
||||
value: valueValidator.optional(),
|
||||
required: requiredValidator.optional(),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export function validateRequiredParameters(customId?: string, style?: TextInputStyle) {
|
||||
customIdValidator.parse(customId);
|
||||
textInputStyleValidator.parse(style);
|
||||
labelValidator.parse(label);
|
||||
}
|
||||
|
||||
@@ -26,19 +26,19 @@ export class TextInputBuilder
|
||||
*
|
||||
* @param data - The API data to create this text input with
|
||||
* @example
|
||||
* Creating a select menu option from an API data object:
|
||||
* Creating a text input from an API data object:
|
||||
* ```ts
|
||||
* const textInput = new TextInputBuilder({
|
||||
* custom_id: 'a cool select menu',
|
||||
* label: 'Type something',
|
||||
* custom_id: 'a cool text input',
|
||||
* placeholder: 'Type something',
|
||||
* style: TextInputStyle.Short,
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a select menu option using setters and API data:
|
||||
* Creating a text input using setters and API data:
|
||||
* ```ts
|
||||
* const textInput = new TextInputBuilder({
|
||||
* label: 'Type something else',
|
||||
* placeholder: 'Type something else',
|
||||
* })
|
||||
* .setCustomId('woah')
|
||||
* .setStyle(TextInputStyle.Paragraph);
|
||||
@@ -62,6 +62,7 @@ export class TextInputBuilder
|
||||
* Sets the label for this text input.
|
||||
*
|
||||
* @param label - The label to use
|
||||
* @deprecated Use a label builder to create a label (and optionally a description) instead.
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this.data.label = labelValidator.parse(label);
|
||||
@@ -132,7 +133,7 @@ export class TextInputBuilder
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public toJSON(): APITextInputComponent {
|
||||
validateRequiredParameters(this.data.custom_id, this.data.style, this.data.label);
|
||||
validateRequiredParameters(this.data.custom_id, this.data.style);
|
||||
|
||||
return {
|
||||
...this.data,
|
||||
@@ -140,7 +141,7 @@ export class TextInputBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc Equatable.equals}
|
||||
* Whether this is equal to another structure.
|
||||
*/
|
||||
public equals(other: APITextInputComponent | JSONEncodable<APITextInputComponent>): boolean {
|
||||
if (isJSONEncodable(other)) {
|
||||
|
||||
72
packages/builders/src/components/v2/Assertions.ts
Normal file
72
packages/builders/src/components/v2/Assertions.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { SeparatorSpacingSize } from 'discord-api-types/v10';
|
||||
import { colorPredicate } from '../../messages/embed/Assertions';
|
||||
import { isValidationEnabled } from '../../util/validation';
|
||||
import { ComponentBuilder } from '../Component';
|
||||
import { ButtonBuilder } from '../button/Button';
|
||||
import type { ContainerComponentBuilder } from './Container';
|
||||
import type { MediaGalleryItemBuilder } from './MediaGalleryItem';
|
||||
import type { TextDisplayBuilder } from './TextDisplay';
|
||||
import { ThumbnailBuilder } from './Thumbnail';
|
||||
|
||||
export const unfurledMediaItemPredicate = s
|
||||
.object({
|
||||
url: s
|
||||
.string()
|
||||
.url(
|
||||
{ allowedProtocols: ['http:', 'https:', 'attachment:'] },
|
||||
{ message: 'Invalid protocol for media URL. Must be http:, https:, or attachment:' },
|
||||
),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const descriptionPredicate = s
|
||||
.string()
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(1_024)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const filePredicate = s
|
||||
.object({
|
||||
url: s
|
||||
.string()
|
||||
.url({ allowedProtocols: ['attachment:'] }, { message: 'Invalid protocol for file URL. Must be attachment:' }),
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const spoilerPredicate = s.boolean();
|
||||
|
||||
export const dividerPredicate = s.boolean();
|
||||
|
||||
export const spacingPredicate = s.nativeEnum(SeparatorSpacingSize);
|
||||
|
||||
export const textDisplayContentPredicate = s
|
||||
.string()
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(4_000)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const accessoryPredicate = s
|
||||
.instance(ButtonBuilder)
|
||||
.or(s.instance(ThumbnailBuilder))
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const containerColorPredicate = colorPredicate.nullish();
|
||||
|
||||
export function assertReturnOfBuilder<ReturnType extends MediaGalleryItemBuilder | TextDisplayBuilder>(
|
||||
input: unknown,
|
||||
ExpectedInstanceOf: new () => ReturnType,
|
||||
): asserts input is ReturnType {
|
||||
s.instance(ExpectedInstanceOf).setValidationEnabled(isValidationEnabled).parse(input);
|
||||
}
|
||||
|
||||
export function validateComponentArray<
|
||||
ReturnType extends ContainerComponentBuilder | MediaGalleryItemBuilder = ContainerComponentBuilder,
|
||||
>(input: unknown, min: number, max: number, ExpectedInstanceOf?: new () => ReturnType): asserts input is ReturnType[] {
|
||||
(ExpectedInstanceOf ? s.instance(ExpectedInstanceOf) : s.instance(ComponentBuilder))
|
||||
.array()
|
||||
.lengthGreaterThanOrEqual(min)
|
||||
.lengthLessThanOrEqual(max)
|
||||
.setValidationEnabled(isValidationEnabled)
|
||||
.parse(input);
|
||||
}
|
||||
239
packages/builders/src/components/v2/Container.ts
Normal file
239
packages/builders/src/components/v2/Container.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
/* eslint-disable jsdoc/check-param-names */
|
||||
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIComponentInContainer,
|
||||
APIComponentInMessageActionRow,
|
||||
APIContainerComponent,
|
||||
APIFileComponent,
|
||||
APIMediaGalleryComponent,
|
||||
APISectionComponent,
|
||||
APISeparatorComponent,
|
||||
APITextDisplayComponent,
|
||||
} from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import type { RGBTuple } from '../../index.js';
|
||||
import { MediaGalleryBuilder, SectionBuilder } from '../../index.js';
|
||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||
import type { AnyComponentBuilder, MessageActionRowComponentBuilder } from '../ActionRow.js';
|
||||
import { ActionRowBuilder } from '../ActionRow.js';
|
||||
import { ComponentBuilder } from '../Component.js';
|
||||
import { createComponentBuilder, resolveBuilder } from '../Components.js';
|
||||
import { containerColorPredicate, spoilerPredicate } from './Assertions.js';
|
||||
import { FileBuilder } from './File.js';
|
||||
import { SeparatorBuilder } from './Separator.js';
|
||||
import { TextDisplayBuilder } from './TextDisplay.js';
|
||||
|
||||
/**
|
||||
* The builders that may be used within a container.
|
||||
*/
|
||||
export type ContainerComponentBuilder =
|
||||
| ActionRowBuilder<AnyComponentBuilder>
|
||||
| FileBuilder
|
||||
| MediaGalleryBuilder
|
||||
| SectionBuilder
|
||||
| SeparatorBuilder
|
||||
| TextDisplayBuilder;
|
||||
|
||||
/**
|
||||
* A builder that creates API-compatible JSON data for a container.
|
||||
*/
|
||||
export class ContainerBuilder extends ComponentBuilder<APIContainerComponent> {
|
||||
/**
|
||||
* The components within this container.
|
||||
*/
|
||||
public readonly components: ContainerComponentBuilder[];
|
||||
|
||||
/**
|
||||
* Creates a new container from API data.
|
||||
*
|
||||
* @param data - The API data to create this container with
|
||||
* @example
|
||||
* Creating a container from an API data object:
|
||||
* ```ts
|
||||
* const container = new ContainerBuilder({
|
||||
* components: [
|
||||
* {
|
||||
* content: "Some text here",
|
||||
* type: ComponentType.TextDisplay,
|
||||
* },
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a container using setters and API data:
|
||||
* ```ts
|
||||
* const container = new ContainerBuilder({
|
||||
* components: [
|
||||
* {
|
||||
* content: "# Heading",
|
||||
* type: ComponentType.TextDisplay,
|
||||
* },
|
||||
* ],
|
||||
* })
|
||||
* .addComponents(separator, section);
|
||||
* ```
|
||||
*/
|
||||
public constructor({ components, ...data }: Partial<APIContainerComponent> = {}) {
|
||||
super({ type: ComponentType.Container, ...data });
|
||||
this.components = (components?.map((component) => createComponentBuilder(component)) ??
|
||||
[]) as ContainerComponentBuilder[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the accent color of this container.
|
||||
*
|
||||
* @param color - The color to use
|
||||
*/
|
||||
public setAccentColor(color?: RGBTuple | number): this {
|
||||
// Data assertions
|
||||
containerColorPredicate.parse(color);
|
||||
|
||||
if (Array.isArray(color)) {
|
||||
const [red, green, blue] = color;
|
||||
this.data.accent_color = (red << 16) + (green << 8) + blue;
|
||||
return this;
|
||||
}
|
||||
|
||||
this.data.accent_color = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the accent color of this container.
|
||||
*/
|
||||
public clearAccentColor() {
|
||||
this.data.accent_color = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds action row components to this container.
|
||||
*
|
||||
* @param components - The action row components to add
|
||||
*/
|
||||
public addActionRowComponents<ComponentType extends MessageActionRowComponentBuilder>(
|
||||
...components: RestOrArray<
|
||||
| ActionRowBuilder<ComponentType>
|
||||
| APIActionRowComponent<APIComponentInMessageActionRow>
|
||||
| ((builder: ActionRowBuilder<ComponentType>) => ActionRowBuilder<ComponentType>)
|
||||
>
|
||||
) {
|
||||
this.components.push(
|
||||
...normalizeArray(components).map((component) => resolveBuilder(component, ActionRowBuilder<ComponentType>)),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds file components to this container.
|
||||
*
|
||||
* @param components - The file components to add
|
||||
*/
|
||||
public addFileComponents(
|
||||
...components: RestOrArray<APIFileComponent | FileBuilder | ((builder: FileBuilder) => FileBuilder)>
|
||||
) {
|
||||
this.components.push(...normalizeArray(components).map((component) => resolveBuilder(component, FileBuilder)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds media gallery components to this container.
|
||||
*
|
||||
* @param components - The media gallery components to add
|
||||
*/
|
||||
public addMediaGalleryComponents(
|
||||
...components: RestOrArray<
|
||||
APIMediaGalleryComponent | MediaGalleryBuilder | ((builder: MediaGalleryBuilder) => MediaGalleryBuilder)
|
||||
>
|
||||
) {
|
||||
this.components.push(
|
||||
...normalizeArray(components).map((component) => resolveBuilder(component, MediaGalleryBuilder)),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds section components to this container.
|
||||
*
|
||||
* @param components - The section components to add
|
||||
*/
|
||||
public addSectionComponents(
|
||||
...components: RestOrArray<APISectionComponent | SectionBuilder | ((builder: SectionBuilder) => SectionBuilder)>
|
||||
) {
|
||||
this.components.push(...normalizeArray(components).map((component) => resolveBuilder(component, SectionBuilder)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds separator components to this container.
|
||||
*
|
||||
* @param components - The separator components to add
|
||||
*/
|
||||
public addSeparatorComponents(
|
||||
...components: RestOrArray<
|
||||
APISeparatorComponent | SeparatorBuilder | ((builder: SeparatorBuilder) => SeparatorBuilder)
|
||||
>
|
||||
) {
|
||||
this.components.push(...normalizeArray(components).map((component) => resolveBuilder(component, SeparatorBuilder)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds text display components to this container.
|
||||
*
|
||||
* @param components - The text display components to add
|
||||
*/
|
||||
public addTextDisplayComponents(
|
||||
...components: RestOrArray<
|
||||
APITextDisplayComponent | TextDisplayBuilder | ((builder: TextDisplayBuilder) => TextDisplayBuilder)
|
||||
>
|
||||
) {
|
||||
this.components.push(
|
||||
...normalizeArray(components).map((component) => resolveBuilder(component, TextDisplayBuilder)),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, or inserts components for this container.
|
||||
*
|
||||
* @param index - The index to start removing, replacing or inserting components
|
||||
* @param deleteCount - The amount of components to remove
|
||||
* @param components - The components to set
|
||||
*/
|
||||
public spliceComponents(
|
||||
index: number,
|
||||
deleteCount: number,
|
||||
...components: RestOrArray<APIComponentInContainer | ContainerComponentBuilder>
|
||||
) {
|
||||
this.components.splice(
|
||||
index,
|
||||
deleteCount,
|
||||
...normalizeArray(components).map((component) =>
|
||||
component instanceof ComponentBuilder ? component : createComponentBuilder(component),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the spoiler status of this container.
|
||||
*
|
||||
* @param spoiler - The spoiler status to use
|
||||
*/
|
||||
public setSpoiler(spoiler = true) {
|
||||
this.data.spoiler = spoilerPredicate.parse(spoiler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public toJSON(): APIContainerComponent {
|
||||
return {
|
||||
...this.data,
|
||||
components: this.components.map((component) => component.toJSON()),
|
||||
} as APIContainerComponent;
|
||||
}
|
||||
}
|
||||
63
packages/builders/src/components/v2/File.ts
Normal file
63
packages/builders/src/components/v2/File.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { ComponentType, type APIFileComponent } from 'discord-api-types/v10';
|
||||
import { ComponentBuilder } from '../Component';
|
||||
import { filePredicate, spoilerPredicate } from './Assertions';
|
||||
|
||||
export class FileBuilder extends ComponentBuilder<APIFileComponent> {
|
||||
/**
|
||||
* Creates a new file from API data.
|
||||
*
|
||||
* @param data - The API data to create this file with
|
||||
* @example
|
||||
* Creating a file from an API data object:
|
||||
* ```ts
|
||||
* const file = new FileBuilder({
|
||||
* spoiler: true,
|
||||
* file: {
|
||||
* url: 'attachment://file.png',
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a file using setters and API data:
|
||||
* ```ts
|
||||
* const file = new FileBuilder({
|
||||
* file: {
|
||||
* url: 'attachment://image.jpg',
|
||||
* },
|
||||
* })
|
||||
* .setSpoiler(false);
|
||||
* ```
|
||||
*/
|
||||
public constructor(data: Partial<APIFileComponent> = {}) {
|
||||
super({ type: ComponentType.File, ...data, file: data.file ? { url: data.file.url } : undefined });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the spoiler status of this file.
|
||||
*
|
||||
* @param spoiler - The spoiler status to use
|
||||
*/
|
||||
public setSpoiler(spoiler = true) {
|
||||
this.data.spoiler = spoilerPredicate.parse(spoiler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media URL of this file.
|
||||
*
|
||||
* @param url - The URL to use
|
||||
*/
|
||||
public setURL(url: string) {
|
||||
this.data.file = filePredicate.parse({ url });
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public override toJSON(): APIFileComponent {
|
||||
filePredicate.parse(this.data.file);
|
||||
|
||||
return { ...this.data, file: { ...this.data.file } } as APIFileComponent;
|
||||
}
|
||||
}
|
||||
117
packages/builders/src/components/v2/MediaGallery.ts
Normal file
117
packages/builders/src/components/v2/MediaGallery.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/* eslint-disable jsdoc/check-param-names */
|
||||
|
||||
import type { APIMediaGalleryComponent, APIMediaGalleryItem } from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||
import { ComponentBuilder } from '../Component.js';
|
||||
import { resolveBuilder } from '../Components.js';
|
||||
import { assertReturnOfBuilder, validateComponentArray } from './Assertions.js';
|
||||
import { MediaGalleryItemBuilder } from './MediaGalleryItem.js';
|
||||
|
||||
/**
|
||||
* A builder that creates API-compatible JSON data for a container.
|
||||
*/
|
||||
export class MediaGalleryBuilder extends ComponentBuilder<APIMediaGalleryComponent> {
|
||||
/**
|
||||
* The components within this container.
|
||||
*/
|
||||
public readonly items: MediaGalleryItemBuilder[];
|
||||
|
||||
/**
|
||||
* Creates a new media gallery from API data.
|
||||
*
|
||||
* @param data - The API data to create this media gallery with
|
||||
* @example
|
||||
* Creating a media gallery from an API data object:
|
||||
* ```ts
|
||||
* const mediaGallery = new MediaGalleryBuilder({
|
||||
* items: [
|
||||
* {
|
||||
* description: "Some text here",
|
||||
* media: {
|
||||
* url: 'https://cdn.discordapp.com/embed/avatars/2.png',
|
||||
* },
|
||||
* },
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a media gallery using setters and API data:
|
||||
* ```ts
|
||||
* const mediaGallery = new MediaGalleryBuilder({
|
||||
* items: [
|
||||
* {
|
||||
* description: "alt text",
|
||||
* media: {
|
||||
* url: 'https://cdn.discordapp.com/embed/avatars/5.png',
|
||||
* },
|
||||
* },
|
||||
* ],
|
||||
* })
|
||||
* .addItems(item2, item3);
|
||||
* ```
|
||||
*/
|
||||
public constructor({ items, ...data }: Partial<APIMediaGalleryComponent> = {}) {
|
||||
super({ type: ComponentType.MediaGallery, ...data });
|
||||
this.items = items?.map((item) => new MediaGalleryItemBuilder(item)) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds items to this media gallery.
|
||||
*
|
||||
* @param items - The items to add
|
||||
*/
|
||||
public addItems(
|
||||
...items: RestOrArray<
|
||||
APIMediaGalleryItem | MediaGalleryItemBuilder | ((builder: MediaGalleryItemBuilder) => MediaGalleryItemBuilder)
|
||||
>
|
||||
) {
|
||||
this.items.push(
|
||||
...normalizeArray(items).map((input) => {
|
||||
const result = resolveBuilder(input, MediaGalleryItemBuilder);
|
||||
|
||||
assertReturnOfBuilder(result, MediaGalleryItemBuilder);
|
||||
return result;
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, or inserts media gallery items for this media gallery.
|
||||
*
|
||||
* @param index - The index to start removing, replacing or inserting items
|
||||
* @param deleteCount - The amount of items to remove
|
||||
* @param items - The items to insert
|
||||
*/
|
||||
public spliceItems(
|
||||
index: number,
|
||||
deleteCount: number,
|
||||
...items: RestOrArray<
|
||||
APIMediaGalleryItem | MediaGalleryItemBuilder | ((builder: MediaGalleryItemBuilder) => MediaGalleryItemBuilder)
|
||||
>
|
||||
) {
|
||||
this.items.splice(
|
||||
index,
|
||||
deleteCount,
|
||||
...normalizeArray(items).map((input) => {
|
||||
const result = resolveBuilder(input, MediaGalleryItemBuilder);
|
||||
|
||||
assertReturnOfBuilder(result, MediaGalleryItemBuilder);
|
||||
return result;
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public toJSON(): APIMediaGalleryComponent {
|
||||
validateComponentArray(this.items, 1, 10, MediaGalleryItemBuilder);
|
||||
return {
|
||||
...this.data,
|
||||
items: this.items.map((item) => item.toJSON()),
|
||||
} as APIMediaGalleryComponent;
|
||||
}
|
||||
}
|
||||
90
packages/builders/src/components/v2/MediaGalleryItem.ts
Normal file
90
packages/builders/src/components/v2/MediaGalleryItem.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import type { JSONEncodable } from '@discordjs/util';
|
||||
import type { APIMediaGalleryItem } from 'discord-api-types/v10';
|
||||
import { descriptionPredicate, spoilerPredicate, unfurledMediaItemPredicate } from './Assertions';
|
||||
|
||||
export class MediaGalleryItemBuilder implements JSONEncodable<APIMediaGalleryItem> {
|
||||
/**
|
||||
* The API data associated with this media gallery item.
|
||||
*/
|
||||
public readonly data: Partial<APIMediaGalleryItem>;
|
||||
|
||||
/**
|
||||
* Creates a new media gallery item from API data.
|
||||
*
|
||||
* @param data - The API data to create this media gallery item with
|
||||
* @example
|
||||
* Creating a media gallery item from an API data object:
|
||||
* ```ts
|
||||
* const item = new MediaGalleryItemBuilder({
|
||||
* description: "Some text here",
|
||||
* media: {
|
||||
* url: 'https://cdn.discordapp.com/embed/avatars/2.png',
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a media gallery item using setters and API data:
|
||||
* ```ts
|
||||
* const item = new MediaGalleryItemBuilder({
|
||||
* media: {
|
||||
* url: 'https://cdn.discordapp.com/embed/avatars/5.png',
|
||||
* },
|
||||
* })
|
||||
* .setDescription("alt text");
|
||||
* ```
|
||||
*/
|
||||
public constructor(data: Partial<APIMediaGalleryItem> = {}) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this media gallery item.
|
||||
*
|
||||
* @param description - The description to use
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
this.data.description = descriptionPredicate.parse(description);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the description of this media gallery item.
|
||||
*/
|
||||
public clearDescription() {
|
||||
this.data.description = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the spoiler status of this media gallery item.
|
||||
*
|
||||
* @param spoiler - The spoiler status to use
|
||||
*/
|
||||
public setSpoiler(spoiler = true) {
|
||||
this.data.spoiler = spoilerPredicate.parse(spoiler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media URL of this media gallery item.
|
||||
*
|
||||
* @param url - The URL to use
|
||||
*/
|
||||
public setURL(url: string) {
|
||||
this.data.media = unfurledMediaItemPredicate.parse({ url });
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this builder to API-compatible JSON data.
|
||||
*
|
||||
* @remarks
|
||||
* This method runs validations on the data before serializing it.
|
||||
* As such, it may throw an error if the data is invalid.
|
||||
*/
|
||||
public toJSON(): APIMediaGalleryItem {
|
||||
unfurledMediaItemPredicate.parse(this.data.media);
|
||||
|
||||
return { ...this.data } as APIMediaGalleryItem;
|
||||
}
|
||||
}
|
||||
153
packages/builders/src/components/v2/Section.ts
Normal file
153
packages/builders/src/components/v2/Section.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
/* eslint-disable jsdoc/check-param-names */
|
||||
|
||||
import type {
|
||||
APIButtonComponent,
|
||||
APISectionComponent,
|
||||
APITextDisplayComponent,
|
||||
APIThumbnailComponent,
|
||||
} from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { ButtonBuilder, ThumbnailBuilder } from '../../index.js';
|
||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
|
||||
import { ComponentBuilder } from '../Component.js';
|
||||
import { createComponentBuilder, resolveBuilder } from '../Components.js';
|
||||
import { accessoryPredicate, assertReturnOfBuilder, validateComponentArray } from './Assertions.js';
|
||||
import { TextDisplayBuilder } from './TextDisplay.js';
|
||||
|
||||
/**
|
||||
* A builder that creates API-compatible JSON data for a section.
|
||||
*/
|
||||
export class SectionBuilder extends ComponentBuilder<APISectionComponent> {
|
||||
/**
|
||||
* The components within this section.
|
||||
*/
|
||||
public readonly components: ComponentBuilder[];
|
||||
|
||||
/**
|
||||
* The accessory of this section.
|
||||
*/
|
||||
public readonly accessory?: ButtonBuilder | ThumbnailBuilder;
|
||||
|
||||
/**
|
||||
* Creates a new section from API data.
|
||||
*
|
||||
* @param data - The API data to create this section with
|
||||
* @example
|
||||
* Creating a section from an API data object:
|
||||
* ```ts
|
||||
* const section = new SectionBuilder({
|
||||
* components: [
|
||||
* {
|
||||
* content: "Some text here",
|
||||
* type: ComponentType.TextDisplay,
|
||||
* },
|
||||
* ],
|
||||
* accessory: {
|
||||
* media: {
|
||||
* url: 'https://cdn.discordapp.com/embed/avatars/3.png',
|
||||
* },
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a section using setters and API data:
|
||||
* ```ts
|
||||
* const section = new SectionBuilder({
|
||||
* components: [
|
||||
* {
|
||||
* content: "# Heading",
|
||||
* type: ComponentType.TextDisplay,
|
||||
* },
|
||||
* ],
|
||||
* })
|
||||
* .setPrimaryButtonAccessory(button);
|
||||
* ```
|
||||
*/
|
||||
public constructor({ components, accessory, ...data }: Partial<APISectionComponent> = {}) {
|
||||
super({ type: ComponentType.Section, ...data });
|
||||
this.components = (components?.map((component) => createComponentBuilder(component)) ?? []) as ComponentBuilder[];
|
||||
this.accessory = accessory ? createComponentBuilder(accessory) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the accessory of this section to a button.
|
||||
*
|
||||
* @param accessory - The accessory to use
|
||||
*/
|
||||
public setButtonAccessory(
|
||||
accessory: APIButtonComponent | ButtonBuilder | ((builder: ButtonBuilder) => ButtonBuilder),
|
||||
): this {
|
||||
Reflect.set(this, 'accessory', accessoryPredicate.parse(resolveBuilder(accessory, ButtonBuilder)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the accessory of this section to a thumbnail.
|
||||
*
|
||||
* @param accessory - The accessory to use
|
||||
*/
|
||||
public setThumbnailAccessory(
|
||||
accessory: APIThumbnailComponent | ThumbnailBuilder | ((builder: ThumbnailBuilder) => ThumbnailBuilder),
|
||||
): this {
|
||||
Reflect.set(this, 'accessory', accessoryPredicate.parse(resolveBuilder(accessory, ThumbnailBuilder)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds text display components to this section.
|
||||
*
|
||||
* @param components - The text display components to add
|
||||
*/
|
||||
public addTextDisplayComponents(
|
||||
...components: RestOrArray<TextDisplayBuilder | ((builder: TextDisplayBuilder) => TextDisplayBuilder)>
|
||||
) {
|
||||
this.components.push(
|
||||
...normalizeArray(components).map((input) => {
|
||||
const result = resolveBuilder(input, TextDisplayBuilder);
|
||||
|
||||
assertReturnOfBuilder(result, TextDisplayBuilder);
|
||||
return result;
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, or inserts text display components for this section.
|
||||
*
|
||||
* @param index - The index to start removing, replacing or inserting text display components
|
||||
* @param deleteCount - The amount of text display components to remove
|
||||
* @param components - The text display components to insert
|
||||
*/
|
||||
public spliceTextDisplayComponents(
|
||||
index: number,
|
||||
deleteCount: number,
|
||||
...components: RestOrArray<
|
||||
APITextDisplayComponent | TextDisplayBuilder | ((builder: TextDisplayBuilder) => TextDisplayBuilder)
|
||||
>
|
||||
) {
|
||||
this.components.splice(
|
||||
index,
|
||||
deleteCount,
|
||||
...normalizeArray(components).map((input) => {
|
||||
const result = resolveBuilder(input, TextDisplayBuilder);
|
||||
|
||||
assertReturnOfBuilder(result, TextDisplayBuilder);
|
||||
return result;
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public toJSON(): APISectionComponent {
|
||||
validateComponentArray(this.components, 1, 3, TextDisplayBuilder);
|
||||
return {
|
||||
...this.data,
|
||||
components: this.components.map((component) => component.toJSON()),
|
||||
accessory: accessoryPredicate.parse(this.accessory).toJSON(),
|
||||
} as APISectionComponent;
|
||||
}
|
||||
}
|
||||
69
packages/builders/src/components/v2/Separator.ts
Normal file
69
packages/builders/src/components/v2/Separator.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { SeparatorSpacingSize, APISeparatorComponent } from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { ComponentBuilder } from '../Component';
|
||||
import { dividerPredicate, spacingPredicate } from './Assertions';
|
||||
|
||||
export class SeparatorBuilder extends ComponentBuilder<APISeparatorComponent> {
|
||||
/**
|
||||
* Creates a new separator from API data.
|
||||
*
|
||||
* @param data - The API data to create this separator with
|
||||
* @example
|
||||
* Creating a separator from an API data object:
|
||||
* ```ts
|
||||
* const separator = new SeparatorBuilder({
|
||||
* spacing: SeparatorSpacingSize.Small,
|
||||
* divider: true,
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a separator using setters and API data:
|
||||
* ```ts
|
||||
* const separator = new SeparatorBuilder({
|
||||
* spacing: SeparatorSpacingSize.Large,
|
||||
* })
|
||||
* .setDivider(false);
|
||||
* ```
|
||||
*/
|
||||
public constructor(data: Partial<APISeparatorComponent> = {}) {
|
||||
super({
|
||||
type: ComponentType.Separator,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this separator should show a divider line.
|
||||
*
|
||||
* @param divider - Whether to show a divider line
|
||||
*/
|
||||
public setDivider(divider = true) {
|
||||
this.data.divider = dividerPredicate.parse(divider);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the spacing of this separator.
|
||||
*
|
||||
* @param spacing - The spacing to use
|
||||
*/
|
||||
public setSpacing(spacing: SeparatorSpacingSize) {
|
||||
this.data.spacing = spacingPredicate.parse(spacing);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the spacing of this separator.
|
||||
*/
|
||||
public clearSpacing() {
|
||||
this.data.spacing = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public override toJSON(): APISeparatorComponent {
|
||||
return { ...this.data } as APISeparatorComponent;
|
||||
}
|
||||
}
|
||||
52
packages/builders/src/components/v2/TextDisplay.ts
Normal file
52
packages/builders/src/components/v2/TextDisplay.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { APITextDisplayComponent } from 'discord-api-types/v10';
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { ComponentBuilder } from '../Component';
|
||||
import { textDisplayContentPredicate } from './Assertions';
|
||||
|
||||
export class TextDisplayBuilder extends ComponentBuilder<APITextDisplayComponent> {
|
||||
/**
|
||||
* Creates a new text display from API data.
|
||||
*
|
||||
* @param data - The API data to create this text display with
|
||||
* @example
|
||||
* Creating a text display from an API data object:
|
||||
* ```ts
|
||||
* const textDisplay = new TextDisplayBuilder({
|
||||
* content: 'some text',
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Creating a text display using setters and API data:
|
||||
* ```ts
|
||||
* const textDisplay = new TextDisplayBuilder({
|
||||
* content: 'old text',
|
||||
* })
|
||||
* .setContent('new text');
|
||||
* ```
|
||||
*/
|
||||
public constructor(data: Partial<APITextDisplayComponent> = {}) {
|
||||
super({
|
||||
type: ComponentType.TextDisplay,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text of this text display.
|
||||
*
|
||||
* @param content - The text to use
|
||||
*/
|
||||
public setContent(content: string) {
|
||||
this.data.content = textDisplayContentPredicate.parse(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ComponentBuilder.toJSON}
|
||||
*/
|
||||
public override toJSON(): APITextDisplayComponent {
|
||||
textDisplayContentPredicate.parse(this.data.content);
|
||||
|
||||
return { ...this.data } as APITextDisplayComponent;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user