mirror of
https://github.com/SethCohen/github-releases-to-discord
synced 2026-09-14 20:13:23 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26399c645d | ||
|
|
07c2e1c3e6 | ||
|
|
f184bc59c7 | ||
|
|
3449e38629 | ||
|
|
93d02ce871 | ||
|
|
5dc41089e6 | ||
|
|
5fd64bf266 | ||
|
|
60a5c60e64 | ||
|
|
925765f099 | ||
|
|
37afa88c8c | ||
|
|
735cca9de3 | ||
|
|
5ca1453a69 | ||
|
|
305fe9299d | ||
|
|
613ba269d7 | ||
|
|
940a1bd2d2 | ||
|
|
7de2fd1c2a | ||
|
|
97a481333d | ||
|
|
b7c20771bd | ||
|
|
8ca9da2ca8 | ||
|
|
254bf79196 | ||
|
|
1ce3bed332 | ||
|
|
933f3cffcf | ||
|
|
24b80abb6f | ||
|
|
f228faeb5e | ||
|
|
ba06d83352 | ||
|
|
ce7fc4a3be | ||
|
|
c240910f89 | ||
|
|
a12acb913f | ||
|
|
c8889535c4 | ||
|
|
c811a53b57 | ||
|
|
e67e0f9064 | ||
|
|
d76d7aafe4 | ||
|
|
a809bb6f0e | ||
|
|
998060befe | ||
|
|
27e9acb517 | ||
|
|
d019e5acd9 | ||
|
|
9e0e0c94da | ||
|
|
6aea64bf70 | ||
|
|
234bc3b25f | ||
|
|
6df64644c1 | ||
|
|
a5abfa09d3 | ||
|
|
a49a5347be | ||
|
|
a0914f433a | ||
|
|
7f6cee8772 | ||
|
|
6382bf4bfb | ||
|
|
3a1a5b68a2 | ||
|
|
6a018ddca1 | ||
|
|
8c4da10759 | ||
|
|
8ebec0c4c4 | ||
|
|
8212a24dfa | ||
|
|
233db9c33f | ||
|
|
5001ed03b7 | ||
|
|
f7e8576166 | ||
|
|
baa4c82901 | ||
|
|
aea0a60ccd | ||
|
|
e49c674890 | ||
|
|
f84f12e7b0 | ||
|
|
c64205076b | ||
|
|
e384a39c62 | ||
|
|
0e4e0d83ff | ||
|
|
54e99ee992 | ||
|
|
baad0090bf | ||
|
|
21e5e6a514 | ||
|
|
d97f233291 | ||
|
|
fd59991cc1 | ||
|
|
09782e77e8 | ||
|
|
90120c3a9d |
@@ -1,15 +0,0 @@
|
|||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
github-release-to-discord:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Handle Release
|
|
||||||
uses: ./ # Uses an action in the root directory
|
|
||||||
id: release
|
|
||||||
with:
|
|
||||||
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
|
||||||
@@ -8,7 +8,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: google-github-actions/release-please-action@v3
|
- uses: google-github-actions/release-please-action@v3
|
||||||
|
id: release
|
||||||
with:
|
with:
|
||||||
release-type: node
|
release-type: node
|
||||||
package-name: release-please-action
|
package-name: release-please-action
|
||||||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
token: ${{ secrets.WORKFLOW_TOKEN }}
|
||||||
|
changelog-types: '[{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"revert","section":"Reverts","hidden":false},{"type":"docs","section":"Documentation","hidden":false},{"type":"style","section":"Styles","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":false},{"type":"refactor","section":"Code Refactoring","hidden":false}]'
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
name: test-action
|
||||||
|
jobs:
|
||||||
|
github-releases-to-discord:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Github Releases To Discord
|
||||||
|
uses: ./ # Uses an action in the root directory
|
||||||
|
with:
|
||||||
|
webhook_url: ${{ inputs.WEBHOOK_URL }}
|
||||||
|
color: ${{ inputs.COLOR }}
|
||||||
|
username: ${{ inputs.USERNAME }}
|
||||||
|
avatar_url: ${{ inputs.AVATAR_URL }}
|
||||||
|
content: ${{ inputs.CONTENT }}
|
||||||
|
footer_title: ${{ inputs.FOOTER_TITLE }}
|
||||||
|
footer_icon_url: ${{ inputs.FOOTER_ICON_URL }}
|
||||||
|
footer_timestamp: ${{ inputs.FOOTER_TIMESTAMP }}
|
||||||
|
max_description: ${{ inputs.MAX_DESCRIPTION }}
|
||||||
|
reduce_headings: ${{ inputs.REDUCE_HEADINGS }}
|
||||||
|
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
out
|
||||||
|
gen
|
||||||
|
.env
|
||||||
|
tests/sample-test-release.json
|
||||||
|
release.json
|
||||||
|
repository.json
|
||||||
|
user.json
|
||||||
|
tests/sample-test-release-v2.json
|
||||||
Generated
-5
@@ -1,5 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
Generated
-7
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="DiscordProjectSettings">
|
|
||||||
<option name="show" value="ASK" />
|
|
||||||
<option name="description" value="" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-12
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="WEB_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
Generated
-8
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/github-release-to-discord.iml" filepath="$PROJECT_DIR$/.idea/github-release-to-discord.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
+134
@@ -1,5 +1,139 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.16.0](https://github.com/SethCohen/github-releases-to-discord/compare/v1.15.3...v1.16.0) (2024-10-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add function to convert PR, issue, and changelog links to markdown format ([07c2e1c](https://github.com/SethCohen/github-releases-to-discord/commit/07c2e1c3e60591d601b5d4b5bd4fc90e599867f8)), closes [#32](https://github.com/SethCohen/github-releases-to-discord/issues/32)
|
||||||
|
|
||||||
|
## [1.15.3](https://github.com/SethCohen/github-releases-to-discord/compare/v1.15.2...v1.15.3) (2024-10-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* add contribution guidelines to README.md ([5fd64bf](https://github.com/SethCohen/github-releases-to-discord/commit/5fd64bf266cea87ab4952ef9a4c6aaf099f266bc))
|
||||||
|
* update version reference in README.md ([93d02ce](https://github.com/SethCohen/github-releases-to-discord/commit/93d02ce8714c5f3e201f5b379422e978b837774b))
|
||||||
|
|
||||||
|
|
||||||
|
### Miscellaneous
|
||||||
|
|
||||||
|
* update .gitignore ([3449e38](https://github.com/SethCohen/github-releases-to-discord/commit/3449e38629b0c40dde5af524e2fef220dab24ead))
|
||||||
|
* update package-lock.json ([5dc4108](https://github.com/SethCohen/github-releases-to-discord/commit/5dc41089e63d18b5b191533c34cdddeab34a07e8))
|
||||||
|
|
||||||
|
## [1.15.2](https://github.com/SethCohen/github-releases-to-discord/compare/v1.15.1...v1.15.2) (2024-10-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* improve [@mention](https://github.com/mention) parsing for GitHub usernames ([#33](https://github.com/SethCohen/github-releases-to-discord/issues/33)) ([925765f](https://github.com/SethCohen/github-releases-to-discord/commit/925765f099dcdc3b12316eaa6dc3c17506734b51))
|
||||||
|
|
||||||
|
## [1.15.1](https://github.com/SethCohen/github-releases-to-discord/compare/v1.15.0...v1.15.1) (2024-10-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* updated README.md ([613ba26](https://github.com/SethCohen/github-releases-to-discord/commit/613ba269d7fe40e97040da19de58b0ae00b47aaf))
|
||||||
|
|
||||||
|
|
||||||
|
### Miscellaneous
|
||||||
|
|
||||||
|
* added test envs to .gitignore ([735cca9](https://github.com/SethCohen/github-releases-to-discord/commit/735cca9de37b345e69b0c74ff761610eab6f1fd1))
|
||||||
|
* updated test action ([305fe92](https://github.com/SethCohen/github-releases-to-discord/commit/305fe9299dddb3a514f2b1692773570862c34d46))
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* code cleanup ([5ca1453](https://github.com/SethCohen/github-releases-to-discord/commit/5ca1453a69f962930450a9f77d4e20cc37e4110f))
|
||||||
|
|
||||||
|
## [1.15.0](https://github.com/SethCohen/github-releases-to-discord/compare/v1.14.0...v1.15.0) (2023-12-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* added clip description at last newline ([#25](https://github.com/SethCohen/github-releases-to-discord/issues/25)) ([97a4813](https://github.com/SethCohen/github-releases-to-discord/commit/97a481333d0b902f599b12f03b47c4a6cbfa5e52))
|
||||||
|
|
||||||
|
## [1.14.0](https://github.com/SethCohen/github-releases-to-discord/compare/v1.13.1...v1.14.0) (2023-12-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* added additional description formatting ([#23](https://github.com/SethCohen/github-releases-to-discord/issues/23)) ([8ca9da2](https://github.com/SethCohen/github-releases-to-discord/commit/8ca9da2ca8e3435ee9b0d387355c0fae255c16b0))
|
||||||
|
* added max_description option ([8ca9da2](https://github.com/SethCohen/github-releases-to-discord/commit/8ca9da2ca8e3435ee9b0d387355c0fae255c16b0))
|
||||||
|
* added reduce_headings option ([8ca9da2](https://github.com/SethCohen/github-releases-to-discord/commit/8ca9da2ca8e3435ee9b0d387355c0fae255c16b0))
|
||||||
|
* description trimming ([8ca9da2](https://github.com/SethCohen/github-releases-to-discord/commit/8ca9da2ca8e3435ee9b0d387355c0fae255c16b0))
|
||||||
|
|
||||||
|
|
||||||
|
### Styles
|
||||||
|
|
||||||
|
* parse common Github URLs to more appropriate display ([8ca9da2](https://github.com/SethCohen/github-releases-to-discord/commit/8ca9da2ca8e3435ee9b0d387355c0fae255c16b0))
|
||||||
|
* reduce consecutive whitespace/newlines into a minimum of 2 to allow separation in paragraphs ([8ca9da2](https://github.com/SethCohen/github-releases-to-discord/commit/8ca9da2ca8e3435ee9b0d387355c0fae255c16b0))
|
||||||
|
|
||||||
|
|
||||||
|
### Miscellaneous
|
||||||
|
|
||||||
|
* update README.md ([254bf79](https://github.com/SethCohen/github-releases-to-discord/commit/254bf7919618aea9ce0a3db67901010a20426def))
|
||||||
|
|
||||||
|
## [1.13.1](https://github.com/SethCohen/github-releases-to-discord/compare/v1.13.0...v1.13.1) (2023-09-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Miscellaneous
|
||||||
|
|
||||||
|
* updated action info ([c240910](https://github.com/SethCohen/github-releases-to-discord/commit/c240910f8922fb8492346613f67a84811a0fdbac))
|
||||||
|
* updated action info ([c888953](https://github.com/SethCohen/github-releases-to-discord/commit/c8889535c4a02efec3e8a6b26e1ee17fcd36ac7b))
|
||||||
|
* updated dependencies ([24b80ab](https://github.com/SethCohen/github-releases-to-discord/commit/24b80abb6f9c71123456a908af5d3b92ccc755af))
|
||||||
|
* updated README.md ([ba06d83](https://github.com/SethCohen/github-releases-to-discord/commit/ba06d833522e55d3453ee27bea9a7f3655378359))
|
||||||
|
* updated README.md ([ce7fc4a](https://github.com/SethCohen/github-releases-to-discord/commit/ce7fc4a3be811af077c4f43dd005851d65204fb1))
|
||||||
|
* updated release-please action ([933f3cf](https://github.com/SethCohen/github-releases-to-discord/commit/933f3cffcf2139fcd71dddbb8bc76bc7cfa09170))
|
||||||
|
|
||||||
|
## [1.13.0](https://github.com/SethCohen/github-release-to-discord/compare/v1.12.1...v1.13.0) (2022-08-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* created test action ([d76d7aa](https://github.com/SethCohen/github-release-to-discord/commit/d76d7aafe49eadfc8d388bef38a8d3fb0230041b))
|
||||||
|
|
||||||
|
## [1.12.1](https://github.com/SethCohen/github-release-to-discord/compare/v1.12.0...v1.12.1) (2022-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Miscellaneous Chores
|
||||||
|
|
||||||
|
* release 1.12.1 ([6aea64b](https://github.com/SethCohen/github-release-to-discord/commit/6aea64bf70aca011417fed5000ff080269024a93))
|
||||||
|
|
||||||
|
## [1.12.0](https://github.com/SethCohen/github-release-to-discord/compare/v1.11.1...v1.12.0) (2022-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* renamed all `colour`>`color` ([a0914f4](https://github.com/SethCohen/github-release-to-discord/commit/a0914f433a5c2a2626ca39637851235fd4f7adea))
|
||||||
|
|
||||||
|
## [1.11.1](https://github.com/SethCohen/github-release-to-discord/compare/v1.11.0...v1.11.1) (2022-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fixed ReferenceError on require vs import ([233db9c](https://github.com/SethCohen/github-release-to-discord/commit/233db9c33f9059cbe008e61acf7f321e671f352c))
|
||||||
|
|
||||||
|
## [1.11.0](https://github.com/SethCohen/github-release-to-discord/compare/v1.10.1...v1.11.0) (2022-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* updated `action.yml` ([e49c674](https://github.com/SethCohen/github-release-to-discord/commit/e49c674890cd5309d63bf570a550ffa0361c9ebc))
|
||||||
|
* updated `package.json` ([aea0a60](https://github.com/SethCohen/github-release-to-discord/commit/aea0a60ccd2456ae24e6ce7f21c1622a1b3ee18f))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fixed version tag ([baa4c82](https://github.com/SethCohen/github-release-to-discord/commit/baa4c82901455b2fbc18187cfa8f74f2a171f033))
|
||||||
|
|
||||||
|
## [1.10.0](https://github.com/SethCohen/github-release-to-discord/compare/v1.9.0...v1.10.0) (2022-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* added dependencies and removed `.idea` ([fd59991](https://github.com/SethCohen/github-release-to-discord/commit/fd59991cc10608712b6e30a81f3cf0358c7dcf9d))
|
||||||
|
|
||||||
## [1.9.0](https://github.com/SethCohen/github-release-to-discord/compare/v1.8.0...v1.9.0) (2022-08-24)
|
## [1.9.0](https://github.com/SethCohen/github-release-to-discord/compare/v1.8.0...v1.9.0) (2022-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 SethCohen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,19 +1,44 @@
|
|||||||
# Github Release To Discord Action
|
# GitHub Releases To Discord Action
|
||||||
|
|
||||||
This action parses a github release and posts it to a discord channel via a discord webhook.
|
A GitHub Action that sends a stylized Discord webhook containing the description of a GitHub Release to a specified Discord channel. It formats the description to improve readability and includes various configuration options to customize the message.
|
||||||
## Configuration
|
|
||||||
| Variable | Required | Default | Description |
|
|
||||||
|-------------|----------|----------------------------------------------------------------------------------------------------------------|---------------------------------------------|
|
|
||||||
| webhook_url | ✔ | | Discord's webhook url. Use GH repo secrets. |
|
|
||||||
| colour | ❌ | "2105893" | Decimal colour value for embed. |
|
|
||||||
| username | ❌ | "Release Changelog" | String username for webhook. |
|
|
||||||
| avatar_url | ❌ | ["Profile Picture"](https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png) | String url to webhook avatar picture. |
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Formats release descriptions by:
|
||||||
|
- Removing carriage returns and HTML comments.
|
||||||
|
- Styling headers for better readability in Discord (optional).
|
||||||
|
- Cleaning up redundant whitespace and newlines.
|
||||||
|
- Converting common GitHub links (e.g., PRs, Issues) into clickable URLs.
|
||||||
|
- Limits description length to avoid exceeding Discord's character limits.
|
||||||
|
- Provides customization for the Discord message, including:
|
||||||
|
- Webhook URL, message color, footer text/icon, and timestamp.
|
||||||
|
- Username and avatar for the webhook.
|
||||||
|
- Optional message content outside the embed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||

|
|
||||||
|
|
||||||
## Example usage
|

|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Variable | Required | Default | Description |
|
||||||
|
|-----------------|----------|-------------------------------------------------------------------------------------------------------|-------------------------------------------------|
|
||||||
|
| webhook_url | ✔ | | Discord's webhook url. Use GH repo secrets. |
|
||||||
|
| color | ❌ | "2105893" | Decimal color value for embed. |
|
||||||
|
| username | ❌ | | String username for webhook. |
|
||||||
|
| avatar_url | ❌ | | String url to webhook avatar picture. |
|
||||||
|
| content | ❌ | | String content for webhook. |
|
||||||
|
| footer_title | ❌ | | String title for the webhook footer. |
|
||||||
|
| footer_icon_url | ❌ | | String url for the webhook footer picture. |
|
||||||
|
| footer_timestamp| ❌ | | Boolean to enable footer timestamp. |
|
||||||
|
| max_description | ❌ | "4096" | Max length for the description. |
|
||||||
|
| reduce_headings | ❌ | false | Converts H3 to bold, h2 to bold & underline. |
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
`.github/workflows/github-releases-to-discord.yml`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
on:
|
on:
|
||||||
@@ -21,14 +46,86 @@ on:
|
|||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
github-release-to-discord:
|
github-releases-to-discord:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Handle Release
|
- name: Github Releases To Discord
|
||||||
uses: ./ # Uses an action in the root directory
|
uses: SethCohen/github-releases-to-discord@v1
|
||||||
id: release
|
|
||||||
with:
|
with:
|
||||||
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
||||||
```
|
color: "2105893"
|
||||||
|
username: "Release Changelog"
|
||||||
|
avatar_url: "https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png"
|
||||||
|
content: "||@everyone||"
|
||||||
|
footer_title: "Changelog"
|
||||||
|
footer_icon_url: "https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png"
|
||||||
|
footer_timestamp: true
|
||||||
|
max_description: '4096'
|
||||||
|
reduce_headings: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
1. Open your **Server Settings** and head into the **Integrations** tab:
|
||||||
|
2. Click the "**Create Webhook**" button to create a new webhook!
|
||||||
|

|
||||||
|

|
||||||
|
3. Copy the webhook url
|
||||||
|
4. Create a new GitHub repository secret called WEBHOOK_URL and paste the webhook url into it.
|
||||||
|

|
||||||
|
5. Save the secret.
|
||||||
|
6. Add the secret to your action configuration.
|
||||||
|
|
||||||
|
And you're done! Whenever you create a new release, the workflow should run and, if properly setup, post to your specified Discord channel.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
If you have suggestions for how GitHub Releases To Discord Action could be improved, or want to report a bug, open an issue! We'd love all and any contributions.
|
||||||
|
|
||||||
|
1. Fork the repository.
|
||||||
|
2. Install node and run `npm install`.
|
||||||
|
3. Install [Github Action tester `act`](https://github.com/nektos/act)
|
||||||
|
4. Create a sample test Release file to simulate a webhook payload object such as `tests/sample-test-release.json` with the following structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "published",
|
||||||
|
"release": {
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This file will be used to test the action locally and simulate a real release event webhook payload. Refer to the [GitHub Webhook documentation](https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=published#release) and the [Github API Documentation](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#get-a-release) for more information on the webhook payload structure.
|
||||||
|
|
||||||
|
6. Fill the test json file with the necessary fields to simulate a release event.
|
||||||
|
|
||||||
|
5. Create a test file called `.env` in the main root of the project with the following environmental variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
INPUT_WEBHOOK_URL=
|
||||||
|
INPUT_COLOR=
|
||||||
|
INPUT_USERNAME=
|
||||||
|
INPUT_AVATAR_URL=
|
||||||
|
INPUT_CONTENT=
|
||||||
|
INPUT_FOOTER_TITLE=
|
||||||
|
INPUT_FOOTER_ICON_URL=
|
||||||
|
INPUT_FOOTER_TIMESTAMP=
|
||||||
|
INPUT_MAX_DESCRIPTION=
|
||||||
|
INPUT_REDUCE_HEADINGS=
|
||||||
|
```
|
||||||
|
|
||||||
|
8. Fill the `.env` file with your chosen environmental variables values.
|
||||||
|
9. Create a Discord webhook in your server, making sure to add the webhook url to the `.env` file under `INPUT_WEBHOOK_URL=`.
|
||||||
|
10. Run the action locally with `act release -e <your.json>` (e.g. `act release -e tests/sample-test-release.json`) and check the output in your Discord server.
|
||||||
|
11. Confirm that the action works as expected.
|
||||||
|
12. Make your changes and commit them: `git commit -m '<commit_message>'`. Please follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
|
||||||
|
13. Create the pull request.
|
||||||
|
|||||||
+28
-6
@@ -1,21 +1,43 @@
|
|||||||
name: Github Releases To Discord
|
name: Github Releases To Discord
|
||||||
description: Send automatic styled releases to Discord.
|
author: SethCohen
|
||||||
|
description: Automatically send a stylized Discord webhook of a GitHub Release description to a specified Discord channel.
|
||||||
inputs:
|
inputs:
|
||||||
webhook_url:
|
webhook_url:
|
||||||
description: Discord's webhook url. Use GH repo secrets.
|
description: Discord's webhook url. Use GH repo secrets.
|
||||||
required: true
|
required: true
|
||||||
colour:
|
color:
|
||||||
description: Decimal colour value for embed.
|
description: Decimal color value for embed.
|
||||||
required: false
|
required: false
|
||||||
default: '2105893'
|
default: '2105893'
|
||||||
username:
|
username:
|
||||||
description: String username for webhook.
|
description: String username for webhook.
|
||||||
required: false
|
required: false
|
||||||
default: 'Release Changelog'
|
|
||||||
avatar_url:
|
avatar_url:
|
||||||
description: String url to webhook avatar picture.
|
description: String url to webhook avatar picture.
|
||||||
required: false
|
required: false
|
||||||
default: 'https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png'
|
content:
|
||||||
|
description: String content for webhook.
|
||||||
|
required: false
|
||||||
|
footer_title:
|
||||||
|
description: Title for the footer.
|
||||||
|
required: false
|
||||||
|
footer_icon_url:
|
||||||
|
description: Icon url for the footer.
|
||||||
|
required: false
|
||||||
|
footer_timestamp:
|
||||||
|
description: Timestamp for the footer.
|
||||||
|
required: false
|
||||||
|
max_description:
|
||||||
|
description: Max length for the description.
|
||||||
|
required: false
|
||||||
|
default: '4096'
|
||||||
|
reduce_headings:
|
||||||
|
description: Converts H3 to bold, h2 to bold & underline.
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
runs:
|
runs:
|
||||||
using: 'node16'
|
using: 'node16'
|
||||||
main: 'index.js'
|
main: 'index.js'
|
||||||
|
branding:
|
||||||
|
icon: file-text
|
||||||
|
color: blue
|
||||||
@@ -1,65 +1,237 @@
|
|||||||
const core = require('@actions/core');
|
import core from '@actions/core';
|
||||||
const github = require('@actions/github');
|
import github from '@actions/github';
|
||||||
const fetch = require('node-fetch')
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
async function getContext () {
|
/**
|
||||||
const payload = github.context.payload
|
* Removes carriage return characters.
|
||||||
|
* @param {string} text The input text.
|
||||||
|
* @returns {string} The text without carriage return characters.
|
||||||
|
*/
|
||||||
|
const removeCarriageReturn = (text) => text.replace(/\r/g, '');
|
||||||
|
|
||||||
return {
|
/**
|
||||||
body: payload.release.body.length < 1500
|
* Removes HTML comments.
|
||||||
? payload.release.body
|
* @param {string} text The input text.
|
||||||
: payload.release.body.substring(0, 1500) + ` ([...](${payload.release.html_url}))`,
|
* @returns {string} The text without HTML comments.
|
||||||
version: payload.release.tag_name,
|
*/
|
||||||
html_url: payload.release.html_url
|
const removeHTMLComments = (text) => text.replace(/<!--.*?-->/gs, '');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces redundant newlines and spaces.
|
||||||
|
* Keeps a maximum of 2 newlines to provide spacing between paragraphs.
|
||||||
|
* @param {string} text The input text.
|
||||||
|
* @returns {string} The text with reduced newlines.
|
||||||
|
*/
|
||||||
|
const reduceNewlines = (text) => text.replace(/\n\s*\n/g, (ws) => {
|
||||||
|
const nlCount = (ws.match(/\n/g) || []).length;
|
||||||
|
return nlCount >= 2 ? '\n\n' : '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts @mentions to GitHub profile links for valid GitHub usernames.
|
||||||
|
* @param {string} text The input text.
|
||||||
|
* @returns {string} The text with valid @mentions converted to links.
|
||||||
|
*/
|
||||||
|
const convertMentionsToLinks = (text) => text.replace(
|
||||||
|
/(?<![/@\w])@((?!-)(?!.*?--)[a-zA-Z0-9](?:-?[a-zA-Z0-9]){0,37})(?![.\w/-])(?!.*\])/g,
|
||||||
|
(match, name) => `[@${name}](https://github.com/${name})`
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces headings to a smaller format if 'reduce_headings' is enabled.
|
||||||
|
* Converts H3 to bold+underline, H2 to bold.
|
||||||
|
* @param {string} text The input text.
|
||||||
|
* @returns {string} The text with reduced heading sizes.
|
||||||
|
*/
|
||||||
|
const reduceHeadings = (text) => text
|
||||||
|
.replace(/^###\s+(.+)$/gm, '**__$1__**') // Convert H3 to bold + underline
|
||||||
|
.replace(/^##\s+(.+)$/gm, '**$1**'); // Convert H2 to bold
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts PR links, issue links, and changelog links to markdown format.
|
||||||
|
* - PR links: `https://github.com/OWNER/REPO/pull/1` -> `[PR #1](https://github.com/OWNER/REPO/pull/1)`
|
||||||
|
* - Issue links: `https://github.com/OWNER/REPO/issues/1` -> `[Issue #30](https://github.com/OWNER/REPO/issues/1)`
|
||||||
|
* - Changelog links: `https://github.com/OWNER/REPO/compare/v1.0.0...v1.1.0` -> `[v1.0.0...v1.1.0](https://github.com/OWNER/REPO/compare/v1.0.0...v1.1.0)`
|
||||||
|
* @param {string} text The input text.
|
||||||
|
* @returns {string} The text with links converted to markdown format.
|
||||||
|
*/
|
||||||
|
const convertLinksToMarkdown = (text) => {
|
||||||
|
// Convert PR links
|
||||||
|
text = text.replace(
|
||||||
|
/https:\/\/github\.com\/([\w-]+)\/([\w-]+)\/pull\/(\d+)/g,
|
||||||
|
(match, owner, repo, prNumber) => `[PR #${prNumber}](${match})`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert issue links
|
||||||
|
text = text.replace(
|
||||||
|
/https:\/\/github\.com\/([\w-]+)\/([\w-]+)\/issues\/(\d+)/g,
|
||||||
|
(match, owner, repo, issueNumber) => `[Issue #${issueNumber}](${match})`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert changelog comparison links
|
||||||
|
text = text.replace(
|
||||||
|
/https:\/\/github\.com\/([\w-]+)\/([\w-]+)\/compare\/([v\w.-]+)\.\.\.([v\w.-]+)/g,
|
||||||
|
(match, owner, repo, fromVersion, toVersion) => `[${fromVersion}...${toVersion}](${match})`
|
||||||
|
);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stylizes a markdown body into an appropriate embed message style.
|
||||||
|
* @param {string} description The description to format.
|
||||||
|
* @returns {string} The formatted description.
|
||||||
|
*/
|
||||||
|
const formatDescription = (description) => {
|
||||||
|
let edit = removeCarriageReturn(description);
|
||||||
|
edit = removeHTMLComments(edit);
|
||||||
|
edit = reduceNewlines(edit);
|
||||||
|
edit = convertMentionsToLinks(edit);
|
||||||
|
edit = convertLinksToMarkdown(edit);
|
||||||
|
edit = edit.trim();
|
||||||
|
|
||||||
|
if (core.getBooleanInput('reduce_headings')) {
|
||||||
|
edit = reduceHeadings(edit);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async function run () {
|
return edit;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the max description length, defaulting to 4096 if not set or invalid.
|
||||||
|
* @returns {number} The max description length.
|
||||||
|
*/
|
||||||
|
const getMaxDescription = () => {
|
||||||
try {
|
try {
|
||||||
const webhookUrl = core.getInput('webhook_url')
|
const max = core.getInput('max_description');
|
||||||
const colour = core.getInput('colour')
|
if (max && !isNaN(max)) {
|
||||||
const username = core.getInput('username')
|
return Math.min(parseInt(max, 10), 4096);
|
||||||
const avatarUrl = core.getInput('avatar_url')
|
|
||||||
|
|
||||||
if (!webhookUrl) {
|
|
||||||
return core.setFailed('webhook_url not set. Please set it.')
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
const content = await getContext()
|
core.warning(`Invalid max_description: ${err}`);
|
||||||
|
|
||||||
const description = content.body
|
|
||||||
.replace(/### (.*?)\n/g,function (substring) {
|
|
||||||
const newString = substring.slice(4).replace(/(\r\n|\n|\r)/gm, "")
|
|
||||||
return `**__${newString}__**`
|
|
||||||
})
|
|
||||||
.replace(/## (.*?)\n/g,function (substring) {
|
|
||||||
const newString = substring.slice(3).replace(/(\r\n|\n|\r)/gm, "")
|
|
||||||
return `**${newString}**`
|
|
||||||
})
|
|
||||||
.replace(/\n\s*\n/g, '\n')
|
|
||||||
|
|
||||||
const embedMsg = {
|
|
||||||
title: `Release ${content.version}`,
|
|
||||||
url: content.html_url,
|
|
||||||
color: colour,
|
|
||||||
description: description,
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = { username: username, avatar_url: avatarUrl, embeds: [embedMsg], }
|
|
||||||
|
|
||||||
const url = `${webhookUrl}?wait=true`
|
|
||||||
|
|
||||||
fetch(url, {
|
|
||||||
method: 'post',
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => core.info(JSON.stringify(data)))
|
|
||||||
.catch(err => core.info(err))
|
|
||||||
} catch (error) {
|
|
||||||
core.setFailed(error.message)
|
|
||||||
}
|
}
|
||||||
}
|
return 4096;
|
||||||
|
};
|
||||||
|
|
||||||
run()
|
/**
|
||||||
|
* Get the context of the action, returning a GitHub Release payload.
|
||||||
|
* @returns {object} The context with release details.
|
||||||
|
*/
|
||||||
|
const getContext = () => {
|
||||||
|
const { release } = github.context.payload;
|
||||||
|
return {
|
||||||
|
body: release.body,
|
||||||
|
name: release.name,
|
||||||
|
html_url: release.html_url
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limits the string to a maximum length, optionally adding a URL or clipping at a newline.
|
||||||
|
* @param {string} str The string to limit.
|
||||||
|
* @param {number} maxLength The maximum allowed length.
|
||||||
|
* @param {string} [url] Optional URL for linking the truncated text.
|
||||||
|
* @param {boolean} [clipAtLine=false] Whether to clip at the nearest newline.
|
||||||
|
* @returns {string} The limited string.
|
||||||
|
*/
|
||||||
|
const limitString = (str, maxLength, url, clipAtLine = false) => {
|
||||||
|
if (str.length <= maxLength) return str;
|
||||||
|
|
||||||
|
const replacement = url
|
||||||
|
? `${clipAtLine ? '\n' : ''}([…](${url}))`
|
||||||
|
: (clipAtLine ? '\n…' : '…');
|
||||||
|
|
||||||
|
maxLength -= replacement.length;
|
||||||
|
str = str.substring(0, maxLength);
|
||||||
|
|
||||||
|
const lastNewline = str.search(new RegExp(`[^${clipAtLine ? '\n' : '\s'}]*$`));
|
||||||
|
if (lastNewline > -1) {
|
||||||
|
str = str.substring(0, lastNewline);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str + replacement;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the embed message for the Discord webhook.
|
||||||
|
* @param {string} name The title or name of the release.
|
||||||
|
* @param {string} html_url The URL of the release.
|
||||||
|
* @param {string} description The formatted description of the release.
|
||||||
|
* @returns {object} The embed message to send in the webhook.
|
||||||
|
*/
|
||||||
|
const buildEmbedMessage = (name, html_url, description) => {
|
||||||
|
const embedMsg = {
|
||||||
|
title: limitString(name, 256),
|
||||||
|
url: html_url,
|
||||||
|
color: core.getInput('color'),
|
||||||
|
description: limitString(description, Math.min(getMaxDescription(), 6000 - name.length)),
|
||||||
|
footer: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (core.getInput('footer_title')) {
|
||||||
|
embedMsg.footer.text = limitString(core.getInput('footer_title'), 2048);
|
||||||
|
}
|
||||||
|
if (core.getInput('footer_icon_url')) {
|
||||||
|
embedMsg.footer.icon_url = core.getInput('footer_icon_url');
|
||||||
|
}
|
||||||
|
if (core.getInput('footer_timestamp') === 'true') {
|
||||||
|
embedMsg.timestamp = new Date().toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return embedMsg;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the webhook request to Discord.
|
||||||
|
* @param {string} webhookUrl The URL of the Discord webhook.
|
||||||
|
* @param {object} requestBody The payload to send in the webhook.
|
||||||
|
*/
|
||||||
|
const sendWebhook = async (webhookUrl, requestBody) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${webhookUrl}?wait=true`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(requestBody),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
core.info(JSON.stringify(data));
|
||||||
|
} catch (err) {
|
||||||
|
core.setFailed(err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the request body for the Discord webhook.
|
||||||
|
* @param {object} embedMsg The embed message to include in the request body.
|
||||||
|
* @returns {object} The request body for the webhook.
|
||||||
|
*/
|
||||||
|
const buildRequestBody = (embedMsg) => {
|
||||||
|
return {
|
||||||
|
embeds: [embedMsg],
|
||||||
|
...(core.getInput('username') && { username: core.getInput('username') }),
|
||||||
|
...(core.getInput('avatar_url') && { avatar_url: core.getInput('avatar_url') }),
|
||||||
|
...(core.getInput('content') && { content: core.getInput('content') })
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to handle the action: get inputs, format the message, and send the webhook.
|
||||||
|
*/
|
||||||
|
const run = async () => {
|
||||||
|
const webhookUrl = core.getInput('webhook_url');
|
||||||
|
if (!webhookUrl) return core.setFailed('webhook_url not set.');
|
||||||
|
|
||||||
|
const { body, html_url, name } = getContext();
|
||||||
|
const description = formatDescription(body);
|
||||||
|
|
||||||
|
const embedMsg = buildEmbedMessage(name, html_url, description);
|
||||||
|
|
||||||
|
const requestBody = buildRequestBody(embedMsg);
|
||||||
|
|
||||||
|
await sendWebhook(webhookUrl, requestBody);
|
||||||
|
};
|
||||||
|
|
||||||
|
run()
|
||||||
|
.then(() => core.info('Action completed successfully'))
|
||||||
|
.catch(err => core.setFailed(err.message));
|
||||||
|
|||||||
+120
-36
@@ -1,22 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "github-release-to-discord",
|
"name": "github-releases-to-discord",
|
||||||
"version": "1.0.0",
|
"version": "1.15.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "1.9.1",
|
"version": "1.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz",
|
||||||
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
|
"integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": "^2.0.1",
|
"@actions/http-client": "^2.0.1",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/github": {
|
"node_modules/@actions/github": {
|
||||||
"version": "5.0.3",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
|
||||||
"integrity": "sha512-myjA/pdLQfhUGLtRZC/J4L1RXOG4o6aYdiEq+zr5wVVKljzbFld+xv10k1FX6IkIJtNxbAq44BdwSNpQ015P0A==",
|
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": "^2.0.1",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@octokit/core": "^3.6.0",
|
"@octokit/core": "^3.6.0",
|
||||||
@@ -125,33 +125,7 @@
|
|||||||
"once": "^1.4.0"
|
"once": "^1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/types": {
|
"node_modules/@octokit/request/node_modules/node-fetch": {
|
||||||
"version": "6.41.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
|
|
||||||
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@octokit/openapi-types": "^12.11.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/before-after-hook": {
|
|
||||||
"version": "2.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
|
|
||||||
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
|
|
||||||
},
|
|
||||||
"node_modules/deprecation": {
|
|
||||||
"version": "2.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
|
||||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
|
||||||
},
|
|
||||||
"node_modules/is-plain-object": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-fetch": {
|
|
||||||
"version": "2.6.7",
|
"version": "2.6.7",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||||
@@ -170,6 +144,108 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/types": {
|
||||||
|
"version": "6.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
|
||||||
|
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^12.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/before-after-hook": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
|
||||||
|
},
|
||||||
|
"node_modules/data-uri-to-buffer": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/deprecation": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
||||||
|
},
|
||||||
|
"node_modules/fetch-blob": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/jimmywarting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paypal",
|
||||||
|
"url": "https://paypal.me/jimmywarting"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"node-domexception": "^1.0.0",
|
||||||
|
"web-streams-polyfill": "^3.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20 || >= 14.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/formdata-polyfill": {
|
||||||
|
"version": "4.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||||
|
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||||
|
"dependencies": {
|
||||||
|
"fetch-blob": "^3.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-plain-object": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-domexception": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/jimmywarting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://paypal.me/jimmywarting"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||||
|
"dependencies": {
|
||||||
|
"data-uri-to-buffer": "^4.0.0",
|
||||||
|
"fetch-blob": "^3.1.4",
|
||||||
|
"formdata-polyfill": "^4.0.10"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/node-fetch"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
@@ -204,6 +280,14 @@
|
|||||||
"uuid": "dist/bin/uuid"
|
"uuid": "dist/bin/uuid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/web-streams-polyfill": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
|||||||
+2
-2
@@ -121,7 +121,7 @@ const result = await core.group('Do something async', async () => {
|
|||||||
|
|
||||||
This library has 3 methods that will produce [annotations](https://docs.github.com/en/rest/reference/checks#create-a-check-run).
|
This library has 3 methods that will produce [annotations](https://docs.github.com/en/rest/reference/checks#create-a-check-run).
|
||||||
```js
|
```js
|
||||||
core.error('This is a bad error. This will also fail the build.')
|
core.error('This is a bad error, action may still succeed though.')
|
||||||
|
|
||||||
core.warning('Something went wrong, but it\'s not bad enough to fail the build.')
|
core.warning('Something went wrong, but it\'s not bad enough to fail the build.')
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ export interface AnnotationProperties {
|
|||||||
startColumn?: number
|
startColumn?: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
* The end column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||||
* Defaults to `startColumn` when `startColumn` is provided.
|
* Defaults to `startColumn` when `startColumn` is provided.
|
||||||
*/
|
*/
|
||||||
endColumn?: number
|
endColumn?: number
|
||||||
|
|||||||
+2
-2
@@ -21,7 +21,7 @@ export declare enum ExitCode {
|
|||||||
Failure = 1
|
Failure = 1
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Optional properties that can be sent with annotatation commands (notice, error, and warning)
|
* Optional properties that can be sent with annotation commands (notice, error, and warning)
|
||||||
* See: https://docs.github.com/en/rest/reference/checks#create-a-check-run for more information about annotations.
|
* See: https://docs.github.com/en/rest/reference/checks#create-a-check-run for more information about annotations.
|
||||||
*/
|
*/
|
||||||
export interface AnnotationProperties {
|
export interface AnnotationProperties {
|
||||||
@@ -46,7 +46,7 @@ export interface AnnotationProperties {
|
|||||||
*/
|
*/
|
||||||
startColumn?: number;
|
startColumn?: number;
|
||||||
/**
|
/**
|
||||||
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
* The end column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||||
* Defaults to `startColumn` when `startColumn` is provided.
|
* Defaults to `startColumn` when `startColumn` is provided.
|
||||||
*/
|
*/
|
||||||
endColumn?: number;
|
endColumn?: number;
|
||||||
|
|||||||
+17
-18
@@ -34,7 +34,6 @@ const file_command_1 = require("./file-command");
|
|||||||
const utils_1 = require("./utils");
|
const utils_1 = require("./utils");
|
||||||
const os = __importStar(require("os"));
|
const os = __importStar(require("os"));
|
||||||
const path = __importStar(require("path"));
|
const path = __importStar(require("path"));
|
||||||
const uuid_1 = require("uuid");
|
|
||||||
const oidc_utils_1 = require("./oidc-utils");
|
const oidc_utils_1 = require("./oidc-utils");
|
||||||
/**
|
/**
|
||||||
* The code to exit an action
|
* The code to exit an action
|
||||||
@@ -64,20 +63,9 @@ function exportVariable(name, val) {
|
|||||||
process.env[name] = convertedVal;
|
process.env[name] = convertedVal;
|
||||||
const filePath = process.env['GITHUB_ENV'] || '';
|
const filePath = process.env['GITHUB_ENV'] || '';
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
const delimiter = `ghadelimiter_${uuid_1.v4()}`;
|
return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val));
|
||||||
// These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter.
|
|
||||||
if (name.includes(delimiter)) {
|
|
||||||
throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
|
|
||||||
}
|
|
||||||
if (convertedVal.includes(delimiter)) {
|
|
||||||
throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
|
|
||||||
}
|
|
||||||
const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`;
|
|
||||||
file_command_1.issueCommand('ENV', commandValue);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
command_1.issueCommand('set-env', { name }, convertedVal);
|
|
||||||
}
|
}
|
||||||
|
command_1.issueCommand('set-env', { name }, convertedVal);
|
||||||
}
|
}
|
||||||
exports.exportVariable = exportVariable;
|
exports.exportVariable = exportVariable;
|
||||||
/**
|
/**
|
||||||
@@ -95,7 +83,7 @@ exports.setSecret = setSecret;
|
|||||||
function addPath(inputPath) {
|
function addPath(inputPath) {
|
||||||
const filePath = process.env['GITHUB_PATH'] || '';
|
const filePath = process.env['GITHUB_PATH'] || '';
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
file_command_1.issueCommand('PATH', inputPath);
|
file_command_1.issueFileCommand('PATH', inputPath);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
command_1.issueCommand('add-path', {}, inputPath);
|
command_1.issueCommand('add-path', {}, inputPath);
|
||||||
@@ -135,7 +123,10 @@ function getMultilineInput(name, options) {
|
|||||||
const inputs = getInput(name, options)
|
const inputs = getInput(name, options)
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter(x => x !== '');
|
.filter(x => x !== '');
|
||||||
return inputs;
|
if (options && options.trimWhitespace === false) {
|
||||||
|
return inputs;
|
||||||
|
}
|
||||||
|
return inputs.map(input => input.trim());
|
||||||
}
|
}
|
||||||
exports.getMultilineInput = getMultilineInput;
|
exports.getMultilineInput = getMultilineInput;
|
||||||
/**
|
/**
|
||||||
@@ -168,8 +159,12 @@ exports.getBooleanInput = getBooleanInput;
|
|||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function setOutput(name, value) {
|
function setOutput(name, value) {
|
||||||
|
const filePath = process.env['GITHUB_OUTPUT'] || '';
|
||||||
|
if (filePath) {
|
||||||
|
return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value));
|
||||||
|
}
|
||||||
process.stdout.write(os.EOL);
|
process.stdout.write(os.EOL);
|
||||||
command_1.issueCommand('set-output', { name }, value);
|
command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value));
|
||||||
}
|
}
|
||||||
exports.setOutput = setOutput;
|
exports.setOutput = setOutput;
|
||||||
/**
|
/**
|
||||||
@@ -298,7 +293,11 @@ exports.group = group;
|
|||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function saveState(name, value) {
|
function saveState(name, value) {
|
||||||
command_1.issueCommand('save-state', { name }, value);
|
const filePath = process.env['GITHUB_STATE'] || '';
|
||||||
|
if (filePath) {
|
||||||
|
return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value));
|
||||||
|
}
|
||||||
|
command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value));
|
||||||
}
|
}
|
||||||
exports.saveState = saveState;
|
exports.saveState = saveState;
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+2
-1
@@ -1 +1,2 @@
|
|||||||
export declare function issueCommand(command: string, message: any): void;
|
export declare function issueFileCommand(command: string, message: any): void;
|
||||||
|
export declare function prepareKeyValueMessage(key: string, value: any): string;
|
||||||
|
|||||||
+19
-3
@@ -20,13 +20,14 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.issueCommand = void 0;
|
exports.prepareKeyValueMessage = exports.issueFileCommand = void 0;
|
||||||
// We use any as a valid input type
|
// We use any as a valid input type
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const fs = __importStar(require("fs"));
|
const fs = __importStar(require("fs"));
|
||||||
const os = __importStar(require("os"));
|
const os = __importStar(require("os"));
|
||||||
|
const uuid_1 = require("uuid");
|
||||||
const utils_1 = require("./utils");
|
const utils_1 = require("./utils");
|
||||||
function issueCommand(command, message) {
|
function issueFileCommand(command, message) {
|
||||||
const filePath = process.env[`GITHUB_${command}`];
|
const filePath = process.env[`GITHUB_${command}`];
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
throw new Error(`Unable to find environment variable for file command ${command}`);
|
throw new Error(`Unable to find environment variable for file command ${command}`);
|
||||||
@@ -38,5 +39,20 @@ function issueCommand(command, message) {
|
|||||||
encoding: 'utf8'
|
encoding: 'utf8'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
exports.issueCommand = issueCommand;
|
exports.issueFileCommand = issueFileCommand;
|
||||||
|
function prepareKeyValueMessage(key, value) {
|
||||||
|
const delimiter = `ghadelimiter_${uuid_1.v4()}`;
|
||||||
|
const convertedValue = utils_1.toCommandValue(value);
|
||||||
|
// These should realistically never happen, but just in case someone finds a
|
||||||
|
// way to exploit uuid generation let's not allow keys or values that contain
|
||||||
|
// the delimiter.
|
||||||
|
if (key.includes(delimiter)) {
|
||||||
|
throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
|
||||||
|
}
|
||||||
|
if (convertedValue.includes(delimiter)) {
|
||||||
|
throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
|
||||||
|
}
|
||||||
|
return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`;
|
||||||
|
}
|
||||||
|
exports.prepareKeyValueMessage = prepareKeyValueMessage;
|
||||||
//# sourceMappingURL=file-command.js.map
|
//# sourceMappingURL=file-command.js.map
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"file-command.js","sourceRoot":"","sources":["../src/file-command.ts"],"names":[],"mappings":";AAAA,uCAAuC;;;;;;;;;;;;;;;;;;;;;;AAEvC,mCAAmC;AACnC,uDAAuD;AAEvD,uCAAwB;AACxB,uCAAwB;AACxB,mCAAsC;AAEtC,SAAgB,YAAY,CAAC,OAAe,EAAE,OAAY;IACxD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAA;IACjD,IAAI,CAAC,QAAQ,EAAE;QACb,MAAM,IAAI,KAAK,CACb,wDAAwD,OAAO,EAAE,CAClE,CAAA;KACF;IACD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,EAAE,CAAC,CAAA;KACrD;IAED,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,sBAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;QACjE,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAA;AACJ,CAAC;AAdD,oCAcC"}
|
{"version":3,"file":"file-command.js","sourceRoot":"","sources":["../src/file-command.ts"],"names":[],"mappings":";AAAA,uCAAuC;;;;;;;;;;;;;;;;;;;;;;AAEvC,mCAAmC;AACnC,uDAAuD;AAEvD,uCAAwB;AACxB,uCAAwB;AACxB,+BAAiC;AACjC,mCAAsC;AAEtC,SAAgB,gBAAgB,CAAC,OAAe,EAAE,OAAY;IAC5D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAA;IACjD,IAAI,CAAC,QAAQ,EAAE;QACb,MAAM,IAAI,KAAK,CACb,wDAAwD,OAAO,EAAE,CAClE,CAAA;KACF;IACD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,EAAE,CAAC,CAAA;KACrD;IAED,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,sBAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;QACjE,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAA;AACJ,CAAC;AAdD,4CAcC;AAED,SAAgB,sBAAsB,CAAC,GAAW,EAAE,KAAU;IAC5D,MAAM,SAAS,GAAG,gBAAgB,SAAM,EAAE,EAAE,CAAA;IAC5C,MAAM,cAAc,GAAG,sBAAc,CAAC,KAAK,CAAC,CAAA;IAE5C,4EAA4E;IAC5E,6EAA6E;IAC7E,iBAAiB;IACjB,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAC3B,MAAM,IAAI,KAAK,CACb,4DAA4D,SAAS,GAAG,CACzE,CAAA;KACF;IAED,IAAI,cAAc,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;QACtC,MAAM,IAAI,KAAK,CACb,6DAA6D,SAAS,GAAG,CAC1E,CAAA;KACF;IAED,OAAO,GAAG,GAAG,KAAK,SAAS,GAAG,EAAE,CAAC,GAAG,GAAG,cAAc,GAAG,EAAE,CAAC,GAAG,GAAG,SAAS,EAAE,CAAA;AAC9E,CAAC;AApBD,wDAoBC"}
|
||||||
+1
-1
@@ -44,7 +44,7 @@ class OidcClient {
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
throw new Error(`Failed to get ID Token. \n
|
throw new Error(`Failed to get ID Token. \n
|
||||||
Error Code : ${error.statusCode}\n
|
Error Code : ${error.statusCode}\n
|
||||||
Error Message: ${error.result.message}`);
|
Error Message: ${error.message}`);
|
||||||
});
|
});
|
||||||
const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value;
|
const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value;
|
||||||
if (!id_token) {
|
if (!id_token) {
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"oidc-utils.js","sourceRoot":"","sources":["../src/oidc-utils.ts"],"names":[],"mappings":";;;;;;;;;;;;AAGA,sDAA+C;AAC/C,wDAAqE;AACrE,iCAAuC;AAKvC,MAAa,UAAU;IACb,MAAM,CAAC,gBAAgB,CAC7B,UAAU,GAAG,IAAI,EACjB,QAAQ,GAAG,EAAE;QAEb,MAAM,cAAc,GAAmB;YACrC,YAAY,EAAE,UAAU;YACxB,UAAU,EAAE,QAAQ;SACrB,CAAA;QAED,OAAO,IAAI,wBAAU,CACnB,qBAAqB,EACrB,CAAC,IAAI,8BAAuB,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC,EAC3D,cAAc,CACf,CAAA;IACH,CAAC;IAEO,MAAM,CAAC,eAAe;QAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAA;QAC3D,IAAI,CAAC,KAAK,EAAE;YACV,MAAM,IAAI,KAAK,CACb,2DAA2D,CAC5D,CAAA;SACF;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAEO,MAAM,CAAC,aAAa;QAC1B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAA;QAC9D,IAAI,CAAC,UAAU,EAAE;YACf,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;SAC3E;QACD,OAAO,UAAU,CAAA;IACnB,CAAC;IAEO,MAAM,CAAO,OAAO,CAAC,YAAoB;;;YAC/C,MAAM,UAAU,GAAG,UAAU,CAAC,gBAAgB,EAAE,CAAA;YAEhD,MAAM,GAAG,GAAG,MAAM,UAAU;iBACzB,OAAO,CAAgB,YAAY,CAAC;iBACpC,KAAK,CAAC,KAAK,CAAC,EAAE;gBACb,MAAM,IAAI,KAAK,CACb;uBACa,KAAK,CAAC,UAAU;yBACd,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CACtC,CAAA;YACH,CAAC,CAAC,CAAA;YAEJ,MAAM,QAAQ,SAAG,GAAG,CAAC,MAAM,0CAAE,KAAK,CAAA;YAClC,IAAI,CAAC,QAAQ,EAAE;gBACb,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;aACjE;YACD,OAAO,QAAQ,CAAA;;KAChB;IAED,MAAM,CAAO,UAAU,CAAC,QAAiB;;YACvC,IAAI;gBACF,gDAAgD;gBAChD,IAAI,YAAY,GAAW,UAAU,CAAC,aAAa,EAAE,CAAA;gBACrD,IAAI,QAAQ,EAAE;oBACZ,MAAM,eAAe,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAA;oBACpD,YAAY,GAAG,GAAG,YAAY,aAAa,eAAe,EAAE,CAAA;iBAC7D;gBAED,YAAK,CAAC,mBAAmB,YAAY,EAAE,CAAC,CAAA;gBAExC,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;gBACvD,gBAAS,CAAC,QAAQ,CAAC,CAAA;gBACnB,OAAO,QAAQ,CAAA;aAChB;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;aACnD;QACH,CAAC;KAAA;CACF;AAzED,gCAyEC"}
|
{"version":3,"file":"oidc-utils.js","sourceRoot":"","sources":["../src/oidc-utils.ts"],"names":[],"mappings":";;;;;;;;;;;;AAGA,sDAA+C;AAC/C,wDAAqE;AACrE,iCAAuC;AAKvC,MAAa,UAAU;IACb,MAAM,CAAC,gBAAgB,CAC7B,UAAU,GAAG,IAAI,EACjB,QAAQ,GAAG,EAAE;QAEb,MAAM,cAAc,GAAmB;YACrC,YAAY,EAAE,UAAU;YACxB,UAAU,EAAE,QAAQ;SACrB,CAAA;QAED,OAAO,IAAI,wBAAU,CACnB,qBAAqB,EACrB,CAAC,IAAI,8BAAuB,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC,EAC3D,cAAc,CACf,CAAA;IACH,CAAC;IAEO,MAAM,CAAC,eAAe;QAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAA;QAC3D,IAAI,CAAC,KAAK,EAAE;YACV,MAAM,IAAI,KAAK,CACb,2DAA2D,CAC5D,CAAA;SACF;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAEO,MAAM,CAAC,aAAa;QAC1B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAA;QAC9D,IAAI,CAAC,UAAU,EAAE;YACf,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;SAC3E;QACD,OAAO,UAAU,CAAA;IACnB,CAAC;IAEO,MAAM,CAAO,OAAO,CAAC,YAAoB;;;YAC/C,MAAM,UAAU,GAAG,UAAU,CAAC,gBAAgB,EAAE,CAAA;YAEhD,MAAM,GAAG,GAAG,MAAM,UAAU;iBACzB,OAAO,CAAgB,YAAY,CAAC;iBACpC,KAAK,CAAC,KAAK,CAAC,EAAE;gBACb,MAAM,IAAI,KAAK,CACb;uBACa,KAAK,CAAC,UAAU;yBACd,KAAK,CAAC,OAAO,EAAE,CAC/B,CAAA;YACH,CAAC,CAAC,CAAA;YAEJ,MAAM,QAAQ,SAAG,GAAG,CAAC,MAAM,0CAAE,KAAK,CAAA;YAClC,IAAI,CAAC,QAAQ,EAAE;gBACb,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;aACjE;YACD,OAAO,QAAQ,CAAA;;KAChB;IAED,MAAM,CAAO,UAAU,CAAC,QAAiB;;YACvC,IAAI;gBACF,gDAAgD;gBAChD,IAAI,YAAY,GAAW,UAAU,CAAC,aAAa,EAAE,CAAA;gBACrD,IAAI,QAAQ,EAAE;oBACZ,MAAM,eAAe,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAA;oBACpD,YAAY,GAAG,GAAG,YAAY,aAAa,eAAe,EAAE,CAAA;iBAC7D;gBAED,YAAK,CAAC,mBAAmB,YAAY,EAAE,CAAC,CAAA;gBAExC,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;gBACvD,gBAAS,CAAC,QAAQ,CAAC,CAAA;gBACnB,OAAO,QAAQ,CAAA;aAChB;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;aACnD;QACH,CAAC;KAAA;CACF;AAzED,gCAyEC"}
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/core",
|
"name": "@actions/core",
|
||||||
"version": "1.9.1",
|
"version": "1.10.1",
|
||||||
"description": "Actions core lib",
|
"description": "Actions core lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json",
|
"audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json",
|
||||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||||
"tsc": "tsc"
|
"tsc": "tsc -p tsconfig.json"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/actions/toolkit/issues"
|
"url": "https://github.com/actions/toolkit/issues"
|
||||||
@@ -43,4 +43,4 @@
|
|||||||
"@types/node": "^12.0.2",
|
"@types/node": "^12.0.2",
|
||||||
"@types/uuid": "^8.3.4"
|
"@types/uuid": "^8.3.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import * as Context from './context';
|
import * as Context from './context';
|
||||||
import { GitHub } from './utils';
|
import { GitHub } from './utils';
|
||||||
import { OctokitOptions } from '@octokit/core/dist-types/types';
|
import { OctokitOptions, OctokitPlugin } from '@octokit/core/dist-types/types';
|
||||||
export declare const context: Context.Context;
|
export declare const context: Context.Context;
|
||||||
/**
|
/**
|
||||||
* Returns a hydrated octokit ready to use for GitHub Actions
|
* Returns a hydrated octokit ready to use for GitHub Actions
|
||||||
@@ -8,4 +8,4 @@ export declare const context: Context.Context;
|
|||||||
* @param token the repo PAT or GITHUB_TOKEN
|
* @param token the repo PAT or GITHUB_TOKEN
|
||||||
* @param options other options to set
|
* @param options other options to set
|
||||||
*/
|
*/
|
||||||
export declare function getOctokit(token: string, options?: OctokitOptions): InstanceType<typeof GitHub>;
|
export declare function getOctokit(token: string, options?: OctokitOptions, ...additionalPlugins: OctokitPlugin[]): InstanceType<typeof GitHub>;
|
||||||
|
|||||||
+3
-2
@@ -29,8 +29,9 @@ exports.context = new Context.Context();
|
|||||||
* @param token the repo PAT or GITHUB_TOKEN
|
* @param token the repo PAT or GITHUB_TOKEN
|
||||||
* @param options other options to set
|
* @param options other options to set
|
||||||
*/
|
*/
|
||||||
function getOctokit(token, options) {
|
function getOctokit(token, options, ...additionalPlugins) {
|
||||||
return new utils_1.GitHub(utils_1.getOctokitOptions(token, options));
|
const GitHubWithPlugins = utils_1.GitHub.plugin(...additionalPlugins);
|
||||||
|
return new GitHubWithPlugins(utils_1.getOctokitOptions(token, options));
|
||||||
}
|
}
|
||||||
exports.getOctokit = getOctokit;
|
exports.getOctokit = getOctokit;
|
||||||
//# sourceMappingURL=github.js.map
|
//# sourceMappingURL=github.js.map
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,mDAAoC;AACpC,mCAAiD;AAKpC,QAAA,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAA;AAE5C;;;;;GAKG;AACH,SAAgB,UAAU,CACxB,KAAa,EACb,OAAwB;IAExB,OAAO,IAAI,cAAM,CAAC,yBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAA;AACtD,CAAC;AALD,gCAKC"}
|
{"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,mDAAoC;AACpC,mCAAiD;AAKpC,QAAA,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAA;AAE5C;;;;;GAKG;AACH,SAAgB,UAAU,CACxB,KAAa,EACb,OAAwB,EACxB,GAAG,iBAAkC;IAErC,MAAM,iBAAiB,GAAG,cAAM,CAAC,MAAM,CAAC,GAAG,iBAAiB,CAAC,CAAA;IAC7D,OAAO,IAAI,iBAAiB,CAAC,yBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAA;AACjE,CAAC;AAPD,gCAOC"}
|
||||||
+1
@@ -2,6 +2,7 @@ import * as Context from './context';
|
|||||||
import { Octokit } from '@octokit/core';
|
import { Octokit } from '@octokit/core';
|
||||||
import { OctokitOptions } from '@octokit/core/dist-types/types';
|
import { OctokitOptions } from '@octokit/core/dist-types/types';
|
||||||
export declare const context: Context.Context;
|
export declare const context: Context.Context;
|
||||||
|
export declare const defaults: OctokitOptions;
|
||||||
export declare const GitHub: typeof Octokit & import("@octokit/core/dist-types/types").Constructor<import("@octokit/plugin-rest-endpoint-methods/dist-types/types").Api & {
|
export declare const GitHub: typeof Octokit & import("@octokit/core/dist-types/types").Constructor<import("@octokit/plugin-rest-endpoint-methods/dist-types/types").Api & {
|
||||||
paginate: import("@octokit/plugin-paginate-rest").PaginateInterface;
|
paginate: import("@octokit/plugin-paginate-rest").PaginateInterface;
|
||||||
}>;
|
}>;
|
||||||
|
|||||||
+3
-3
@@ -19,7 +19,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.getOctokitOptions = exports.GitHub = exports.context = void 0;
|
exports.getOctokitOptions = exports.GitHub = exports.defaults = exports.context = void 0;
|
||||||
const Context = __importStar(require("./context"));
|
const Context = __importStar(require("./context"));
|
||||||
const Utils = __importStar(require("./internal/utils"));
|
const Utils = __importStar(require("./internal/utils"));
|
||||||
// octokit + plugins
|
// octokit + plugins
|
||||||
@@ -28,13 +28,13 @@ const plugin_rest_endpoint_methods_1 = require("@octokit/plugin-rest-endpoint-me
|
|||||||
const plugin_paginate_rest_1 = require("@octokit/plugin-paginate-rest");
|
const plugin_paginate_rest_1 = require("@octokit/plugin-paginate-rest");
|
||||||
exports.context = new Context.Context();
|
exports.context = new Context.Context();
|
||||||
const baseUrl = Utils.getApiBaseUrl();
|
const baseUrl = Utils.getApiBaseUrl();
|
||||||
const defaults = {
|
exports.defaults = {
|
||||||
baseUrl,
|
baseUrl,
|
||||||
request: {
|
request: {
|
||||||
agent: Utils.getProxyAgent(baseUrl)
|
agent: Utils.getProxyAgent(baseUrl)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
exports.GitHub = core_1.Octokit.plugin(plugin_rest_endpoint_methods_1.restEndpointMethods, plugin_paginate_rest_1.paginateRest).defaults(defaults);
|
exports.GitHub = core_1.Octokit.plugin(plugin_rest_endpoint_methods_1.restEndpointMethods, plugin_paginate_rest_1.paginateRest).defaults(exports.defaults);
|
||||||
/**
|
/**
|
||||||
* Convience function to correctly format Octokit Options to pass into the constructor.
|
* Convience function to correctly format Octokit Options to pass into the constructor.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,mDAAoC;AACpC,wDAAyC;AAEzC,oBAAoB;AACpB,wCAAqC;AAErC,wFAAyE;AACzE,wEAA0D;AAE7C,QAAA,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAA;AAE5C,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,EAAE,CAAA;AACrC,MAAM,QAAQ,GAAG;IACf,OAAO;IACP,OAAO,EAAE;QACP,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC;KACpC;CACF,CAAA;AAEY,QAAA,MAAM,GAAG,cAAO,CAAC,MAAM,CAClC,kDAAmB,EACnB,mCAAY,CACb,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;AAEpB;;;;;GAKG;AACH,SAAgB,iBAAiB,CAC/B,KAAa,EACb,OAAwB;IAExB,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,IAAI,EAAE,CAAC,CAAA,CAAC,iEAAiE;IAE/G,OAAO;IACP,MAAM,IAAI,GAAG,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IAC7C,IAAI,IAAI,EAAE;QACR,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;KACjB;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAbD,8CAaC"}
|
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,mDAAoC;AACpC,wDAAyC;AAEzC,oBAAoB;AACpB,wCAAqC;AAErC,wFAAyE;AACzE,wEAA0D;AAE7C,QAAA,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAA;AAE5C,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,EAAE,CAAA;AACxB,QAAA,QAAQ,GAAmB;IACtC,OAAO;IACP,OAAO,EAAE;QACP,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC;KACpC;CACF,CAAA;AAEY,QAAA,MAAM,GAAG,cAAO,CAAC,MAAM,CAClC,kDAAmB,EACnB,mCAAY,CACb,CAAC,QAAQ,CAAC,gBAAQ,CAAC,CAAA;AAEpB;;;;;GAKG;AACH,SAAgB,iBAAiB,CAC/B,KAAa,EACb,OAAwB;IAExB,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,IAAI,EAAE,CAAC,CAAA,CAAC,iEAAiE;IAE/G,OAAO;IACP,MAAM,IAAI,GAAG,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IAC7C,IAAI,IAAI,EAAE;QACR,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;KACjB;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAbD,8CAaC"}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/github",
|
"name": "@actions/github",
|
||||||
"version": "5.0.3",
|
"version": "5.1.1",
|
||||||
"description": "Actions github lib",
|
"description": "Actions github lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
|
|||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 David Frank
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
+590
@@ -0,0 +1,590 @@
|
|||||||
|
node-fetch
|
||||||
|
==========
|
||||||
|
|
||||||
|
[![npm version][npm-image]][npm-url]
|
||||||
|
[![build status][travis-image]][travis-url]
|
||||||
|
[![coverage status][codecov-image]][codecov-url]
|
||||||
|
[![install size][install-size-image]][install-size-url]
|
||||||
|
[![Discord][discord-image]][discord-url]
|
||||||
|
|
||||||
|
A light-weight module that brings `window.fetch` to Node.js
|
||||||
|
|
||||||
|
(We are looking for [v2 maintainers and collaborators](https://github.com/bitinn/node-fetch/issues/567))
|
||||||
|
|
||||||
|
[![Backers][opencollective-image]][opencollective-url]
|
||||||
|
|
||||||
|
<!-- TOC -->
|
||||||
|
|
||||||
|
- [Motivation](#motivation)
|
||||||
|
- [Features](#features)
|
||||||
|
- [Difference from client-side fetch](#difference-from-client-side-fetch)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Loading and configuring the module](#loading-and-configuring-the-module)
|
||||||
|
- [Common Usage](#common-usage)
|
||||||
|
- [Plain text or HTML](#plain-text-or-html)
|
||||||
|
- [JSON](#json)
|
||||||
|
- [Simple Post](#simple-post)
|
||||||
|
- [Post with JSON](#post-with-json)
|
||||||
|
- [Post with form parameters](#post-with-form-parameters)
|
||||||
|
- [Handling exceptions](#handling-exceptions)
|
||||||
|
- [Handling client and server errors](#handling-client-and-server-errors)
|
||||||
|
- [Advanced Usage](#advanced-usage)
|
||||||
|
- [Streams](#streams)
|
||||||
|
- [Buffer](#buffer)
|
||||||
|
- [Accessing Headers and other Meta data](#accessing-headers-and-other-meta-data)
|
||||||
|
- [Extract Set-Cookie Header](#extract-set-cookie-header)
|
||||||
|
- [Post data using a file stream](#post-data-using-a-file-stream)
|
||||||
|
- [Post with form-data (detect multipart)](#post-with-form-data-detect-multipart)
|
||||||
|
- [Request cancellation with AbortSignal](#request-cancellation-with-abortsignal)
|
||||||
|
- [API](#api)
|
||||||
|
- [fetch(url[, options])](#fetchurl-options)
|
||||||
|
- [Options](#options)
|
||||||
|
- [Class: Request](#class-request)
|
||||||
|
- [Class: Response](#class-response)
|
||||||
|
- [Class: Headers](#class-headers)
|
||||||
|
- [Interface: Body](#interface-body)
|
||||||
|
- [Class: FetchError](#class-fetcherror)
|
||||||
|
- [License](#license)
|
||||||
|
- [Acknowledgement](#acknowledgement)
|
||||||
|
|
||||||
|
<!-- /TOC -->
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Instead of implementing `XMLHttpRequest` in Node.js to run browser-specific [Fetch polyfill](https://github.com/github/fetch), why not go from native `http` to `fetch` API directly? Hence, `node-fetch`, minimal code for a `window.fetch` compatible API on Node.js runtime.
|
||||||
|
|
||||||
|
See Matt Andrews' [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) or Leonardo Quixada's [cross-fetch](https://github.com/lquixada/cross-fetch) for isomorphic usage (exports `node-fetch` for server-side, `whatwg-fetch` for client-side).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Stay consistent with `window.fetch` API.
|
||||||
|
- Make conscious trade-off when following [WHATWG fetch spec][whatwg-fetch] and [stream spec](https://streams.spec.whatwg.org/) implementation details, document known differences.
|
||||||
|
- Use native promise but allow substituting it with [insert your favorite promise library].
|
||||||
|
- Use native Node streams for body on both request and response.
|
||||||
|
- Decode content encoding (gzip/deflate) properly and convert string output (such as `res.text()` and `res.json()`) to UTF-8 automatically.
|
||||||
|
- Useful extensions such as timeout, redirect limit, response size limit, [explicit errors](ERROR-HANDLING.md) for troubleshooting.
|
||||||
|
|
||||||
|
## Difference from client-side fetch
|
||||||
|
|
||||||
|
- See [Known Differences](LIMITS.md) for details.
|
||||||
|
- If you happen to use a missing feature that `window.fetch` offers, feel free to open an issue.
|
||||||
|
- Pull requests are welcomed too!
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Current stable release (`2.x`)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm install node-fetch
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loading and configuring the module
|
||||||
|
We suggest you load the module via `require` until the stabilization of ES modules in node:
|
||||||
|
```js
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using a Promise library other than native, set it through `fetch.Promise`:
|
||||||
|
```js
|
||||||
|
const Bluebird = require('bluebird');
|
||||||
|
|
||||||
|
fetch.Promise = Bluebird;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Usage
|
||||||
|
|
||||||
|
NOTE: The documentation below is up-to-date with `2.x` releases; see the [`1.x` readme](https://github.com/bitinn/node-fetch/blob/1.x/README.md), [changelog](https://github.com/bitinn/node-fetch/blob/1.x/CHANGELOG.md) and [2.x upgrade guide](UPGRADE-GUIDE.md) for the differences.
|
||||||
|
|
||||||
|
#### Plain text or HTML
|
||||||
|
```js
|
||||||
|
fetch('https://github.com/')
|
||||||
|
.then(res => res.text())
|
||||||
|
.then(body => console.log(body));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### JSON
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
fetch('https://api.github.com/users/github')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => console.log(json));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Simple Post
|
||||||
|
```js
|
||||||
|
fetch('https://httpbin.org/post', { method: 'POST', body: 'a=1' })
|
||||||
|
.then(res => res.json()) // expecting a json response
|
||||||
|
.then(json => console.log(json));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Post with JSON
|
||||||
|
|
||||||
|
```js
|
||||||
|
const body = { a: 1 };
|
||||||
|
|
||||||
|
fetch('https://httpbin.org/post', {
|
||||||
|
method: 'post',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => console.log(json));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Post with form parameters
|
||||||
|
`URLSearchParams` is available in Node.js as of v7.5.0. See [official documentation](https://nodejs.org/api/url.html#url_class_urlsearchparams) for more usage methods.
|
||||||
|
|
||||||
|
NOTE: The `Content-Type` header is only set automatically to `x-www-form-urlencoded` when an instance of `URLSearchParams` is given as such:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { URLSearchParams } = require('url');
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('a', 1);
|
||||||
|
|
||||||
|
fetch('https://httpbin.org/post', { method: 'POST', body: params })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => console.log(json));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Handling exceptions
|
||||||
|
NOTE: 3xx-5xx responses are *NOT* exceptions and should be handled in `then()`; see the next section for more information.
|
||||||
|
|
||||||
|
Adding a catch to the fetch promise chain will catch *all* exceptions, such as errors originating from node core libraries, network errors and operational errors, which are instances of FetchError. See the [error handling document](ERROR-HANDLING.md) for more details.
|
||||||
|
|
||||||
|
```js
|
||||||
|
fetch('https://domain.invalid/')
|
||||||
|
.catch(err => console.error(err));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Handling client and server errors
|
||||||
|
It is common to create a helper function to check that the response contains no client (4xx) or server (5xx) error responses:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function checkStatus(res) {
|
||||||
|
if (res.ok) { // res.status >= 200 && res.status < 300
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
throw MyCustomError(res.statusText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('https://httpbin.org/status/400')
|
||||||
|
.then(checkStatus)
|
||||||
|
.then(res => console.log('will not get here...'))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
#### Streams
|
||||||
|
The "Node.js way" is to use streams when possible:
|
||||||
|
|
||||||
|
```js
|
||||||
|
fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png')
|
||||||
|
.then(res => {
|
||||||
|
const dest = fs.createWriteStream('./octocat.png');
|
||||||
|
res.body.pipe(dest);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Buffer
|
||||||
|
If you prefer to cache binary data in full, use buffer(). (NOTE: `buffer()` is a `node-fetch`-only API)
|
||||||
|
|
||||||
|
```js
|
||||||
|
const fileType = require('file-type');
|
||||||
|
|
||||||
|
fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png')
|
||||||
|
.then(res => res.buffer())
|
||||||
|
.then(buffer => fileType(buffer))
|
||||||
|
.then(type => { /* ... */ });
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Accessing Headers and other Meta data
|
||||||
|
```js
|
||||||
|
fetch('https://github.com/')
|
||||||
|
.then(res => {
|
||||||
|
console.log(res.ok);
|
||||||
|
console.log(res.status);
|
||||||
|
console.log(res.statusText);
|
||||||
|
console.log(res.headers.raw());
|
||||||
|
console.log(res.headers.get('content-type'));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Extract Set-Cookie Header
|
||||||
|
|
||||||
|
Unlike browsers, you can access raw `Set-Cookie` headers manually using `Headers.raw()`. This is a `node-fetch` only API.
|
||||||
|
|
||||||
|
```js
|
||||||
|
fetch(url).then(res => {
|
||||||
|
// returns an array of values, instead of a string of comma-separated values
|
||||||
|
console.log(res.headers.raw()['set-cookie']);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Post data using a file stream
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { createReadStream } = require('fs');
|
||||||
|
|
||||||
|
const stream = createReadStream('input.txt');
|
||||||
|
|
||||||
|
fetch('https://httpbin.org/post', { method: 'POST', body: stream })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => console.log(json));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Post with form-data (detect multipart)
|
||||||
|
|
||||||
|
```js
|
||||||
|
const FormData = require('form-data');
|
||||||
|
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('a', 1);
|
||||||
|
|
||||||
|
fetch('https://httpbin.org/post', { method: 'POST', body: form })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => console.log(json));
|
||||||
|
|
||||||
|
// OR, using custom headers
|
||||||
|
// NOTE: getHeaders() is non-standard API
|
||||||
|
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('a', 1);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
body: form,
|
||||||
|
headers: form.getHeaders()
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('https://httpbin.org/post', options)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => console.log(json));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Request cancellation with AbortSignal
|
||||||
|
|
||||||
|
> NOTE: You may cancel streamed requests only on Node >= v8.0.0
|
||||||
|
|
||||||
|
You may cancel requests with `AbortController`. A suggested implementation is [`abort-controller`](https://www.npmjs.com/package/abort-controller).
|
||||||
|
|
||||||
|
An example of timing out a request after 150ms could be achieved as the following:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import AbortController from 'abort-controller';
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(
|
||||||
|
() => { controller.abort(); },
|
||||||
|
150,
|
||||||
|
);
|
||||||
|
|
||||||
|
fetch(url, { signal: controller.signal })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(
|
||||||
|
data => {
|
||||||
|
useData(data)
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
if (err.name === 'AbortError') {
|
||||||
|
// request was aborted
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
See [test cases](https://github.com/bitinn/node-fetch/blob/master/test/test.js) for more examples.
|
||||||
|
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### fetch(url[, options])
|
||||||
|
|
||||||
|
- `url` A string representing the URL for fetching
|
||||||
|
- `options` [Options](#fetch-options) for the HTTP(S) request
|
||||||
|
- Returns: <code>Promise<[Response](#class-response)></code>
|
||||||
|
|
||||||
|
Perform an HTTP(S) fetch.
|
||||||
|
|
||||||
|
`url` should be an absolute url, such as `https://example.com/`. A path-relative URL (`/file/under/root`) or protocol-relative URL (`//can-be-http-or-https.com/`) will result in a rejected `Promise`.
|
||||||
|
|
||||||
|
<a id="fetch-options"></a>
|
||||||
|
### Options
|
||||||
|
|
||||||
|
The default values are shown after each option key.
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
// These properties are part of the Fetch Standard
|
||||||
|
method: 'GET',
|
||||||
|
headers: {}, // request headers. format is the identical to that accepted by the Headers constructor (see below)
|
||||||
|
body: null, // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream
|
||||||
|
redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect
|
||||||
|
signal: null, // pass an instance of AbortSignal to optionally abort requests
|
||||||
|
|
||||||
|
// The following properties are node-fetch extensions
|
||||||
|
follow: 20, // maximum redirect count. 0 to not follow redirect
|
||||||
|
timeout: 0, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
|
||||||
|
compress: true, // support gzip/deflate content encoding. false to disable
|
||||||
|
size: 0, // maximum response body size in bytes. 0 to disable
|
||||||
|
agent: null // http(s).Agent instance or function that returns an instance (see below)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Default Headers
|
||||||
|
|
||||||
|
If no values are set, the following request headers will be sent automatically:
|
||||||
|
|
||||||
|
Header | Value
|
||||||
|
------------------- | --------------------------------------------------------
|
||||||
|
`Accept-Encoding` | `gzip,deflate` _(when `options.compress === true`)_
|
||||||
|
`Accept` | `*/*`
|
||||||
|
`Connection` | `close` _(when no `options.agent` is present)_
|
||||||
|
`Content-Length` | _(automatically calculated, if possible)_
|
||||||
|
`Transfer-Encoding` | `chunked` _(when `req.body` is a stream)_
|
||||||
|
`User-Agent` | `node-fetch/1.0 (+https://github.com/bitinn/node-fetch)`
|
||||||
|
|
||||||
|
Note: when `body` is a `Stream`, `Content-Length` is not set automatically.
|
||||||
|
|
||||||
|
##### Custom Agent
|
||||||
|
|
||||||
|
The `agent` option allows you to specify networking related options which are out of the scope of Fetch, including and not limited to the following:
|
||||||
|
|
||||||
|
- Support self-signed certificate
|
||||||
|
- Use only IPv4 or IPv6
|
||||||
|
- Custom DNS Lookup
|
||||||
|
|
||||||
|
See [`http.Agent`](https://nodejs.org/api/http.html#http_new_agent_options) for more information.
|
||||||
|
|
||||||
|
In addition, the `agent` option accepts a function that returns `http`(s)`.Agent` instance given current [URL](https://nodejs.org/api/url.html), this is useful during a redirection chain across HTTP and HTTPS protocol.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const httpAgent = new http.Agent({
|
||||||
|
keepAlive: true
|
||||||
|
});
|
||||||
|
const httpsAgent = new https.Agent({
|
||||||
|
keepAlive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
agent: function (_parsedURL) {
|
||||||
|
if (_parsedURL.protocol == 'http:') {
|
||||||
|
return httpAgent;
|
||||||
|
} else {
|
||||||
|
return httpsAgent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="class-request"></a>
|
||||||
|
### Class: Request
|
||||||
|
|
||||||
|
An HTTP(S) request containing information about URL, method, headers, and the body. This class implements the [Body](#iface-body) interface.
|
||||||
|
|
||||||
|
Due to the nature of Node.js, the following properties are not implemented at this moment:
|
||||||
|
|
||||||
|
- `type`
|
||||||
|
- `destination`
|
||||||
|
- `referrer`
|
||||||
|
- `referrerPolicy`
|
||||||
|
- `mode`
|
||||||
|
- `credentials`
|
||||||
|
- `cache`
|
||||||
|
- `integrity`
|
||||||
|
- `keepalive`
|
||||||
|
|
||||||
|
The following node-fetch extension properties are provided:
|
||||||
|
|
||||||
|
- `follow`
|
||||||
|
- `compress`
|
||||||
|
- `counter`
|
||||||
|
- `agent`
|
||||||
|
|
||||||
|
See [options](#fetch-options) for exact meaning of these extensions.
|
||||||
|
|
||||||
|
#### new Request(input[, options])
|
||||||
|
|
||||||
|
<small>*(spec-compliant)*</small>
|
||||||
|
|
||||||
|
- `input` A string representing a URL, or another `Request` (which will be cloned)
|
||||||
|
- `options` [Options][#fetch-options] for the HTTP(S) request
|
||||||
|
|
||||||
|
Constructs a new `Request` object. The constructor is identical to that in the [browser](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request).
|
||||||
|
|
||||||
|
In most cases, directly `fetch(url, options)` is simpler than creating a `Request` object.
|
||||||
|
|
||||||
|
<a id="class-response"></a>
|
||||||
|
### Class: Response
|
||||||
|
|
||||||
|
An HTTP(S) response. This class implements the [Body](#iface-body) interface.
|
||||||
|
|
||||||
|
The following properties are not implemented in node-fetch at this moment:
|
||||||
|
|
||||||
|
- `Response.error()`
|
||||||
|
- `Response.redirect()`
|
||||||
|
- `type`
|
||||||
|
- `trailer`
|
||||||
|
|
||||||
|
#### new Response([body[, options]])
|
||||||
|
|
||||||
|
<small>*(spec-compliant)*</small>
|
||||||
|
|
||||||
|
- `body` A `String` or [`Readable` stream][node-readable]
|
||||||
|
- `options` A [`ResponseInit`][response-init] options dictionary
|
||||||
|
|
||||||
|
Constructs a new `Response` object. The constructor is identical to that in the [browser](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response).
|
||||||
|
|
||||||
|
Because Node.js does not implement service workers (for which this class was designed), one rarely has to construct a `Response` directly.
|
||||||
|
|
||||||
|
#### response.ok
|
||||||
|
|
||||||
|
<small>*(spec-compliant)*</small>
|
||||||
|
|
||||||
|
Convenience property representing if the request ended normally. Will evaluate to true if the response status was greater than or equal to 200 but smaller than 300.
|
||||||
|
|
||||||
|
#### response.redirected
|
||||||
|
|
||||||
|
<small>*(spec-compliant)*</small>
|
||||||
|
|
||||||
|
Convenience property representing if the request has been redirected at least once. Will evaluate to true if the internal redirect counter is greater than 0.
|
||||||
|
|
||||||
|
<a id="class-headers"></a>
|
||||||
|
### Class: Headers
|
||||||
|
|
||||||
|
This class allows manipulating and iterating over a set of HTTP headers. All methods specified in the [Fetch Standard][whatwg-fetch] are implemented.
|
||||||
|
|
||||||
|
#### new Headers([init])
|
||||||
|
|
||||||
|
<small>*(spec-compliant)*</small>
|
||||||
|
|
||||||
|
- `init` Optional argument to pre-fill the `Headers` object
|
||||||
|
|
||||||
|
Construct a new `Headers` object. `init` can be either `null`, a `Headers` object, an key-value map object or any iterable object.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Example adapted from https://fetch.spec.whatwg.org/#example-headers-class
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
'Content-Type': 'text/xml',
|
||||||
|
'Breaking-Bad': '<3'
|
||||||
|
};
|
||||||
|
const headers = new Headers(meta);
|
||||||
|
|
||||||
|
// The above is equivalent to
|
||||||
|
const meta = [
|
||||||
|
[ 'Content-Type', 'text/xml' ],
|
||||||
|
[ 'Breaking-Bad', '<3' ]
|
||||||
|
];
|
||||||
|
const headers = new Headers(meta);
|
||||||
|
|
||||||
|
// You can in fact use any iterable objects, like a Map or even another Headers
|
||||||
|
const meta = new Map();
|
||||||
|
meta.set('Content-Type', 'text/xml');
|
||||||
|
meta.set('Breaking-Bad', '<3');
|
||||||
|
const headers = new Headers(meta);
|
||||||
|
const copyOfHeaders = new Headers(headers);
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="iface-body"></a>
|
||||||
|
### Interface: Body
|
||||||
|
|
||||||
|
`Body` is an abstract interface with methods that are applicable to both `Request` and `Response` classes.
|
||||||
|
|
||||||
|
The following methods are not yet implemented in node-fetch at this moment:
|
||||||
|
|
||||||
|
- `formData()`
|
||||||
|
|
||||||
|
#### body.body
|
||||||
|
|
||||||
|
<small>*(deviation from spec)*</small>
|
||||||
|
|
||||||
|
* Node.js [`Readable` stream][node-readable]
|
||||||
|
|
||||||
|
Data are encapsulated in the `Body` object. Note that while the [Fetch Standard][whatwg-fetch] requires the property to always be a WHATWG `ReadableStream`, in node-fetch it is a Node.js [`Readable` stream][node-readable].
|
||||||
|
|
||||||
|
#### body.bodyUsed
|
||||||
|
|
||||||
|
<small>*(spec-compliant)*</small>
|
||||||
|
|
||||||
|
* `Boolean`
|
||||||
|
|
||||||
|
A boolean property for if this body has been consumed. Per the specs, a consumed body cannot be used again.
|
||||||
|
|
||||||
|
#### body.arrayBuffer()
|
||||||
|
#### body.blob()
|
||||||
|
#### body.json()
|
||||||
|
#### body.text()
|
||||||
|
|
||||||
|
<small>*(spec-compliant)*</small>
|
||||||
|
|
||||||
|
* Returns: <code>Promise</code>
|
||||||
|
|
||||||
|
Consume the body and return a promise that will resolve to one of these formats.
|
||||||
|
|
||||||
|
#### body.buffer()
|
||||||
|
|
||||||
|
<small>*(node-fetch extension)*</small>
|
||||||
|
|
||||||
|
* Returns: <code>Promise<Buffer></code>
|
||||||
|
|
||||||
|
Consume the body and return a promise that will resolve to a Buffer.
|
||||||
|
|
||||||
|
#### body.textConverted()
|
||||||
|
|
||||||
|
<small>*(node-fetch extension)*</small>
|
||||||
|
|
||||||
|
* Returns: <code>Promise<String></code>
|
||||||
|
|
||||||
|
Identical to `body.text()`, except instead of always converting to UTF-8, encoding sniffing will be performed and text converted to UTF-8 if possible.
|
||||||
|
|
||||||
|
(This API requires an optional dependency of the npm package [encoding](https://www.npmjs.com/package/encoding), which you need to install manually. `webpack` users may see [a warning message](https://github.com/bitinn/node-fetch/issues/412#issuecomment-379007792) due to this optional dependency.)
|
||||||
|
|
||||||
|
<a id="class-fetcherror"></a>
|
||||||
|
### Class: FetchError
|
||||||
|
|
||||||
|
<small>*(node-fetch extension)*</small>
|
||||||
|
|
||||||
|
An operational error in the fetching process. See [ERROR-HANDLING.md][] for more info.
|
||||||
|
|
||||||
|
<a id="class-aborterror"></a>
|
||||||
|
### Class: AbortError
|
||||||
|
|
||||||
|
<small>*(node-fetch extension)*</small>
|
||||||
|
|
||||||
|
An Error thrown when the request is aborted in response to an `AbortSignal`'s `abort` event. It has a `name` property of `AbortError`. See [ERROR-HANDLING.MD][] for more info.
|
||||||
|
|
||||||
|
## Acknowledgement
|
||||||
|
|
||||||
|
Thanks to [github/fetch](https://github.com/github/fetch) for providing a solid implementation reference.
|
||||||
|
|
||||||
|
`node-fetch` v1 was maintained by [@bitinn](https://github.com/bitinn); v2 was maintained by [@TimothyGu](https://github.com/timothygu), [@bitinn](https://github.com/bitinn) and [@jimmywarting](https://github.com/jimmywarting); v2 readme is written by [@jkantr](https://github.com/jkantr).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
[npm-image]: https://flat.badgen.net/npm/v/node-fetch
|
||||||
|
[npm-url]: https://www.npmjs.com/package/node-fetch
|
||||||
|
[travis-image]: https://flat.badgen.net/travis/bitinn/node-fetch
|
||||||
|
[travis-url]: https://travis-ci.org/bitinn/node-fetch
|
||||||
|
[codecov-image]: https://flat.badgen.net/codecov/c/github/bitinn/node-fetch/master
|
||||||
|
[codecov-url]: https://codecov.io/gh/bitinn/node-fetch
|
||||||
|
[install-size-image]: https://flat.badgen.net/packagephobia/install/node-fetch
|
||||||
|
[install-size-url]: https://packagephobia.now.sh/result?p=node-fetch
|
||||||
|
[discord-image]: https://img.shields.io/discord/619915844268326952?color=%237289DA&label=Discord&style=flat-square
|
||||||
|
[discord-url]: https://discord.gg/Zxbndcm
|
||||||
|
[opencollective-image]: https://opencollective.com/node-fetch/backers.svg
|
||||||
|
[opencollective-url]: https://opencollective.com/node-fetch
|
||||||
|
[whatwg-fetch]: https://fetch.spec.whatwg.org/
|
||||||
|
[response-init]: https://fetch.spec.whatwg.org/#responseinit
|
||||||
|
[node-readable]: https://nodejs.org/api/stream.html#stream_readable_streams
|
||||||
|
[mdn-headers]: https://developer.mozilla.org/en-US/docs/Web/API/Headers
|
||||||
|
[LIMITS.md]: https://github.com/bitinn/node-fetch/blob/master/LIMITS.md
|
||||||
|
[ERROR-HANDLING.md]: https://github.com/bitinn/node-fetch/blob/master/ERROR-HANDLING.md
|
||||||
|
[UPGRADE-GUIDE.md]: https://github.com/bitinn/node-fetch/blob/master/UPGRADE-GUIDE.md
|
||||||
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
+76
@@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"name": "node-fetch",
|
||||||
|
"version": "2.6.7",
|
||||||
|
"description": "A light-weight module that brings window.fetch to node.js",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"browser": "./browser.js",
|
||||||
|
"module": "lib/index.mjs",
|
||||||
|
"files": [
|
||||||
|
"lib/index.js",
|
||||||
|
"lib/index.mjs",
|
||||||
|
"lib/index.es.js",
|
||||||
|
"browser.js"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "cross-env BABEL_ENV=rollup rollup -c",
|
||||||
|
"prepare": "npm run build",
|
||||||
|
"test": "cross-env BABEL_ENV=test mocha --require babel-register --throw-deprecation test/test.js",
|
||||||
|
"report": "cross-env BABEL_ENV=coverage nyc --reporter lcov --reporter text mocha -R spec test/test.js",
|
||||||
|
"coverage": "cross-env BABEL_ENV=coverage nyc --reporter json --reporter text mocha -R spec test/test.js && codecov -f coverage/coverage-final.json"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/bitinn/node-fetch.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"fetch",
|
||||||
|
"http",
|
||||||
|
"promise"
|
||||||
|
],
|
||||||
|
"author": "David Frank",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/bitinn/node-fetch/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/bitinn/node-fetch",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@ungap/url-search-params": "^0.1.2",
|
||||||
|
"abort-controller": "^1.1.0",
|
||||||
|
"abortcontroller-polyfill": "^1.3.0",
|
||||||
|
"babel-core": "^6.26.3",
|
||||||
|
"babel-plugin-istanbul": "^4.1.6",
|
||||||
|
"babel-preset-env": "^1.6.1",
|
||||||
|
"babel-register": "^6.16.3",
|
||||||
|
"chai": "^3.5.0",
|
||||||
|
"chai-as-promised": "^7.1.1",
|
||||||
|
"chai-iterator": "^1.1.1",
|
||||||
|
"chai-string": "~1.3.0",
|
||||||
|
"codecov": "3.3.0",
|
||||||
|
"cross-env": "^5.2.0",
|
||||||
|
"form-data": "^2.3.3",
|
||||||
|
"is-builtin-module": "^1.0.0",
|
||||||
|
"mocha": "^5.0.0",
|
||||||
|
"nyc": "11.9.0",
|
||||||
|
"parted": "^0.1.1",
|
||||||
|
"promise": "^8.0.3",
|
||||||
|
"resumer": "0.0.0",
|
||||||
|
"rollup": "^0.63.4",
|
||||||
|
"rollup-plugin-babel": "^3.0.7",
|
||||||
|
"string-to-arraybuffer": "^1.0.2",
|
||||||
|
"teeny-request": "3.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
data-uri-to-buffer
|
||||||
|
==================
|
||||||
|
### Generate a Buffer instance from a [Data URI][rfc] string
|
||||||
|
[](https://travis-ci.org/TooTallNate/node-data-uri-to-buffer)
|
||||||
|
|
||||||
|
This module accepts a ["data" URI][rfc] String of data, and returns a
|
||||||
|
node.js `Buffer` instance with the decoded data.
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Install with `npm`:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
$ npm install data-uri-to-buffer
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
``` js
|
||||||
|
var dataUriToBuffer = require('data-uri-to-buffer');
|
||||||
|
|
||||||
|
// plain-text data is supported
|
||||||
|
var uri = 'data:,Hello%2C%20World!';
|
||||||
|
var decoded = dataUriToBuffer(uri);
|
||||||
|
console.log(decoded.toString());
|
||||||
|
// 'Hello, World!'
|
||||||
|
|
||||||
|
// base64-encoded data is supported
|
||||||
|
uri = 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D';
|
||||||
|
decoded = dataUriToBuffer(uri);
|
||||||
|
console.log(decoded.toString());
|
||||||
|
// 'Hello, World!'
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
API
|
||||||
|
---
|
||||||
|
|
||||||
|
### dataUriToBuffer(String uri) → Buffer
|
||||||
|
|
||||||
|
The `type` property on the Buffer instance gets set to the main type portion of
|
||||||
|
the "mediatype" portion of the "data" URI, or defaults to `"text/plain"` if not
|
||||||
|
specified.
|
||||||
|
|
||||||
|
The `typeFull` property on the Buffer instance gets set to the entire
|
||||||
|
"mediatype" portion of the "data" URI (including all parameters), or defaults
|
||||||
|
to `"text/plain;charset=US-ASCII"` if not specified.
|
||||||
|
|
||||||
|
The `charset` property on the Buffer instance gets set to the Charset portion of
|
||||||
|
the "mediatype" portion of the "data" URI, or defaults to `"US-ASCII"` if the
|
||||||
|
entire type is not specified, or defaults to `""` otherwise.
|
||||||
|
|
||||||
|
*Note*: If the only the main type is specified but not the charset, e.g.
|
||||||
|
`"data:text/plain,abc"`, the charset is set to the empty string. The spec only
|
||||||
|
defaults to US-ASCII as charset if the entire type is not specified.
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Nathan Rajlich <nathan@tootallnate.net>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
'Software'), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
[rfc]: http://tools.ietf.org/html/rfc2397
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
export interface MimeBuffer extends Buffer {
|
||||||
|
type: string;
|
||||||
|
typeFull: string;
|
||||||
|
charset: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns a `Buffer` instance from the given data URI `uri`.
|
||||||
|
*
|
||||||
|
* @param {String} uri Data URI to turn into a Buffer instance
|
||||||
|
* @returns {Buffer} Buffer instance from Data URI
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
export declare function dataUriToBuffer(uri: string): MimeBuffer;
|
||||||
|
export default dataUriToBuffer;
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Returns a `Buffer` instance from the given data URI `uri`.
|
||||||
|
*
|
||||||
|
* @param {String} uri Data URI to turn into a Buffer instance
|
||||||
|
* @returns {Buffer} Buffer instance from Data URI
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
export function dataUriToBuffer(uri) {
|
||||||
|
if (!/^data:/i.test(uri)) {
|
||||||
|
throw new TypeError('`uri` does not appear to be a Data URI (must begin with "data:")');
|
||||||
|
}
|
||||||
|
// strip newlines
|
||||||
|
uri = uri.replace(/\r?\n/g, '');
|
||||||
|
// split the URI up into the "metadata" and the "data" portions
|
||||||
|
const firstComma = uri.indexOf(',');
|
||||||
|
if (firstComma === -1 || firstComma <= 4) {
|
||||||
|
throw new TypeError('malformed data: URI');
|
||||||
|
}
|
||||||
|
// remove the "data:" scheme and parse the metadata
|
||||||
|
const meta = uri.substring(5, firstComma).split(';');
|
||||||
|
let charset = '';
|
||||||
|
let base64 = false;
|
||||||
|
const type = meta[0] || 'text/plain';
|
||||||
|
let typeFull = type;
|
||||||
|
for (let i = 1; i < meta.length; i++) {
|
||||||
|
if (meta[i] === 'base64') {
|
||||||
|
base64 = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
typeFull += `;${meta[i]}`;
|
||||||
|
if (meta[i].indexOf('charset=') === 0) {
|
||||||
|
charset = meta[i].substring(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// defaults to US-ASCII only if type is not provided
|
||||||
|
if (!meta[0] && !charset.length) {
|
||||||
|
typeFull += ';charset=US-ASCII';
|
||||||
|
charset = 'US-ASCII';
|
||||||
|
}
|
||||||
|
// get the encoded data portion and decode URI-encoded chars
|
||||||
|
const encoding = base64 ? 'base64' : 'ascii';
|
||||||
|
const data = unescape(uri.substring(firstComma + 1));
|
||||||
|
const buffer = Buffer.from(data, encoding);
|
||||||
|
// set `.type` and `.typeFull` properties to MIME type
|
||||||
|
buffer.type = type;
|
||||||
|
buffer.typeFull = typeFull;
|
||||||
|
// set the `.charset` property
|
||||||
|
buffer.charset = charset;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
export default dataUriToBuffer;
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IAC1C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACzB,MAAM,IAAI,SAAS,CAClB,kEAAkE,CAClE,CAAC;KACF;IAED,iBAAiB;IACjB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEhC,+DAA+D;IAC/D,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE;QACzC,MAAM,IAAI,SAAS,CAAC,qBAAqB,CAAC,CAAC;KAC3C;IAED,mDAAmD;IACnD,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAErD,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC;IACrC,IAAI,QAAQ,GAAG,IAAI,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACrC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE;YACzB,MAAM,GAAG,IAAI,CAAC;SACd;aAAM;YACN,QAAQ,IAAI,IAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE;gBACtC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;aAC/B;SACD;KACD;IACD,oDAAoD;IACpD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;QAChC,QAAQ,IAAI,mBAAmB,CAAC;QAChC,OAAO,GAAG,UAAU,CAAC;KACrB;IAED,4DAA4D;IAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7C,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAe,CAAC;IAEzD,sDAAsD;IACtD,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAE3B,8BAA8B;IAC9B,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;IAEzB,OAAO,MAAM,CAAC;AACf,CAAC;AAED,eAAe,eAAe,CAAC"}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
export interface MimeBuffer extends Buffer {
|
||||||
|
type: string;
|
||||||
|
typeFull: string;
|
||||||
|
charset: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns a `Buffer` instance from the given data URI `uri`.
|
||||||
|
*
|
||||||
|
* @param {String} uri Data URI to turn into a Buffer instance
|
||||||
|
* @returns {Buffer} Buffer instance from Data URI
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
export declare function dataUriToBuffer(uri: string): MimeBuffer;
|
||||||
|
export default dataUriToBuffer;
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Returns a `Buffer` instance from the given data URI `uri`.
|
||||||
|
*
|
||||||
|
* @param {String} uri Data URI to turn into a Buffer instance
|
||||||
|
* @returns {Buffer} Buffer instance from Data URI
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
export function dataUriToBuffer(uri) {
|
||||||
|
if (!/^data:/i.test(uri)) {
|
||||||
|
throw new TypeError('`uri` does not appear to be a Data URI (must begin with "data:")');
|
||||||
|
}
|
||||||
|
// strip newlines
|
||||||
|
uri = uri.replace(/\r?\n/g, '');
|
||||||
|
// split the URI up into the "metadata" and the "data" portions
|
||||||
|
const firstComma = uri.indexOf(',');
|
||||||
|
if (firstComma === -1 || firstComma <= 4) {
|
||||||
|
throw new TypeError('malformed data: URI');
|
||||||
|
}
|
||||||
|
// remove the "data:" scheme and parse the metadata
|
||||||
|
const meta = uri.substring(5, firstComma).split(';');
|
||||||
|
let charset = '';
|
||||||
|
let base64 = false;
|
||||||
|
const type = meta[0] || 'text/plain';
|
||||||
|
let typeFull = type;
|
||||||
|
for (let i = 1; i < meta.length; i++) {
|
||||||
|
if (meta[i] === 'base64') {
|
||||||
|
base64 = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
typeFull += `;${meta[i]}`;
|
||||||
|
if (meta[i].indexOf('charset=') === 0) {
|
||||||
|
charset = meta[i].substring(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// defaults to US-ASCII only if type is not provided
|
||||||
|
if (!meta[0] && !charset.length) {
|
||||||
|
typeFull += ';charset=US-ASCII';
|
||||||
|
charset = 'US-ASCII';
|
||||||
|
}
|
||||||
|
// get the encoded data portion and decode URI-encoded chars
|
||||||
|
const encoding = base64 ? 'base64' : 'ascii';
|
||||||
|
const data = unescape(uri.substring(firstComma + 1));
|
||||||
|
const buffer = Buffer.from(data, encoding);
|
||||||
|
// set `.type` and `.typeFull` properties to MIME type
|
||||||
|
buffer.type = type;
|
||||||
|
buffer.typeFull = typeFull;
|
||||||
|
// set the `.charset` property
|
||||||
|
buffer.charset = charset;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
export default dataUriToBuffer;
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IAC1C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACzB,MAAM,IAAI,SAAS,CAClB,kEAAkE,CAClE,CAAC;KACF;IAED,iBAAiB;IACjB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEhC,+DAA+D;IAC/D,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE;QACzC,MAAM,IAAI,SAAS,CAAC,qBAAqB,CAAC,CAAC;KAC3C;IAED,mDAAmD;IACnD,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAErD,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC;IACrC,IAAI,QAAQ,GAAG,IAAI,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACrC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE;YACzB,MAAM,GAAG,IAAI,CAAC;SACd;aAAM;YACN,QAAQ,IAAI,IAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE;gBACtC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;aAC/B;SACD;KACD;IACD,oDAAoD;IACpD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;QAChC,QAAQ,IAAI,mBAAmB,CAAC;QAChC,OAAO,GAAG,UAAU,CAAC;KACrB;IAED,4DAA4D;IAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7C,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAe,CAAC;IAEzD,sDAAsD;IACtD,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAE3B,8BAA8B;IAC9B,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;IAEzB,OAAO,MAAM,CAAC;AACf,CAAC;AAED,eAAe,eAAe,CAAC"}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
export {};
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
import assert from 'assert';
|
||||||
|
import dataUriToBuffer from '../src';
|
||||||
|
describe('data-uri-to-buffer', function () {
|
||||||
|
it('should decode bare-bones Data URIs', function () {
|
||||||
|
var uri = 'data:,Hello%2C%20World!';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('text/plain;charset=US-ASCII', buf.typeFull);
|
||||||
|
assert.equal('US-ASCII', buf.charset);
|
||||||
|
assert.equal('Hello, World!', buf.toString());
|
||||||
|
});
|
||||||
|
it('should decode bare-bones "base64" Data URIs', function () {
|
||||||
|
var uri = 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('Hello, World!', buf.toString());
|
||||||
|
});
|
||||||
|
it('should decode plain-text Data URIs', function () {
|
||||||
|
var html = '<!DOCTYPE html>' +
|
||||||
|
'<html lang="en">' +
|
||||||
|
'<head><title>Embedded Window</title></head>' +
|
||||||
|
'<body><h1>42</h1></body>' +
|
||||||
|
'</html>';
|
||||||
|
// Escape the HTML for URL formatting
|
||||||
|
var uri = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/html', buf.type);
|
||||||
|
assert.equal('utf-8', buf.charset);
|
||||||
|
assert.equal(html, buf.toString());
|
||||||
|
});
|
||||||
|
// the next 4 tests are from:
|
||||||
|
// https://bug161965.bugzilla.mozilla.org/attachment.cgi?id=94670&action=view
|
||||||
|
it('should decode "ISO-8859-8 in Base64" URIs', function () {
|
||||||
|
var uri = 'data:text/plain;charset=iso-8859-8-i;base64,+ezl7Q==';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('iso-8859-8-i', buf.charset);
|
||||||
|
assert.equal(4, buf.length);
|
||||||
|
assert.equal(0xf9, buf[0]);
|
||||||
|
assert.equal(0xec, buf[1]);
|
||||||
|
assert.equal(0xe5, buf[2]);
|
||||||
|
assert.equal(0xed, buf[3]);
|
||||||
|
});
|
||||||
|
it('should decode "ISO-8859-8 in URL-encoding" URIs', function () {
|
||||||
|
var uri = 'data:text/plain;charset=iso-8859-8-i,%f9%ec%e5%ed';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('iso-8859-8-i', buf.charset);
|
||||||
|
assert.equal(4, buf.length);
|
||||||
|
assert.equal(0xf9, buf[0]);
|
||||||
|
assert.equal(0xec, buf[1]);
|
||||||
|
assert.equal(0xe5, buf[2]);
|
||||||
|
assert.equal(0xed, buf[3]);
|
||||||
|
});
|
||||||
|
it('should decode "UTF-8 in Base64" URIs', function () {
|
||||||
|
var uri = 'data:text/plain;charset=UTF-8;base64,16nXnNeV150=';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('UTF-8', buf.charset);
|
||||||
|
assert.equal(8, buf.length);
|
||||||
|
assert.equal('שלום', buf.toString('utf8'));
|
||||||
|
});
|
||||||
|
it('should decode "UTF-8 in URL-encoding" URIs', function () {
|
||||||
|
var uri = 'data:text/plain;charset=UTF-8,%d7%a9%d7%9c%d7%95%d7%9d';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('UTF-8', buf.charset);
|
||||||
|
assert.equal(8, buf.length);
|
||||||
|
assert.equal('שלום', buf.toString('utf8'));
|
||||||
|
});
|
||||||
|
// this next one is from Wikipedia IIRC
|
||||||
|
it('should decode "base64" Data URIs with newlines', function () {
|
||||||
|
var uri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA\n' +
|
||||||
|
'AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO\n' +
|
||||||
|
'9TXL0Y4OHwAAAABJRU5ErkJggg==';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('image/png', buf.type);
|
||||||
|
assert.equal('iVBORw0KGgoAAAANSUhEUgAAAAUA' +
|
||||||
|
'AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO' +
|
||||||
|
'9TXL0Y4OHwAAAABJRU5ErkJggg==', buf.toString('base64'));
|
||||||
|
});
|
||||||
|
it('should decode a plain-text URI with a space character in it', function () {
|
||||||
|
var uri = 'data:,foo bar';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('foo bar', buf.toString());
|
||||||
|
});
|
||||||
|
it('should decode data with a "," comma char', function () {
|
||||||
|
var uri = 'data:,a,b';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('a,b', buf.toString());
|
||||||
|
});
|
||||||
|
it('should decode data with traditionally reserved characters like ";"', function () {
|
||||||
|
var uri = 'data:,;test';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal(';test', buf.toString());
|
||||||
|
});
|
||||||
|
it('should not default to US-ASCII if main type is provided', function () {
|
||||||
|
var uri = 'data:text/plain,abc';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('text/plain', buf.typeFull);
|
||||||
|
assert.equal('', buf.charset);
|
||||||
|
assert.equal('abc', buf.toString());
|
||||||
|
});
|
||||||
|
it('should default to text/plain if main type is not provided', function () {
|
||||||
|
var uri = 'data:;charset=UTF-8,abc';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('text/plain;charset=UTF-8', buf.typeFull);
|
||||||
|
assert.equal('UTF-8', buf.charset);
|
||||||
|
assert.equal('abc', buf.toString());
|
||||||
|
});
|
||||||
|
it('should not allow charset without a leading ;', function () {
|
||||||
|
var uri = 'data:charset=UTF-8,abc';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('charset=UTF-8', buf.type);
|
||||||
|
assert.equal('charset=UTF-8', buf.typeFull);
|
||||||
|
assert.equal('', buf.charset);
|
||||||
|
assert.equal('abc', buf.toString());
|
||||||
|
});
|
||||||
|
it('should allow custom media type parameters', function () {
|
||||||
|
var uri = 'data:application/javascript;version=1.8;charset=UTF-8,abc';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('application/javascript', buf.type);
|
||||||
|
assert.equal('application/javascript;version=1.8;charset=UTF-8', buf.typeFull);
|
||||||
|
assert.equal('UTF-8', buf.charset);
|
||||||
|
assert.equal('abc', buf.toString());
|
||||||
|
});
|
||||||
|
it('should allow base64 annotation anywhere', function () {
|
||||||
|
var uri = 'data:text/plain;base64;charset=UTF-8,YWJj';
|
||||||
|
var buf = dataUriToBuffer(uri);
|
||||||
|
assert.equal('text/plain', buf.type);
|
||||||
|
assert.equal('text/plain;charset=UTF-8', buf.typeFull);
|
||||||
|
assert.equal('UTF-8', buf.charset);
|
||||||
|
assert.equal('abc', buf.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=test.js.map
|
||||||
+1
File diff suppressed because one or more lines are too long
+62
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"name": "data-uri-to-buffer",
|
||||||
|
"version": "4.0.0",
|
||||||
|
"description": "Generate a Buffer instance from a Data URI string",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"test": "jest",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/TooTallNate/node-data-uri-to-buffer.git"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"data",
|
||||||
|
"uri",
|
||||||
|
"datauri",
|
||||||
|
"data-uri",
|
||||||
|
"buffer",
|
||||||
|
"convert",
|
||||||
|
"rfc2397",
|
||||||
|
"2397"
|
||||||
|
],
|
||||||
|
"author": "Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/TooTallNate/node-data-uri-to-buffer/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/TooTallNate/node-data-uri-to-buffer",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/es6-promisify": "^5.0.0",
|
||||||
|
"@types/mocha": "^9.0.0",
|
||||||
|
"@types/node": "^10.5.3",
|
||||||
|
"jest": "^27.2.2",
|
||||||
|
"ts-jest": "^27.0.5",
|
||||||
|
"typescript": "^4.4.3"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "ts-jest",
|
||||||
|
"globals": {
|
||||||
|
"ts-jest": {
|
||||||
|
"diagnostics": false,
|
||||||
|
"isolatedModules": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verbose": false,
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"testMatch": [
|
||||||
|
"<rootDir>/test/**/*.test.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
export interface MimeBuffer extends Buffer {
|
||||||
|
type: string;
|
||||||
|
typeFull: string;
|
||||||
|
charset: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a `Buffer` instance from the given data URI `uri`.
|
||||||
|
*
|
||||||
|
* @param {String} uri Data URI to turn into a Buffer instance
|
||||||
|
* @returns {Buffer} Buffer instance from Data URI
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
export function dataUriToBuffer(uri: string): MimeBuffer {
|
||||||
|
if (!/^data:/i.test(uri)) {
|
||||||
|
throw new TypeError(
|
||||||
|
'`uri` does not appear to be a Data URI (must begin with "data:")'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip newlines
|
||||||
|
uri = uri.replace(/\r?\n/g, '');
|
||||||
|
|
||||||
|
// split the URI up into the "metadata" and the "data" portions
|
||||||
|
const firstComma = uri.indexOf(',');
|
||||||
|
if (firstComma === -1 || firstComma <= 4) {
|
||||||
|
throw new TypeError('malformed data: URI');
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the "data:" scheme and parse the metadata
|
||||||
|
const meta = uri.substring(5, firstComma).split(';');
|
||||||
|
|
||||||
|
let charset = '';
|
||||||
|
let base64 = false;
|
||||||
|
const type = meta[0] || 'text/plain';
|
||||||
|
let typeFull = type;
|
||||||
|
for (let i = 1; i < meta.length; i++) {
|
||||||
|
if (meta[i] === 'base64') {
|
||||||
|
base64 = true;
|
||||||
|
} else {
|
||||||
|
typeFull += `;${ meta[i]}`;
|
||||||
|
if (meta[i].indexOf('charset=') === 0) {
|
||||||
|
charset = meta[i].substring(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// defaults to US-ASCII only if type is not provided
|
||||||
|
if (!meta[0] && !charset.length) {
|
||||||
|
typeFull += ';charset=US-ASCII';
|
||||||
|
charset = 'US-ASCII';
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the encoded data portion and decode URI-encoded chars
|
||||||
|
const encoding = base64 ? 'base64' : 'ascii';
|
||||||
|
const data = unescape(uri.substring(firstComma + 1));
|
||||||
|
const buffer = Buffer.from(data, encoding) as MimeBuffer;
|
||||||
|
|
||||||
|
// set `.type` and `.typeFull` properties to MIME type
|
||||||
|
buffer.type = type;
|
||||||
|
buffer.typeFull = typeFull;
|
||||||
|
|
||||||
|
// set the `.charset` property
|
||||||
|
buffer.charset = charset;
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default dataUriToBuffer;
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 David Frank
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
# fetch-blob
|
||||||
|
|
||||||
|
[![npm version][npm-image]][npm-url]
|
||||||
|
[![build status][ci-image]][ci-url]
|
||||||
|
[![coverage status][codecov-image]][codecov-url]
|
||||||
|
[![install size][install-size-image]][install-size-url]
|
||||||
|
|
||||||
|
A Blob implementation in Node.js, originally from [node-fetch](https://github.com/node-fetch/node-fetch).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install fetch-blob
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Upgrading from 2x to 3x</summary>
|
||||||
|
|
||||||
|
Updating from 2 to 3 should be a breeze since there is not many changes to the blob specification.
|
||||||
|
The major cause of a major release is coding standards.
|
||||||
|
- internal WeakMaps was replaced with private fields
|
||||||
|
- internal Buffer.from was replaced with TextEncoder/Decoder
|
||||||
|
- internal buffers was replaced with Uint8Arrays
|
||||||
|
- CommonJS was replaced with ESM
|
||||||
|
- The node stream returned by calling `blob.stream()` was replaced with whatwg streams
|
||||||
|
- (Read "Differences from other blobs" for more info.)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Differences from other Blobs</summary>
|
||||||
|
|
||||||
|
- Unlike NodeJS `buffer.Blob` (Added in: v15.7.0) and browser native Blob this polyfilled version can't be sent via PostMessage
|
||||||
|
- This blob version is more arbitrary, it can be constructed with blob parts that isn't a instance of itself
|
||||||
|
it has to look and behave as a blob to be accepted as a blob part.
|
||||||
|
- The benefit of this is that you can create other types of blobs that don't contain any internal data that has to be read in other ways, such as the `BlobDataItem` created in `from.js` that wraps a file path into a blob-like item and read lazily (nodejs plans to [implement this][fs-blobs] as well)
|
||||||
|
- The `blob.stream()` is the most noticeable differences. It returns a WHATWG stream now. to keep it as a node stream you would have to do:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {Readable} from 'stream'
|
||||||
|
const stream = Readable.from(blob.stream())
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Ways to import
|
||||||
|
// (PS it's dependency free ESM package so regular http-import from CDN works too)
|
||||||
|
import Blob from 'fetch-blob'
|
||||||
|
import File from 'fetch-blob/file.js'
|
||||||
|
|
||||||
|
import {Blob} from 'fetch-blob'
|
||||||
|
import {File} from 'fetch-blob/file.js'
|
||||||
|
|
||||||
|
const {Blob} = await import('fetch-blob')
|
||||||
|
|
||||||
|
|
||||||
|
// Ways to read the blob:
|
||||||
|
const blob = new Blob(['hello, world'])
|
||||||
|
|
||||||
|
await blob.text()
|
||||||
|
await blob.arrayBuffer()
|
||||||
|
for await (let chunk of blob.stream()) { ... }
|
||||||
|
blob.stream().getReader().read()
|
||||||
|
blob.stream().getReader({mode: 'byob'}).read(view)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Blob part backed up by filesystem
|
||||||
|
|
||||||
|
`fetch-blob/from.js` comes packed with tools to convert any filepath into either a Blob or a File
|
||||||
|
It will not read the content into memory. It will only stat the file for last modified date and file size.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// The default export is sync and use fs.stat to retrieve size & last modified as a blob
|
||||||
|
import blobFromSync from 'fetch-blob/from.js'
|
||||||
|
import {File, Blob, blobFrom, blobFromSync, fileFrom, fileFromSync} from 'fetch-blob/from.js'
|
||||||
|
|
||||||
|
const fsFile = fileFromSync('./2-GiB-file.bin', 'application/octet-stream')
|
||||||
|
const fsBlob = await blobFrom('./2-GiB-file.mp4')
|
||||||
|
|
||||||
|
// Not a 4 GiB memory snapshot, just holds references
|
||||||
|
// points to where data is located on the disk
|
||||||
|
const blob = new Blob([fsFile, fsBlob, 'memory', new Uint8Array(10)])
|
||||||
|
console.log(blob.size) // ~4 GiB
|
||||||
|
```
|
||||||
|
|
||||||
|
`blobFrom|blobFromSync|fileFrom|fileFromSync(path, [mimetype])`
|
||||||
|
|
||||||
|
### Creating Blobs backed up by other async sources
|
||||||
|
Our Blob & File class are more generic then any other polyfills in the way that it can accept any blob look-a-like item
|
||||||
|
An example of this is that our blob implementation can be constructed with parts coming from [BlobDataItem](https://github.com/node-fetch/fetch-blob/blob/8ef89adad40d255a3bbd55cf38b88597c1cd5480/from.js#L32) (aka a filepath) or from [buffer.Blob](https://nodejs.org/api/buffer.html#buffer_new_buffer_blob_sources_options), It dose not have to implement all the methods - just enough that it can be read/understood by our Blob implementation. The minium requirements is that it has `Symbol.toStringTag`, `size`, `slice()` and either a `stream()` or a `arrayBuffer()` method. If you then wrap it in our Blob or File `new Blob([blobDataItem])` then you get all of the other methods that should be implemented in a blob or file
|
||||||
|
|
||||||
|
An example of this could be to create a file or blob like item coming from a remote HTTP request. Or from a DataBase
|
||||||
|
|
||||||
|
See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [tests](https://github.com/node-fetch/fetch-blob/blob/master/test.js) for more details of how to use the Blob.
|
||||||
|
|
||||||
|
[npm-image]: https://flat.badgen.net/npm/v/fetch-blob
|
||||||
|
[npm-url]: https://www.npmjs.com/package/fetch-blob
|
||||||
|
[ci-image]: https://github.com/node-fetch/fetch-blob/workflows/CI/badge.svg
|
||||||
|
[ci-url]: https://github.com/node-fetch/fetch-blob/actions
|
||||||
|
[codecov-image]: https://flat.badgen.net/codecov/c/github/node-fetch/fetch-blob/master
|
||||||
|
[codecov-url]: https://codecov.io/gh/node-fetch/fetch-blob
|
||||||
|
[install-size-image]: https://flat.badgen.net/packagephobia/install/fetch-blob
|
||||||
|
[install-size-url]: https://packagephobia.now.sh/result?p=fetch-blob
|
||||||
|
[fs-blobs]: https://github.com/nodejs/node/issues/37340
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
/** @type {typeof globalThis.File} */ export const File: typeof globalThis.File;
|
||||||
|
export default File;
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
import Blob from './index.js'
|
||||||
|
|
||||||
|
const _File = class File extends Blob {
|
||||||
|
#lastModified = 0
|
||||||
|
#name = ''
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*[]} fileBits
|
||||||
|
* @param {string} fileName
|
||||||
|
* @param {{lastModified?: number, type?: string}} options
|
||||||
|
*/// @ts-ignore
|
||||||
|
constructor (fileBits, fileName, options = {}) {
|
||||||
|
if (arguments.length < 2) {
|
||||||
|
throw new TypeError(`Failed to construct 'File': 2 arguments required, but only ${arguments.length} present.`)
|
||||||
|
}
|
||||||
|
super(fileBits, options)
|
||||||
|
|
||||||
|
if (options === null) options = {}
|
||||||
|
|
||||||
|
// Simulate WebIDL type casting for NaN value in lastModified option.
|
||||||
|
const lastModified = options.lastModified === undefined ? Date.now() : Number(options.lastModified)
|
||||||
|
if (!Number.isNaN(lastModified)) {
|
||||||
|
this.#lastModified = lastModified
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#name = String(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
get name () {
|
||||||
|
return this.#name
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastModified () {
|
||||||
|
return this.#lastModified
|
||||||
|
}
|
||||||
|
|
||||||
|
get [Symbol.toStringTag] () {
|
||||||
|
return 'File'
|
||||||
|
}
|
||||||
|
|
||||||
|
static [Symbol.hasInstance] (object) {
|
||||||
|
return !!object && object instanceof Blob &&
|
||||||
|
/^(File)$/.test(object[Symbol.toStringTag])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof globalThis.File} */// @ts-ignore
|
||||||
|
export const File = _File
|
||||||
|
export default File
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
export default blobFromSync;
|
||||||
|
/**
|
||||||
|
* @param {string} path filepath on the disk
|
||||||
|
* @param {string} [type] mimetype to use
|
||||||
|
*/
|
||||||
|
export function blobFromSync(path: string, type?: string): Blob;
|
||||||
|
import File from "./file.js";
|
||||||
|
import Blob from "./index.js";
|
||||||
|
/**
|
||||||
|
* @param {string} path filepath on the disk
|
||||||
|
* @param {string} [type] mimetype to use
|
||||||
|
* @returns {Promise<Blob>}
|
||||||
|
*/
|
||||||
|
export function blobFrom(path: string, type?: string): Promise<Blob>;
|
||||||
|
/**
|
||||||
|
* @param {string} path filepath on the disk
|
||||||
|
* @param {string} [type] mimetype to use
|
||||||
|
* @returns {Promise<File>}
|
||||||
|
*/
|
||||||
|
export function fileFrom(path: string, type?: string): Promise<File>;
|
||||||
|
/**
|
||||||
|
* @param {string} path filepath on the disk
|
||||||
|
* @param {string} [type] mimetype to use
|
||||||
|
*/
|
||||||
|
export function fileFromSync(path: string, type?: string): File;
|
||||||
|
export { File, Blob };
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
import { statSync, createReadStream, promises as fs } from 'node:fs'
|
||||||
|
import { basename } from 'node:path'
|
||||||
|
import DOMException from 'node-domexception'
|
||||||
|
|
||||||
|
import File from './file.js'
|
||||||
|
import Blob from './index.js'
|
||||||
|
|
||||||
|
const { stat } = fs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path filepath on the disk
|
||||||
|
* @param {string} [type] mimetype to use
|
||||||
|
*/
|
||||||
|
const blobFromSync = (path, type) => fromBlob(statSync(path), path, type)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path filepath on the disk
|
||||||
|
* @param {string} [type] mimetype to use
|
||||||
|
* @returns {Promise<Blob>}
|
||||||
|
*/
|
||||||
|
const blobFrom = (path, type) => stat(path).then(stat => fromBlob(stat, path, type))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path filepath on the disk
|
||||||
|
* @param {string} [type] mimetype to use
|
||||||
|
* @returns {Promise<File>}
|
||||||
|
*/
|
||||||
|
const fileFrom = (path, type) => stat(path).then(stat => fromFile(stat, path, type))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path filepath on the disk
|
||||||
|
* @param {string} [type] mimetype to use
|
||||||
|
*/
|
||||||
|
const fileFromSync = (path, type) => fromFile(statSync(path), path, type)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const fromBlob = (stat, path, type = '') => new Blob([new BlobDataItem({
|
||||||
|
path,
|
||||||
|
size: stat.size,
|
||||||
|
lastModified: stat.mtimeMs,
|
||||||
|
start: 0
|
||||||
|
})], { type })
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const fromFile = (stat, path, type = '') => new File([new BlobDataItem({
|
||||||
|
path,
|
||||||
|
size: stat.size,
|
||||||
|
lastModified: stat.mtimeMs,
|
||||||
|
start: 0
|
||||||
|
})], basename(path), { type, lastModified: stat.mtimeMs })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a blob backed up by a file on the disk
|
||||||
|
* with minium requirement. Its wrapped around a Blob as a blobPart
|
||||||
|
* so you have no direct access to this.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
class BlobDataItem {
|
||||||
|
#path
|
||||||
|
#start
|
||||||
|
|
||||||
|
constructor (options) {
|
||||||
|
this.#path = options.path
|
||||||
|
this.#start = options.start
|
||||||
|
this.size = options.size
|
||||||
|
this.lastModified = options.lastModified
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slicing arguments is first validated and formatted
|
||||||
|
* to not be out of range by Blob.prototype.slice
|
||||||
|
*/
|
||||||
|
slice (start, end) {
|
||||||
|
return new BlobDataItem({
|
||||||
|
path: this.#path,
|
||||||
|
lastModified: this.lastModified,
|
||||||
|
size: end - start,
|
||||||
|
start: this.#start + start
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async * stream () {
|
||||||
|
const { mtimeMs } = await stat(this.#path)
|
||||||
|
if (mtimeMs > this.lastModified) {
|
||||||
|
throw new DOMException('The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.', 'NotReadableError')
|
||||||
|
}
|
||||||
|
yield * createReadStream(this.#path, {
|
||||||
|
start: this.#start,
|
||||||
|
end: this.#start + this.size - 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get [Symbol.toStringTag] () {
|
||||||
|
return 'Blob'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default blobFromSync
|
||||||
|
export { File, Blob, blobFrom, blobFromSync, fileFrom, fileFromSync }
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
/** @type {typeof globalThis.Blob} */
|
||||||
|
export const Blob: typeof globalThis.Blob;
|
||||||
|
export default Blob;
|
||||||
+250
@@ -0,0 +1,250 @@
|
|||||||
|
/*! fetch-blob. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
// TODO (jimmywarting): in the feature use conditional loading with top level await (requires 14.x)
|
||||||
|
// Node has recently added whatwg stream into core
|
||||||
|
|
||||||
|
import './streams.cjs'
|
||||||
|
|
||||||
|
// 64 KiB (same size chrome slice theirs blob into Uint8array's)
|
||||||
|
const POOL_SIZE = 65536
|
||||||
|
|
||||||
|
/** @param {(Blob | Uint8Array)[]} parts */
|
||||||
|
async function * toIterator (parts, clone = true) {
|
||||||
|
for (const part of parts) {
|
||||||
|
if ('stream' in part) {
|
||||||
|
yield * (/** @type {AsyncIterableIterator<Uint8Array>} */ (part.stream()))
|
||||||
|
} else if (ArrayBuffer.isView(part)) {
|
||||||
|
if (clone) {
|
||||||
|
let position = part.byteOffset
|
||||||
|
const end = part.byteOffset + part.byteLength
|
||||||
|
while (position !== end) {
|
||||||
|
const size = Math.min(end - position, POOL_SIZE)
|
||||||
|
const chunk = part.buffer.slice(position, position + size)
|
||||||
|
position += chunk.byteLength
|
||||||
|
yield new Uint8Array(chunk)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield part
|
||||||
|
}
|
||||||
|
/* c8 ignore next 10 */
|
||||||
|
} else {
|
||||||
|
// For blobs that have arrayBuffer but no stream method (nodes buffer.Blob)
|
||||||
|
let position = 0, b = (/** @type {Blob} */ (part))
|
||||||
|
while (position !== b.size) {
|
||||||
|
const chunk = b.slice(position, Math.min(b.size, position + POOL_SIZE))
|
||||||
|
const buffer = await chunk.arrayBuffer()
|
||||||
|
position += buffer.byteLength
|
||||||
|
yield new Uint8Array(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Blob = class Blob {
|
||||||
|
/** @type {Array.<(Blob|Uint8Array)>} */
|
||||||
|
#parts = []
|
||||||
|
#type = ''
|
||||||
|
#size = 0
|
||||||
|
#endings = 'transparent'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Blob() constructor returns a new Blob object. The content
|
||||||
|
* of the blob consists of the concatenation of the values given
|
||||||
|
* in the parameter array.
|
||||||
|
*
|
||||||
|
* @param {*} blobParts
|
||||||
|
* @param {{ type?: string, endings?: string }} [options]
|
||||||
|
*/
|
||||||
|
constructor (blobParts = [], options = {}) {
|
||||||
|
if (typeof blobParts !== 'object' || blobParts === null) {
|
||||||
|
throw new TypeError('Failed to construct \'Blob\': The provided value cannot be converted to a sequence.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof blobParts[Symbol.iterator] !== 'function') {
|
||||||
|
throw new TypeError('Failed to construct \'Blob\': The object must have a callable @@iterator property.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options !== 'object' && typeof options !== 'function') {
|
||||||
|
throw new TypeError('Failed to construct \'Blob\': parameter 2 cannot convert to dictionary.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options === null) options = {}
|
||||||
|
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
for (const element of blobParts) {
|
||||||
|
let part
|
||||||
|
if (ArrayBuffer.isView(element)) {
|
||||||
|
part = new Uint8Array(element.buffer.slice(element.byteOffset, element.byteOffset + element.byteLength))
|
||||||
|
} else if (element instanceof ArrayBuffer) {
|
||||||
|
part = new Uint8Array(element.slice(0))
|
||||||
|
} else if (element instanceof Blob) {
|
||||||
|
part = element
|
||||||
|
} else {
|
||||||
|
part = encoder.encode(`${element}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#size += ArrayBuffer.isView(part) ? part.byteLength : part.size
|
||||||
|
this.#parts.push(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#endings = `${options.endings === undefined ? 'transparent' : options.endings}`
|
||||||
|
const type = options.type === undefined ? '' : String(options.type)
|
||||||
|
this.#type = /^[\x20-\x7E]*$/.test(type) ? type : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Blob interface's size property returns the
|
||||||
|
* size of the Blob in bytes.
|
||||||
|
*/
|
||||||
|
get size () {
|
||||||
|
return this.#size
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type property of a Blob object returns the MIME type of the file.
|
||||||
|
*/
|
||||||
|
get type () {
|
||||||
|
return this.#type
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text() method in the Blob interface returns a Promise
|
||||||
|
* that resolves with a string containing the contents of
|
||||||
|
* the blob, interpreted as UTF-8.
|
||||||
|
*
|
||||||
|
* @return {Promise<string>}
|
||||||
|
*/
|
||||||
|
async text () {
|
||||||
|
// More optimized than using this.arrayBuffer()
|
||||||
|
// that requires twice as much ram
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
let str = ''
|
||||||
|
for await (const part of toIterator(this.#parts, false)) {
|
||||||
|
str += decoder.decode(part, { stream: true })
|
||||||
|
}
|
||||||
|
// Remaining
|
||||||
|
str += decoder.decode()
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arrayBuffer() method in the Blob interface returns a
|
||||||
|
* Promise that resolves with the contents of the blob as
|
||||||
|
* binary data contained in an ArrayBuffer.
|
||||||
|
*
|
||||||
|
* @return {Promise<ArrayBuffer>}
|
||||||
|
*/
|
||||||
|
async arrayBuffer () {
|
||||||
|
// Easier way... Just a unnecessary overhead
|
||||||
|
// const view = new Uint8Array(this.size);
|
||||||
|
// await this.stream().getReader({mode: 'byob'}).read(view);
|
||||||
|
// return view.buffer;
|
||||||
|
|
||||||
|
const data = new Uint8Array(this.size)
|
||||||
|
let offset = 0
|
||||||
|
for await (const chunk of toIterator(this.#parts, false)) {
|
||||||
|
data.set(chunk, offset)
|
||||||
|
offset += chunk.length
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
stream () {
|
||||||
|
const it = toIterator(this.#parts, true)
|
||||||
|
|
||||||
|
return new globalThis.ReadableStream({
|
||||||
|
// @ts-ignore
|
||||||
|
type: 'bytes',
|
||||||
|
async pull (ctrl) {
|
||||||
|
const chunk = await it.next()
|
||||||
|
chunk.done ? ctrl.close() : ctrl.enqueue(chunk.value)
|
||||||
|
},
|
||||||
|
|
||||||
|
async cancel () {
|
||||||
|
await it.return()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Blob interface's slice() method creates and returns a
|
||||||
|
* new Blob object which contains data from a subset of the
|
||||||
|
* blob on which it's called.
|
||||||
|
*
|
||||||
|
* @param {number} [start]
|
||||||
|
* @param {number} [end]
|
||||||
|
* @param {string} [type]
|
||||||
|
*/
|
||||||
|
slice (start = 0, end = this.size, type = '') {
|
||||||
|
const { size } = this
|
||||||
|
|
||||||
|
let relativeStart = start < 0 ? Math.max(size + start, 0) : Math.min(start, size)
|
||||||
|
let relativeEnd = end < 0 ? Math.max(size + end, 0) : Math.min(end, size)
|
||||||
|
|
||||||
|
const span = Math.max(relativeEnd - relativeStart, 0)
|
||||||
|
const parts = this.#parts
|
||||||
|
const blobParts = []
|
||||||
|
let added = 0
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
// don't add the overflow to new blobParts
|
||||||
|
if (added >= span) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = ArrayBuffer.isView(part) ? part.byteLength : part.size
|
||||||
|
if (relativeStart && size <= relativeStart) {
|
||||||
|
// Skip the beginning and change the relative
|
||||||
|
// start & end position as we skip the unwanted parts
|
||||||
|
relativeStart -= size
|
||||||
|
relativeEnd -= size
|
||||||
|
} else {
|
||||||
|
let chunk
|
||||||
|
if (ArrayBuffer.isView(part)) {
|
||||||
|
chunk = part.subarray(relativeStart, Math.min(size, relativeEnd))
|
||||||
|
added += chunk.byteLength
|
||||||
|
} else {
|
||||||
|
chunk = part.slice(relativeStart, Math.min(size, relativeEnd))
|
||||||
|
added += chunk.size
|
||||||
|
}
|
||||||
|
relativeEnd -= size
|
||||||
|
blobParts.push(chunk)
|
||||||
|
relativeStart = 0 // All next sequential parts should start at 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob([], { type: String(type).toLowerCase() })
|
||||||
|
blob.#size = span
|
||||||
|
blob.#parts = blobParts
|
||||||
|
|
||||||
|
return blob
|
||||||
|
}
|
||||||
|
|
||||||
|
get [Symbol.toStringTag] () {
|
||||||
|
return 'Blob'
|
||||||
|
}
|
||||||
|
|
||||||
|
static [Symbol.hasInstance] (object) {
|
||||||
|
return (
|
||||||
|
object &&
|
||||||
|
typeof object === 'object' &&
|
||||||
|
typeof object.constructor === 'function' &&
|
||||||
|
(
|
||||||
|
typeof object.stream === 'function' ||
|
||||||
|
typeof object.arrayBuffer === 'function'
|
||||||
|
) &&
|
||||||
|
/^(Blob|File)$/.test(object[Symbol.toStringTag])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperties(_Blob.prototype, {
|
||||||
|
size: { enumerable: true },
|
||||||
|
type: { enumerable: true },
|
||||||
|
slice: { enumerable: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
/** @type {typeof globalThis.Blob} */
|
||||||
|
export const Blob = _Blob
|
||||||
|
export default Blob
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"name": "fetch-blob",
|
||||||
|
"version": "3.2.0",
|
||||||
|
"description": "Blob & File implementation in Node.js, originally from node-fetch.",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"files": [
|
||||||
|
"from.js",
|
||||||
|
"file.js",
|
||||||
|
"file.d.ts",
|
||||||
|
"index.js",
|
||||||
|
"index.d.ts",
|
||||||
|
"from.d.ts",
|
||||||
|
"streams.cjs"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "node --experimental-loader ./test/http-loader.js ./test/test-wpt-in-node.js",
|
||||||
|
"report": "c8 --reporter json --reporter text npm run test",
|
||||||
|
"coverage": "npm run report && codecov -f coverage/coverage-final.json",
|
||||||
|
"prepublishOnly": "tsc --declaration --emitDeclarationOnly --allowJs index.js from.js"
|
||||||
|
},
|
||||||
|
"repository": "https://github.com/node-fetch/fetch-blob.git",
|
||||||
|
"keywords": [
|
||||||
|
"blob",
|
||||||
|
"file",
|
||||||
|
"node-fetch"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20 || >= 14.13"
|
||||||
|
},
|
||||||
|
"author": "Jimmy Wärting <jimmy@warting.se> (https://jimmy.warting.se)",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/node-fetch/fetch-blob/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/node-fetch/fetch-blob#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^17.0.9",
|
||||||
|
"c8": "^7.11.0",
|
||||||
|
"typescript": "^4.5.4"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/jimmywarting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paypal",
|
||||||
|
"url": "https://paypal.me/jimmywarting"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"node-domexception": "^1.0.0",
|
||||||
|
"web-streams-polyfill": "^3.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/* c8 ignore start */
|
||||||
|
// 64 KiB (same size chrome slice theirs blob into Uint8array's)
|
||||||
|
const POOL_SIZE = 65536
|
||||||
|
|
||||||
|
if (!globalThis.ReadableStream) {
|
||||||
|
// `node:stream/web` got introduced in v16.5.0 as experimental
|
||||||
|
// and it's preferred over the polyfilled version. So we also
|
||||||
|
// suppress the warning that gets emitted by NodeJS for using it.
|
||||||
|
try {
|
||||||
|
const process = require('node:process')
|
||||||
|
const { emitWarning } = process
|
||||||
|
try {
|
||||||
|
process.emitWarning = () => {}
|
||||||
|
Object.assign(globalThis, require('node:stream/web'))
|
||||||
|
process.emitWarning = emitWarning
|
||||||
|
} catch (error) {
|
||||||
|
process.emitWarning = emitWarning
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// fallback to polyfill implementation
|
||||||
|
Object.assign(globalThis, require('web-streams-polyfill/dist/ponyfill.es2018.js'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Don't use node: prefix for this, require+node: is not supported until node v14.14
|
||||||
|
// Only `import()` can use prefix in 12.20 and later
|
||||||
|
const { Blob } = require('buffer')
|
||||||
|
if (Blob && !Blob.prototype.stream) {
|
||||||
|
Blob.prototype.stream = function name (params) {
|
||||||
|
let position = 0
|
||||||
|
const blob = this
|
||||||
|
|
||||||
|
return new ReadableStream({
|
||||||
|
type: 'bytes',
|
||||||
|
async pull (ctrl) {
|
||||||
|
const chunk = blob.slice(position, Math.min(blob.size, position + POOL_SIZE))
|
||||||
|
const buffer = await chunk.arrayBuffer()
|
||||||
|
position += buffer.byteLength
|
||||||
|
ctrl.enqueue(new Uint8Array(buffer))
|
||||||
|
|
||||||
|
if (position === blob.size) {
|
||||||
|
ctrl.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
/* c8 ignore end */
|
||||||
+441
@@ -0,0 +1,441 @@
|
|||||||
|
/* formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
/* global FormData self Blob File */
|
||||||
|
/* eslint-disable no-inner-declarations */
|
||||||
|
|
||||||
|
if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData.prototype.keys)) {
|
||||||
|
const global = typeof globalThis === 'object'
|
||||||
|
? globalThis
|
||||||
|
: typeof window === 'object'
|
||||||
|
? window
|
||||||
|
: typeof self === 'object' ? self : this
|
||||||
|
|
||||||
|
// keep a reference to native implementation
|
||||||
|
const _FormData = global.FormData
|
||||||
|
|
||||||
|
// To be monkey patched
|
||||||
|
const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send
|
||||||
|
const _fetch = global.Request && global.fetch
|
||||||
|
const _sendBeacon = global.navigator && global.navigator.sendBeacon
|
||||||
|
// Might be a worker thread...
|
||||||
|
const _match = global.Element && global.Element.prototype
|
||||||
|
|
||||||
|
// Unable to patch Request/Response constructor correctly #109
|
||||||
|
// only way is to use ES6 class extend
|
||||||
|
// https://github.com/babel/babel/issues/1966
|
||||||
|
|
||||||
|
const stringTag = global.Symbol && Symbol.toStringTag
|
||||||
|
|
||||||
|
// Add missing stringTags to blob and files
|
||||||
|
if (stringTag) {
|
||||||
|
if (!Blob.prototype[stringTag]) {
|
||||||
|
Blob.prototype[stringTag] = 'Blob'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('File' in global && !File.prototype[stringTag]) {
|
||||||
|
File.prototype[stringTag] = 'File'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix so you can construct your own File
|
||||||
|
try {
|
||||||
|
new File([], '') // eslint-disable-line
|
||||||
|
} catch (a) {
|
||||||
|
global.File = function File (b, d, c) {
|
||||||
|
const blob = new Blob(b, c || {})
|
||||||
|
const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date()
|
||||||
|
|
||||||
|
Object.defineProperties(blob, {
|
||||||
|
name: {
|
||||||
|
value: d
|
||||||
|
},
|
||||||
|
lastModified: {
|
||||||
|
value: +t
|
||||||
|
},
|
||||||
|
toString: {
|
||||||
|
value () {
|
||||||
|
return '[object File]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (stringTag) {
|
||||||
|
Object.defineProperty(blob, stringTag, {
|
||||||
|
value: 'File'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureArgs (args, expected) {
|
||||||
|
if (args.length < expected) {
|
||||||
|
throw new TypeError(`${expected} argument required, but only ${args.length} present.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string | undefined} filename
|
||||||
|
* @returns {[string, File|string]}
|
||||||
|
*/
|
||||||
|
function normalizeArgs (name, value, filename) {
|
||||||
|
if (value instanceof Blob) {
|
||||||
|
filename = filename !== undefined
|
||||||
|
? String(filename + '')
|
||||||
|
: typeof value.name === 'string'
|
||||||
|
? value.name
|
||||||
|
: 'blob'
|
||||||
|
|
||||||
|
if (value.name !== filename || Object.prototype.toString.call(value) === '[object Blob]') {
|
||||||
|
value = new File([value], filename)
|
||||||
|
}
|
||||||
|
return [String(name), value]
|
||||||
|
}
|
||||||
|
return [String(name), String(value)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize line feeds for textarea
|
||||||
|
// https://html.spec.whatwg.org/multipage/form-elements.html#textarea-line-break-normalisation-transformation
|
||||||
|
function normalizeLinefeeds (value) {
|
||||||
|
return value.replace(/\r?\n|\r/g, '\r\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {ArrayLike<T>} arr
|
||||||
|
* @param {{ (elm: T): void; }} cb
|
||||||
|
*/
|
||||||
|
function each (arr, cb) {
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
cb(arr[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements {Iterable}
|
||||||
|
*/
|
||||||
|
class FormDataPolyfill {
|
||||||
|
/**
|
||||||
|
* FormData class
|
||||||
|
*
|
||||||
|
* @param {HTMLFormElement=} form
|
||||||
|
*/
|
||||||
|
constructor (form) {
|
||||||
|
/** @type {[string, string|File][]} */
|
||||||
|
this._data = []
|
||||||
|
|
||||||
|
const self = this
|
||||||
|
form && each(form.elements, (/** @type {HTMLInputElement} */ elm) => {
|
||||||
|
if (
|
||||||
|
!elm.name ||
|
||||||
|
elm.disabled ||
|
||||||
|
elm.type === 'submit' ||
|
||||||
|
elm.type === 'button' ||
|
||||||
|
elm.matches('form fieldset[disabled] *')
|
||||||
|
) return
|
||||||
|
|
||||||
|
if (elm.type === 'file') {
|
||||||
|
const files = elm.files && elm.files.length
|
||||||
|
? elm.files
|
||||||
|
: [new File([], '', { type: 'application/octet-stream' })] // #78
|
||||||
|
|
||||||
|
each(files, file => {
|
||||||
|
self.append(elm.name, file)
|
||||||
|
})
|
||||||
|
} else if (elm.type === 'select-multiple' || elm.type === 'select-one') {
|
||||||
|
each(elm.options, opt => {
|
||||||
|
!opt.disabled && opt.selected && self.append(elm.name, opt.value)
|
||||||
|
})
|
||||||
|
} else if (elm.type === 'checkbox' || elm.type === 'radio') {
|
||||||
|
if (elm.checked) self.append(elm.name, elm.value)
|
||||||
|
} else {
|
||||||
|
const value = elm.type === 'textarea' ? normalizeLinefeeds(elm.value) : elm.value
|
||||||
|
self.append(elm.name, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a field
|
||||||
|
*
|
||||||
|
* @param {string} name field name
|
||||||
|
* @param {string|Blob|File} value string / blob / file
|
||||||
|
* @param {string=} filename filename to use with blob
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
append (name, value, filename) {
|
||||||
|
ensureArgs(arguments, 2)
|
||||||
|
this._data.push(normalizeArgs(name, value, filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all fields values given name
|
||||||
|
*
|
||||||
|
* @param {string} name Field name
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
delete (name) {
|
||||||
|
ensureArgs(arguments, 1)
|
||||||
|
const result = []
|
||||||
|
name = String(name)
|
||||||
|
|
||||||
|
each(this._data, entry => {
|
||||||
|
entry[0] !== name && result.push(entry)
|
||||||
|
})
|
||||||
|
|
||||||
|
this._data = result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over all fields as [name, value]
|
||||||
|
*
|
||||||
|
* @return {Iterator}
|
||||||
|
*/
|
||||||
|
* entries () {
|
||||||
|
for (var i = 0; i < this._data.length; i++) {
|
||||||
|
yield this._data[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over all fields
|
||||||
|
*
|
||||||
|
* @param {Function} callback Executed for each item with parameters (value, name, thisArg)
|
||||||
|
* @param {Object=} thisArg `this` context for callback function
|
||||||
|
*/
|
||||||
|
forEach (callback, thisArg) {
|
||||||
|
ensureArgs(arguments, 1)
|
||||||
|
for (const [name, value] of this) {
|
||||||
|
callback.call(thisArg, value, name, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return first field value given name
|
||||||
|
* or null if non existent
|
||||||
|
*
|
||||||
|
* @param {string} name Field name
|
||||||
|
* @return {string|File|null} value Fields value
|
||||||
|
*/
|
||||||
|
get (name) {
|
||||||
|
ensureArgs(arguments, 1)
|
||||||
|
const entries = this._data
|
||||||
|
name = String(name)
|
||||||
|
for (let i = 0; i < entries.length; i++) {
|
||||||
|
if (entries[i][0] === name) {
|
||||||
|
return entries[i][1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all fields values given name
|
||||||
|
*
|
||||||
|
* @param {string} name Fields name
|
||||||
|
* @return {Array} [{String|File}]
|
||||||
|
*/
|
||||||
|
getAll (name) {
|
||||||
|
ensureArgs(arguments, 1)
|
||||||
|
const result = []
|
||||||
|
name = String(name)
|
||||||
|
each(this._data, data => {
|
||||||
|
data[0] === name && result.push(data[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for field name existence
|
||||||
|
*
|
||||||
|
* @param {string} name Field name
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
has (name) {
|
||||||
|
ensureArgs(arguments, 1)
|
||||||
|
name = String(name)
|
||||||
|
for (let i = 0; i < this._data.length; i++) {
|
||||||
|
if (this._data[i][0] === name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over all fields name
|
||||||
|
*
|
||||||
|
* @return {Iterator}
|
||||||
|
*/
|
||||||
|
* keys () {
|
||||||
|
for (const [name] of this) {
|
||||||
|
yield name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrite all values given name
|
||||||
|
*
|
||||||
|
* @param {string} name Filed name
|
||||||
|
* @param {string} value Field value
|
||||||
|
* @param {string=} filename Filename (optional)
|
||||||
|
*/
|
||||||
|
set (name, value, filename) {
|
||||||
|
ensureArgs(arguments, 2)
|
||||||
|
name = String(name)
|
||||||
|
/** @type {[string, string|File][]} */
|
||||||
|
const result = []
|
||||||
|
const args = normalizeArgs(name, value, filename)
|
||||||
|
let replace = true
|
||||||
|
|
||||||
|
// - replace the first occurrence with same name
|
||||||
|
// - discards the remaining with same name
|
||||||
|
// - while keeping the same order items where added
|
||||||
|
each(this._data, data => {
|
||||||
|
data[0] === name
|
||||||
|
? replace && (replace = !result.push(args))
|
||||||
|
: result.push(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
replace && result.push(args)
|
||||||
|
|
||||||
|
this._data = result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over all fields
|
||||||
|
*
|
||||||
|
* @return {Iterator}
|
||||||
|
*/
|
||||||
|
* values () {
|
||||||
|
for (const [, value] of this) {
|
||||||
|
yield value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a native (perhaps degraded) FormData with only a `append` method
|
||||||
|
* Can throw if it's not supported
|
||||||
|
*
|
||||||
|
* @return {FormData}
|
||||||
|
*/
|
||||||
|
['_asNative'] () {
|
||||||
|
const fd = new _FormData()
|
||||||
|
|
||||||
|
for (const [name, value] of this) {
|
||||||
|
fd.append(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [_blob description]
|
||||||
|
*
|
||||||
|
* @return {Blob} [description]
|
||||||
|
*/
|
||||||
|
['_blob'] () {
|
||||||
|
const boundary = '----formdata-polyfill-' + Math.random(),
|
||||||
|
chunks = [],
|
||||||
|
p = `--${boundary}\r\nContent-Disposition: form-data; name="`
|
||||||
|
this.forEach((value, name) => typeof value == 'string'
|
||||||
|
? chunks.push(p + escape(normalizeLinefeeds(name)) + `"\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
|
||||||
|
: chunks.push(p + escape(normalizeLinefeeds(name)) + `"; filename="${escape(value.name)}"\r\nContent-Type: ${value.type||"application/octet-stream"}\r\n\r\n`, value, `\r\n`))
|
||||||
|
chunks.push(`--${boundary}--`)
|
||||||
|
return new Blob(chunks, {
|
||||||
|
type: "multipart/form-data; boundary=" + boundary
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class itself is iterable
|
||||||
|
* alias for formdata.entries()
|
||||||
|
*
|
||||||
|
* @return {Iterator}
|
||||||
|
*/
|
||||||
|
[Symbol.iterator] () {
|
||||||
|
return this.entries()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the default string description.
|
||||||
|
*
|
||||||
|
* @return {string} [object FormData]
|
||||||
|
*/
|
||||||
|
toString () {
|
||||||
|
return '[object FormData]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_match && !_match.matches) {
|
||||||
|
_match.matches =
|
||||||
|
_match.matchesSelector ||
|
||||||
|
_match.mozMatchesSelector ||
|
||||||
|
_match.msMatchesSelector ||
|
||||||
|
_match.oMatchesSelector ||
|
||||||
|
_match.webkitMatchesSelector ||
|
||||||
|
function (s) {
|
||||||
|
var matches = (this.document || this.ownerDocument).querySelectorAll(s)
|
||||||
|
var i = matches.length
|
||||||
|
while (--i >= 0 && matches.item(i) !== this) {}
|
||||||
|
return i > -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stringTag) {
|
||||||
|
/**
|
||||||
|
* Create the default string description.
|
||||||
|
* It is accessed internally by the Object.prototype.toString().
|
||||||
|
*/
|
||||||
|
FormDataPolyfill.prototype[stringTag] = 'FormData'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch xhr's send method to call _blob transparently
|
||||||
|
if (_send) {
|
||||||
|
const setRequestHeader = global.XMLHttpRequest.prototype.setRequestHeader
|
||||||
|
|
||||||
|
global.XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
|
||||||
|
setRequestHeader.call(this, name, value)
|
||||||
|
if (name.toLowerCase() === 'content-type') this._hasContentType = true
|
||||||
|
}
|
||||||
|
|
||||||
|
global.XMLHttpRequest.prototype.send = function (data) {
|
||||||
|
// need to patch send b/c old IE don't send blob's type (#44)
|
||||||
|
if (data instanceof FormDataPolyfill) {
|
||||||
|
const blob = data['_blob']()
|
||||||
|
if (!this._hasContentType) this.setRequestHeader('Content-Type', blob.type)
|
||||||
|
_send.call(this, blob)
|
||||||
|
} else {
|
||||||
|
_send.call(this, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch fetch's function to call _blob transparently
|
||||||
|
if (_fetch) {
|
||||||
|
global.fetch = function (input, init) {
|
||||||
|
if (init && init.body && init.body instanceof FormDataPolyfill) {
|
||||||
|
init.body = init.body['_blob']()
|
||||||
|
}
|
||||||
|
|
||||||
|
return _fetch.call(this, input, init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch navigator.sendBeacon to use native FormData
|
||||||
|
if (_sendBeacon) {
|
||||||
|
global.navigator.sendBeacon = function (url, data) {
|
||||||
|
if (data instanceof FormDataPolyfill) {
|
||||||
|
data = data['_asNative']()
|
||||||
|
}
|
||||||
|
return _sendBeacon.call(this, url, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global['FormData'] = FormDataPolyfill
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Jimmy Karl Roland Wärting
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
### A `FormData` polyfill for the browser ...and a module for NodeJS (`New!`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install formdata-polyfill
|
||||||
|
```
|
||||||
|
|
||||||
|
The browser polyfill will likely have done its part already, and i hope you stop supporting old browsers c",)<br>
|
||||||
|
But NodeJS still laks a proper FormData<br>The good old form-data package is a very old and isn't spec compatible and dose some abnormal stuff to construct and read FormData instances that other http libraries are not happy about when it comes to follow the spec.
|
||||||
|
|
||||||
|
### The NodeJS / ESM version
|
||||||
|
- The modular (~2.3 KiB minified uncompressed) version of this package is independent of any browser stuff and don't patch anything
|
||||||
|
- It's as pure/spec compatible as it possible gets the test are run by WPT.
|
||||||
|
- It's compatible with [node-fetch](https://github.com/node-fetch/node-fetch).
|
||||||
|
- It have higher platform dependencies as it uses classes, symbols, ESM & private fields
|
||||||
|
- Only dependency it has is [fetch-blob](https://github.com/node-fetch/fetch-blob)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Node example
|
||||||
|
import fetch from 'node-fetch'
|
||||||
|
import File from 'fetch-blob/file.js'
|
||||||
|
import { fileFromSync } from 'fetch-blob/from.js'
|
||||||
|
import { FormData } from 'formdata-polyfill/esm.min.js'
|
||||||
|
|
||||||
|
const file = fileFromSync('./README.md')
|
||||||
|
const fd = new FormData()
|
||||||
|
|
||||||
|
fd.append('file-upload', new File(['abc'], 'hello-world.txt'))
|
||||||
|
fd.append('file-upload', file)
|
||||||
|
|
||||||
|
// it's also possible to append file/blob look-a-like items
|
||||||
|
// if you have streams coming from other destinations
|
||||||
|
fd.append('file-upload', {
|
||||||
|
size: 123,
|
||||||
|
type: '',
|
||||||
|
name: 'cat-video.mp4',
|
||||||
|
stream() { return stream },
|
||||||
|
[Symbol.toStringTag]: 'File'
|
||||||
|
})
|
||||||
|
|
||||||
|
fetch('https://httpbin.org/post', { method: 'POST', body: fd })
|
||||||
|
```
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
It also comes with way to convert FormData into Blobs - it's not something that every developer should have to deal with.
|
||||||
|
It's mainly for [node-fetch](https://github.com/node-fetch/node-fetch) and other http library to ease the process of serializing a FormData into a blob and just wish to deal with Blobs instead (Both Deno and Undici adapted a version of this [formDataToBlob](https://github.com/jimmywarting/FormData/blob/5ddea9e0de2fc5e246ab1b2f9d404dee0c319c02/formdata-to-blob.js) to the core and passes all WPT tests run by the browser itself)
|
||||||
|
```js
|
||||||
|
import { Readable } from 'node:stream'
|
||||||
|
import { FormData, formDataToBlob } from 'formdata-polyfill/esm.min.js'
|
||||||
|
|
||||||
|
const blob = formDataToBlob(new FormData())
|
||||||
|
fetch('https://httpbin.org/post', { method: 'POST', body: blob })
|
||||||
|
|
||||||
|
// node built in http and other similar http library have to do:
|
||||||
|
const stream = Readable.from(blob.stream())
|
||||||
|
const req = http.request('http://httpbin.org/post', {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Content-Length': blob.size,
|
||||||
|
'Content-Type': blob.type
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stream.pipe(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
PS: blob & file that are appended to the FormData will not be read until any of the serialized blob read-methods gets called
|
||||||
|
...so uploading very large files is no biggie
|
||||||
|
|
||||||
|
### Browser polyfill
|
||||||
|
|
||||||
|
usage:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import 'formdata-polyfill' // that's it
|
||||||
|
```
|
||||||
|
|
||||||
|
The browser polyfill conditionally replaces the native implementation rather than fixing the missing functions,
|
||||||
|
since otherwise there is no way to get or delete existing values in the FormData object.
|
||||||
|
Therefore this also patches `XMLHttpRequest.prototype.send` and `fetch` to send the `FormData` as a blob,
|
||||||
|
and `navigator.sendBeacon` to send native `FormData`.
|
||||||
|
|
||||||
|
I was unable to patch the Response/Request constructor
|
||||||
|
so if you are constructing them with FormData then you need to call `fd._blob()` manually.
|
||||||
|
|
||||||
|
```js
|
||||||
|
new Request(url, {
|
||||||
|
method: 'post',
|
||||||
|
body: fd._blob ? fd._blob() : fd
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
---
|
||||||
|
|
||||||
|
If you need to support IE <= 9 then I recommend you to include eligrey's [blob.js]
|
||||||
|
(which i hope you don't - since IE is now dead)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Updating from 2.x to 3.x</summary>
|
||||||
|
|
||||||
|
Previously you had to import the polyfill and use that,
|
||||||
|
since it didn't replace the global (existing) FormData implementation.
|
||||||
|
But now it transparently calls `_blob()` for you when you are sending something with fetch or XHR,
|
||||||
|
by way of monkey-patching the `XMLHttpRequest.prototype.send` and `fetch` functions.
|
||||||
|
|
||||||
|
So you maybe had something like this:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var FormData = require('formdata-polyfill')
|
||||||
|
var fd = new FormData(form)
|
||||||
|
xhr.send(fd._blob())
|
||||||
|
```
|
||||||
|
|
||||||
|
There is no longer anything exported from the module
|
||||||
|
(though you of course still need to import it to install the polyfill),
|
||||||
|
so you can now use the FormData object as normal:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
require('formdata-polyfill')
|
||||||
|
var fd = new FormData(form)
|
||||||
|
xhr.send(fd)
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Native Browser compatibility (as of 2021-05-08)
|
||||||
|
---
|
||||||
|
Based on this you can decide for yourself if you need this polyfill.
|
||||||
|
|
||||||
|
[](https://developer.mozilla.org/en-US/docs/Web/API/FormData#Browser_compatibility)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
This normalizes support for the FormData API:
|
||||||
|
|
||||||
|
- `append` with filename
|
||||||
|
- `delete()`, `get()`, `getAll()`, `has()`, `set()`
|
||||||
|
- `entries()`, `keys()`, `values()`, and support for `for...of`
|
||||||
|
- Available in web workers (just include the polyfill)
|
||||||
|
|
||||||
|
[npm-image]: https://img.shields.io/npm/v/formdata-polyfill.svg
|
||||||
|
[npm-url]: https://www.npmjs.com/package/formdata-polyfill
|
||||||
|
[blob.js]: https://github.com/eligrey/Blob.js
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
export declare const FormData: {
|
||||||
|
new (): FormData;
|
||||||
|
prototype: FormData;
|
||||||
|
};
|
||||||
|
export declare function formDataToBlob(formData: FormData): Blob;
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
import C from 'fetch-blob'
|
||||||
|
import F from 'fetch-blob/file.js'
|
||||||
|
|
||||||
|
var {toStringTag:t,iterator:i,hasInstance:h}=Symbol,
|
||||||
|
r=Math.random,
|
||||||
|
m='append,set,get,getAll,delete,keys,values,entries,forEach,constructor'.split(','),
|
||||||
|
f=(a,b,c)=>(a+='',/^(Blob|File)$/.test(b && b[t])?[(c=c!==void 0?c+'':b[t]=='File'?b.name:'blob',a),b.name!==c||b[t]=='blob'?new F([b],c,b):b]:[a,b+'']),
|
||||||
|
e=(c,f)=>(f?c:c.replace(/\r?\n|\r/g,'\r\n')).replace(/\n/g,'%0A').replace(/\r/g,'%0D').replace(/"/g,'%22'),
|
||||||
|
x=(n, a, e)=>{if(a.length<e){throw new TypeError(`Failed to execute '${n}' on 'FormData': ${e} arguments required, but only ${a.length} present.`)}}
|
||||||
|
|
||||||
|
export const File = F
|
||||||
|
|
||||||
|
/** @type {typeof globalThis.FormData} */
|
||||||
|
export const FormData = class FormData {
|
||||||
|
#d=[];
|
||||||
|
constructor(...a){if(a.length)throw new TypeError(`Failed to construct 'FormData': parameter 1 is not of type 'HTMLFormElement'.`)}
|
||||||
|
get [t]() {return 'FormData'}
|
||||||
|
[i](){return this.entries()}
|
||||||
|
static [h](o) {return o&&typeof o==='object'&&o[t]==='FormData'&&!m.some(m=>typeof o[m]!='function')}
|
||||||
|
append(...a){x('append',arguments,2);this.#d.push(f(...a))}
|
||||||
|
delete(a){x('delete',arguments,1);a+='';this.#d=this.#d.filter(([b])=>b!==a)}
|
||||||
|
get(a){x('get',arguments,1);a+='';for(var b=this.#d,l=b.length,c=0;c<l;c++)if(b[c][0]===a)return b[c][1];return null}
|
||||||
|
getAll(a,b){x('getAll',arguments,1);b=[];a+='';this.#d.forEach(c=>c[0]===a&&b.push(c[1]));return b}
|
||||||
|
has(a){x('has',arguments,1);a+='';return this.#d.some(b=>b[0]===a)}
|
||||||
|
forEach(a,b){x('forEach',arguments,1);for(var [c,d]of this)a.call(b,d,c,this)}
|
||||||
|
set(...a){x('set',arguments,2);var b=[],c=!0;a=f(...a);this.#d.forEach(d=>{d[0]===a[0]?c&&(c=!b.push(a)):b.push(d)});c&&b.push(a);this.#d=b}
|
||||||
|
*entries(){yield*this.#d}
|
||||||
|
*keys(){for(var[a]of this)yield a}
|
||||||
|
*values(){for(var[,a]of this)yield a}}
|
||||||
|
|
||||||
|
/** @param {FormData} F */
|
||||||
|
export function formDataToBlob (F,B=C){
|
||||||
|
var b=`${r()}${r()}`.replace(/\./g, '').slice(-28).padStart(32, '-'),c=[],p=`--${b}\r\nContent-Disposition: form-data; name="`
|
||||||
|
F.forEach((v,n)=>typeof v=='string'
|
||||||
|
?c.push(p+e(n)+`"\r\n\r\n${v.replace(/\r(?!\n)|(?<!\r)\n/g, '\r\n')}\r\n`)
|
||||||
|
:c.push(p+e(n)+`"; filename="${e(v.name, 1)}"\r\nContent-Type: ${v.type||"application/octet-stream"}\r\n\r\n`, v, '\r\n'))
|
||||||
|
c.push(`--${b}--`)
|
||||||
|
return new B(c,{type:"multipart/form-data; boundary="+b})}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
const escape = (str, filename) =>
|
||||||
|
(filename ? str : str.replace(/\r?\n|\r/g, '\r\n'))
|
||||||
|
.replace(/\n/g, '%0A')
|
||||||
|
.replace(/\r/g, '%0D')
|
||||||
|
.replace(/"/g, '%22')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pure function to convert any formData instance to a Blob
|
||||||
|
* instances synchronous without reading all of the files
|
||||||
|
*
|
||||||
|
* @param {FormData|*} formData an instance of a formData Class
|
||||||
|
* @param {Blob|*} [BlobClass=Blob] the Blob class to use when constructing it
|
||||||
|
*/
|
||||||
|
export function formDataToBlob (formData, BlobClass = Blob) {
|
||||||
|
const boundary = ('----formdata-polyfill-' + Math.random())
|
||||||
|
const chunks = []
|
||||||
|
const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`
|
||||||
|
|
||||||
|
for (let [name, value] of formData) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
chunks.push(prefix + escape(name) + `"\r\n\r\n${value.replace(/\r(?!\n)|(?<!\r)\n/g, '\r\n')}\r\n`)
|
||||||
|
} else {
|
||||||
|
chunks.push(
|
||||||
|
prefix + escape(name) + `"; filename="${escape(value.name, 1)}"\r\n` +
|
||||||
|
`Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
|
||||||
|
value,
|
||||||
|
'\r\n'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.push(`--${boundary}--`)
|
||||||
|
|
||||||
|
return new BlobClass(chunks, {
|
||||||
|
type: 'multipart/form-data; boundary=' + boundary
|
||||||
|
})
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
/*! formdata-polyfill. MIT License. Jimmy W?rting <https://jimmy.warting.se/opensource> */
|
||||||
|
;(function(){var h;function l(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}}var m="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};
|
||||||
|
function n(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");}var q=n(this);function r(a,b){if(b)a:{var c=q;a=a.split(".");for(var d=0;d<a.length-1;d++){var e=a[d];if(!(e in c))break a;c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&m(c,a,{configurable:!0,writable:!0,value:b})}}
|
||||||
|
r("Symbol",function(a){function b(f){if(this instanceof b)throw new TypeError("Symbol is not a constructor");return new c(d+(f||"")+"_"+e++,f)}function c(f,g){this.A=f;m(this,"description",{configurable:!0,writable:!0,value:g})}if(a)return a;c.prototype.toString=function(){return this.A};var d="jscomp_symbol_"+(1E9*Math.random()>>>0)+"_",e=0;return b});
|
||||||
|
r("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var b="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),c=0;c<b.length;c++){var d=q[b[c]];"function"===typeof d&&"function"!=typeof d.prototype[a]&&m(d.prototype,a,{configurable:!0,writable:!0,value:function(){return u(l(this))}})}return a});function u(a){a={next:a};a[Symbol.iterator]=function(){return this};return a}
|
||||||
|
function v(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):{next:l(a)}}var w;if("function"==typeof Object.setPrototypeOf)w=Object.setPrototypeOf;else{var y;a:{var z={a:!0},A={};try{A.__proto__=z;y=A.a;break a}catch(a){}y=!1}w=y?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null}var B=w;function C(){this.m=!1;this.j=null;this.v=void 0;this.h=1;this.u=this.C=0;this.l=null}
|
||||||
|
function D(a){if(a.m)throw new TypeError("Generator is already running");a.m=!0}C.prototype.o=function(a){this.v=a};C.prototype.s=function(a){this.l={D:a,F:!0};this.h=this.C||this.u};C.prototype.return=function(a){this.l={return:a};this.h=this.u};function E(a,b){a.h=3;return{value:b}}function F(a){this.g=new C;this.G=a}F.prototype.o=function(a){D(this.g);if(this.g.j)return G(this,this.g.j.next,a,this.g.o);this.g.o(a);return H(this)};
|
||||||
|
function I(a,b){D(a.g);var c=a.g.j;if(c)return G(a,"return"in c?c["return"]:function(d){return{value:d,done:!0}},b,a.g.return);a.g.return(b);return H(a)}F.prototype.s=function(a){D(this.g);if(this.g.j)return G(this,this.g.j["throw"],a,this.g.o);this.g.s(a);return H(this)};
|
||||||
|
function G(a,b,c,d){try{var e=b.call(a.g.j,c);if(!(e instanceof Object))throw new TypeError("Iterator result "+e+" is not an object");if(!e.done)return a.g.m=!1,e;var f=e.value}catch(g){return a.g.j=null,a.g.s(g),H(a)}a.g.j=null;d.call(a.g,f);return H(a)}function H(a){for(;a.g.h;)try{var b=a.G(a.g);if(b)return a.g.m=!1,{value:b.value,done:!1}}catch(c){a.g.v=void 0,a.g.s(c)}a.g.m=!1;if(a.g.l){b=a.g.l;a.g.l=null;if(b.F)throw b.D;return{value:b.return,done:!0}}return{value:void 0,done:!0}}
|
||||||
|
function J(a){this.next=function(b){return a.o(b)};this.throw=function(b){return a.s(b)};this.return=function(b){return I(a,b)};this[Symbol.iterator]=function(){return this}}function K(a,b){b=new J(new F(b));B&&a.prototype&&B(b,a.prototype);return b}function L(a,b){a instanceof String&&(a+="");var c=0,d=!1,e={next:function(){if(!d&&c<a.length){var f=c++;return{value:b(f,a[f]),done:!1}}d=!0;return{done:!0,value:void 0}}};e[Symbol.iterator]=function(){return e};return e}
|
||||||
|
r("Array.prototype.entries",function(a){return a?a:function(){return L(this,function(b,c){return[b,c]})}});
|
||||||
|
if("undefined"!==typeof Blob&&("undefined"===typeof FormData||!FormData.prototype.keys)){var M=function(a,b){for(var c=0;c<a.length;c++)b(a[c])},N=function(a){return a.replace(/\r?\n|\r/g,"\r\n")},O=function(a,b,c){if(b instanceof Blob){c=void 0!==c?String(c+""):"string"===typeof b.name?b.name:"blob";if(b.name!==c||"[object Blob]"===Object.prototype.toString.call(b))b=new File([b],c);return[String(a),b]}return[String(a),String(b)]},P=function(a,b){if(a.length<b)throw new TypeError(b+" argument required, but only "+
|
||||||
|
a.length+" present.");},Q="object"===typeof globalThis?globalThis:"object"===typeof window?window:"object"===typeof self?self:this,R=Q.FormData,S=Q.XMLHttpRequest&&Q.XMLHttpRequest.prototype.send,T=Q.Request&&Q.fetch,U=Q.navigator&&Q.navigator.sendBeacon,V=Q.Element&&Q.Element.prototype,W=Q.Symbol&&Symbol.toStringTag;W&&(Blob.prototype[W]||(Blob.prototype[W]="Blob"),"File"in Q&&!File.prototype[W]&&(File.prototype[W]="File"));try{new File([],"")}catch(a){Q.File=function(b,c,d){b=new Blob(b,d||{});
|
||||||
|
Object.defineProperties(b,{name:{value:c},lastModified:{value:+(d&&void 0!==d.lastModified?new Date(d.lastModified):new Date)},toString:{value:function(){return"[object File]"}}});W&&Object.defineProperty(b,W,{value:"File"});return b}}var escape=function(a){return a.replace(/\n/g,"%0A").replace(/\r/g,"%0D").replace(/"/g,"%22")},X=function(a){this.i=[];var b=this;a&&M(a.elements,function(c){if(c.name&&!c.disabled&&"submit"!==c.type&&"button"!==c.type&&!c.matches("form fieldset[disabled] *"))if("file"===
|
||||||
|
c.type){var d=c.files&&c.files.length?c.files:[new File([],"",{type:"application/octet-stream"})];M(d,function(e){b.append(c.name,e)})}else"select-multiple"===c.type||"select-one"===c.type?M(c.options,function(e){!e.disabled&&e.selected&&b.append(c.name,e.value)}):"checkbox"===c.type||"radio"===c.type?c.checked&&b.append(c.name,c.value):(d="textarea"===c.type?N(c.value):c.value,b.append(c.name,d))})};h=X.prototype;h.append=function(a,b,c){P(arguments,2);this.i.push(O(a,b,c))};h.delete=function(a){P(arguments,
|
||||||
|
1);var b=[];a=String(a);M(this.i,function(c){c[0]!==a&&b.push(c)});this.i=b};h.entries=function b(){var c,d=this;return K(b,function(e){1==e.h&&(c=0);if(3!=e.h)return c<d.i.length?e=E(e,d.i[c]):(e.h=0,e=void 0),e;c++;e.h=2})};h.forEach=function(b,c){P(arguments,1);for(var d=v(this),e=d.next();!e.done;e=d.next()){var f=v(e.value);e=f.next().value;f=f.next().value;b.call(c,f,e,this)}};h.get=function(b){P(arguments,1);var c=this.i;b=String(b);for(var d=0;d<c.length;d++)if(c[d][0]===b)return c[d][1];
|
||||||
|
return null};h.getAll=function(b){P(arguments,1);var c=[];b=String(b);M(this.i,function(d){d[0]===b&&c.push(d[1])});return c};h.has=function(b){P(arguments,1);b=String(b);for(var c=0;c<this.i.length;c++)if(this.i[c][0]===b)return!0;return!1};h.keys=function c(){var d=this,e,f,g,k,p;return K(c,function(t){1==t.h&&(e=v(d),f=e.next());if(3!=t.h){if(f.done){t.h=0;return}g=f.value;k=v(g);p=k.next().value;return E(t,p)}f=e.next();t.h=2})};h.set=function(c,d,e){P(arguments,2);c=String(c);var f=[],g=O(c,
|
||||||
|
d,e),k=!0;M(this.i,function(p){p[0]===c?k&&(k=!f.push(g)):f.push(p)});k&&f.push(g);this.i=f};h.values=function d(){var e=this,f,g,k,p,t;return K(d,function(x){1==x.h&&(f=v(e),g=f.next());if(3!=x.h){if(g.done){x.h=0;return}k=g.value;p=v(k);p.next();t=p.next().value;return E(x,t)}g=f.next();x.h=2})};X.prototype._asNative=function(){for(var d=new R,e=v(this),f=e.next();!f.done;f=e.next()){var g=v(f.value);f=g.next().value;g=g.next().value;d.append(f,g)}return d};X.prototype._blob=function(){var d="----formdata-polyfill-"+
|
||||||
|
Math.random(),e=[],f="--"+d+'\r\nContent-Disposition: form-data; name="';this.forEach(function(g,k){return"string"==typeof g?e.push(f+escape(N(k))+('"\r\n\r\n'+N(g)+"\r\n")):e.push(f+escape(N(k))+('"; filename="'+escape(g.name)+'"\r\nContent-Type: '+(g.type||"application/octet-stream")+"\r\n\r\n"),g,"\r\n")});e.push("--"+d+"--");return new Blob(e,{type:"multipart/form-data; boundary="+d})};X.prototype[Symbol.iterator]=function(){return this.entries()};X.prototype.toString=function(){return"[object FormData]"};
|
||||||
|
V&&!V.matches&&(V.matches=V.matchesSelector||V.mozMatchesSelector||V.msMatchesSelector||V.oMatchesSelector||V.webkitMatchesSelector||function(d){d=(this.document||this.ownerDocument).querySelectorAll(d);for(var e=d.length;0<=--e&&d.item(e)!==this;);return-1<e});W&&(X.prototype[W]="FormData");if(S){var Y=Q.XMLHttpRequest.prototype.setRequestHeader;Q.XMLHttpRequest.prototype.setRequestHeader=function(d,e){Y.call(this,d,e);"content-type"===d.toLowerCase()&&(this.B=!0)};Q.XMLHttpRequest.prototype.send=
|
||||||
|
function(d){d instanceof X?(d=d._blob(),this.B||this.setRequestHeader("Content-Type",d.type),S.call(this,d)):S.call(this,d)}}T&&(Q.fetch=function(d,e){e&&e.body&&e.body instanceof X&&(e.body=e.body._blob());return T.call(this,d,e)});U&&(Q.navigator.sendBeacon=function(d,e){e instanceof X&&(e=e._asNative());return U.call(this,d,e)});Q.FormData=X};})();
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"name": "formdata-polyfill",
|
||||||
|
"version": "4.0.10",
|
||||||
|
"description": "HTML5 `FormData` for Browsers and Node.",
|
||||||
|
"type": "module",
|
||||||
|
"main": "formdata.min.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "node build.js",
|
||||||
|
"test": "node test/test-esm.js",
|
||||||
|
"test-wpt": "node --experimental-loader ./test/http-loader.js ./test/test-wpt-in-node.js",
|
||||||
|
"test-polyfill": "php -S localhost:4445 & open http://localhost:4445/test/test-polyfill.html"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://jimmywarting@github.com/jimmywarting/FormData.git"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"esm.min.js",
|
||||||
|
"esm.min.d.ts",
|
||||||
|
"FormData.js",
|
||||||
|
"formdata-to-blob.js",
|
||||||
|
"formdata.min.js",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"formdata",
|
||||||
|
"fetch",
|
||||||
|
"node-fetch",
|
||||||
|
"html5",
|
||||||
|
"browser",
|
||||||
|
"polyfill"
|
||||||
|
],
|
||||||
|
"author": "Jimmy Wärting",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/jimmywarting/FormData/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/jimmywarting/FormData#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"fetch-blob": "^3.1.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/google-closure-compiler": "^0.0.19",
|
||||||
|
"@types/node": "^16.7.10",
|
||||||
|
"google-closure-compiler": "^20210808.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
# node-domexception
|
||||||
|
An implementation of the DOMException class from NodeJS
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
# DOMException
|
||||||
|
An implementation of the DOMException class from NodeJS
|
||||||
|
|
||||||
|
This package implements the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class, from NodeJS itself.
|
||||||
|
NodeJS has DOMException built in, but it's not globally available, and you can't require/import it from somewhere.
|
||||||
|
|
||||||
|
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
|
||||||
|
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
|
||||||
|
The instanceof check would not have worked with a custom class such as the DOMexception provided by domenic which also is much larger in size.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import DOMException from 'node-domexception'
|
||||||
|
|
||||||
|
hello().catch(err => {
|
||||||
|
if (err instanceof DOMException) {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const e1 = new DOMException("Something went wrong", "BadThingsError");
|
||||||
|
console.assert(e1.name === "BadThingsError");
|
||||||
|
console.assert(e1.code === 0);
|
||||||
|
|
||||||
|
const e2 = new DOMException("Another exciting error message", "NoModificationAllowedError");
|
||||||
|
console.assert(e2.name === "NoModificationAllowedError");
|
||||||
|
console.assert(e2.code === 7);
|
||||||
|
|
||||||
|
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10);
|
||||||
|
```
|
||||||
|
|
||||||
|
## APIs
|
||||||
|
|
||||||
|
This package exposes two flavors of the `DOMException` interface depending on the imported module.
|
||||||
|
|
||||||
|
### `domexception` module
|
||||||
|
|
||||||
|
This module default-exports the `DOMException` interface constructor.
|
||||||
|
|
||||||
|
### `domexception/webidl2js-wrapper` module
|
||||||
|
|
||||||
|
This module exports the `DOMException` [interface wrapper API](https://github.com/jsdom/webidl2js#for-interfaces) generated by [webidl2js](https://github.com/jsdom/webidl2js).
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
# DOMException
|
||||||
|
An implementation of the DOMException class from NodeJS
|
||||||
|
|
||||||
|
This package implements the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class, from NodeJS itself. (including the legacy codes)
|
||||||
|
NodeJS has DOMException built in, but it's not globally available, and you can't require/import it from somewhere.
|
||||||
|
|
||||||
|
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
|
||||||
|
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
|
||||||
|
The instanceof check would not have worked with a custom class such as the DOMException provided by domenic which also is much larger in size.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import DOMException from 'node-domexception'
|
||||||
|
import { MessageChannel } from 'worker_threads'
|
||||||
|
|
||||||
|
async function hello() {
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
}
|
||||||
|
|
||||||
|
hello().catch(err => {
|
||||||
|
console.assert(err.name === 'DataCloneError')
|
||||||
|
console.assert(err.code === 25)
|
||||||
|
console.assert(err instanceof DOMException)
|
||||||
|
})
|
||||||
|
|
||||||
|
const e1 = new DOMException('Something went wrong', 'BadThingsError')
|
||||||
|
console.assert(e1.name === 'BadThingsError')
|
||||||
|
console.assert(e1.code === 0)
|
||||||
|
|
||||||
|
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.name === 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.code === 7)
|
||||||
|
|
||||||
|
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
|
||||||
|
```
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
# DOMException
|
||||||
|
An implementation of the DOMException class from NodeJS
|
||||||
|
|
||||||
|
This package implements the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class that comes from NodeJS itself. (including the legacy codes)
|
||||||
|
NodeJS has DOMException built in, but it's not globally available, and you can't require/import it from somewhere.
|
||||||
|
|
||||||
|
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
|
||||||
|
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
|
||||||
|
The instanceof check would not have worked with a custom class such as the DOMException provided by domenic which also is much larger in size.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import DOMException from 'node-domexception'
|
||||||
|
import { MessageChannel } from 'worker_threads'
|
||||||
|
|
||||||
|
async function hello() {
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
}
|
||||||
|
|
||||||
|
hello().catch(err => {
|
||||||
|
console.assert(err.name === 'DataCloneError')
|
||||||
|
console.assert(err.code === 25)
|
||||||
|
console.assert(err instanceof DOMException)
|
||||||
|
})
|
||||||
|
|
||||||
|
const e1 = new DOMException('Something went wrong', 'BadThingsError')
|
||||||
|
console.assert(e1.name === 'BadThingsError')
|
||||||
|
console.assert(e1.code === 0)
|
||||||
|
|
||||||
|
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.name === 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.code === 7)
|
||||||
|
|
||||||
|
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
|
||||||
|
```
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
# DOMException
|
||||||
|
An implementation of the DOMException class from NodeJS
|
||||||
|
|
||||||
|
This package exposes the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class that comes from NodeJS itself. (including all of the deprecated legacy codes)
|
||||||
|
NodeJS has it built in, but it's not globally available, and you can't require/import it from somewhere.
|
||||||
|
|
||||||
|
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
|
||||||
|
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
|
||||||
|
The instanceof check would not have worked with a custom class such as the DOMException provided by domenic which also is much larger in size since it has to re-construct the hole class from the ground up.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import DOMException from 'node-domexception'
|
||||||
|
import { MessageChannel } from 'worker_threads'
|
||||||
|
|
||||||
|
async function hello() {
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
}
|
||||||
|
|
||||||
|
hello().catch(err => {
|
||||||
|
console.assert(err.name === 'DataCloneError')
|
||||||
|
console.assert(err.code === 25)
|
||||||
|
console.assert(err instanceof DOMException)
|
||||||
|
})
|
||||||
|
|
||||||
|
const e1 = new DOMException('Something went wrong', 'BadThingsError')
|
||||||
|
console.assert(e1.name === 'BadThingsError')
|
||||||
|
console.assert(e1.code === 0)
|
||||||
|
|
||||||
|
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.name === 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.code === 7)
|
||||||
|
|
||||||
|
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
|
||||||
|
```
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
# DOMException
|
||||||
|
An implementation of the DOMException class from NodeJS
|
||||||
|
|
||||||
|
This package exposes the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class that comes from NodeJS itself. (including all of the deprecated legacy codes)
|
||||||
|
NodeJS has it built in, but it's not globally available, and you can't require/import it from somewhere.
|
||||||
|
|
||||||
|
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
|
||||||
|
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
|
||||||
|
The instanceof check would not have worked with a custom class such as the DOMException provided by domenic which also is much larger in size since it has to re-construct the hole class from the ground up.
|
||||||
|
|
||||||
|
(plz don't depend on this package in any other environment other than node >=10.5)
|
||||||
|
|
||||||
|
```js
|
||||||
|
import DOMException from 'node-domexception'
|
||||||
|
import { MessageChannel } from 'worker_threads'
|
||||||
|
|
||||||
|
async function hello() {
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
}
|
||||||
|
|
||||||
|
hello().catch(err => {
|
||||||
|
console.assert(err.name === 'DataCloneError')
|
||||||
|
console.assert(err.code === 25)
|
||||||
|
console.assert(err instanceof DOMException)
|
||||||
|
})
|
||||||
|
|
||||||
|
const e1 = new DOMException('Something went wrong', 'BadThingsError')
|
||||||
|
console.assert(e1.name === 'BadThingsError')
|
||||||
|
console.assert(e1.code === 0)
|
||||||
|
|
||||||
|
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.name === 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.code === 7)
|
||||||
|
|
||||||
|
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
|
||||||
|
```
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
# DOMException
|
||||||
|
An implementation of the DOMException class from NodeJS
|
||||||
|
|
||||||
|
This package exposes the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class that comes from NodeJS itself. (including all of the deprecated legacy codes)
|
||||||
|
NodeJS has it built in, but it's not globally available, and you can't require/import it from somewhere.
|
||||||
|
|
||||||
|
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
|
||||||
|
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
|
||||||
|
The instanceof check would not have worked with a custom class such as the DOMException provided by domenic which also is much larger in size since it has to re-construct the hole class from the ground up.
|
||||||
|
|
||||||
|
(plz don't depend on this package in any other environment other than node >=10.5)
|
||||||
|
|
||||||
|
```js
|
||||||
|
import DOMException from 'node-domexception'
|
||||||
|
import { MessageChannel } from 'worker_threads'
|
||||||
|
|
||||||
|
async function hello() {
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
}
|
||||||
|
|
||||||
|
hello().catch(err => {
|
||||||
|
console.assert(err.name === 'DataCloneError')
|
||||||
|
console.assert(err.code === 25)
|
||||||
|
console.assert(err instanceof DOMException)
|
||||||
|
})
|
||||||
|
|
||||||
|
const e1 = new DOMException('Something went wrong', 'BadThingsError')
|
||||||
|
console.assert(e1.name === 'BadThingsError')
|
||||||
|
console.assert(e1.code === 0)
|
||||||
|
|
||||||
|
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.name === 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.code === 7)
|
||||||
|
|
||||||
|
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
|
||||||
|
```
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
const { MessageChannel } = require('worker_threads')
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) { globalThis.DOMException = err.constructor }
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads')
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) { globalThis.DOMException = err.constructor }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads')
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) { globalThis.DOMException = err.constructor }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads')
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) { globalThis.DOMException = err.constructor }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads')
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) { globalThis.DOMException = err.constructor }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
var { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) {
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) {
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) {
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
|
|
||||||
|
const e1 = new DOMException("Something went wrong", "BadThingsError");
|
||||||
|
console.assert(e1.name === "BadThingsError");
|
||||||
|
console.assert(e1.code === 0);
|
||||||
|
|
||||||
|
const e2 = new DOMException("Another exciting error message", "NoModificationAllowedError");
|
||||||
|
console.assert(e2.name === "NoModificationAllowedError");
|
||||||
|
console.assert(e2.code === 7);
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) {
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
|
|
||||||
|
const e1 = new DOMException("Something went wrong", "BadThingsError");
|
||||||
|
console.assert(e1.name === "BadThingsError");
|
||||||
|
console.assert(e1.code === 0);
|
||||||
|
|
||||||
|
const e2 = new DOMException("Another exciting error message", "NoModificationAllowedError");
|
||||||
|
console.assert(e2.name === "NoModificationAllowedError");
|
||||||
|
console.assert(e2.code === 2);
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) {
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) {
|
||||||
|
console.log(err.code)
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) {
|
||||||
|
console.log(err.code, err.name, err.message)
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
/*! node-DOMException. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
try { port.postMessage(ab, [ab, ab]) }
|
||||||
|
catch (err) {
|
||||||
|
console.log(err.code, err.name, err.message)
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
/*! node-DOMException. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
try {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
catch (err) {
|
||||||
|
console.log(err.code, err.name, err.message)
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
/*! node-DOMException. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
try {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err.code, err.name, err.message)
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
/*! node-DOMException. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
try {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
} catch (err) {
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
/*! node-domexception. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
try {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
} catch (err) {
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/*! node-domexception. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
try {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
} catch (err) {
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
|
|
||||||
|
|
||||||
|
const { MessageChannel } = require('worker_threads')
|
||||||
|
|
||||||
|
async function hello() {
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
}
|
||||||
|
|
||||||
|
hello().catch(err => {
|
||||||
|
console.assert(err.name === 'DataCloneError')
|
||||||
|
console.assert(err.code === 25)
|
||||||
|
console.assert(err instanceof DOMException)
|
||||||
|
})
|
||||||
|
|
||||||
|
const e1 = new DOMException('Something went wrong', 'BadThingsError')
|
||||||
|
console.assert(e1.name === 'BadThingsError')
|
||||||
|
console.assert(e1.code === 0)
|
||||||
|
|
||||||
|
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.name === 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.code === 7)
|
||||||
|
|
||||||
|
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/*! node-domexception. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
try {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
} catch (err) {
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
|
|
||||||
|
|
||||||
|
const { MessageChannel } = require('worker_threads')
|
||||||
|
|
||||||
|
async function hello() {
|
||||||
|
const port = new MessageChannel().port1
|
||||||
|
const ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
}
|
||||||
|
|
||||||
|
hello().catch(err => {
|
||||||
|
console.assert(err.name === 'DataCloneError')
|
||||||
|
console.assert(err.code === 21)
|
||||||
|
console.assert(err instanceof DOMException)
|
||||||
|
})
|
||||||
|
|
||||||
|
const e1 = new DOMException('Something went wrong', 'BadThingsError')
|
||||||
|
console.assert(e1.name === 'BadThingsError')
|
||||||
|
console.assert(e1.code === 0)
|
||||||
|
|
||||||
|
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.name === 'NoModificationAllowedError')
|
||||||
|
console.assert(e2.code === 7)
|
||||||
|
|
||||||
|
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
/*! node-domexception. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
|
|
||||||
|
if (!globalThis.DOMException) {
|
||||||
|
try {
|
||||||
|
const { MessageChannel } = require('worker_threads'),
|
||||||
|
port = new MessageChannel().port1,
|
||||||
|
ab = new ArrayBuffer()
|
||||||
|
port.postMessage(ab, [ab, ab])
|
||||||
|
} catch (err) {
|
||||||
|
err.constructor.name === 'DOMException' && (
|
||||||
|
globalThis.DOMException = err.constructor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = globalThis.DOMException
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "domexception",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "An implementation of the DOMException class from NodeJS",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/jimmywarting/node-domexception.git"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/jimmywarting/node-domexception/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/jimmywarting/node-domexception#readme"
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "node-domexception",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "An implementation of the DOMException class from NodeJS",
|
||||||
|
"main": "index.js",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/jimmywarting/node-domexception.git"
|
||||||
|
},
|
||||||
|
"author": "Jimmy Wärting",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/jimmywarting/node-domexception/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/jimmywarting/node-domexception#readme"
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "node-domexception",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "An implementation of the DOMException class from NodeJS",
|
||||||
|
"main": "index.js",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/jimmywarting/node-domexception.git"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.5.0"
|
||||||
|
},
|
||||||
|
"author": "Jimmy Wärting",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/jimmywarting/node-domexception/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/jimmywarting/node-domexception#readme"
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user