├── .gitignore ├── package.json ├── LICENSE.md ├── .github └── workflows │ └── npm-publish.yml ├── README.md ├── lib └── index.js └── pnpm-lock.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strapi-provider-upload-do", 3 | "version": "3.6.9", 4 | "description": "Digital ocean spaces provider for Strapi upload plugin", 5 | "homepage": "http://strapi.io", 6 | "keywords": [ 7 | "upload", 8 | "digitalocean", 9 | "spaces", 10 | "strapi", 11 | "provider" 12 | ], 13 | "directories": { 14 | "lib": "./lib" 15 | }, 16 | "main": "./lib", 17 | "dependencies": { 18 | "aws-sdk": "2.1418.0", 19 | "urijs": "1.19.11" 20 | }, 21 | "strapi": { 22 | "isProvider": true 23 | }, 24 | "author": { 25 | "email": "stanley@hsjm.io", 26 | "name": "Stanley Horwood", 27 | "url": "https://hsjm.io" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git://github.com/shorwood/strapi-provider-upload-do.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/shorwood/strapi-provider-upload-do/issues" 35 | }, 36 | "engines": { 37 | "node": ">= 10.0.0", 38 | "npm": ">= 6.0.0" 39 | }, 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2019 Strapi Solutions. 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Publish Node.js Package 5 | 6 | on: push 7 | 8 | jobs: 9 | build: 10 | name: Building and testing Node.js Package. 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: 12 17 | - run: npm ci 18 | - run: npm test 19 | 20 | publish-npm: 21 | name: Publishing Node.js Package to NPM package registry. 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v1 25 | - uses: actions/setup-node@v1 26 | with: 27 | node-version: 12 28 | - run: npm install 29 | - run: npm test 30 | - uses: JS-DevTools/npm-publish@v1 31 | with: 32 | registry: https://registry.npmjs.org/ 33 | token: ${{ secrets.NPM_TOKEN }} 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Strapi Upload Provider for Digital Ocean Spaces 2 | - This provider is a fork of [AdamZikmund's](https://github.com/AdamZikmund) [strapi upload provider](https://github.com/AdamZikmund/strapi-provider-upload-digitalocean) for Digital Ocean spaces. 3 | 4 | This provider will upload to the space using the AWS S3 API. 5 | 6 | ## Parameters 7 | - **key** : [Space access key](https://cloud.digitalocean.com/account/api/tokens) 8 | - **secret** : [Space access secret](https://cloud.digitalocean.com/account/api/tokens) 9 | - **endpoint** : Base URL of the space (e.g. `fra.digitaloceanspaces.com`) 10 | - **space** : Name of the space in the Digital Ocean panel. 11 | - **directory** : Name of the sub-directory you want to store your files in. (Optionnal - e.g. `/example`) 12 | - **cdn** : CDN Endpoint - URL of the cdn of the space (Optionnal - e.g. `cdn.example.com`) 13 | 14 | ## How to use 15 | 16 | 1. Install this package 17 | 18 | ```bash 19 | npm i strapi-provider-upload-do 20 | ``` 21 | ```bash 22 | yarn add strapi-provider-upload-do 23 | ``` 24 | ```bash 25 | pnpm add strapi-provider-upload-do 26 | ``` 27 | 28 | 2. Create or update config in `./config/plugins.js` with content 29 | 30 | ```js 31 | module.exports = ({env}) => ({ 32 | // ... 33 | upload: { 34 | config: { 35 | provider: "strapi-provider-upload-do", 36 | providerOptions: { 37 | key: env('DO_SPACE_ACCESS_KEY'), 38 | secret: env('DO_SPACE_SECRET_KEY'), 39 | endpoint: env('DO_SPACE_ENDPOINT'), 40 | space: env('DO_SPACE_BUCKET'), 41 | directory: env('DO_SPACE_DIRECTORY'), 42 | cdn: env('DO_SPACE_CDN'), 43 | } 44 | }, 45 | }, 46 | // ... 47 | }) 48 | 49 | ``` 50 | 51 | 3. Create `.env` and add provide Digital Ocean config. 52 | 53 | ```bash 54 | DO_SPACE_ACCESS_KEY= 55 | DO_SPACE_SECRET_KEY= 56 | DO_SPACE_ENDPOINT= 57 | DO_SPACE_BUCKET= 58 | DO_SPACE_DIRECTORY= 59 | DO_SPACE_CDN= 60 | ``` 61 | 62 | with values obtained from tutorial: 63 | 64 | > https://www.digitalocean.com/community/tutorials/how-to-create-a-digitalocean-space-and-api-key 65 | 66 | Parameter `DO_SPACE_DIRECTORY` and `DO_SPACE_CDN` is optional and you can ommit them both in `.env` and `settings`. 67 | 68 | ## Resources 69 | 70 | - [MIT License](LICENSE.md) 71 | 72 | ## Links 73 | 74 | - [Strapi website](http://strapi.io/) 75 | - [Strapi community on Slack](http://slack.strapi.io) 76 | - [Strapi news on Twitter](https://twitter.com/strapijs) 77 | - [Strapi docs about upload](https://strapi.io/documentation/3.0.0-beta.x/plugins/upload.html#configuration) 78 | 79 | ## Contributors 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const AWS = require('aws-sdk'); 3 | const URI = require('urijs'); 4 | const crypto = require('crypto'); 5 | 6 | class FileLocationConverter { 7 | constructor(config) { 8 | this.config = config; 9 | } 10 | 11 | getKey(file) { 12 | const filename = `${file.hash}${file.ext}`; 13 | if (!this.config.directory) return filename; 14 | return `${this.config.directory}/${filename}`; 15 | } 16 | 17 | getUrl(data) { 18 | if (!this.config.cdn) return data.Location; 19 | var parts = {}; 20 | URI.parseHost(this.config.cdn, parts); 21 | parts.protocol = "https"; // Force https 22 | parts.path = data.Key; 23 | return URI.build(parts); 24 | } 25 | } 26 | 27 | module.exports = { 28 | provider: "do", 29 | name: "Digital Ocean Spaces", 30 | auth: { 31 | key: { 32 | label: "Key", 33 | type: "text" 34 | }, 35 | secret: { 36 | label: "Secret", 37 | type: "text" 38 | }, 39 | endpoint: { 40 | label: "Endpoint (e.g. 'fra1.digitaloceanspaces.com')", 41 | type: "text", 42 | }, 43 | cdn: { 44 | label: "CDN Endpoint (Optional - e.g. 'https://cdn.space.com')", 45 | type: "text", 46 | }, 47 | space: { 48 | label: "Space (e.g. myspace)", 49 | type: "text", 50 | }, 51 | directory: { 52 | label: 'Directory (Optional - e.g. directory - place when you want to save files)', 53 | type: 'text' 54 | } 55 | }, 56 | init: config => { 57 | const endpoint = new AWS.Endpoint(config.endpoint); 58 | const converter = new FileLocationConverter(config); 59 | 60 | const S3 = new AWS.S3({ 61 | endpoint: endpoint, 62 | accessKeyId: config.key, 63 | secretAccessKey: config.secret, 64 | params: { 65 | ACL: 'public-read', 66 | Bucket: config.space, 67 | CacheControl: 'public, max-age=31536000, immutable' 68 | }, 69 | }); 70 | 71 | const upload = file => new Promise((resolve, reject) => { 72 | //--- Compute the file key. 73 | file.hash = crypto.createHash('md5').update(file.hash).digest("hex"); 74 | 75 | //--- Upload the file into the space (technically the S3 Bucket) 76 | S3.upload({ 77 | Key: converter.getKey(file), 78 | Body: Buffer.from(file.buffer, "binary"), 79 | ContentType: file.mime 80 | }, 81 | 82 | //--- Callback handler 83 | (err, data) => { 84 | if (err) return reject(err); 85 | file.url = converter.getUrl(data); 86 | delete file.buffer; 87 | resolve(); 88 | }); 89 | }); 90 | 91 | return { 92 | upload, 93 | 94 | uploadStream: file => new Promise((resolve, reject) => { 95 | const _buf = []; 96 | 97 | file.stream.on('data', chunk => _buf.push(chunk)); 98 | file.stream.on('end', () => { 99 | file.buffer = Buffer.concat(_buf); 100 | resolve(upload(file)); 101 | }); 102 | file.stream.on('error', err => reject(err)); 103 | }), 104 | 105 | delete: file => new Promise((resolve, reject) => { 106 | 107 | //--- Delete the file from the space 108 | S3.deleteObject({ 109 | Bucket: config.bucket, 110 | Key: converter.getKey(file), 111 | }, 112 | 113 | //--- Callback handler 114 | (err, data) => { 115 | if (err) return reject(err); 116 | else resolve(); 117 | }) 118 | } 119 | ) 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | aws-sdk: 9 | specifier: 2.1418.0 10 | version: 2.1418.0 11 | urijs: 12 | specifier: 1.19.11 13 | version: 1.19.11 14 | 15 | packages: 16 | 17 | /available-typed-arrays@1.0.5: 18 | resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} 19 | engines: {node: '>= 0.4'} 20 | dev: false 21 | 22 | /aws-sdk@2.1418.0: 23 | resolution: {integrity: sha512-6WDMJQAWKwVt+44+61c/SAXKpUSwToqBMeaqizhEe3GN8TWfxMc9RfCnsYIIwS+L+5hedmKC5oc6Fg2ujs8KUQ==} 24 | engines: {node: '>= 10.0.0'} 25 | dependencies: 26 | buffer: 4.9.2 27 | events: 1.1.1 28 | ieee754: 1.1.13 29 | jmespath: 0.16.0 30 | querystring: 0.2.0 31 | sax: 1.2.1 32 | url: 0.10.3 33 | util: 0.12.5 34 | uuid: 8.0.0 35 | xml2js: 0.5.0 36 | dev: false 37 | 38 | /base64-js@1.5.1: 39 | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 40 | dev: false 41 | 42 | /buffer@4.9.2: 43 | resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==} 44 | dependencies: 45 | base64-js: 1.5.1 46 | ieee754: 1.1.13 47 | isarray: 1.0.0 48 | dev: false 49 | 50 | /call-bind@1.0.5: 51 | resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} 52 | dependencies: 53 | function-bind: 1.1.2 54 | get-intrinsic: 1.2.2 55 | set-function-length: 1.1.1 56 | dev: false 57 | 58 | /define-data-property@1.1.1: 59 | resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} 60 | engines: {node: '>= 0.4'} 61 | dependencies: 62 | get-intrinsic: 1.2.2 63 | gopd: 1.0.1 64 | has-property-descriptors: 1.0.1 65 | dev: false 66 | 67 | /events@1.1.1: 68 | resolution: {integrity: sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==} 69 | engines: {node: '>=0.4.x'} 70 | dev: false 71 | 72 | /for-each@0.3.3: 73 | resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} 74 | dependencies: 75 | is-callable: 1.2.7 76 | dev: false 77 | 78 | /function-bind@1.1.2: 79 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 80 | dev: false 81 | 82 | /get-intrinsic@1.2.2: 83 | resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} 84 | dependencies: 85 | function-bind: 1.1.2 86 | has-proto: 1.0.1 87 | has-symbols: 1.0.3 88 | hasown: 2.0.0 89 | dev: false 90 | 91 | /gopd@1.0.1: 92 | resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} 93 | dependencies: 94 | get-intrinsic: 1.2.2 95 | dev: false 96 | 97 | /has-property-descriptors@1.0.1: 98 | resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} 99 | dependencies: 100 | get-intrinsic: 1.2.2 101 | dev: false 102 | 103 | /has-proto@1.0.1: 104 | resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} 105 | engines: {node: '>= 0.4'} 106 | dev: false 107 | 108 | /has-symbols@1.0.3: 109 | resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 110 | engines: {node: '>= 0.4'} 111 | dev: false 112 | 113 | /has-tostringtag@1.0.0: 114 | resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} 115 | engines: {node: '>= 0.4'} 116 | dependencies: 117 | has-symbols: 1.0.3 118 | dev: false 119 | 120 | /hasown@2.0.0: 121 | resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} 122 | engines: {node: '>= 0.4'} 123 | dependencies: 124 | function-bind: 1.1.2 125 | dev: false 126 | 127 | /ieee754@1.1.13: 128 | resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==} 129 | dev: false 130 | 131 | /inherits@2.0.4: 132 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 133 | dev: false 134 | 135 | /is-arguments@1.1.1: 136 | resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} 137 | engines: {node: '>= 0.4'} 138 | dependencies: 139 | call-bind: 1.0.5 140 | has-tostringtag: 1.0.0 141 | dev: false 142 | 143 | /is-callable@1.2.7: 144 | resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 145 | engines: {node: '>= 0.4'} 146 | dev: false 147 | 148 | /is-generator-function@1.0.10: 149 | resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} 150 | engines: {node: '>= 0.4'} 151 | dependencies: 152 | has-tostringtag: 1.0.0 153 | dev: false 154 | 155 | /is-typed-array@1.1.12: 156 | resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} 157 | engines: {node: '>= 0.4'} 158 | dependencies: 159 | which-typed-array: 1.1.13 160 | dev: false 161 | 162 | /isarray@1.0.0: 163 | resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} 164 | dev: false 165 | 166 | /jmespath@0.16.0: 167 | resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==} 168 | engines: {node: '>= 0.6.0'} 169 | dev: false 170 | 171 | /punycode@1.3.2: 172 | resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} 173 | dev: false 174 | 175 | /querystring@0.2.0: 176 | resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} 177 | engines: {node: '>=0.4.x'} 178 | deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. 179 | dev: false 180 | 181 | /sax@1.2.1: 182 | resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==} 183 | dev: false 184 | 185 | /set-function-length@1.1.1: 186 | resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} 187 | engines: {node: '>= 0.4'} 188 | dependencies: 189 | define-data-property: 1.1.1 190 | get-intrinsic: 1.2.2 191 | gopd: 1.0.1 192 | has-property-descriptors: 1.0.1 193 | dev: false 194 | 195 | /urijs@1.19.11: 196 | resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} 197 | dev: false 198 | 199 | /url@0.10.3: 200 | resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==} 201 | dependencies: 202 | punycode: 1.3.2 203 | querystring: 0.2.0 204 | dev: false 205 | 206 | /util@0.12.5: 207 | resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} 208 | dependencies: 209 | inherits: 2.0.4 210 | is-arguments: 1.1.1 211 | is-generator-function: 1.0.10 212 | is-typed-array: 1.1.12 213 | which-typed-array: 1.1.13 214 | dev: false 215 | 216 | /uuid@8.0.0: 217 | resolution: {integrity: sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==} 218 | hasBin: true 219 | dev: false 220 | 221 | /which-typed-array@1.1.13: 222 | resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} 223 | engines: {node: '>= 0.4'} 224 | dependencies: 225 | available-typed-arrays: 1.0.5 226 | call-bind: 1.0.5 227 | for-each: 0.3.3 228 | gopd: 1.0.1 229 | has-tostringtag: 1.0.0 230 | dev: false 231 | 232 | /xml2js@0.5.0: 233 | resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} 234 | engines: {node: '>=4.0.0'} 235 | dependencies: 236 | sax: 1.2.1 237 | xmlbuilder: 11.0.1 238 | dev: false 239 | 240 | /xmlbuilder@11.0.1: 241 | resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} 242 | engines: {node: '>=4.0'} 243 | dev: false 244 | --------------------------------------------------------------------------------