Compare commits

..

45 Commits

Author SHA1 Message Date
Vlad Frangu
0830fe8dbc chore(ws): release @discordjs/ws@1.2.3 2025-06-16 15:24:19 +03:00
Vlad Frangu
30ab200c06 chore: bump rest 2025-06-16 15:23:39 +03:00
Jiralite
dac50b345b build: Update Undici to 6.21.3 (#10907)
build: update Undici to 6.21.3
2025-05-20 11:52:41 +01:00
Jiralite
4ccc3df19f build: update zlib-sync to 0.1.10 2025-05-20 11:40:41 +01:00
Vlad Frangu
da8f08785f chore: tie the flow 2025-04-26 00:56:33 +03:00
Vlad Frangu
68282eeac4 chore: lock file 2025-04-26 00:54:44 +03:00
Vlad Frangu
cc5fbd8cc3 chore(ws): release @discordjs/ws@1.2.2 2025-04-26 00:53:36 +03:00
Vlad Frangu
25cc7282d0 chore(ws): release @discordjs/ws@1.2.1 2025-02-11 01:00:54 +02:00
Vlad Frangu
d8c10a7825 build: bump @discordjs/rest to 2.4.3 2025-02-11 00:59:53 +02:00
Jiralite
ea5471aa0a build: bump undici to 6.21.1 2025-02-08 15:40:17 +00:00
Jiralite
7a5e6ffc70 build: bump discord-api-types to 0.37.119 2025-02-08 15:32:36 +00:00
Jiralite
e1c4ee90b5 build: bump discord-api-types to 0.37.118 2025-01-29 09:40:48 +00:00
Qjuh
06c2b820c6 feat(website): include reexported members in docs (#10518)
* feat(website): add re-exported members to docs site

* refactor(scripts): rewrite sourceURL for externals

* feat(website): add external badge

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-10 04:05:10 +00:00
Vlad Frangu
f65ff060ae chore(ws): release @discordjs/ws@1.2.0 2025-01-02 00:02:20 +02:00
Vlad Frangu
835c4496ab chore(ws): release @discordjs/ws@1.2.0 2025-01-01 23:57:54 +02:00
didinele
a589c6d492 fix(SimpleIdentifyThrottler): don't sleep negative amounts 2025-01-01 23:55:47 +02:00
Jiralite
fdf0b8455c build: bump discord-api-types to 0.37.114 2025-01-01 23:55:47 +02:00
Jiralite
3f9c3dc497 chore: ignore unrelated code 2025-01-01 23:55:47 +02:00
Jiralite
437b2d459d build: bump discord-api-types to 0.37.113 2025-01-01 23:55:47 +02:00
DD
5b8a08ebb6 fix: retry for EAI_AGAIN I/O error (#10383) 2025-01-01 23:55:47 +02:00
Jiralite
22e013b3e9 refactor(GuildChannelManager): Remove redundant edit code (#10370)
refactor(GuildChannelManager): remove redundant edit code

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-01 23:55:47 +02:00
Almeida
e133aa9a1e feat(GuildAuditLogsEntry): onboarding events (#9726)
Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-01 23:55:46 +02:00
Almeida
c1b5242d2f test: complete collection coverage (#10380) 2025-01-01 23:55:46 +02:00
Jiralite
9779baea84 feat: Premium buttons (#10353)
* feat: premium buttons

* docs: deprecation string

* feat(InteractionResponses): add deprecation message

* feat(builders): add tests

* chore: remove @ts-expect-errors

* test: update method name

* refactor(formatters): stricter types

* docs: deprecate method in typings

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-01 23:55:46 +02:00
DD
a5437a41f3 feat(WebSocketShard): explicit time out network error handling (#10375)
* feat(WebSocketShard): explicit time out network error handling

* refactor: use constant
2025-01-01 23:55:43 +02:00
Jiralite
11dd1c0666 fix(GuildMemberManager): Fix data type check for add() method (#10338)
fix(GuildMemberManager): fix data type check

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-01 23:55:01 +02:00
TÆMBØ
95fae30606 feat: add user-installable apps support (#10348)
* feat(SlashCommandBuilder): `addContexts()` and `addIntegrationTypes()`

* Add methods to ContextMenuCommandbuilder

* Fix JSDoc

* Use `setX` over `addX`

* Fix tests

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
2025-01-01 23:55:01 +02:00
Jiralite
93eeaeb56b types(ApplicationCommandManager): Snowflake fetch (#10366) 2025-01-01 23:55:00 +02:00
Jiralite
886a701251 chore: Remove "typings", "wip", and "workflow" scope (#10340)
* chore: remove "typings" commit lint

* chore: remove "workflow" too

* chore: also remove wip

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-01 23:55:00 +02:00
Jiralite
6dc0b7c18a build: Bump discord-api-types to 0.37.90 (#10354)
build: bump discord-api-types

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-01 23:55:00 +02:00
Adnan Khan
c4eba873ea ci: Reference title via environment variable (#10342)
Reference title via environment variable.

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-01 23:55:00 +02:00
DD
5c023bd64b fix: package gen script (#10352)
* fix: package gen script

* fix: files without extensions didn't have handlebars stripped

* chore: requested change
2025-01-01 23:55:00 +02:00
Qjuh
e7cc754fd3 fix(website): link tags to events named same as methods (#10351)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-01 23:55:00 +02:00
Qjuh
6e32ee565f fix(website): link tags with explicit URL showed undefined (#10350) 2025-01-01 23:54:59 +02:00
Jiralite
c6710e56cc fix: Consistent debug log spacing (#10349)
* fix: consistent debug log spacing

* refactor: simplify formatting

* refactor: more readable ternary

Co-Authored-By: Synbulat Biishev <contact@syjalo.dev>

* fix: modify parameters and types

---------

Co-authored-by: Synbulat Biishev <contact@syjalo.dev>
2025-01-01 23:54:59 +02:00
Qjuh
5761f2cbfd fix(website): remove merged interface from sitemap (#10343) 2025-01-01 23:54:22 +02:00
Jiralite
dd9ba1ad9d ci(pr-triage): Split job up (#10341)
ci: split job up
2025-01-01 23:54:22 +02:00
Danial Raza
0b0abfa283 feat(Message): add call (#10283)
* feat(Message): add `call`

* refactor: make `endedAt` a getter

* types: fix `endedAt` return type

* types(Message): add `call` property

* docs: requested changes

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
2025-01-01 23:54:22 +02:00
Jiralite
419266d839 ci: Check pull request titles for the commit convention format (#10334)
ci: check pull request titles
2025-01-01 23:54:22 +02:00
Jiralite
5e7ba2a016 docs: Update rule trigger types (#9708)
docs: update rule trigger types
2025-01-01 23:54:22 +02:00
Jiralite
efd6b36227 fix: Correct base path for GIF stickers (#10330)
* fix: correct base path for GIF stickers

* test: add sticker GIF

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-01 23:54:21 +02:00
Amir Farzamnia
66774c1b53 docs(stageInstances): Correct reference for stage instance creation (#10333)
Update stageInstances.ts

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-01 23:54:21 +02:00
Jiralite
728c2686bd fix: Update config file to address labeller file changes (#10332)
fix: update label script
2025-01-01 23:54:21 +02:00
ckohen
1bafba3fff ci: fix coverage upload (#10331) 2025-01-01 23:54:21 +02:00
Jiralite
a193147f96 build: Bump dependencies (#10322)
* build: bump dependencies

* build: update pnpm to 9.1.4
2025-01-01 23:54:21 +02:00
365 changed files with 15956 additions and 22886 deletions

View File

@@ -7,7 +7,6 @@
"always",
["chore", "build", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test", "types"]
],
"scope-case": [0],
"subject-exclamation-mark": [0]
"scope-case": [0]
}
}

2
.github/CODEOWNERS vendored
View File

@@ -1,5 +1,5 @@
# Learn how to add code owners here:
# https://help.github.com/articles/about-code-owners
# https://help.github.com/en/articles/about-code-owners
* @iCrawl

View File

@@ -7,7 +7,7 @@
Messages must be matched by the following regex:
```js
/^(revert: )?(feat|fix|docs|style|refactor|perf|test|build|ci|chore|types)(\(.+\))?!?: .{1,72}/;
/^(revert: )?(feat|fix|docs|style|refactor|perf|test|build|ci|chore|types)(\(.+\))?: .{1,72}/;
```
#### Examples
@@ -55,7 +55,6 @@ 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

View File

@@ -1,4 +1,4 @@
# https://docs.github.com/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
name: Cleanup caches
on:
pull_request:

View File

@@ -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/${{ github.ref_name }}!' "packages/${{ steps.extract-tag.outputs.package}}/api-extractor.json"
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}}/api-extractor.json"
- name: Build docs
run: pnpm run docs
@@ -103,15 +103,7 @@ jobs:
if: ${{ env.REF_TYPE == 'tag' && (!inputs.ref || inputs.ref == 'main') }}
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
CF_D1_DOCS_API_KEY: ${{ secrets.CF_D1_DOCS_API_KEY }}
CF_D1_DOCS_ID: ${{ secrets.CF_D1_DOCS_ID }}
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
CF_R2_DOCS_BUCKET_URL: ${{ secrets.CF_R2_DOCS_BUCKET_URL }}
uses: ./packages/actions/src/uploadDocumentation
with:
package: ${{ steps.extract-tag.outputs.package }}
@@ -121,15 +113,7 @@ jobs:
if: ${{ env.REF_TYPE == 'tag' && inputs.ref && inputs.ref != 'main' }}
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
CF_D1_DOCS_API_KEY: ${{ secrets.CF_D1_DOCS_API_KEY }}
CF_D1_DOCS_ID: ${{ secrets.CF_D1_DOCS_ID }}
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
CF_R2_DOCS_BUCKET_URL: ${{ secrets.CF_R2_DOCS_BUCKET_URL }}
uses: ./main/packages/actions/src/uploadDocumentation
with:
package: ${{ steps.extract-tag.outputs.package }}
@@ -139,10 +123,6 @@ jobs:
if: ${{ env.REF_TYPE == 'tag' && (!inputs.ref || inputs.ref == 'main') }}
env:
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
uses: ./packages/actions/src/uploadSplitDocumentation
with:
package: ${{ steps.extract-tag.outputs.package }}
@@ -152,10 +132,6 @@ jobs:
if: ${{ env.REF_TYPE == 'tag' && inputs.ref && inputs.ref != 'main' }}
env:
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
uses: ./main/packages/actions/src/uploadSplitDocumentation
with:
package: ${{ steps.extract-tag.outputs.package }}
@@ -179,50 +155,26 @@ jobs:
if: ${{ env.REF_TYPE == 'branch' && (!inputs.ref || inputs.ref == 'main') }}
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
CF_D1_DOCS_API_KEY: ${{ secrets.CF_D1_DOCS_API_KEY }}
CF_D1_DOCS_ID: ${{ secrets.CF_D1_DOCS_ID }}
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
CF_R2_DOCS_BUCKET_URL: ${{ secrets.CF_R2_DOCS_BUCKET_URL }}
uses: ./packages/actions/src/uploadDocumentation
- name: Upload documentation to database
if: ${{ env.REF_TYPE == 'branch' && inputs.ref && inputs.ref != 'main' }}
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
CF_D1_DOCS_API_KEY: ${{ secrets.CF_D1_DOCS_API_KEY }}
CF_D1_DOCS_ID: ${{ secrets.CF_D1_DOCS_ID }}
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
CF_R2_DOCS_BUCKET_URL: ${{ secrets.CF_R2_DOCS_BUCKET_URL }}
uses: ./main/packages/actions/src/uploadDocumentation
- name: Upload split documentation to blob storage
if: ${{ env.REF_TYPE == 'branch' && (!inputs.ref || inputs.ref == 'main') }}
env:
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
uses: ./packages/actions/src/uploadSplitDocumentation
- name: Upload split documentation to blob storage
if: ${{ env.REF_TYPE == 'branch' && inputs.ref && inputs.ref != 'main' }}
env:
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
uses: ./main/packages/actions/src/uploadSplitDocumentation
- name: Move docs to correct directory

View File

@@ -26,7 +26,7 @@ jobs:
env:
TITLE: ${{ github.event.pull_request.title }}
run: |
REGEX="^(revert: )?(feat|fix|docs|style|refactor|perf|test|build|ci|chore|types)(\\(.+\\))?!?: .{1,72}$"
REGEX="^(revert: )?(feat|fix|docs|style|refactor|perf|test|build|ci|chore|types)(\\(.+\\))?: .{1,72}$"
echo "Title: \"$TITLE\""

View File

@@ -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)" --skip-changelog
pnpm --filter=${{ matrix.package }} run release --preid "dev.$(date +%s)-$(git rev-parse --short HEAD)"
pnpm --filter=${{ matrix.package }} publish --provenance --no-git-checks --tag dev || true
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}

View File

@@ -35,7 +35,8 @@ jobs:
tag: ${{ github.ref_name }}
- name: Publish package
if: ${{ steps.extract-tag.outputs.package == 'ws' }}
run: |
pnpm --filter=${{ steps.extract-tag.outputs.subpackage == 'true' && '@discordjs/' || '' }}${{ steps.extract-tag.outputs.package }} publish --provenance --no-git-checks
pnpm --filter=${{ steps.extract-tag.outputs.subpackage == 'true' && '@discordjs/' || '' }}${{ steps.extract-tag.outputs.package }} publish --provenance --no-git-checks --tag v1-lts
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}

View File

@@ -9,9 +9,7 @@
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="npm downloads" /></a>
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Tests status" /></a>
<a href="https://github.com/discordjs/discord.js/commits/main"><img src="https://img.shields.io/github/last-commit/discordjs/discord.js.svg?logo=github&logoColor=ffffff" alt="Last commit." /></a>
<a href="https://github.com/discordjs/discord.js/graphs/contributors"><img src="https://img.shields.io/github/contributors/discordjs/discord.js.svg?maxAge=3600&logo=github&logoColor=fff&color=00c7be" alt="contributors" /></a>
<a href="https://codecov.io/gh/discordjs/discord.js"><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2" alt="Code coverage" /></a>
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2" alt="Code coverage" /></a>
</p>
<p>
<a href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"><img src="https://raw.githubusercontent.com/discordjs/discord.js/main/.github/powered-by-vercel.svg" alt="Vercel" /></a>

View File

@@ -69,14 +69,14 @@
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
"@types/html-escaper": "^3.0.2",
"@types/node": "^18.19.45",
"@types/node": "18.18.8",
"@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",
"@vitest/coverage-v8": "^1.6.0",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-config-neon": "^0.1.62",
@@ -86,12 +86,12 @@
"hastscript": "^8.0.0",
"html-escaper": "^3.0.3",
"postcss": "^8.4.38",
"prettier": "^3.3.3",
"turbo": "^2.0.14",
"typescript": "~5.5.4",
"prettier": "^3.3.0",
"turbo": "^1.13.3",
"typescript": "^5.4.5",
"unocss": "^0.60.4",
"vercel": "^37.0.0",
"vitest": "^2.0.5"
"vercel": "^34.2.4",
"vitest": "^1.6.0"
},
"engines": {
"node": ">=18"

View File

@@ -50,9 +50,10 @@
"@radix-ui/react-collapsible": "^1.0.3",
"@react-icons/all-files": "^4.1.0",
"@vercel/analytics": "^1.3.1",
"@vercel/blob": "^0.23.3",
"@vercel/edge-config": "^1.1.1",
"@vercel/og": "^0.6.2",
"@vercel/postgres": "^0.9.0",
"@vercel/postgres": "^0.8.0",
"cmdk": "^1.0.0",
"geist": "^1.3.0",
"jotai": "^2.8.2",
@@ -75,11 +76,11 @@
"@tailwindcss/typography": "^0.5.13",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^18.19.45",
"@types/node": "18.18.8",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0",
"@vitest/coverage-v8": "^2.0.5",
"@vitest/coverage-v8": "^1.6.0",
"autoprefixer": "^10.4.19",
"babel-plugin-react-compiler": "0.0.0-experimental-592953e-20240517",
"cpy-cli": "^5.0.0",
@@ -89,16 +90,16 @@
"eslint-formatter-pretty": "^6.0.1",
"happy-dom": "^14.12.0",
"postcss": "^8.4.38",
"prettier": "^3.3.3",
"prettier": "^3.3.0",
"prettier-plugin-tailwindcss": "^0.5.14",
"remark-gfm": "^4.0.0",
"remark-rehype": "^11.1.0",
"shiki": "^1.6.2",
"tailwindcss": "^3.4.3",
"turbo": "^2.0.14",
"typescript": "~5.5.4",
"vercel": "^37.0.0",
"vitest": "^2.0.5"
"turbo": "^1.13.3",
"typescript": "^5.4.5",
"vercel": "^34.2.4",
"vitest": "^1.6.0"
},
"engines": {
"node": ">=18"

View File

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

View File

@@ -29,7 +29,6 @@ export async function ParameterNode({
{description ? <Badges node={parameter} /> : null}
{parameter.name}
{parameter.isOptional ? '?' : ''}: <ExcerptNode node={parameter.typeExcerpt} version={version} />
{parameter.defaultValue ? ` = ${parameter.defaultValue}` : ''}
</span>
{description && parameter.description?.length ? (
<div className="mt-4 pl-4">

View File

@@ -51,13 +51,7 @@ export async function PropertyNode({
<LinkIcon aria-hidden size={16} />
</Link>
{property.displayName}
{property.isOptional ? '?' : ''} : <ExcerptNode node={property.typeExcerpt} version={version} />{' '}
{property.summary?.defaultValueBlock.length
? `= ${property.summary.defaultValueBlock.reduce(
(acc: string, def: { kind: string; text: string }) => `${acc}${def.text}`,
'',
)}`
: ''}
{property.isOptional ? '?' : ''} : <ExcerptNode node={property.typeExcerpt} version={version} />
</span>
</h3>

View File

@@ -20,7 +20,7 @@ export async function fetchDependencies({
return Object.entries<string>(parsedDependencies)
.filter(([key]) => key.startsWith('@discordjs/') && !key.includes('api-extractor'))
.map(([key, value]) => `${key.replace('@discordjs/', '').replaceAll('.', '-')}-${sanitizeVersion(value)}`);
.map(([key, value]) => `${key.replace('@discordjs/', '').replaceAll('.', '-')}-${value.replaceAll('.', '-')}`);
} catch {
return [];
}
@@ -36,12 +36,8 @@ export async function fetchDependencies({
return Object.entries<string>(parsedDependencies)
.filter(([key]) => key.startsWith('@discordjs/') && !key.includes('api-extractor'))
.map(([key, value]) => `${key.replace('@discordjs/', '').replaceAll('.', '-')}-${sanitizeVersion(value)}`);
.map(([key, value]) => `${key.replace('@discordjs/', '').replaceAll('.', '-')}-${value.replaceAll('.', '-')}`);
} catch {
return [];
}
}
function sanitizeVersion(version: string) {
return version.replaceAll('.', '-').replace(/^[\^~]/, '');
}

View File

@@ -6,19 +6,19 @@
"private": true,
"scripts": {
"build": "turbo run build --concurrency=4",
"build:affected": "turbo run build --filter=...[origin/v14] --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/v14] --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/v14] --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/v14] --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/v14] --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/v14] --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/v14] --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 <me@vladfrangu.dev>",
"Vlad Frangu <kingdgrizzle@gmail.com>",
"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.4.0",
"@commitlint/cli": "^19.3.0",
"@commitlint/config-angular": "^19.3.0",
"@favware/cliff-jumper": "^4.1.0",
"@favware/cliff-jumper": "^3.0.3",
"@favware/npm-deprecate": "^1.0.7",
"@types/lodash.merge": "^4.6.9",
"@unocss/eslint-plugin": "^0.59.4",
"@vitest/coverage-v8": "^2.0.5",
"@vitest/coverage-v8": "^1.6.0",
"conventional-changelog-cli": "^4.1.0",
"eslint": "^8.57.0",
"eslint-config-neon": "^0.1.62",
"husky": "^9.1.5",
"husky": "^9.0.11",
"is-ci": "^3.0.1",
"lint-staged": "^15.2.9",
"lint-staged": "^15.2.5",
"lodash.merge": "^4.6.2",
"prettier": "^3.3.3",
"tsup": "^8.2.4",
"turbo": "^2.0.14",
"typescript": "~5.5.4",
"typescript-eslint": "^8.2.0",
"prettier": "^3.3.0",
"tsup": "^8.1.0",
"turbo": "^1.13.3",
"typescript": "^5.4.5",
"typescript-eslint": "^7.11.0",
"unocss": "^0.60.4",
"vercel": "^37.0.0",
"vitest": "^2.0.5"
"vercel": "^34.2.4",
"vitest": "^1.6.0"
},
"pnpm": {
"peerDependencyRules": {
@@ -97,5 +97,5 @@
"engines": {
"node": ">=18"
},
"packageManager": "pnpm@9.8.0"
"packageManager": "pnpm@9.1.4"
}

View File

@@ -41,32 +41,28 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@actions/core": "^1.11.1",
"@actions/glob": "^0.5.0",
"@aws-sdk/client-s3": "^3.787.0",
"@actions/core": "^1.10.1",
"@actions/glob": "^0.4.0",
"@discordjs/scripts": "workspace:^",
"@vercel/blob": "^0.27.3",
"@vercel/postgres": "^0.9.0",
"cloudflare": "^4.2.0",
"@vercel/blob": "^0.22.3",
"@vercel/postgres": "^0.8.0",
"meilisearch": "^0.38.0",
"p-limit": "^6.2.0",
"p-queue": "^8.1.0",
"tslib": "^2.8.1",
"undici": "7.8.0"
"p-limit": "^5.0.0",
"tslib": "^2.6.2",
"undici": "6.21.1"
},
"devDependencies": {
"@types/node": "^22.14.0",
"@vitest/coverage-v8": "^3.1.1",
"@types/node": "18.18.8",
"@vitest/coverage-v8": "^1.6.0",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-config-neon": "^0.1.62",
"eslint-formatter-pretty": "^6.0.1",
"prettier": "^3.5.3",
"terser": "^5.37.0",
"tsup": "^8.4.0",
"turbo": "^2.5.0",
"typescript": "~5.8.3",
"vitest": "^3.1.1"
"prettier": "^3.3.0",
"tsup": "^8.1.0",
"turbo": "^1.13.3",
"typescript": "^5.4.5",
"vitest": "^1.6.0"
},
"engines": {
"node": ">=18"

View File

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

View File

@@ -9,7 +9,7 @@ runs:
with:
swap-size-gb: 10
- uses: pnpm/action-setup@v4.1.0
- uses: pnpm/action-setup@v4.0.0
name: Install pnpm
with:
run_install: false

View File

@@ -8,7 +8,6 @@ runs:
using: 'composite'
steps:
- name: Upload Guide Coverage
if: ${{ hashFiles('apps/guide/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./apps/guide/coverage/cobertura-coverage.xml
@@ -17,7 +16,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Website Coverage
if: ${{ hashFiles('apps/website/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./apps/website/coverage/cobertura-coverage.xml
@@ -26,7 +24,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Brokers Coverage
if: ${{ hashFiles('packages/brokers/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/brokers/coverage/cobertura-coverage.xml
@@ -35,7 +32,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Builders Coverage
if: ${{ hashFiles('packages/builders/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/builders/coverage/cobertura-coverage.xml
@@ -44,7 +40,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Collection Coverage
if: ${{ hashFiles('packages/collection/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/collection/coverage/cobertura-coverage.xml
@@ -53,7 +48,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Discord.js Coverage
if: ${{ hashFiles('packages/discord.js/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/discord.js/coverage/cobertura-coverage.xml
@@ -62,7 +56,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Formatters Coverage
if: ${{ hashFiles('packages/formatters/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/formatters/coverage/cobertura-coverage.xml
@@ -71,7 +64,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Next Coverage
if: ${{ hashFiles('packages/next/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/next/coverage/cobertura-coverage.xml
@@ -80,7 +72,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Proxy Coverage
if: ${{ hashFiles('packages/proxy/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/proxy/coverage/cobertura-coverage.xml
@@ -89,7 +80,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Rest Coverage
if: ${{ hashFiles('packages/rest/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/rest/coverage/cobertura-coverage.xml
@@ -98,7 +88,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Voice Coverage
if: ${{ hashFiles('packages/voice/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/voice/coverage/cobertura-coverage.xml
@@ -107,7 +96,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload WS Coverage
if: ${{ hashFiles('packages/ws/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/ws/coverage/cobertura-coverage.xml
@@ -116,7 +104,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Util Coverage
if: ${{ hashFiles('packages/util/coverage/cobertura-coverage.xml') != '' }}
uses: codecov/codecov-action@v4
with:
files: ./packages/util/coverage/cobertura-coverage.xml
@@ -125,7 +112,6 @@ runs:
token: ${{ inputs.CODECOV_TOKEN }}
- name: Upload Utilities Coverage
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

View File

@@ -1,26 +1,13 @@
/* eslint-disable @typescript-eslint/no-loop-func */
import { readFile } from 'node:fs/promises';
import process from 'node:process';
import { getInput, setFailed } from '@actions/core';
import { create } from '@actions/glob';
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { put } from '@vercel/blob';
import { createPool } from '@vercel/postgres';
import Cloudflare from 'cloudflare';
import pLimit from 'p-limit';
if (
!process.env.DATABASE_URL ||
!process.env.CF_R2_DOCS_URL ||
!process.env.CF_R2_DOCS_ACCESS_KEY_ID ||
!process.env.CF_R2_DOCS_SECRET_ACCESS_KEY ||
!process.env.CF_R2_DOCS_BUCKET ||
!process.env.CF_R2_DOCS_BUCKET_URL ||
!process.env.CF_D1_DOCS_API_KEY ||
!process.env.CF_D1_DOCS_ID ||
!process.env.CF_ACCOUNT_ID
) {
setFailed('Missing environment variables');
if (!process.env.DATABASE_URL) {
setFailed('DATABASE_URL is not set');
}
const pkg = getInput('package') || '*';
@@ -30,21 +17,6 @@ const pool = createPool({
connectionString: process.env.DATABASE_URL,
});
const S3 = new S3Client({
region: 'auto',
endpoint: process.env.CF_R2_DOCS_URL!,
credentials: {
accessKeyId: process.env.CF_R2_DOCS_ACCESS_KEY_ID!,
secretAccessKey: process.env.CF_R2_DOCS_SECRET_ACCESS_KEY!,
},
requestChecksumCalculation: 'WHEN_REQUIRED',
responseChecksumValidation: 'WHEN_REQUIRED',
});
const client = new Cloudflare({
apiToken: process.env.CF_D1_DOCS_API_KEY,
});
const limit = pLimit(10);
const promises = [];
@@ -54,14 +26,12 @@ for await (const file of globber.globGenerator()) {
const data = await readFile(file, 'utf8');
try {
promises.push(
// eslint-disable-next-line @typescript-eslint/no-loop-func
limit(async () => {
console.log(`Uploading ${file} with ${version}...`);
const json = JSON.parse(data);
const name = json.name ?? json.n;
const key = `${name.replace('@discordjs/', '')}/${version}.json`;
const { url } = await put(key, data, {
const { url } = await put(`${name.replace('@discordjs/', '')}/${version}.json`, data, {
access: 'public',
addRandomSuffix: false,
});
@@ -69,19 +39,6 @@ for await (const file of globber.globGenerator()) {
'@discordjs/',
'',
)}, ${version}, ${url}) on conflict (name, version) do update set url = EXCLUDED.url`;
await S3.send(
new PutObjectCommand({
Bucket: process.env.CF_R2_DOCS_BUCKET,
Key: key,
Body: data,
}),
);
await client.d1.database.raw(process.env.CF_D1_DOCS_ID!, {
account_id: process.env.CF_ACCOUNT_ID!,
sql: `insert into documentation (name, version, url) values (?, ?, ?) on conflict (name, version) do update set url = excluded.url;`,
params: [name.replace('@discordjs/', ''), version, process.env.CF_R2_DOCS_BUCKET_URL + '/' + key],
});
}),
);
} catch (error) {

View File

@@ -53,6 +53,7 @@ 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}...`);

View File

@@ -1,82 +1,32 @@
/* eslint-disable @typescript-eslint/no-loop-func */
import { readFile } from 'node:fs/promises';
import { basename, dirname, relative, sep } from 'node:path';
import process from 'node:process';
import { setTimeout as sleep } from 'node:timers/promises';
import { setFailed, getInput } from '@actions/core';
import { cwd } from 'node:process';
import { getInput } from '@actions/core';
import { create } from '@actions/glob';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { put } from '@vercel/blob';
import PQueue from 'p-queue';
if (
!process.env.CF_R2_DOCS_URL ||
!process.env.CF_R2_DOCS_ACCESS_KEY_ID ||
!process.env.CF_R2_DOCS_SECRET_ACCESS_KEY ||
!process.env.CF_R2_DOCS_BUCKET
) {
setFailed('Missing environment variables');
}
import pLimit from 'p-limit';
const pkg = getInput('package') || '*';
const version = getInput('version') || 'main';
const queue = new PQueue({ concurrency: 10, interval: 60_000, intervalCap: 1_000 });
const limit = pLimit(10);
const promises = [];
const failedUploads: string[] = [];
const S3 = new S3Client({
region: 'auto',
endpoint: process.env.CF_R2_DOCS_URL!,
credentials: {
accessKeyId: process.env.CF_R2_DOCS_ACCESS_KEY_ID!,
secretAccessKey: process.env.CF_R2_DOCS_SECRET_ACCESS_KEY!,
},
requestChecksumCalculation: 'WHEN_REQUIRED',
responseChecksumValidation: 'WHEN_REQUIRED',
});
const globber = await create(`packages/${pkg}/docs/${pkg}/split/*.api.json`);
console.log('Glob: ', await globber.glob());
for await (const file of globber.globGenerator()) {
const data = await readFile(file, 'utf8');
const pkgName = dirname(relative(process.cwd(), file)).split(sep)[1];
const pkgName = dirname(relative(cwd(), file)).split(sep)[1];
try {
promises.push(
queue.add(async () => {
// eslint-disable-next-line @typescript-eslint/no-loop-func
limit(async () => {
console.log(`Uploading ${file} with ${version} from ${pkgName}...`);
const name = basename(file).replace('main.', '');
async function upload(retries = 0) {
try {
await put(`rewrite/${pkgName}/${version}.${name}`, data, {
access: 'public',
addRandomSuffix: false,
});
await S3.send(
new PutObjectCommand({
Bucket: process.env.CF_R2_DOCS_BUCKET,
Key: `${pkgName}/${version}.${name}`,
Body: data,
}),
);
} catch (error) {
if (retries > 3) {
console.error(`Could not upload ${file} after 3 retries`, error);
failedUploads.push(name);
return;
}
if (typeof error === 'object' && error && 'retryAfter' in error && typeof error.retryAfter === 'number') {
await sleep(error.retryAfter * 1_000);
return upload(retries + 1);
} else {
console.error(`Could not upload ${file}`, error);
failedUploads.push(name);
}
}
}
await upload();
await put(`rewrite/${pkgName}/${version}.${name}`, data, {
access: 'public',
addRandomSuffix: false,
});
}),
);
} catch (error) {
@@ -86,9 +36,6 @@ for await (const file of globber.globGenerator()) {
try {
await Promise.all(promises);
if (failedUploads.length) {
setFailed(`Failed to upload ${failedUploads.length} files: ${failedUploads.join(', ')}`);
}
} catch (error) {
console.log(error);
}

View File

@@ -1,10 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"skipLibCheck": true
},
"include": ["__tests__/**/*.ts"],
"exclude": ["node_modules"]
}

View File

@@ -37,15 +37,14 @@
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^18.19.45",
"@types/node": "^18.19.33",
"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.3.3",
"tsup": "^8.2.4",
"turbo": "^2.0.14",
"typescript": "~5.5.4"
"prettier": "^3.3.0",
"tsup": "^8.1.0",
"turbo": "^1.13.3"
}
}

View File

@@ -14,7 +14,6 @@ import type { IExcerptTokenRange } from './Excerpt.js';
* @public
*/
export interface IApiParameterOptions {
defaultValue: string | undefined;
isOptional: boolean;
isRest: boolean;
parameterName: string;
@@ -125,7 +124,6 @@ export function ApiParameterListMixin<TBaseClass extends IApiItemConstructor>(
isOptional: Boolean(parameterOptions.isOptional),
isRest: Boolean(parameterOptions.isRest),
parent: this,
defaultValue: parameterOptions.defaultValue,
});
this[_parameters].push(parameter);
@@ -173,7 +171,6 @@ export function ApiParameterListMixin<TBaseClass extends IApiItemConstructor>(
parameterTypeTokenRange: parameter.parameterTypeExcerpt.tokenRange,
isOptional: parameter.isOptional,
isRest: parameter.isRest,
defaultValue: parameter.defaultValue,
});
}

View File

@@ -41,7 +41,6 @@ const MinifyJSONMapping = {
constraintTokenRange: 'ctr',
dependencies: 'dp',
defaultTypeTokenRange: 'dtr',
defaultValue: 'dv',
docComment: 'd',
endIndex: 'en',
excerptTokens: 'ex',
@@ -381,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,

View File

@@ -182,9 +182,7 @@ 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 {
@@ -194,7 +192,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]) => [
@@ -262,7 +260,6 @@ function mapParam(
startIndex: 1 + index + paramTokens.slice(0, index).reduce((akk, num) => akk + num, 0),
endIndex: 1 + index + paramTokens.slice(0, index + 1).reduce((akk, num) => akk + num, 0),
},
defaultValue: param.default,
};
}

View File

@@ -12,7 +12,6 @@ import type { Excerpt } from '../mixins/Excerpt.js';
* @public
*/
export interface IParameterOptions {
defaultValue: string | undefined;
isOptional: boolean;
isRest: boolean;
name: string;
@@ -57,11 +56,6 @@ export class Parameter {
*/
public isRest: boolean;
/**
* The default value for this parameter if optional
*/
public defaultValue: string | undefined;
private readonly _parent: ApiParameterListMixin;
public constructor(options: IParameterOptions) {
@@ -70,7 +64,6 @@ export class Parameter {
this.isOptional = options.isOptional;
this.isRest = options.isRest;
this._parent = options.parent;
this.defaultValue = options.defaultValue;
}
/**

View File

@@ -50,15 +50,15 @@
"@microsoft/tsdoc": "0.14.2"
},
"devDependencies": {
"@types/node": "^18.19.45",
"@types/node": "18.18.8",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-config-neon": "^0.1.62",
"eslint-formatter-pretty": "^6.0.1",
"prettier": "^3.3.3",
"tsup": "^8.2.4",
"turbo": "^2.0.14",
"typescript": "~5.5.4"
"prettier": "^3.3.0",
"tsup": "^8.1.0",
"turbo": "^1.13.3",
"typescript": "^5.4.5"
},
"engines": {
"node": ">=18"

View File

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

View File

@@ -61,12 +61,12 @@
"resolve": "~1.22.1",
"semver": "~7.5.4",
"source-map": "0.6.1",
"typescript": "~5.5.4"
"typescript": "^5.4.5"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.4",
"@types/node": "^18.19.45",
"@types/node": "^18.19.33",
"@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.3.3",
"tsup": "^8.2.4",
"turbo": "^2.0.14"
"prettier": "^3.3.0",
"tsup": "^8.1.0",
"turbo": "^1.13.3"
}
}

View File

@@ -114,7 +114,7 @@ interface DocgenEventJson {
}
interface DocgenParamJson {
default?: boolean | number | string;
default?: string;
description: string;
name: string;
nullable?: boolean;
@@ -155,7 +155,7 @@ interface DocgenMethodJson {
interface DocgenPropertyJson {
abstract?: boolean;
access?: DocgenAccess;
default?: boolean | number | string;
default?: string;
deprecated?: DocgenDeprecated;
description: string;
meta: DocgenMetaJson;
@@ -843,7 +843,6 @@ export class ApiModelGenerator {
const parameters: IApiParameterOptions[] = this._captureParameters(
nodesToCapture,
functionDeclaration.parameters,
jsDoc?.params,
);
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
@@ -1044,7 +1043,6 @@ export class ApiModelGenerator {
const parameters: IApiParameterOptions[] = this._captureParameters(
nodesToCapture,
methodDeclaration.parameters,
jsDoc?.params,
);
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
@@ -1139,11 +1137,7 @@ export class ApiModelGenerator {
methodSignature.typeParameters,
);
const parameters: IApiParameterOptions[] = this._captureParameters(
nodesToCapture,
methodSignature.parameters,
jsDoc?.params,
);
const parameters: IApiParameterOptions[] = this._captureParameters(nodesToCapture, methodSignature.parameters);
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
@@ -1270,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) ?? ''}${jsDoc.default === undefined ? '' : ` (default: ${this._escapeSpecialChars(jsDoc.default)})`}\n${
`/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${
'see' in jsDoc ? jsDoc.see.map((see) => ` * @see ${see}\n`).join('') : ''
}${'readonly' in jsDoc && jsDoc.readonly ? ' * @readonly\n' : ''}${
'deprecated' in jsDoc && jsDoc.deprecated
@@ -1348,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) ?? ''}${jsDoc.default === undefined ? '' : `\n * @defaultValue ${this._escapeSpecialChars(jsDoc.default)}`}\n${
`/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${
'see' in jsDoc ? jsDoc.see.map((see) => ` * @see ${see}\n`).join('') : ''
}${'readonly' in jsDoc && jsDoc.readonly ? ' * @readonly\n' : ''}${
'deprecated' in jsDoc && jsDoc.deprecated
@@ -1429,9 +1423,7 @@ 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('')
: ''
} */`,
@@ -1516,17 +1508,15 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = [
{
kind: ExcerptTokenKind.Content,
text: `public on(eventName: '${name}', listener: (${
jsDoc.params?.length
? `${jsDoc.params[0]?.name}${jsDoc.params[0]?.optional ? '?' : ''}: `
: ') => void): this;'
text: `on('${name}', (${
jsDoc.params?.length ? `${jsDoc.params[0]?.name}${jsDoc.params[0]?.nullable ? '?' : ''}: ` : ') => {})'
}`,
},
];
const parameters: IApiParameterOptions[] = [];
for (let index = 0; index < (jsDoc.params?.length ?? 0) - 1; index++) {
const parameter = jsDoc.params![index]!;
const newTokens = this._mapVarType(parameter.type, parameter.nullable);
const newTokens = this._mapVarType(parameter.type);
parameters.push({
parameterName: parameter.name,
parameterTypeTokenRange: {
@@ -1535,7 +1525,6 @@ export class ApiModelGenerator {
},
isOptional: Boolean(parameter.optional),
isRest: parameter.name.startsWith('...'),
defaultValue: parameter.default?.toString(),
});
excerptTokens.push(...newTokens);
excerptTokens.push({
@@ -1546,7 +1535,7 @@ export class ApiModelGenerator {
if (jsDoc.params?.length) {
const parameter = jsDoc.params![jsDoc.params.length - 1]!;
const newTokens = this._mapVarType(parameter.type, parameter.nullable);
const newTokens = this._mapVarType(parameter.type);
parameters.push({
parameterName: parameter.name,
parameterTypeTokenRange: {
@@ -1555,12 +1544,11 @@ export class ApiModelGenerator {
},
isOptional: Boolean(parameter.optional),
isRest: parameter.name.startsWith('...'),
defaultValue: parameter.default?.toString(),
});
excerptTokens.push(...newTokens);
excerptTokens.push({
kind: ExcerptTokenKind.Content,
text: `) => void): this;`,
text: `) => {})`,
});
}
@@ -1648,7 +1636,6 @@ export class ApiModelGenerator {
private _captureParameters(
nodesToCapture: IExcerptBuilderNodeToCapture[],
parameterNodes: ts.NodeArray<ts.ParameterDeclaration>,
jsDoc?: DocgenParamJson[] | undefined,
): IApiParameterOptions[] {
const parameters: IApiParameterOptions[] = [];
for (const parameter of parameterNodes) {
@@ -1659,9 +1646,6 @@ export class ApiModelGenerator {
parameterTypeTokenRange,
isOptional: this._collector.typeChecker.isOptionalParameter(parameter),
isRest: Boolean(parameter.dotDotDotToken),
defaultValue:
parameter.initializer?.getText() ??
jsDoc?.find((param) => param.name === parameter.name.getText().trim())?.default?.toString(),
});
}
@@ -1760,14 +1744,6 @@ 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) => {
@@ -1787,8 +1763,8 @@ export class ApiModelGenerator {
.replaceAll('* ', '\n * * ');
}
private _mapVarType(typey: DocgenVarTypeJson, nullable?: boolean): IExcerptToken[] {
const mapper = Array.isArray(typey) ? typey : (typey.types ?? []);
private _mapVarType(typey: DocgenVarTypeJson): IExcerptToken[] {
const mapper = Array.isArray(typey) ? typey : typey.types ?? [];
const lookup: { [K in ts.SyntaxKind]?: string } = {
[ts.SyntaxKind.ClassDeclaration]: 'class',
[ts.SyntaxKind.EnumDeclaration]: 'enum',
@@ -1812,40 +1788,23 @@ export class ApiModelGenerator {
{
kind: type?.includes("'") ? ExcerptTokenKind.Content : ExcerptTokenKind.Reference,
text: fixPrimitiveTypes(type ?? 'unknown', symbol),
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(),
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(),
},
{ kind: ExcerptTokenKind.Content, text: symbol ?? '' },
];
}, []);
return index === 0
? mapper.length === 1 && (nullable || ('nullable' in typey && typey.nullable))
? [
...result,
{ kind: ExcerptTokenKind.Content, text: ' | ' },
{ kind: ExcerptTokenKind.Reference, text: 'null' },
]
: result
: index === mapper.length - 1 && (nullable || ('nullable' in typey && typey.nullable))
? [
{ kind: ExcerptTokenKind.Content, text: ' | ' },
...result,
{ kind: ExcerptTokenKind.Content, text: ' | ' },
{ kind: ExcerptTokenKind.Reference, text: 'null' },
]
: [{ kind: ExcerptTokenKind.Content, text: ' | ' }, ...result];
return index === 0 ? result : [{ kind: ExcerptTokenKind.Content, text: ' | ' }, ...result];
})
.filter((excerpt) => excerpt.text.length);
}
@@ -1860,7 +1819,7 @@ export class ApiModelGenerator {
isOptional: Boolean(prop.nullable),
isReadonly: Boolean(prop.readonly),
docComment: this._tsDocParser.parseString(
`/**\n * ${this._fixLinkTags(prop.description) ?? ''}\n${prop.default ? ` * @defaultValue ${this._escapeSpecialChars(prop.default)}\n` : ''}${
`/**\n * ${this._fixLinkTags(prop.description) ?? ''}\n${
prop.see?.map((see) => ` * @see ${see}\n`).join('') ?? ''
}${prop.readonly ? ' * @readonly\n' : ''} */`,
).docComment,
@@ -1872,7 +1831,7 @@ export class ApiModelGenerator {
}${prop.name} :`,
},
...mappedVarType,
{ kind: ExcerptTokenKind.Content, text: `${prop.default ? ` = ${prop.default}` : ''};` },
{ kind: ExcerptTokenKind.Content, text: ';' },
],
propertyTypeTokenRange: { startIndex: 1, endIndex: 1 + mappedVarType.length },
releaseTag: prop.access === 'private' ? ReleaseTag.Internal : ReleaseTag.Public,
@@ -1895,7 +1854,6 @@ export class ApiModelGenerator {
startIndex: 1 + index + paramTokens.slice(0, index).reduce((akk, num) => akk + num, 0),
endIndex: 1 + index + paramTokens.slice(0, index + 1).reduce((akk, num) => akk + num, 0),
},
defaultValue: param.default?.toString(),
};
}
@@ -1909,7 +1867,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 ? '?' : ''}`
: '): '
}`,
});
@@ -1920,7 +1878,7 @@ export class ApiModelGenerator {
excerptTokens.push(...newTokens);
excerptTokens.push({
kind: ExcerptTokenKind.Content,
text: `${method.params![index]!.default ? ` = ${method.params![index]!.default}` : ''}, ${method.params![index + 1]!.name}${
text: `, ${method.params![index + 1]!.name}${
method.params![index + 1]!.nullable || method.params![index + 1]!.optional ? '?' : ''
}: `,
});
@@ -1930,10 +1888,7 @@ export class ApiModelGenerator {
const newTokens = this._mapVarType(method.params[method.params.length - 1]!.type);
paramTokens.push(newTokens.length);
excerptTokens.push(...newTokens);
excerptTokens.push({
kind: ExcerptTokenKind.Content,
text: `${method.params![method.params.length - 1]!.default ? ` = ${method.params![method.params.length - 1]!.default}` : ''}): `,
});
excerptTokens.push({ kind: ExcerptTokenKind.Content, text: `): ` });
}
const returnTokens = this._mapVarType(method.returns?.[0] ?? []);

View File

@@ -2,14 +2,6 @@
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

View File

@@ -9,8 +9,7 @@
<a href="https://www.npmjs.com/package/@discordjs/brokers"><img src="https://img.shields.io/npm/v/@discordjs/brokers.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/@discordjs/brokers"><img src="https://img.shields.io/npm/dt/@discordjs/brokers.svg?maxAge=3600" alt="npm downloads" /></a>
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Build status" /></a>
<a href="https://github.com/discordjs/discord.js/commits/main/packages/brokers"><img alt="Last commit." src="https://img.shields.io/github/last-commit/discordjs/discord.js?logo=github&logoColor=ffffff&path=packages%2Fbrokers"></a>
<a href="https://codecov.io/gh/discordjs/discord.js"><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2&flag=brokers" alt="Code coverage" /></a>
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2&flag=brokers" alt="Code coverage" /></a>
</p>
<p>
<a href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"><img src="https://raw.githubusercontent.com/discordjs/discord.js/main/.github/powered-by-vercel.svg" alt="Vercel" /></a>
@@ -24,7 +23,7 @@
## Installation
**Node.js 20 or newer is required.**
**Node.js 18 or newer is required.**
```sh
npm install @discordjs/brokers

View File

@@ -5,16 +5,13 @@ 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 %}\
({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }})\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}\
({{ self::remote_url() }}/tree/{{ version }})\
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
@@ -27,23 +24,14 @@ body = """
- {% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ 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 %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% if commit.breaking %}\
{% for footer in commit.footers %}\
{% if footer.breaking %}\
\n{% raw %} {% endraw %}- **{{ footer.token }}{{ footer.separator }}** {{ footer.value }}\
{% endif %}\
{% for breakingChange in commit.footers %}\
\n{% raw %} {% endraw %}- **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\
{% endfor %}\
{% endif %}\
{% endfor %}
{% 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
{% endfor %}\n
"""
trim = true
footer = ""
@@ -71,9 +59,5 @@ commit_parsers = [
filter_commits = true
tag_pattern = "@discordjs/brokers@[0-9]*"
ignore_tags = ""
topo_order = false
topo_order = true
sort_commits = "newest"
[remote.github]
owner = "discordjs"
repo = "discord.js"

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@discordjs/brokers",
"version": "1.0.0",
"version": "0.3.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 <me@vladfrangu.dev>",
"Vlad Frangu <kingdgrizzle@gmail.com>",
"Aura Roman <kyradiscord@gmail.com>",
"DD <didinele.dev@gmail.com>"
],
@@ -68,28 +68,28 @@
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@msgpack/msgpack": "^3.0.0-beta2",
"@vladfrangu/async_event_emitter": "^2.4.6",
"@vladfrangu/async_event_emitter": "^2.2.4",
"ioredis": "^5.4.1"
},
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^4.1.0",
"@types/node": "^18.19.45",
"@vitest/coverage-v8": "^2.0.5",
"@favware/cliff-jumper": "^3.0.3",
"@types/node": "18.18.8",
"@vitest/coverage-v8": "^1.6.0",
"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.3.3",
"tsup": "^8.2.4",
"turbo": "^2.0.14",
"typescript": "~5.5.4",
"vitest": "^2.0.5"
"prettier": "^3.3.0",
"tsup": "^8.1.0",
"turbo": "^1.13.3",
"typescript": "^5.4.5",
"vitest": "^1.6.0"
},
"engines": {
"node": ">=20"
"node": ">=18"
},
"publishConfig": {
"access": "public",

View File

@@ -30,17 +30,17 @@ 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 {}> {
export interface IBaseBroker<TEvents extends Record<string, any>> {
/**
* Subscribes to the given events
*/
@@ -51,7 +51,7 @@ export interface IBaseBroker<TEvents extends {}> {
unsubscribe(events: (keyof TEvents)[]): Promise<void>;
}
export interface IPubSubBroker<TEvents extends {}>
export interface IPubSubBroker<TEvents extends Record<string, any>>
extends IBaseBroker<TEvents>,
AsyncEventEmitter<ToEventMap<TEvents>> {
/**
@@ -60,7 +60,7 @@ export interface IPubSubBroker<TEvents extends {}>
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>> {
/**

View File

@@ -57,11 +57,8 @@ export const DefaultRedisBrokerOptions = {
/**
* Helper class with shared Redis logic
*/
export abstract class BaseRedisBroker<
TEvents extends Record<string, any[]>,
TResponses extends Record<keyof TEvents, any> | undefined = undefined,
>
extends AsyncEventEmitter<ToEventMap<TEvents, TResponses>>
export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
extends AsyncEventEmitter<ToEventMap<TEvents>>
implements IBaseBroker<TEvents>
{
/**
@@ -185,7 +182,6 @@ export abstract class BaseRedisBroker<
}
}
} catch (error) {
// @ts-expect-error: Intended
this.emit('error', error);
break;
}

View File

@@ -48,7 +48,6 @@ export class PubSubRedisBroker<TEvents extends Record<string, any>>
},
};
// @ts-expect-error: Intended
this.emit(event, payload);
}
}

View File

@@ -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, TResponses>
export class RPCRedisBroker<TEvents extends Record<string, any>, TResponses extends Record<keyof TEvents, any>>
extends BaseRedisBroker<TEvents>
implements IRPCBroker<TEvents, TResponses>
{
/**
@@ -125,7 +125,6 @@ export class RPCRedisBroker<TEvents extends Record<string, any[]>, TResponses ex
},
};
// @ts-expect-error: Intended
this.emit(event, payload);
}
}

View File

@@ -2,62 +2,6 @@
All notable changes to this project will be documented in this file.
# [@discordjs/builders@1.13.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.12.2...@discordjs/builders@1.13.0) - (2025-10-24)
## Features
- V1 builders file uploads support (#11196) ([1417c49](https://github.com/discordjs/discord.js/commit/1417c498a40b843d772ecf88dfff5f87a1665042))
## Testing
- Fix type error ([f780c6a](https://github.com/discordjs/discord.js/commit/f780c6a5500f7ea5c7a1ea7cd6720f6159d9d36e))
# [@discordjs/builders@1.12.2](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.12.1...@discordjs/builders@1.12.2) - (2025-10-09)
## Bug Fixes
- **Assertions:** Literal default values ([43362c9](https://github.com/discordjs/discord.js/commit/43362c93525f98d72b894eb0fc6b358d30ec45b9))
# [@discordjs/builders@1.12.1](https://github.com/discordjs/discord.js/compare/@discordjs/builders@1.12.0...@discordjs/builders@1.12.1) - (2025-10-08)
## Bug Fixes
- **builders:** Text display component support for modals (#11155) ([99b8436](https://github.com/discordjs/discord.js/commit/99b8436117bc12654278337abc4a23f5bdf4ba46))
# [@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
@@ -326,6 +270,261 @@ 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

View File

@@ -9,8 +9,7 @@
<a href="https://www.npmjs.com/package/@discordjs/builders"><img src="https://img.shields.io/npm/v/@discordjs/builders.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/@discordjs/builders"><img src="https://img.shields.io/npm/dt/@discordjs/builders.svg?maxAge=3600" alt="npm downloads" /></a>
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Build status" /></a>
<a href="https://github.com/discordjs/discord.js/commits/main/packages/builders"><img alt="Last commit." src="https://img.shields.io/github/last-commit/discordjs/discord.js?logo=github&logoColor=ffffff&path=packages%2Fbuilders"></a>
<a href="https://codecov.io/gh/discordjs/discord.js"><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2&flag=builders" alt="Code coverage" /></a>
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2&flag=builders" alt="Code coverage" /></a>
</p>
<p>
<a href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"><img src="https://raw.githubusercontent.com/discordjs/discord.js/main/.github/powered-by-vercel.svg" alt="Vercel" /></a>

View File

@@ -2,7 +2,7 @@ import {
ButtonStyle,
ComponentType,
type APIActionRowComponent,
type APIComponentInMessageActionRow,
type APIMessageActionRowComponent,
} 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<APIComponentInMessageActionRow> = {
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
type: ComponentType.ActionRow,
components: [
{
@@ -25,7 +25,7 @@ const rowWithButtonData: APIActionRowComponent<APIComponentInMessageActionRow> =
],
};
const rowWithSelectMenuData: APIActionRowComponent<APIComponentInMessageActionRow> = {
const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent> = {
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<APIComponentInMessageActionRow> = {
const actionRowData: APIActionRowComponent<APIMessageActionRowComponent> = {
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<APIComponentInMessageActionRow> = {
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
type: ComponentType.ActionRow,
components: [
{
@@ -104,7 +104,7 @@ describe('Action Row Components', () => {
],
};
const rowWithSelectMenuData: APIActionRowComponent<APIComponentInMessageActionRow> = {
const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent> = {
type: ComponentType.ActionRow,
components: [
{

View File

@@ -3,7 +3,7 @@ import {
ComponentType,
TextInputStyle,
type APIButtonComponent,
type APIComponentInMessageActionRow,
type APIMessageActionRowComponent,
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<APIComponentInMessageActionRow> = {
const actionRow: APIActionRowComponent<APIMessageActionRowComponent> = {
components: [],
type: ComponentType.ActionRow,
};

View File

@@ -1,46 +0,0 @@
import type { APIFileUploadComponent } from 'discord-api-types/v10';
import { ComponentType } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import { FileUploadBuilder } from '../../src/components/fileUpload/FileUpload.js';
describe('File Upload Components', () => {
test('Valid builder does not throw.', () => {
expect(() => new FileUploadBuilder().setCustomId('file_upload').toJSON()).not.toThrowError();
expect(() => new FileUploadBuilder().setCustomId('file_upload').setId(5).toJSON()).not.toThrowError();
expect(() =>
new FileUploadBuilder().setCustomId('file_upload').setMaxValues(5).setMinValues(2).toJSON(),
).not.toThrowError();
expect(() => new FileUploadBuilder().setCustomId('file_upload').setRequired(false).toJSON()).not.toThrowError();
});
test('Invalid builder does throw.', () => {
expect(() => new FileUploadBuilder().toJSON()).toThrowError();
expect(() => new FileUploadBuilder().setCustomId('file_upload').setId(-3).toJSON()).toThrowError();
expect(() => new FileUploadBuilder().setMaxValues(5).setMinValues(2).setId(10).toJSON()).toThrowError();
expect(() => new FileUploadBuilder().setCustomId('file_upload').setMaxValues(500).toJSON()).toThrowError();
expect(() =>
new FileUploadBuilder().setCustomId('file_upload').setMinValues(500).setMaxValues(501).toJSON(),
).toThrowError();
expect(() => new FileUploadBuilder().setRequired(false).toJSON()).toThrowError();
});
test('API data equals toJSON().', () => {
const fileUploadData = {
type: ComponentType.FileUpload,
custom_id: 'file_upload',
min_values: 4,
max_values: 9,
required: false,
} satisfies APIFileUploadComponent;
expect(new FileUploadBuilder(fileUploadData).toJSON()).toEqual(fileUploadData);
expect(
new FileUploadBuilder().setCustomId('file_upload').setMinValues(4).setMaxValues(9).setRequired(false).toJSON(),
).toEqual(fileUploadData);
});
});

View File

@@ -100,11 +100,11 @@ describe('Text Input Components', () => {
.setPlaceholder('hello')
.setStyle(TextInputStyle.Paragraph)
.toJSON();
}).not.toThrowError();
}).toThrowError();
});
test('GIVEN valid input THEN valid JSON outputs are given', () => {
const textInputData = {
const textInputData: APITextInputComponent = {
type: ComponentType.TextInput,
label: 'label',
custom_id: 'custom id',
@@ -114,7 +114,7 @@ describe('Text Input Components', () => {
value: 'value',
required: false,
style: TextInputStyle.Paragraph,
} satisfies APITextInputComponent;
};
expect(new TextInputBuilder(textInputData).toJSON()).toEqual(textInputData);
expect(

View File

@@ -1,248 +0,0 @@
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,
});
});
});
});

View File

@@ -1,44 +0,0 @@
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 });
});
});
});

View File

@@ -1,150 +0,0 @@
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);
});
});
});

View File

@@ -1,191 +0,0 @@
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);
});
});
});

View File

@@ -1,35 +0,0 @@
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 });
});
});
});

View File

@@ -1,23 +0,0 @@
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' });
});
});
});

View File

@@ -1,69 +0,0 @@
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 });
});
});
});

View File

@@ -16,8 +16,8 @@ describe('Context Menu Commands', () => {
// Too short of a name
expect(() => ContextMenuCommandAssertions.validateName('')).toThrowError();
// This should be fine, even with trailing and leading spaces (API trims it).
expect(() => ContextMenuCommandAssertions.validateName(' 🩵 ABC 123 $%^& ')).not.toThrowError();
// Invalid characters used
expect(() => ContextMenuCommandAssertions.validateName('ABC123$%^&')).toThrowError();
// Too long of a name
expect(() =>
@@ -60,6 +60,8 @@ describe('Context Menu Commands', () => {
});
test('GIVEN invalid name THEN throw error', () => {
expect(() => getBuilder().setName('$$$')).toThrowError();
expect(() => getBuilder().setName(' ')).toThrowError();
});

View File

@@ -1,5 +1,4 @@
import {
ApplicationCommandType,
ApplicationIntegrationType,
ChannelType,
InteractionContextType,
@@ -134,10 +133,6 @@ 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()

View File

@@ -324,16 +324,12 @@ 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' },
{ name: '', value: '' },
]);
embed.addFields([{ name: 'foo', value: 'bar' }]);
expect(embed.toJSON()).toStrictEqual({
fields: [
{ name: 'foo', value: 'bar' },
{ name: 'foo', value: 'bar' },
{ name: '', value: '' },
],
});
});
@@ -385,24 +381,38 @@ describe('Embed', () => {
expect(() => embed.setFields(Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })))).toThrowError();
});
test('GIVEN invalid field amount THEN throws error', () => {
const embed = new EmbedBuilder();
describe('GIVEN invalid field amount THEN throws error', () => {
test('1', () => {
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();
});
});
test('GIVEN invalid field name length THEN throws error', () => {
const embed = new EmbedBuilder();
describe('GIVEN invalid field name THEN throws error', () => {
test('2', () => {
const embed = new EmbedBuilder();
expect(() => embed.addFields({ name: 'a'.repeat(257), value: 'bar' })).toThrowError();
expect(() => embed.addFields({ name: '', value: 'bar' })).toThrowError();
});
});
test('GIVEN invalid field value length THEN throws error', () => {
const embed = new EmbedBuilder();
describe('GIVEN invalid field name length THEN throws error', () => {
test('3', () => {
const embed = new EmbedBuilder();
expect(() => embed.addFields({ name: '', value: 'a'.repeat(1_025) })).toThrowError();
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();
});
});
});
});

View File

@@ -5,16 +5,13 @@ 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 %}\
({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }})\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}\
({{ self::remote_url() }}/tree/{{ version }})\
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
@@ -27,23 +24,14 @@ body = """
- {% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ 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 %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% if commit.breaking %}\
{% for footer in commit.footers %}\
{% if footer.breaking %}\
\n{% raw %} {% endraw %}- **{{ footer.token }}{{ footer.separator }}** {{ footer.value }}\
{% endif %}\
{% for breakingChange in commit.footers %}\
\n{% raw %} {% endraw %}- **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\
{% endfor %}\
{% endif %}\
{% endfor %}
{% 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
{% endfor %}\n
"""
trim = true
footer = ""
@@ -71,9 +59,5 @@ commit_parsers = [
filter_commits = true
tag_pattern = "@discordjs/builders@[0-9]*"
ignore_tags = ""
topo_order = false
topo_order = true
sort_commits = "newest"
[remote.github]
owner = "discordjs"
repo = "discord.js"

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@discordjs/builders",
"version": "1.13.0",
"version": "1.8.2",
"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 <me@vladfrangu.dev>",
"Vlad Frangu <kingdgrizzle@gmail.com>",
"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": "^4.0.0",
"discord-api-types": "^0.38.32",
"@sapphire/shapeshift": "^3.9.7",
"discord-api-types": "^0.37.119",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.4",
"tslib": "^2.6.3"
"tslib": "^2.6.2"
},
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^4.1.0",
"@types/node": "^16.18.105",
"@vitest/coverage-v8": "^2.0.5",
"@favware/cliff-jumper": "^3.0.3",
"@types/node": "16.18.60",
"@vitest/coverage-v8": "^1.6.0",
"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.3.3",
"tsup": "^8.2.4",
"turbo": "^2.0.14",
"typescript": "~5.5.4",
"vitest": "^2.0.5"
"prettier": "^3.3.0",
"tsup": "^8.1.0",
"turbo": "^1.13.3",
"typescript": "^5.4.5",
"vitest": "^1.6.0"
},
"engines": {
"node": ">=16.11.0"

View File

@@ -3,9 +3,9 @@
import {
type APIActionRowComponent,
ComponentType,
type APIComponentInMessageActionRow,
type APIComponentInModalActionRow,
type APIComponentInActionRow,
type APIMessageActionRowComponent,
type APIModalActionRowComponent,
type APIActionRowComponentTypes,
} from 'discord-api-types/v10';
import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js';
import { ComponentBuilder } from './Component.js';
@@ -18,6 +18,13 @@ 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.
*/
@@ -50,7 +57,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<APIComponentInMessageActionRow | APIComponentInModalActionRow>
APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent>
> {
/**
* The components within this action row.
@@ -91,7 +98,7 @@ export class ActionRowBuilder<ComponentType extends AnyComponentBuilder> extends
* .addComponents(button2, button3);
* ```
*/
public constructor({ components, ...data }: Partial<APIActionRowComponent<APIComponentInActionRow>> = {}) {
public constructor({ components, ...data }: Partial<APIActionRowComponent<APIActionRowComponentTypes>> = {}) {
super({ type: ComponentType.ActionRow, ...data });
this.components = (components?.map((component) => createComponentBuilder(component)) ?? []) as ComponentType[];
}

View File

@@ -3,49 +3,35 @@ import { ButtonStyle, ChannelType, type APIMessageComponentEmoji } from 'discord
import { isValidationEnabled } from '../util/validation.js';
import { StringSelectMenuOptionBuilder } from './selectMenu/StringSelectMenuOption.js';
export const idValidator = s
.number()
.safeInt()
.greaterThanOrEqual(1)
.lessThan(4_294_967_296) // 2^32 - 1
.setValidationEnabled(isValidationEnabled);
export const customIdValidator = s
.string()
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);
@@ -54,21 +40,18 @@ 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);
@@ -78,17 +61,16 @@ 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:'],
})

View File

@@ -1,22 +1,15 @@
import type { JSONEncodable } from '@discordjs/util';
import type {
APIActionRowComponent,
APIComponentInActionRow,
APIActionRowComponentTypes,
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<APIComponentInActionRow>
| APIComponentInActionRow
| APIMessageComponent
| APIModalComponent;
export type AnyAPIActionRowComponent = APIActionRowComponent<APIActionRowComponentTypes> | APIActionRowComponentTypes;
/**
* The base component builder that contains common symbols for all sorts of components.
@@ -49,22 +42,4 @@ 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;
}
}

View File

@@ -1,42 +1,18 @@
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 { FileUploadBuilder } from './fileUpload/FileUpload.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.
@@ -74,42 +50,6 @@ export interface MappedComponentTypes {
* 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;
/**
* The file upload component type is associated with a {@link FileUploadBuilder}.
*/
[ComponentType.FileUpload]: FileUploadBuilder;
}
/**
@@ -157,48 +97,8 @@ 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);
case ComponentType.FileUpload:
return new FileUploadBuilder(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);
}

View File

@@ -1,12 +0,0 @@
import { s } from '@sapphire/shapeshift';
import { ComponentType } from 'discord-api-types/v10';
import { customIdValidator, idValidator } from '../Assertions.js';
export const fileUploadPredicate = s.object({
type: s.literal(ComponentType.FileUpload),
id: idValidator.optional(),
custom_id: customIdValidator,
min_values: s.number().greaterThanOrEqual(0).lessThanOrEqual(10).optional(),
max_values: s.number().greaterThanOrEqual(1).lessThanOrEqual(10).optional(),
required: s.boolean().optional(),
});

View File

@@ -1,99 +0,0 @@
import { type APIFileUploadComponent, ComponentType } from 'discord-api-types/v10';
import { ComponentBuilder } from '../Component.js';
import { fileUploadPredicate } from './Assertions.js';
/**
* A builder that creates API-compatible JSON data for file uploads.
*/
export class FileUploadBuilder extends ComponentBuilder<APIFileUploadComponent> {
/**
* Creates a new file upload.
*
* @param data - The API data to create this file upload with
* @example
* Creating a file upload from an API data object:
* ```ts
* const fileUpload = new FileUploadBuilder({
* custom_id: "file_upload",
* min_values: 2,
* max_values: 5,
* });
* ```
* @example
* Creating a file upload using setters and API data:
* ```ts
* const fileUpload = new FileUploadBuilder({
* custom_id: "file_upload",
* min_values: 2,
* max_values: 5,
* }).setRequired();
* ```
*/
public constructor(data: Partial<APIFileUploadComponent> = {}) {
super({ type: ComponentType.FileUpload, ...data });
}
/**
* Sets the custom id for this file upload.
*
* @param customId - The custom id to use
*/
public setCustomId(customId: string) {
this.data.custom_id = customId;
return this;
}
/**
* Sets the minimum number of file uploads required.
*
* @param minValues - The minimum values that must be uploaded
*/
public setMinValues(minValues: number) {
this.data.min_values = minValues;
return this;
}
/**
* Clears the minimum values.
*/
public clearMinValues() {
this.data.min_values = undefined;
return this;
}
/**
* Sets the maximum number of file uploads required.
*
* @param maxValues - The maximum values that can be uploaded
*/
public setMaxValues(maxValues: number) {
this.data.max_values = maxValues;
return this;
}
/**
* Clears the maximum values.
*/
public clearMaxValues() {
this.data.max_values = undefined;
return this;
}
/**
* Sets whether this file upload is required.
*
* @param required - Whether this file upload is required
*/
public setRequired(required = true) {
this.data.required = required;
return this;
}
/**
* {@inheritDoc ComponentBuilder.toJSON}
*/
public toJSON(): APIFileUploadComponent {
fileUploadPredicate.parse(this.data);
return this.data as APIFileUploadComponent;
}
}

View File

@@ -1,31 +0,0 @@
import { s } from '@sapphire/shapeshift';
import { ComponentType } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation.js';
import { idValidator } from '../Assertions.js';
import { fileUploadPredicate } from '../fileUpload/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,
fileUploadPredicate,
]),
})
.setValidationEnabled(isValidationEnabled);

View File

@@ -1,213 +0,0 @@
import type {
APIChannelSelectComponent,
APIFileUploadComponent,
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 { FileUploadBuilder } from '../fileUpload/FileUpload.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
| FileUploadBuilder
| 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;
}
/**
* Sets a file upload component to this label.
*
* @param input - A function that returns a component builder or an already built builder
*/
public setFileUploadComponent(
input: APIFileUploadComponent | FileUploadBuilder | ((builder: FileUploadBuilder) => FileUploadBuilder),
): this {
this.data.component = resolveBuilder(input, FileUploadBuilder);
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;
}
}

View File

@@ -1,92 +0,0 @@
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.union([s.literal(SelectMenuDefaultValueType.Role), s.literal(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);

View File

@@ -1,7 +1,6 @@
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.
@@ -32,9 +31,9 @@ export abstract class BaseSelectMenuBuilder<
}
/**
* Sets the maximum values that can be selected in the select menu.
* Sets the maximum values that must be selected in the select menu.
*
* @param maxValues - The maximum values that can be selected
* @param maxValues - The maximum values that must be selected
*/
public setMaxValues(maxValues: number) {
this.data.max_values = minMaxValidator.parse(maxValues);
@@ -61,17 +60,6 @@ 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}
*/

View File

@@ -1,45 +1,27 @@
import { s } from '@sapphire/shapeshift';
import { ComponentType, TextInputStyle } from 'discord-api-types/v10';
import { TextInputStyle } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation.js';
import { customIdValidator, idValidator } from '../Assertions.js';
import { customIdValidator } from '../Assertions.js';
export const textInputStyleValidator = s.nativeEnum(TextInputStyle).setValidationEnabled(isValidationEnabled);
export const minLengthValidator = s
.number()
.int()
export const textInputStyleValidator = s.nativeEnum(TextInputStyle);
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().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()
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
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(45)
.setValidationEnabled(isValidationEnabled);
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) {
export function validateRequiredParameters(customId?: string, style?: TextInputStyle, label?: string) {
customIdValidator.parse(customId);
textInputStyleValidator.parse(style);
labelValidator.parse(label);
}

View File

@@ -30,7 +30,7 @@ export class TextInputBuilder
* ```ts
* const textInput = new TextInputBuilder({
* custom_id: 'a cool text input',
* placeholder: 'Type something',
* label: 'Type something',
* style: TextInputStyle.Short,
* });
* ```
@@ -38,7 +38,7 @@ export class TextInputBuilder
* Creating a text input using setters and API data:
* ```ts
* const textInput = new TextInputBuilder({
* placeholder: 'Type something else',
* label: 'Type something else',
* })
* .setCustomId('woah')
* .setStyle(TextInputStyle.Paragraph);
@@ -62,7 +62,6 @@ 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);
@@ -133,7 +132,7 @@ export class TextInputBuilder
* {@inheritDoc ComponentBuilder.toJSON}
*/
public toJSON(): APITextInputComponent {
validateRequiredParameters(this.data.custom_id, this.data.style);
validateRequiredParameters(this.data.custom_id, this.data.style, this.data.label);
return {
...this.data,

View File

@@ -1,72 +0,0 @@
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);
}

View File

@@ -1,239 +0,0 @@
/* 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;
}
}

View File

@@ -1,63 +0,0 @@
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;
}
}

View File

@@ -1,117 +0,0 @@
/* 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;
}
}

View File

@@ -1,90 +0,0 @@
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;
}
}

View File

@@ -1,153 +0,0 @@
/* 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;
}
}

View File

@@ -1,69 +0,0 @@
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;
}
}

View File

@@ -1,52 +0,0 @@
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;
}
}

View File

@@ -1,86 +0,0 @@
import type { APIThumbnailComponent } from 'discord-api-types/v10';
import { ComponentType } from 'discord-api-types/v10';
import { ComponentBuilder } from '../Component';
import { descriptionPredicate, spoilerPredicate, unfurledMediaItemPredicate } from './Assertions';
export class ThumbnailBuilder extends ComponentBuilder<APIThumbnailComponent> {
/**
* Creates a new thumbnail from API data.
*
* @param data - The API data to create this thumbnail with
* @example
* Creating a thumbnail from an API data object:
* ```ts
* const thumbnail = new ThumbnailBuilder({
* description: 'some text',
* media: {
* url: 'https://cdn.discordapp.com/embed/avatars/4.png',
* },
* });
* ```
* @example
* Creating a thumbnail using setters and API data:
* ```ts
* const thumbnail = new ThumbnailBuilder({
* media: {
* url: 'attachment://image.png',
* },
* })
* .setDescription('alt text');
* ```
*/
public constructor(data: Partial<APIThumbnailComponent> = {}) {
super({
type: ComponentType.Thumbnail,
...data,
media: data.media ? { url: data.media.url } : undefined,
});
}
/**
* Sets the description of this thumbnail.
*
* @param description - The description to use
*/
public setDescription(description: string) {
this.data.description = descriptionPredicate.parse(description);
return this;
}
/**
* Clears the description of this thumbnail.
*/
public clearDescription() {
this.data.description = undefined;
return this;
}
/**
* Sets the spoiler status of this thumbnail.
*
* @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 thumbnail.
*
* @param url - The URL to use
*/
public setURL(url: string) {
this.data.media = unfurledMediaItemPredicate.parse({ url });
return this;
}
/**
* {@inheritdoc ComponentBuilder.toJSON}
*/
public override toJSON(): APIThumbnailComponent {
unfurledMediaItemPredicate.parse(this.data.media);
return { ...this.data } as APIThumbnailComponent;
}
}

View File

@@ -34,22 +34,6 @@ export {
export * from './components/selectMenu/StringSelectMenuOption.js';
export * from './components/selectMenu/UserSelectMenu.js';
export * from './components/fileUpload/FileUpload.js';
export * as FileUploadAssertions from './components/fileUpload/Assertions.js';
export * from './components/label/Label.js';
export * as LabelAssertions from './components/label/Assertions.js';
export * as ComponentsV2Assertions from './components/v2/Assertions.js';
export * from './components/v2/Container.js';
export * from './components/v2/File.js';
export * from './components/v2/MediaGallery.js';
export * from './components/v2/MediaGalleryItem.js';
export * from './components/v2/Section.js';
export * from './components/v2/Separator.js';
export * from './components/v2/TextDisplay.js';
export * from './components/v2/Thumbnail.js';
export * as SlashCommandAssertions from './interactions/slashCommands/Assertions.js';
export * from './interactions/slashCommands/SlashCommandBuilder.js';
export * from './interactions/slashCommands/SlashCommandSubcommands.js';

View File

@@ -3,16 +3,16 @@ import { ApplicationCommandType, ApplicationIntegrationType, InteractionContextT
import { isValidationEnabled } from '../../util/validation.js';
import type { ContextMenuCommandType } from './ContextMenuCommandBuilder.js';
const namePredicate = s
.string()
const namePredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(32)
.regex(/\S/)
// eslint-disable-next-line prefer-named-capture-group
.regex(/^( *[\p{P}\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}]+ *)+$/u)
.setValidationEnabled(isValidationEnabled);
const typePredicate = s
.union([s.literal(ApplicationCommandType.User), s.literal(ApplicationCommandType.Message)])
.union(s.literal(ApplicationCommandType.User), s.literal(ApplicationCommandType.Message))
.setValidationEnabled(isValidationEnabled);
const booleanPredicate = s.boolean();
const booleanPredicate = s.boolean;
export function validateDefaultPermission(value: unknown): asserts value is boolean {
booleanPredicate.parse(value);
@@ -34,22 +34,17 @@ export function validateRequiredParameters(name: string, type: number) {
validateType(type);
}
const dmPermissionPredicate = s.boolean().nullish();
const dmPermissionPredicate = s.boolean.nullish;
export function validateDMPermission(value: unknown): asserts value is boolean | null | undefined {
dmPermissionPredicate.parse(value);
}
const memberPermissionPredicate = s
.union([
s.bigint().transform((value) => value.toString()),
s
.number()
.safeInt()
.transform((value) => value.toString()),
s.string().regex(/^\d+$/),
])
.nullish();
const memberPermissionPredicate = s.union(
s.bigint.transform((value) => value.toString()),
s.number.safeInt.transform((value) => value.toString()),
s.string.regex(/^\d+$/),
).nullish;
export function validateDefaultMemberPermissions(permissions: unknown) {
return memberPermissionPredicate.parse(permissions);

View File

@@ -67,8 +67,6 @@ export class ContextMenuCommandBuilder {
*
* @remarks
* By default, commands are visible. This property is only for global commands.
* @deprecated
* Use {@link ContextMenuCommandBuilder.contexts} instead.
*/
public readonly dm_permission: boolean | undefined = undefined;
@@ -169,7 +167,6 @@ export class ContextMenuCommandBuilder {
* By default, commands are visible. This method is only for global commands.
* @param enabled - Whether the command should be enabled in direct messages
* @see {@link https://discord.com/developers/docs/interactions/application-commands#permissions}
* @deprecated Use {@link ContextMenuCommandBuilder.setContexts} instead.
*/
public setDMPermission(enabled: boolean | null | undefined) {
// Assert the value matches the conditions

View File

@@ -1,25 +1,21 @@
import { s } from '@sapphire/shapeshift';
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow.js';
import { customIdValidator } from '../../components/Assertions.js';
import { LabelBuilder } from '../../components/label/Label.js';
import { TextDisplayBuilder } from '../../components/v2/TextDisplay.js';
import { isValidationEnabled } from '../../util/validation.js';
export const titleValidator = s
.string()
export const titleValidator = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(45)
.setValidationEnabled(isValidationEnabled);
export const componentsValidator = s
.union([s.instance(ActionRowBuilder), s.instance(LabelBuilder), s.instance(TextDisplayBuilder)])
.array()
.lengthGreaterThanOrEqual(1)
.instance(ActionRowBuilder)
.array.lengthGreaterThanOrEqual(1)
.setValidationEnabled(isValidationEnabled);
export function validateRequiredParameters(
customId?: string,
title?: string,
components?: (ActionRowBuilder<ModalActionRowComponentBuilder> | LabelBuilder | TextDisplayBuilder)[],
components?: ActionRowBuilder<ModalActionRowComponentBuilder>[],
) {
customIdValidator.parse(customId);
titleValidator.parse(title);

View File

@@ -2,20 +2,13 @@
import type { JSONEncodable } from '@discordjs/util';
import type {
APITextInputComponent,
APIActionRowComponent,
APIComponentInModalActionRow,
APILabelComponent,
APIModalActionRowComponent,
APIModalInteractionResponseCallbackData,
APITextDisplayComponent,
} from 'discord-api-types/v10';
import { ComponentType } from 'discord-api-types/v10';
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow.js';
import { customIdValidator } from '../../components/Assertions.js';
import { createComponentBuilder, resolveBuilder } from '../../components/Components.js';
import { LabelBuilder } from '../../components/label/Label.js';
import { TextInputBuilder } from '../../components/textInput/TextInput.js';
import { TextDisplayBuilder } from '../../components/v2/TextDisplay.js';
import { createComponentBuilder } from '../../components/Components.js';
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
import { titleValidator, validateRequiredParameters } from './Assertions.js';
@@ -31,8 +24,7 @@ export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCa
/**
* The components within this modal.
*/
public readonly components: (ActionRowBuilder<ModalActionRowComponentBuilder> | LabelBuilder | TextDisplayBuilder)[] =
[];
public readonly components: ActionRowBuilder<ModalActionRowComponentBuilder>[] = [];
/**
* Creates a new modal from API data.
@@ -41,10 +33,8 @@ export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCa
*/
public constructor({ components, ...data }: Partial<APIModalInteractionResponseCallbackData> = {}) {
this.data = { ...data };
this.components = (components?.map((component) => createComponentBuilder(component)) ?? []) as (
| ActionRowBuilder<ModalActionRowComponentBuilder>
| LabelBuilder
)[];
this.components = (components?.map((component) => createComponentBuilder(component)) ??
[]) as ActionRowBuilder<ModalActionRowComponentBuilder>[];
}
/**
@@ -71,182 +61,28 @@ export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCa
* Adds components to this modal.
*
* @param components - The components to add
* @deprecated Use {@link ModalBuilder.addLabelComponents} or {@link ModalBuilder.addTextDisplayComponents} instead
*/
public addComponents(
...components: RestOrArray<
| ActionRowBuilder<ModalActionRowComponentBuilder>
| APIActionRowComponent<APIComponentInModalActionRow>
| APILabelComponent
| APITextDisplayComponent
| APITextInputComponent
| LabelBuilder
| TextDisplayBuilder
| TextInputBuilder
ActionRowBuilder<ModalActionRowComponentBuilder> | APIActionRowComponent<APIModalActionRowComponent>
>
) {
this.components.push(
...normalizeArray(components).map((component, idx) => {
if (
component instanceof ActionRowBuilder ||
component instanceof LabelBuilder ||
component instanceof TextDisplayBuilder
) {
return component;
}
// Deprecated support
if (component instanceof TextInputBuilder) {
return new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(component);
}
if ('type' in component) {
if (component.type === ComponentType.ActionRow) {
return new ActionRowBuilder<ModalActionRowComponentBuilder>(component);
}
if (component.type === ComponentType.Label) {
return new LabelBuilder(component);
}
if (component.type === ComponentType.TextDisplay) {
return new TextDisplayBuilder(component);
}
// Deprecated, should go in a label component
if (component.type === ComponentType.TextInput) {
return new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
new TextInputBuilder(component),
);
}
}
throw new TypeError(`Invalid component passed in ModalBuilder.addComponents at index ${idx}!`);
}),
...normalizeArray(components).map((component) =>
component instanceof ActionRowBuilder
? component
: new ActionRowBuilder<ModalActionRowComponentBuilder>(component),
),
);
return this;
}
/**
* Adds label components to this modal.
*
* @param components - The components to add
*/
public addLabelComponents(
...components: RestOrArray<APILabelComponent | LabelBuilder | ((builder: LabelBuilder) => LabelBuilder)>
) {
const normalized = normalizeArray(components);
const resolved = normalized.map((label) => resolveBuilder(label, LabelBuilder));
this.components.push(...resolved);
return this;
}
/**
* Adds text display components to this modal.
*
* @param components - The components to add
*/
public addTextDisplayComponents(
...components: RestOrArray<
APITextDisplayComponent | TextDisplayBuilder | ((builder: TextDisplayBuilder) => TextDisplayBuilder)
>
) {
const normalized = normalizeArray(components);
const resolved = normalized.map((row) => resolveBuilder(row, TextDisplayBuilder));
this.components.push(...resolved);
return this;
}
/**
* Adds action rows to this modal.
*
* @param components - The components to add
* @deprecated Use {@link ModalBuilder.addLabelComponents} instead
*/
public addActionRowComponents(
...components: RestOrArray<
| ActionRowBuilder<ModalActionRowComponentBuilder>
| APIActionRowComponent<APIComponentInModalActionRow>
| ((
builder: ActionRowBuilder<ModalActionRowComponentBuilder>,
) => ActionRowBuilder<ModalActionRowComponentBuilder>)
>
) {
const normalized = normalizeArray(components);
const resolved = normalized.map((row) => resolveBuilder(row, ActionRowBuilder<ModalActionRowComponentBuilder>));
this.components.push(...resolved);
return this;
}
/**
* Sets the labels for this modal.
*
* @param components - The components to set
*/
public setLabelComponents(
...components: RestOrArray<APILabelComponent | LabelBuilder | ((builder: LabelBuilder) => LabelBuilder)>
) {
const normalized = normalizeArray(components);
this.spliceLabelComponents(0, this.components.length, ...normalized);
return this;
}
/**
* Removes, replaces, or inserts labels for this modal.
*
* @remarks
* This method behaves similarly
* to {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/splice | Array.prototype.splice()}.
* The maximum amount of labels that can be added is 5.
*
* It's useful for modifying and adjusting order of the already-existing labels of a modal.
* @example
* Remove the first label:
* ```ts
* modal.spliceLabelComponents(0, 1);
* ```
* @example
* Remove the first n labels:
* ```ts
* const n = 4;
* modal.spliceLabelComponents(0, n);
* ```
* @example
* Remove the last label:
* ```ts
* modal.spliceLabelComponents(-1, 1);
* ```
* @param index - The index to start at
* @param deleteCount - The number of labels to remove
* @param labels - The replacing label objects
*/
public spliceLabelComponents(
index: number,
deleteCount: number,
...labels: (APILabelComponent | LabelBuilder | ((builder: LabelBuilder) => LabelBuilder))[]
): this {
const resolved = labels.map((label) => resolveBuilder(label, LabelBuilder));
this.components.splice(index, deleteCount, ...resolved);
return this;
}
/**
* Sets components for this modal.
*
* @param components - The components to set
* @deprecated Use {@link ModalBuilder.setLabelComponents} instead
*/
public setComponents(
...components: RestOrArray<ActionRowBuilder<ModalActionRowComponentBuilder> | LabelBuilder | TextDisplayBuilder>
) {
public setComponents(...components: RestOrArray<ActionRowBuilder<ModalActionRowComponentBuilder>>) {
this.components.splice(0, this.components.length, ...normalizeArray(components));
return this;
}

View File

@@ -11,8 +11,7 @@ import type { ToAPIApplicationCommandOptions } from './SlashCommandBuilder.js';
import type { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder } from './SlashCommandSubcommands.js';
import type { ApplicationCommandOptionBase } from './mixins/ApplicationCommandOptionBase.js';
const namePredicate = s
.string()
const namePredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(32)
.regex(/^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u)
@@ -22,8 +21,7 @@ export function validateName(name: unknown): asserts name is string {
namePredicate.parse(name);
}
const descriptionPredicate = s
.string()
const descriptionPredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(100)
.setValidationEnabled(isValidationEnabled);
@@ -33,7 +31,7 @@ export function validateDescription(description: unknown): asserts description i
descriptionPredicate.parse(description);
}
const maxArrayLengthPredicate = s.unknown().array().lengthLessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
const maxArrayLengthPredicate = s.unknown.array.lengthLessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
export function validateLocale(locale: unknown) {
return localePredicate.parse(locale);
}
@@ -57,7 +55,7 @@ export function validateRequiredParameters(
validateMaxOptionsLength(options);
}
const booleanPredicate = s.boolean();
const booleanPredicate = s.boolean;
export function validateDefaultPermission(value: unknown): asserts value is boolean {
booleanPredicate.parse(value);
@@ -67,7 +65,7 @@ export function validateRequired(required: unknown): asserts required is boolean
booleanPredicate.parse(required);
}
const choicesLengthPredicate = s.number().lessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
const choicesLengthPredicate = s.number.lessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
export function validateChoicesLength(amountAdding: number, choices?: APIApplicationCommandOptionChoice[]): void {
choicesLengthPredicate.parse((choices?.length ?? 0) + amountAdding);
@@ -80,31 +78,24 @@ export function assertReturnOfBuilder<
}
export const localizationMapPredicate = s
.object<LocalizationMap>(Object.fromEntries(Object.values(Locale).map((locale) => [locale, s.string().nullish()])))
.strict()
.nullish()
.setValidationEnabled(isValidationEnabled);
.object<LocalizationMap>(Object.fromEntries(Object.values(Locale).map((locale) => [locale, s.string.nullish])))
.strict.nullish.setValidationEnabled(isValidationEnabled);
export function validateLocalizationMap(value: unknown): asserts value is LocalizationMap {
localizationMapPredicate.parse(value);
}
const dmPermissionPredicate = s.boolean().nullish();
const dmPermissionPredicate = s.boolean.nullish;
export function validateDMPermission(value: unknown): asserts value is boolean | null | undefined {
dmPermissionPredicate.parse(value);
}
const memberPermissionPredicate = s
.union([
s.bigint().transform((value) => value.toString()),
s
.number()
.safeInt()
.transform((value) => value.toString()),
s.string().regex(/^\d+$/),
])
.nullish();
const memberPermissionPredicate = s.union(
s.bigint.transform((value) => value.toString()),
s.number.safeInt.transform((value) => value.toString()),
s.string.regex(/^\d+$/),
).nullish;
export function validateDefaultMemberPermissions(permissions: unknown) {
return memberPermissionPredicate.parse(permissions);

View File

@@ -63,8 +63,6 @@ export class SlashCommandBuilder {
*
* @remarks
* By default, commands are visible. This property is only for global commands.
* @deprecated
* Use {@link SlashCommandBuilder.contexts} instead.
*/
public readonly dm_permission: boolean | undefined = undefined;

View File

@@ -26,7 +26,7 @@ const allowedChannelTypes = [
*/
export type ApplicationCommandOptionAllowedChannelTypes = (typeof allowedChannelTypes)[number];
const channelTypesPredicate = s.array(s.union(allowedChannelTypes.map((type) => s.literal(type))));
const channelTypesPredicate = s.array(s.union(...allowedChannelTypes.map((type) => s.literal(type))));
/**
* This mixin holds channel type symbols used for options.

View File

@@ -1,7 +1,7 @@
import { s } from '@sapphire/shapeshift';
import type { ApplicationCommandOptionType } from 'discord-api-types/v10';
const booleanPredicate = s.boolean();
const booleanPredicate = s.boolean;
/**
* This mixin holds choices and autocomplete symbols used for options.

View File

@@ -3,15 +3,13 @@ import { ApplicationCommandOptionType, type APIApplicationCommandOptionChoice }
import { normalizeArray, type RestOrArray } from '../../../util/normalizeArray.js';
import { localizationMapPredicate, validateChoicesLength } from '../Assertions.js';
const stringPredicate = s.string().lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
const numberPredicate = s.number().greaterThan(Number.NEGATIVE_INFINITY).lessThan(Number.POSITIVE_INFINITY);
const choicesPredicate = s
.object({
name: stringPredicate,
name_localizations: localizationMapPredicate,
value: s.union([stringPredicate, numberPredicate]),
})
.array();
const stringPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
const numberPredicate = s.number.greaterThan(Number.NEGATIVE_INFINITY).lessThan(Number.POSITIVE_INFINITY);
const choicesPredicate = s.object({
name: stringPredicate,
name_localizations: localizationMapPredicate,
value: s.union(stringPredicate, numberPredicate),
}).array;
/**
* This mixin holds choices and autocomplete symbols used for options.

View File

@@ -1,10 +1,9 @@
import {
ApplicationCommandType,
type ApplicationIntegrationType,
type InteractionContextType,
type LocalizationMap,
type Permissions,
type RESTPostAPIChatInputApplicationCommandsJSONBody,
import type {
ApplicationIntegrationType,
InteractionContextType,
LocalizationMap,
Permissions,
RESTPostAPIChatInputApplicationCommandsJSONBody,
} from 'discord-api-types/v10';
import type { RestOrArray } from '../../../util/normalizeArray.js';
import { normalizeArray } from '../../../util/normalizeArray.js';
@@ -43,9 +42,6 @@ export class SharedSlashCommand {
public readonly default_member_permissions: Permissions | null | undefined = undefined;
/**
* @deprecated Use {@link SharedSlashCommand.contexts} instead.
*/
public readonly dm_permission: boolean | undefined = undefined;
public readonly integration_types?: ApplicationIntegrationType[];
@@ -116,8 +112,6 @@ export class SharedSlashCommand {
* By default, commands are visible. This method is only for global commands.
* @param enabled - Whether the command should be enabled in direct messages
* @see {@link https://discord.com/developers/docs/interactions/application-commands#permissions}
* @deprecated
* Use {@link SharedSlashCommand.setContexts} instead.
*/
public setDMPermission(enabled: boolean | null | undefined) {
// Assert the value matches the conditions
@@ -155,7 +149,6 @@ export class SharedSlashCommand {
return {
...this,
type: ApplicationCommandType.ChatInput,
options: this.options.map((option) => option.toJSON()),
};
}

View File

@@ -6,7 +6,7 @@ import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOption
import { ApplicationCommandOptionWithAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithAutocompleteMixin.js';
import { ApplicationCommandOptionWithChoicesMixin } from '../mixins/ApplicationCommandOptionWithChoicesMixin.js';
const numberValidator = s.number().int();
const numberValidator = s.number.int;
/**
* A slash command integer option.

View File

@@ -6,7 +6,7 @@ import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOption
import { ApplicationCommandOptionWithAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithAutocompleteMixin.js';
import { ApplicationCommandOptionWithChoicesMixin } from '../mixins/ApplicationCommandOptionWithChoicesMixin.js';
const numberValidator = s.number();
const numberValidator = s.number;
/**
* A slash command number option.

View File

@@ -5,8 +5,8 @@ import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOption
import { ApplicationCommandOptionWithAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithAutocompleteMixin.js';
import { ApplicationCommandOptionWithChoicesMixin } from '../mixins/ApplicationCommandOptionWithChoicesMixin.js';
const minLengthValidator = s.number().greaterThanOrEqual(0).lessThanOrEqual(6_000);
const maxLengthValidator = s.number().greaterThanOrEqual(1).lessThanOrEqual(6_000);
const minLengthValidator = s.number.greaterThanOrEqual(0).lessThanOrEqual(6_000);
const maxLengthValidator = s.number.greaterThanOrEqual(1).lessThanOrEqual(6_000);
/**
* A slash command string option.

View File

@@ -2,11 +2,17 @@ import { s } from '@sapphire/shapeshift';
import type { APIEmbedField } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation.js';
export const fieldNamePredicate = s.string().lengthLessThanOrEqual(256).setValidationEnabled(isValidationEnabled);
export const fieldNamePredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(256)
.setValidationEnabled(isValidationEnabled);
export const fieldValuePredicate = s.string().lengthLessThanOrEqual(1_024).setValidationEnabled(isValidationEnabled);
export const fieldValuePredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(1_024)
.setValidationEnabled(isValidationEnabled);
export const fieldInlinePredicate = s.boolean().optional();
export const fieldInlinePredicate = s.boolean.optional;
export const embedFieldPredicate = s
.object({
@@ -16,34 +22,27 @@ export const embedFieldPredicate = s
})
.setValidationEnabled(isValidationEnabled);
export const embedFieldsArrayPredicate = embedFieldPredicate.array().setValidationEnabled(isValidationEnabled);
export const embedFieldsArrayPredicate = embedFieldPredicate.array.setValidationEnabled(isValidationEnabled);
export const fieldLengthPredicate = s.number().lessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
export const fieldLengthPredicate = s.number.lessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
export function validateFieldLength(amountAdding: number, fields?: APIEmbedField[]): void {
fieldLengthPredicate.parse((fields?.length ?? 0) + amountAdding);
}
export const authorNamePredicate = fieldNamePredicate
.lengthGreaterThanOrEqual(1)
.nullable()
.setValidationEnabled(isValidationEnabled);
export const authorNamePredicate = fieldNamePredicate.nullable.setValidationEnabled(isValidationEnabled);
export const imageURLPredicate = s
.string()
export const imageURLPredicate = s.string
.url({
allowedProtocols: ['http:', 'https:', 'attachment:'],
})
.nullish()
.setValidationEnabled(isValidationEnabled);
.nullish.setValidationEnabled(isValidationEnabled);
export const urlPredicate = s
.string()
export const urlPredicate = s.string
.url({
allowedProtocols: ['http:', 'https:'],
})
.nullish()
.setValidationEnabled(isValidationEnabled);
.nullish.setValidationEnabled(isValidationEnabled);
export const embedAuthorPredicate = s
.object({
@@ -53,34 +52,25 @@ export const embedAuthorPredicate = s
})
.setValidationEnabled(isValidationEnabled);
export const RGBPredicate = s
.number()
.int()
export const RGBPredicate = s.number.int
.greaterThanOrEqual(0)
.lessThanOrEqual(255)
.setValidationEnabled(isValidationEnabled);
export const colorPredicate = s
.number()
.int()
export const colorPredicate = s.number.int
.greaterThanOrEqual(0)
.lessThanOrEqual(0xffffff)
.or(s.tuple([RGBPredicate, RGBPredicate, RGBPredicate]))
.nullable()
.setValidationEnabled(isValidationEnabled);
.nullable.setValidationEnabled(isValidationEnabled);
export const descriptionPredicate = s
.string()
export const descriptionPredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(4_096)
.nullable()
.setValidationEnabled(isValidationEnabled);
.nullable.setValidationEnabled(isValidationEnabled);
export const footerTextPredicate = s
.string()
export const footerTextPredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(2_048)
.nullable()
.setValidationEnabled(isValidationEnabled);
.nullable.setValidationEnabled(isValidationEnabled);
export const embedFooterPredicate = s
.object({
@@ -89,9 +79,6 @@ export const embedFooterPredicate = s
})
.setValidationEnabled(isValidationEnabled);
export const timestampPredicate = s.union([s.number(), s.date()]).nullable().setValidationEnabled(isValidationEnabled);
export const timestampPredicate = s.union(s.number, s.date).nullable.setValidationEnabled(isValidationEnabled);
export const titlePredicate = fieldNamePredicate
.lengthGreaterThanOrEqual(1)
.nullable()
.setValidationEnabled(isValidationEnabled);
export const titlePredicate = fieldNamePredicate.nullable.setValidationEnabled(isValidationEnabled);

View File

@@ -125,7 +125,7 @@ export class EmbedBuilder {
*
* @remarks
* This method behaves similarly
* to {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/splice | Array.prototype.splice()}.
* to {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice | Array.prototype.splice()}.
* The maximum amount of fields that can be added is 25.
*
* It's useful for modifying and adjusting order of the already-existing fields of an embed.

View File

@@ -2,20 +2,6 @@
All notable changes to this project will be documented in this file.
# [@discordjs/collection@2.1.1](https://github.com/discordjs/discord.js/compare/@discordjs/collection@2.1.0...@discordjs/collection@2.1.1) - (2024-09-01)
## Bug Fixes
- **build:** Update to support strictBuiltinIteratorReturn (#10394) ([bf83db9](https://github.com/discordjs/discord.js/commit/bf83db9480e9f31d5dadee38a2d053a543150776))
## Testing
- Complete collection coverage (#10380) ([d8e94d8](https://github.com/discordjs/discord.js/commit/d8e94d8f10367d165d15904f7c7a31165842f9ec))
## Typings
- **collection:** Reduce* method signatures (#10405) ([6b38335](https://github.com/discordjs/discord.js/commit/6b383350a6de6d26b62cf62f619c89ffb0d6b0d1))
# [@discordjs/collection@2.1.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@2.0.0...@discordjs/collection@2.1.0) - (2024-05-04)
## Bug Fixes
@@ -194,6 +180,174 @@ 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/collection@1.5.3](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.5.2...@discordjs/collection@1.5.3) - (2023-08-17)
## Documentation
- Update Node.js requirement to 16.11.0 (#9764) ([188877c](https://github.com/discordjs/discord.js/commit/188877c50af70f0d5cffb246620fa277435c6ce6))
# [@discordjs/collection@1.5.2](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.5.1...@discordjs/collection@1.5.2) - (2023-07-31)
## Refactor
- **collection:** Reduce `reduce`'s code (#9581) ([b85a3f2](https://github.com/discordjs/discord.js/commit/b85a3f2ddee8fc5974749b95fc07389a03093df2))
# [@discordjs/collection@1.5.1](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.5.0...@discordjs/collection@1.5.1) - (2023-05-01)
## Bug Fixes
- Fix external links (#9313) ([a7425c2](https://github.com/discordjs/discord.js/commit/a7425c29c4f23f1b31f4c6a463107ca9eb7fd7e2))
## Documentation
- Generate static imports for types with api-extractor ([98a76db](https://github.com/discordjs/discord.js/commit/98a76db482879f79d6bb2fb2e5fc65ac2c34e2d9))
# [@discordjs/collection@1.5.2](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.5.1...@discordjs/collection@1.5.2) - (2023-07-31)
## Refactor
- **collection:** Reduce `reduce`'s code (#9581) ([b85a3f2](https://github.com/discordjs/discord.js/commit/b85a3f2ddee8fc5974749b95fc07389a03093df2))
# [@discordjs/collection@1.5.1](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.5.0...@discordjs/collection@1.5.1) - (2023-05-01)
## Bug Fixes
- Fix external links (#9313) ([a7425c2](https://github.com/discordjs/discord.js/commit/a7425c29c4f23f1b31f4c6a463107ca9eb7fd7e2))
## Documentation
- Generate static imports for types with api-extractor ([98a76db](https://github.com/discordjs/discord.js/commit/98a76db482879f79d6bb2fb2e5fc65ac2c34e2d9))
# [@discordjs/collection@1.5.1](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.5.0...@discordjs/collection@1.5.1) - (2023-05-01)
## Bug Fixes
- Fix external links (#9313) ([a7425c2](https://github.com/discordjs/discord.js/commit/a7425c29c4f23f1b31f4c6a463107ca9eb7fd7e2))
## Documentation
- Generate static imports for types with api-extractor ([98a76db](https://github.com/discordjs/discord.js/commit/98a76db482879f79d6bb2fb2e5fc65ac2c34e2d9))
# [@discordjs/collection@1.5.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.4.0...@discordjs/collection@1.5.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))
## Refactor
- **collection:** Fix/silence linter warnings (#9266) ([d6f4e60](https://github.com/discordjs/discord.js/commit/d6f4e60efd1a1796fc84dbbfbac4f9790e480a1c))
# [@discordjs/collection@1.4.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.3.0...@discordjs/collection@1.4.0) - (2023-03-12)
## Documentation
- 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))
## Refactor
- Compare with `undefined` directly (#9191) ([869153c](https://github.com/discordjs/discord.js/commit/869153c3fdf155783e7c0ecebd3627b087c3a026))
# [@discordjs/collection@1.3.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.2.0...@discordjs/collection@1.3.0) - (2022-11-28)
## Bug Fixes
- Pin @types/node version ([9d8179c](https://github.com/discordjs/discord.js/commit/9d8179c6a78e1c7f9976f852804055964d5385d4))
## Features
- Add `Collection#subtract()` (#8393) ([291f36c](https://github.com/discordjs/discord.js/commit/291f36cd736b5dea058145a1335bf7c78ec1d81d))
# [@discordjs/collection@1.2.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.1.0...@discordjs/collection@1.2.0) - (2022-10-08)
## Bug Fixes
- Footer / sidebar / deprecation alert ([ba3e0ed](https://github.com/discordjs/discord.js/commit/ba3e0ed348258fe8e51eefb4aa7379a1230616a9))
## Documentation
- Change name (#8604) ([dd5a089](https://github.com/discordjs/discord.js/commit/dd5a08944c258a847fc4377f1d5e953264ab47d0))
- Remove xml tag from collection#find (#8550) ([4032457](https://github.com/discordjs/discord.js/commit/40324574ebea9894cadcc967e0db0e4e21d62768))
## Features
- Web-components (#8715) ([0ac3e76](https://github.com/discordjs/discord.js/commit/0ac3e766bd9dbdeb106483fa4bb085d74de346a2))
## Refactor
- 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))
## Typings
- **Collection:** Make fn return type unknown (#8676) ([822b7f2](https://github.com/discordjs/discord.js/commit/822b7f234af053c8f917b0a998b82abfccd33801))
# [@discordjs/collection@1.1.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@1.0.1...@discordjs/collection@1.1.0) - (2022-08-22)
## Bug Fixes
- Use proper format for `@link` text (#8384) ([2655639](https://github.com/discordjs/discord.js/commit/26556390a3800e954974a00c1328ff47d3e67e9a))
## Documentation
- Fence examples in codeblocks ([193b252](https://github.com/discordjs/discord.js/commit/193b252672440a860318d3c2968aedd9cb88e0ce))
- Use link tags (#8382) ([5494791](https://github.com/discordjs/discord.js/commit/549479131318c659f86f0eb18578d597e22522d3))
## 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))
## Refactor
- **website:** Adjust typography (#8503) ([0f83402](https://github.com/discordjs/discord.js/commit/0f834029850d2448981596cf082ff59917018d66))
- Docs design (#8487) ([4ab1d09](https://github.com/discordjs/discord.js/commit/4ab1d09997a18879a9eb9bda39df6f15aa22557e))
# [@discordjs/collection@0.8.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@0.7.0...@discordjs/collection@0.8.0) - (2022-07-17)
## Bug Fixes
- **Collection:** Make error messages consistent (#8224) ([5bd6b28](https://github.com/discordjs/discord.js/commit/5bd6b28b3ebfced1cb9d23e83bd7c0def7a12404))
- Check for function type (#8064) ([3bb9c0e](https://github.com/discordjs/discord.js/commit/3bb9c0e5c37311044ff41761b572ac4f91cda57c))
## Documentation
- Add codecov coverage badge to readmes (#8226) ([f6db285](https://github.com/discordjs/discord.js/commit/f6db285c073898a749fe4591cbd4463d1896daf5))
## Features
- 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))
- 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))
## Refactor
- **collection:** Remove `default` property (#8055) ([c8f1690](https://github.com/discordjs/discord.js/commit/c8f1690896f55f06e05a83704262783cfc2bb91d))
- **collection:** Remove default export (#8053) ([16810f3](https://github.com/discordjs/discord.js/commit/16810f3e410bf35ed7e6e7412d517ea74c792c5d))
- Move all the config files to root (#8033) ([769ea0b](https://github.com/discordjs/discord.js/commit/769ea0bfe78c4f1d413c6b397c604ffe91e39c6a))
## Testing
- **collection:** Improve coverage (#8222) ([a51f721](https://github.com/discordjs/discord.js/commit/a51f7215eca67a0f46fba8b2d706f7ec6f6dc228))
# [@discordjs/collection@0.7.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@0.6.0...@discordjs/collection@0.7.0) - (2022-06-04)
## Styling
- Cleanup tests and tsup configs ([6b8ef20](https://github.com/discordjs/discord.js/commit/6b8ef20cb3af5b5cfd176dd0aa0a1a1e98551629))
# [@discordjs/collection@0.6.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@0.5.0...@discordjs/collection@0.6.0) - (2022-04-17)
## Features

View File

@@ -9,8 +9,7 @@
<a href="https://www.npmjs.com/package/@discordjs/collection"><img src="https://img.shields.io/npm/v/@discordjs/collection.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/@discordjs/collection"><img src="https://img.shields.io/npm/dt/@discordjs/collection.svg?maxAge=3600" alt="npm downloads" /></a>
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Build status" /></a>
<a href="https://github.com/discordjs/discord.js/commits/main/packages/collection"><img alt="Last commit." src="https://img.shields.io/github/last-commit/discordjs/discord.js?logo=github&logoColor=ffffff&path=packages%2Fcollection"></a>
<a href="https://codecov.io/gh/discordjs/discord.js"><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2&flag=collection" alt="Code coverage" /></a>
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2&flag=collection" alt="Code coverage" /></a>
</p>
<p>
<a href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"><img src="https://raw.githubusercontent.com/discordjs/discord.js/main/.github/powered-by-vercel.svg" alt="Vercel" /></a>

View File

@@ -713,57 +713,14 @@ describe('reduce() tests', () => {
expect<number>(sum).toStrictEqual(6);
});
test('reduce collection into a single value with different accumulator type', () => {
const str = coll.reduce((a, x) => a.concat(x.toString()), '');
expect<string>(str).toStrictEqual('123');
});
test('reduce empty collection with initial value', () => {
const coll = createCollection();
expect<number>(coll.reduce((a, x) => a + x, 0)).toStrictEqual(0);
expect(coll.reduce((a, x) => a + x, 0)).toStrictEqual(0);
});
test('reduce empty collection without initial value', () => {
const coll = createCollection();
expect(() => coll.reduce((a, x) => a + x)).toThrowError(
new TypeError('Reduce of empty collection with no initial value'),
);
});
});
describe('reduceRight() tests', () => {
const coll = createTestCollection();
test('throws if fn is not a function', () => {
// @ts-expect-error: Invalid function
expectInvalidFunctionError(() => coll.reduceRight());
// @ts-expect-error: Invalid function
expectInvalidFunctionError(() => coll.reduceRight(123), 123);
});
test('reduce collection into a single value with initial value', () => {
const sum = coll.reduceRight((a, x) => a + x, 0);
expect<number>(sum).toStrictEqual(6);
});
test('reduce collection into a single value without initial value', () => {
const sum = coll.reduceRight((a, x) => a + x);
expect<number>(sum).toStrictEqual(6);
});
test('reduce collection into a single value with different accumulator type', () => {
const str = coll.reduceRight((a, x) => a.concat(x.toString()), '');
expect<string>(str).toStrictEqual('321');
});
test('reduce empty collection with initial value', () => {
const coll = createCollection();
expect<number>(coll.reduceRight((a, x) => a + x, 0)).toStrictEqual(0);
});
test('reduce empty collection without initial value', () => {
const coll = createCollection();
expect(() => coll.reduceRight((a, x) => a + x)).toThrowError(
expect(() => coll.reduce((a: number, x) => a + x)).toThrowError(
new TypeError('Reduce of empty collection with no initial value'),
);
});
@@ -1056,3 +1013,31 @@ describe('findLastKey() tests', () => {
}, null);
});
});
describe('reduceRight() tests', () => {
const coll = createTestCollection();
test('throws if fn is not a function', () => {
// @ts-expect-error: Invalid function
expectInvalidFunctionError(() => coll.reduceRight());
// @ts-expect-error: Invalid function
expectInvalidFunctionError(() => coll.reduceRight(123), 123);
});
test('reduce collection into a single value with initial value', () => {
const sum = coll.reduceRight((a, x) => a + x, 0);
expect(sum).toStrictEqual(6);
});
test('reduce collection into a single value without initial value', () => {
const sum = coll.reduceRight<number>((a, x) => a + x);
expect(sum).toStrictEqual(6);
});
test('reduce empty collection without initial value', () => {
const coll = createCollection();
expect(() => coll.reduceRight((a: number, x) => a + x)).toThrowError(
new TypeError('Reduce of empty collection with no initial value'),
);
});
});

View File

@@ -5,16 +5,13 @@ 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 %}\
({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }})\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}\
({{ self::remote_url() }}/tree/{{ version }})\
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
@@ -27,23 +24,14 @@ body = """
- {% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ 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 %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% if commit.breaking %}\
{% for footer in commit.footers %}\
{% if footer.breaking %}\
\n{% raw %} {% endraw %}- **{{ footer.token }}{{ footer.separator }}** {{ footer.value }}\
{% endif %}\
{% for breakingChange in commit.footers %}\
\n{% raw %} {% endraw %}- **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\
{% endfor %}\
{% endif %}\
{% endfor %}
{% 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
{% endfor %}\n
"""
trim = true
footer = ""
@@ -71,9 +59,5 @@ commit_parsers = [
filter_commits = true
tag_pattern = "@discordjs/collection@[0-9]*"
ignore_tags = ""
topo_order = false
topo_order = true
sort_commits = "newest"
[remote.github]
owner = "discordjs"
repo = "discord.js"

Some files were not shown because too many files have changed in this diff Show More