├── .editorconfig ├── .ember-cli ├── .eslintrc.js ├── .github ├── PULL_REQUEST_TEMPLATE └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .watchmanconfig ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── bin └── changelog ├── config ├── ember-try.js └── environment.js ├── index.js ├── lib └── s3.js ├── package.json ├── tests ├── .eslintrc.js ├── fixtures │ └── dist │ │ ├── app.css │ │ ├── app.css.br │ │ ├── app.css.gz │ │ ├── app.js │ │ ├── index │ │ └── manifest.txt ├── index-test.js ├── lib │ └── s3-test.js └── runner.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 6, 5 | sourceType: 'module' 6 | }, 7 | extends: 'eslint:recommended', 8 | env: { 9 | browser: true 10 | }, 11 | rules: { 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | ## What Changed & Why 2 | Explain what changed and why. 3 | 4 | ## Related issues 5 | Link to related issues in this or other repositories (if any) 6 | 7 | ## PR Checklist 8 | - [ ] Add tests 9 | - [ ] Add documentation 10 | - [ ] Prefix documentation-only commits with [DOC] 11 | 12 | ## People 13 | Mention people who would be interested in the changeset (if any) 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [14.x, 16.x, 18.x, 20.x] 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | cache: 'yarn' 21 | - run: yarn install 22 | - run: yarn test 23 | 24 | test-floating: 25 | name: Floating Dependencies 26 | runs-on: ubuntu-latest 27 | strategy: 28 | matrix: 29 | node-version: [14.x, 16.x, 18.x, 20.x] 30 | steps: 31 | - uses: actions/checkout@v2 32 | - name: Use Node.js ${{ matrix.node-version }} 33 | uses: actions/setup-node@v3 34 | with: 35 | node-version: ${{ matrix.node-version }} 36 | cache: 'yarn' 37 | - name: install dependencies 38 | run: yarn install --no-lockfile 39 | - name: test 40 | run: yarn test 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log* 17 | testem.log 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /bower_components 2 | /config/ember-try.js 3 | /dist 4 | /tests 5 | /tmp 6 | **/.gitkeep 7 | .bowerrc 8 | .editorconfig 9 | .ember-cli 10 | .gitignore 11 | .jshintrc 12 | .watchmanconfig 13 | .travis.yml 14 | bower.json 15 | ember-cli-build.js 16 | testem.js 17 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 5.0.1 (2025-02-02) 4 | 5 | #### :bug: Bug Fix 6 | * [#192](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/192) Provide session token to credentials ([@bobisjan](https://github.com/bobisjan)) 7 | 8 | #### Committers: 1 9 | - Jan Bobisud ([@bobisjan](https://github.com/bobisjan)) 10 | 11 | ## 5.0.0 (2024-07-26) 12 | 13 | ## 5.0.0-beta.0 (2024-03-28) 14 | 15 | #### :boom: Breaking Change 16 | * [#190](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/190) Upgrade aws-sdk to v3 ([@lukemelia](https://github.com/lukemelia)) 17 | 18 | #### Committers: 1 19 | - Luke Melia ([@lukemelia](https://github.com/lukemelia)) 20 | 21 | ## 4.0.4 (2024-03-28) 22 | 23 | #### :bug: Bug Fix 24 | * [#189](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/189) Revert to 4.0.1 to back out of breaking AWS SDK upgrade ([@lukemelia](https://github.com/lukemelia)) 25 | 26 | #### Committers: 1 27 | - Luke Melia ([@lukemelia](https://github.com/lukemelia)) 28 | 29 | ## 4.0.1 (2023-08-01) 30 | 31 | ## 4.0.0 (2023-06-04) 32 | 33 | #### :boom: Breaking Change 34 | * [#176](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/176) [BREAKING] update dependencies and node version requirements ([@lukemelia](https://github.com/lukemelia)) 35 | 36 | #### Committers: 1 37 | - Luke Melia ([@lukemelia](https://github.com/lukemelia)) 38 | 39 | ## 3.3.0 (2023-05-30) 40 | 41 | #### :rocket: Enhancement 42 | * [#174](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/174) Optionally ignore ACLs ([@khornberg](https://github.com/khornberg)) 43 | 44 | #### Committers: 2 45 | - Kyle Hornberg ([@khornberg](https://github.com/khornberg)) 46 | - Prasad ([@sbonasu](https://github.com/sbonasu)) 47 | 48 | ## 3.2.0 (2022-10-27) 49 | 50 | #### :rocket: Enhancement 51 | * [#161](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/161) add immutable to cache control header ([@BryanCrotaz](https://github.com/BryanCrotaz)) 52 | 53 | #### Committers: 1 54 | - Bryan ([@BryanCrotaz](https://github.com/BryanCrotaz)) 55 | 56 | ## 3.1.0 (2022-01-19) 57 | 58 | #### :rocket: Enhancement 59 | * [#150](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/150) Add webp to `filePattern` ([@Aierie](https://github.com/Aierie)) 60 | 61 | #### :memo: Documentation 62 | * [#137](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/137) Update CORS example to use JSON ([@lukemelia](https://github.com/lukemelia)) 63 | 64 | #### :house: Internal 65 | * [#146](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/146) Bump path-parse from 1.0.6 to 1.0.7 ([@dependabot[bot]](https://github.com/apps/dependabot)) 66 | * [#153](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/153) Bump forwarded from 0.1.0 to 0.1.2 ([@dependabot[bot]](https://github.com/apps/dependabot)) 67 | * [#154](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/154) Bump y18n from 4.0.0 to 4.0.3 ([@dependabot[bot]](https://github.com/apps/dependabot)) 68 | * [#144](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/144) Bump glob-parent from 5.1.1 to 5.1.2 ([@dependabot[bot]](https://github.com/apps/dependabot)) 69 | * [#142](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/142) Bump browserslist from 4.16.3 to 4.16.6 ([@dependabot[bot]](https://github.com/apps/dependabot)) 70 | * [#141](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/141) Bump ssri from 7.1.0 to 7.1.1 ([@dependabot[bot]](https://github.com/apps/dependabot)) 71 | * [#140](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/140) Bump handlebars from 4.7.6 to 4.7.7 ([@dependabot[bot]](https://github.com/apps/dependabot)) 72 | * [#139](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/139) Bump underscore from 1.8.3 to 1.13.1 ([@dependabot[bot]](https://github.com/apps/dependabot)) 73 | * [#147](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/147) Bump tar from 6.0.5 to 6.1.11 ([@dependabot[bot]](https://github.com/apps/dependabot)) 74 | * [#149](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/149) Bump tmpl from 1.0.4 to 1.0.5 ([@dependabot[bot]](https://github.com/apps/dependabot)) 75 | * [#151](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/151) Bump follow-redirects from 1.13.0 to 1.14.7 ([@dependabot[bot]](https://github.com/apps/dependabot)) 76 | * [#152](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/152) update proxy-agent ([@timmorey](https://github.com/timmorey)) 77 | * [#136](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/136) Update dependencies ([@lukemelia](https://github.com/lukemelia)) 78 | * [#138](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/138) Move from travis to github actions for CI ([@lukemelia](https://github.com/lukemelia)) 79 | 80 | #### Committers: 3 81 | - Luke Melia ([@lukemelia](https://github.com/lukemelia)) 82 | - Timothy Morey ([@timmorey](https://github.com/timmorey)) 83 | - [@Aierie](https://github.com/Aierie) 84 | 85 | ## 3.0.0 (2020-11-24) 86 | 87 | #### :rocket: Enhancement 88 | * [#128](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/128) Add .json to default `filePattern` ([@simonihmig](https://github.com/simonihmig)) 89 | 90 | #### :house: Internal 91 | * [#131](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/131) Update dependencies ([@lukemelia](https://github.com/lukemelia)) 92 | 93 | #### Committers: 2 94 | - Luke Melia ([@lukemelia](https://github.com/lukemelia)) 95 | - Simon Ihmig ([@simonihmig](https://github.com/simonihmig)) 96 | 97 | ## [2.0.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/2.0.0) (2020-05-15) 98 | 99 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v1.4.0...2.0.0) 100 | 101 | **Merged pull requests:** 102 | 103 | - Update test dependencies [\#122](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/122) ([lukemelia](https://github.com/lukemelia)) 104 | - Bump js-yaml from 3.8.2 to 3.13.1 [\#121](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/121) ([dependabot[bot]](https://github.com/apps/dependabot)) 105 | - Bump eslint from 3.19.0 to 4.18.2 [\#120](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/120) ([dependabot[bot]](https://github.com/apps/dependabot)) 106 | - Update dependencies and move to release-it [\#119](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/119) ([lukemelia](https://github.com/lukemelia)) 107 | - Bump handlebars from 4.0.6 to 4.7.6 [\#118](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/118) ([dependabot[bot]](https://github.com/apps/dependabot)) 108 | - Bump lodash.merge from 4.6.0 to 4.6.2 [\#117](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/117) ([dependabot[bot]](https://github.com/apps/dependabot)) 109 | - \[BREAKING\] Use node 10, lowest current node LTS [\#116](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/116) ([lukemelia](https://github.com/lukemelia)) 110 | - \[DOC\] `filePattern`: add link to minimatch [\#112](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/112) ([dcyriller](https://github.com/dcyriller)) 111 | - Added the ability to specify the AWS endpoint [\#111](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/111) ([tbartelmess](https://github.com/tbartelmess)) 112 | - contentType handled properly [\#106](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/106) ([prem-chandar](https://github.com/prem-chandar)) 113 | - Updated bucket policy based on latest AWS recommendations [\#100](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/100) ([peavers](https://github.com/peavers)) 114 | 115 | ## [v1.4.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v1.4.0) (2018-08-20) 116 | 117 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v1.3.0...v1.4.0) 118 | 119 | **Merged pull requests:** 120 | 121 | - Add fileIgnorePattern option [\#109](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/109) ([mydea](https://github.com/mydea)) 122 | 123 | ## [v1.3.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v1.3.0) (2018-05-30) 124 | 125 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v1.2.0...v1.3.0) 126 | 127 | **Merged pull requests:** 128 | 129 | - update mime dependency to 2.x in order to support newer filetypes like wasm \#105 [\#107](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/107) ([jenweber](https://github.com/jenweber)) 130 | 131 | ## [v1.2.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v1.2.0) (2018-02-05) 132 | 133 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v1.1.0...v1.2.0) 134 | 135 | **Merged pull requests:** 136 | 137 | - Support brotli [\#104](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/104) ([cibernox](https://github.com/cibernox)) 138 | - add wasm to filePattern defaults \#101 [\#102](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/102) ([jenweber](https://github.com/jenweber)) 139 | - \[DOC\] remove trailing comma [\#98](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/98) ([fivetanley](https://github.com/fivetanley)) 140 | 141 | ## [v1.1.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v1.1.0) (2017-05-31) 142 | 143 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v1.0.0...v1.1.0) 144 | 145 | **Merged pull requests:** 146 | 147 | - Fix defaultMimeType option binding [\#94](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/94) ([wytlytningNZ](https://github.com/wytlytningNZ)) 148 | - DOC - formatting updates to README [\#91](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/91) ([et](https://github.com/et)) 149 | 150 | ## [v1.0.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v1.0.0) (2017-04-06) 151 | 152 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v1.0.0-beta.0...v1.0.0) 153 | 154 | ## [v1.0.0-beta.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v1.0.0-beta.0) (2017-03-25) 155 | 156 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v0.4.0...v1.0.0-beta.0) 157 | 158 | **Merged pull requests:** 159 | 160 | - Update ember-cli and dependencies [\#89](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/89) ([lukemelia](https://github.com/lukemelia)) 161 | - Replace `ember-cli/ext/promise` with `rsvp` [\#88](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/88) ([backspace](https://github.com/backspace)) 162 | - Instructions to set up public access to S3 [\#86](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/86) ([RobinDaugherty](https://github.com/RobinDaugherty)) 163 | - \[DOC\] update links to other plugins in REAMD [\#85](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/85) ([lukemelia](https://github.com/lukemelia)) 164 | - Dependency Upgrades [\#84](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/84) ([kturney](https://github.com/kturney)) 165 | - Add confi option for signatureVersion [\#83](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/83) ([pedrokost](https://github.com/pedrokost)) 166 | - Update ember cli 2.10 [\#80](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/80) ([jrjohnson](https://github.com/jrjohnson)) 167 | - Adds support for batching requests to S3 [\#79](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/79) ([tomlagier](https://github.com/tomlagier)) 168 | - \[DOC\] Fix broken link to 'Plugin Documentation' [\#77](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/77) ([mattmazzola](https://github.com/mattmazzola)) 169 | - Update minimatch to ^3.0.3 [\#76](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/76) ([jpadilla](https://github.com/jpadilla)) 170 | - \[Bugfix\] Remove the Principal Policy [\#65](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/65) ([Jaspaul](https://github.com/Jaspaul)) 171 | 172 | ## [v0.4.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v0.4.0) (2016-11-02) 173 | 174 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v0.3.2...v0.4.0) 175 | 176 | **Merged pull requests:** 177 | 178 | - Loosen aws-sdk version [\#74](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/74) ([ryansch](https://github.com/ryansch)) 179 | - Add Server Side Encryption [\#72](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/72) ([sethpollack](https://github.com/sethpollack)) 180 | - Allow support for alternate AWS profiles. [\#71](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/71) ([seanstar12](https://github.com/seanstar12)) 181 | - filesUploaded should return all files when using manifest [\#67](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/67) ([wongpeiyi](https://github.com/wongpeiyi)) 182 | 183 | ## [v0.3.2](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v0.3.2) (2016-09-13) 184 | 185 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v0.3.1...v0.3.2) 186 | 187 | **Merged pull requests:** 188 | 189 | - added config to include hidden folders in the S3 upload [\#70](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/70) ([habdelra](https://github.com/habdelra)) 190 | - \[DOC\] Add tip for slashes in the prefix [\#69](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/69) ([adriaanvanrossum](https://github.com/adriaanvanrossum)) 191 | - Allow setting of default mime type for files with no extension [\#63](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/63) ([robwebdev](https://github.com/robwebdev)) 192 | - Add otf file type to default filePattern [\#62](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/62) ([koriroys](https://github.com/koriroys)) 193 | - Add network proxy option [\#61](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/61) ([v-shan](https://github.com/v-shan)) 194 | 195 | ## [v0.3.1](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v0.3.1) (2016-06-16) 196 | 197 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v0.3.0...v0.3.1) 198 | 199 | **Fixed bugs:** 200 | 201 | - Fix issue \#54: manifest.txt should be uploaded as last [\#55](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/55) ([YoranBrondsema](https://github.com/YoranBrondsema)) 202 | 203 | **Merged pull requests:** 204 | 205 | - fix \#59 [\#60](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/60) ([ghedamat](https://github.com/ghedamat)) 206 | - Update minimum S3 permissions [\#58](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/58) ([jeffreybiles](https://github.com/jeffreybiles)) 207 | - Includes session token if provided to client initialization. [\#57](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/57) ([RickCSong](https://github.com/RickCSong)) 208 | 209 | ## [v0.3.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v0.3.0) (2016-04-19) 210 | 211 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v0.2.2...v0.3.0) 212 | 213 | **Merged pull requests:** 214 | 215 | - Configure Cache-Control and Expires headers on the uploaded files. [\#53](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/53) ([YoranBrondsema](https://github.com/YoranBrondsema)) 216 | 217 | ## [v0.2.2](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v0.2.2) (2016-04-01) 218 | 219 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v0.2.1...v0.2.2) 220 | 221 | **Merged pull requests:** 222 | 223 | - Set the correct content type for gzipped files with the .gz ext [\#51](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/51) ([dannyfallon](https://github.com/dannyfallon)) 224 | 225 | ## [v0.2.1](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v0.2.1) (2016-02-06) 226 | 227 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v0.2.0...v0.2.1) 228 | 229 | **Merged pull requests:** 230 | 231 | - update ember-cli-deploy-plugin [\#45](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/45) ([ghedamat](https://github.com/ghedamat)) 232 | - Fix AWS Credential Resolution link in README [\#43](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/43) ([reidab](https://github.com/reidab)) 233 | 234 | ## [v0.2.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v0.2.0) (2015-12-15) 235 | 236 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v0.1.0...v0.2.0) 237 | 238 | **Merged pull requests:** 239 | 240 | - \[DOC\] add CORS example, fixes \#22 [\#42](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/42) ([ghedamat](https://github.com/ghedamat)) 241 | - \[DOC\] Updated README.md to include S3 perimssions [\#40](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/40) ([MojoJojo](https://github.com/MojoJojo)) 242 | - Remove required keys, to support IAM roles [\#39](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/39) ([kkumler](https://github.com/kkumler)) 243 | - Warn about configuration sharing [\#31](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/31) ([LevelbossMike](https://github.com/LevelbossMike)) 244 | - Fix distFiles default in README [\#28](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/28) ([kpfefferle](https://github.com/kpfefferle)) 245 | - Fix README file typo [\#27](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/27) ([Saicheg](https://github.com/Saicheg)) 246 | - Fix typo in tests [\#26](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/26) ([kimroen](https://github.com/kimroen)) 247 | - add version badge [\#25](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/25) ([ghedamat](https://github.com/ghedamat)) 248 | - Make region a required property [\#24](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/24) ([achambers](https://github.com/achambers)) 249 | - doen't use path.join for urls [\#21](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/21) ([flecno](https://github.com/flecno)) 250 | - Add support for ACL on objects [\#20](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/20) ([flecno](https://github.com/flecno)) 251 | 252 | ## [v0.1.0](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v0.1.0) (2015-10-25) 253 | 254 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v0.1.0-beta.2...v0.1.0) 255 | 256 | **Merged pull requests:** 257 | 258 | - Update to use new verbose option for logging [\#19](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/19) ([lukemelia](https://github.com/lukemelia)) 259 | - add ico to default filePattern [\#16](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/16) ([jasonkriss](https://github.com/jasonkriss)) 260 | - add swf to default filePattern [\#15](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/15) ([jasonkriss](https://github.com/jasonkriss)) 261 | 262 | ## [v0.1.0-beta.2](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v0.1.0-beta.2) (2015-09-14) 263 | 264 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/v0.1.0-beta.1...v0.1.0-beta.2) 265 | 266 | **Merged pull requests:** 267 | 268 | - Update repo url [\#14](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/14) ([achambers](https://github.com/achambers)) 269 | - Update repository [\#13](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/13) ([achambers](https://github.com/achambers)) 270 | - Add content encoding to ContentType for text files [\#11](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/11) ([achambers](https://github.com/achambers)) 271 | 272 | ## [v0.1.0-beta.1](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/tree/v0.1.0-beta.1) (2015-08-08) 273 | 274 | [Full Changelog](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/compare/0b3622bd5b5facc24810dc2ac50b7de48eed7116...v0.1.0-beta.1) 275 | 276 | **Merged pull requests:** 277 | 278 | - Updated README for 0.5.0 [\#10](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/10) ([achambers](https://github.com/achambers)) 279 | - Restructure to make more things pull from config, and use ember-cli-deploy-plugin base class [\#7](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/7) ([lukemelia](https://github.com/lukemelia)) 280 | - Manifest support [\#6](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/6) ([lukemelia](https://github.com/lukemelia)) 281 | - Log with a nice checkmark and the target S3 key [\#5](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/5) ([lukemelia](https://github.com/lukemelia)) 282 | - Include svg and webfonts in default filepattern [\#4](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/4) ([lukemelia](https://github.com/lukemelia)) 283 | - Support for working with a gzip plugin. [\#3](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/3) ([lukemelia](https://github.com/lukemelia)) 284 | - Indicate to ember-cli that this addon should be "before" redis [\#2](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/2) ([lukemelia](https://github.com/lukemelia)) 285 | - Upload to s3 [\#1](https://github.com/ember-cli-deploy/ember-cli-deploy-s3/pull/1) ([achambers](https://github.com/achambers)) 286 | 287 | 288 | 289 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 290 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-cli-deploy-s3 2 | 3 | > An ember-cli-deploy plugin to upload files to S3 4 | 5 | [![](https://ember-cli-deploy.github.io/ember-cli-deploy-version-badges/plugins/ember-cli-deploy-s3.svg)](http://ember-cli-deploy.github.io/ember-cli-deploy-version-badges/) 6 | 7 | This plugin uploads one or more files to an Amazon S3 bucket. It could be used to upload the assets (js, css, images etc) or indeed the application's index.html. 8 | 9 | ## What is an ember-cli-deploy plugin? 10 | 11 | A plugin is an addon that can be executed as a part of the ember-cli-deploy pipeline. A plugin will implement one or more of the ember-cli-deploy's pipeline hooks. 12 | 13 | For more information on what plugins are and how they work, please refer to the [Plugin Documentation][1]. 14 | 15 | ## Quick Start 16 | 17 | To get up and running quickly, do the following: 18 | 19 | - Ensure [ember-cli-deploy-build][2] is installed and configured. 20 | 21 | - Install this plugin 22 | 23 | ```bash 24 | $ ember install ember-cli-deploy-s3 25 | ``` 26 | 27 | - Place the following configuration into `config/deploy.js` 28 | 29 | ```javascript 30 | ENV.s3 = { 31 | accessKeyId: '', 32 | secretAccessKey: '', 33 | bucket: '', 34 | region: '' 35 | } 36 | ``` 37 | 38 | - Run the pipeline 39 | 40 | ```bash 41 | $ ember deploy 42 | ``` 43 | 44 | ## Installation 45 | Run the following command in your terminal: 46 | 47 | ```bash 48 | ember install ember-cli-deploy-s3 49 | ``` 50 | 51 | ## ember-cli-deploy Hooks Implemented 52 | 53 | For detailed information on what plugin hooks are and how they work, please refer to the [Plugin Documentation][1]. 54 | 55 | - `configure` 56 | - `upload` 57 | 58 | ## Configuration Options 59 | 60 | For detailed information on how configuration of plugins works, please refer to the [Plugin Documentation][1]. 61 | 62 |
63 | 64 | **WARNING:** Don't share a configuration object between [ember-cli-deploy-s3-index](https://github.com/ember-cli-deploy/ember-cli-deploy-s3-index) and this plugin. The way these two plugins read their configuration has side effects which will unfortunately break your deploy if you share one configuration object between the two. 65 | 66 |
67 | 68 | ### accessKeyId 69 | 70 | The AWS access key for the user that has the ability to upload to the `bucket`. If this is left undefined, 71 | the normal [AWS SDK credential resolution][5] will take place. 72 | 73 | *Default:* `undefined` 74 | 75 | ### secretAccessKey 76 | 77 | The AWS secret for the user that has the ability to upload to the `bucket`. This must be defined when `accessKeyId` is defined. 78 | 79 | *Default:* `undefined` 80 | 81 | ### sessionToken 82 | 83 | The AWS session token for the user that has the ability to upload to the `bucket`. This may be required if you are using the [AWS Security Token Service][6]. 84 | This requires both `accessKeyId` and `secretAccessKey` to be defined. 85 | 86 | *Default:* `undefined` 87 | 88 | ### profile 89 | 90 | The AWS profile as definied in `~/.aws/credentials`. If this is left undefined, 91 | the normal [AWS SDK credential resolution][5] will take place. 92 | 93 | *Default:* `undefined` 94 | 95 | ### bucket (`required`) 96 | 97 | The AWS bucket that the files will be uploaded to. 98 | 99 | *Default:* `undefined` 100 | 101 | ### region (`required`) 102 | 103 | The region the AWS `bucket` is located in. 104 | 105 | *Default:* `undefined` 106 | 107 | ### endpoint 108 | 109 | AWS (or AWS compatible endpoint) to use. E.g. with DigitalOcean Spaces, Microsoft Azure Blob Storage, 110 | or Openstack Swift 111 | 112 | If `endpoint` set the `region` option will be ignored. 113 | 114 | *Default:* `[region].s3.amazonaws.com` 115 | 116 | ### acl 117 | 118 | The ACL to apply to the objects. 119 | 120 | Set to `false` to not apply any ACLs. 121 | 122 | *Default:* `public-read` 123 | 124 | ### prefix 125 | 126 | A directory within the `bucket` that the files should be uploaded in to. It should not have a leading or trailing slash. 127 | 128 | *Default:* `''` 129 | 130 | ### filePattern 131 | 132 | Files that match this pattern will be uploaded to S3. The file pattern must be relative to `distDir`. For an advanced usage, you may want to check out [isaacs/minimatch](https://github.com/isaacs/minimatch#usage)'s documentation. 133 | 134 | *Default:* `'**/*.{js,css,png,gif,ico,jpg,webp,map,xml,txt,svg,swf,eot,ttf,woff,woff2,otf,wasm,json}'` 135 | 136 | ### fileIgnorePattern 137 | 138 | Files matching this pattern will _not_ be uploaded even if they match filePattern. 139 | 140 | *Default:* `null` 141 | 142 | ### distDir 143 | 144 | The root directory where the files matching `filePattern` will be searched for. By default, this option will use the `distDir` property of the deployment context, provided by [ember-cli-deploy-build][2]. 145 | 146 | *Default:* `context.distDir` 147 | 148 | ### distFiles 149 | 150 | The list of built project files. This option should be relative to `distDir` and should include the files that match `filePattern`. By default, this option will use the `distFiles` property of the deployment context, provided by [ember-cli-deploy-build][2]. 151 | 152 | *Default:* `context.distFiles` 153 | 154 | ### dotFolders 155 | 156 | This is a boolean that can be set to `true` to include hidden folders 157 | (that are prefixed with a `.`) as folders that can be uploaded to S3. 158 | 159 | *Default:* `false` 160 | 161 | ### gzippedFiles 162 | 163 | The list of files that have been gziped. This option should be relative to `distDir`. By default, this option will use the `gzippedFiles` property of the deployment context, provided by [ember-cli-deploy-gzip][3]. 164 | 165 | This option will be used to determine which files in `distDir`, that match `filePattern`, require the gzip content encoding when uploading. 166 | 167 | *Default:* `context.gzippedFiles` 168 | 169 | ### manifestPath 170 | 171 | The path to a manifest that specifies the list of files that are to be uploaded to S3. 172 | 173 | This manifest file will be used to work out which files don't exist on S3 and, therefore, which files should be uploaded. By default, this option will use the `manifestPath` property of the deployment context, provided by [ember-cli-deploy-manifest][4]. 174 | 175 | *Default:* `context.manifestPath` 176 | 177 | ### uploadClient 178 | 179 | The client used to upload files to S3. This allows the user the ability to use their own client for uploading instead of the one provided by this plugin. 180 | 181 | The client specified MUST implement a function called `upload`. 182 | 183 | *Default:* the upload client provided by ember-cli-deploy-s3 184 | 185 | ### s3Client 186 | 187 | The underlying S3 library used to upload the files to S3. This allows the user to use the default upload client provided by this plugin but switch out the underlying library that is used to actually send the files. 188 | 189 | The client specified MUST implement functions called `getObject` and `putObject`. 190 | 191 | *Default:* the default S3 library is `aws-sdk` 192 | 193 | ### cacheControl 194 | 195 | Sets the `Cache-Control` header on the uploaded files. 196 | 197 | *Default:* `max-age=63072000, public, immutable` 198 | 199 | ### expires 200 | 201 | Sets the `Expires` header on the uploaded files. 202 | 203 | *Default:* `Mon Dec 31 2029 21:00:00 GMT-0300 (CLST)` 204 | 205 | ### defaultMimeType 206 | 207 | Sets the default mime type, used when it cannot be determined from the file extension. 208 | 209 | *Default:* `application/octet-stream` 210 | 211 | ### proxy 212 | 213 | The network proxy url used when sending requests to S3. 214 | 215 | *Default:* `undefined` 216 | 217 | ### serverSideEncryption 218 | 219 | The Server-side encryption algorithm used when storing this object in S3 (e.g., AES256, aws:kms). Possible values include: 220 | - "AES256" 221 | - "aws:kms" 222 | 223 | ### batchSize 224 | 225 | S3 `putObject` requests will be performed in `batchSize` increments if set to a value other than 0. 226 | 227 | Useful when deploying applications to [fake-s3](https://github.com/jubos/fake-s3/), or applications large enough to trigger S3 rate limits (very uncommon). 228 | 229 | *Default:* `0` 230 | 231 | 232 | ### signatureVersion 233 | 234 | `signatureVersion` allows for setting the Signature Version. In the Asia Pacific (Mumbai), Asia Pacific (Seoul), EU (Frankfurt) and China (Beijing) regions, Amazon S3 supports only Signature Version 4. In all other regions, Amazon S3 supports both Signature Version 4 and Signature Version 2. 235 | 236 | *Example value*: `'v4'` 237 | 238 | *Default*: `undefined` 239 | 240 | ## Prerequisites 241 | 242 | The following properties are expected to be present on the deployment `context` object: 243 | 244 | - `distDir` (provided by [ember-cli-deploy-build][2]) 245 | - `distFiles` (provided by [ember-cli-deploy-build][2]) 246 | - `gzippedFiles` (provided by [ember-cli-deploy-gzip][3]) 247 | - `manifestPath` (provided by [ember-cli-deploy-manifest][4]) 248 | 249 | ## Configuring Amazon S3 250 | 251 | ### Deployment user and S3 permissions 252 | 253 | The environment in which the `ember deploy` command is run needs to have an AWS account with a policy that allows writing to the S3 bucket. 254 | 255 | It's common for a development machine to be set up with the developer's personal AWS credentials, which likely have the ability to administer the entire AWS account. This will allow deployment to work from the development machine, but it is not a good idea to copy your personal credentials to production. 256 | 257 | The best way to set up non-development deployment is to create an IAM user to be the "deployer", and [place its security credentials][9] (Access Key ID and Access Secret) in the environment on the machine or CI environment where deployment takes place. (The easiest way to do this in CI is to set environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.) 258 | 259 | A bare minimum policy should have the following permissions: 260 | 261 | ```js 262 | { 263 | "Statement": [ 264 | { 265 | "Sid": "Stmt1EmberCLIS3DeployPolicy", 266 | "Effect": "Allow", 267 | "Action": [ 268 | "s3:GetObject", 269 | "s3:PutObject", 270 | "s3:PutObjectACL" 271 | ], 272 | "Resource": [ 273 | "arn:aws:s3:::your-s3-bucket-name/*" 274 | ] 275 | } 276 | ] 277 | } 278 | ``` 279 | 280 | Replace `your-s3-bucket-name` with the name of the actual bucket you are deploying to. 281 | 282 | Also, remember that "PutObject" permission will effectively overwrite any existing files with the same name unless you use a fingerprinting or a manifest plugin. 283 | 284 | ### S3 policy for public access 285 | 286 | If you want the contents of the S3 bucket to be accessible to the world, the following policy can be placed directly in the S3 bucket policy: 287 | 288 | ```js 289 | { 290 | "Version": "2012-10-17", 291 | "Statement": [ 292 | { 293 | "Sid": "PublicReadForGetBucketObjects", 294 | "Effect": "Allow", 295 | "Principal": "*", 296 | "Action": "s3:GetObject", 297 | "Resource": "arn:aws:s3:::your-s3-bucket-name/*" 298 | } 299 | ] 300 | } 301 | ``` 302 | 303 | Replace `your-s3-bucket-name` with the name of the actual bucket you are deploying to. 304 | 305 | ### Sample CORS configuration 306 | 307 | To properly serve certain assets (i.e. webfonts) a basic CORS configuration is needed 308 | 309 | ```json 310 | [ 311 | { 312 | "AllowedHeaders": [], 313 | "AllowedMethods": [ 314 | "GET", 315 | "HEAD" 316 | ], 317 | "AllowedOrigins": [ 318 | "http://www.your-site.com", 319 | "https://www.your-site.com" 320 | ], 321 | "ExposeHeaders": [] 322 | } 323 | ] 324 | ``` 325 | 326 | Replace **www.your-site.com** with your domain. 327 | 328 | Some more info: [Amazon CORS guide][7], [Stackoverflow][8] 329 | 330 | 331 | ## Tests 332 | 333 | * `yarn test` 334 | 335 | ## Why `ember build` and `ember test` don't work 336 | 337 | Since this is a node-only ember-cli addon, this package does not include many files and dependencies which are part of ember-cli's typical `ember build` and `ember test` processes. 338 | 339 | [1]: http://ember-cli-deploy.com/plugins/ "Plugin Documentation" 340 | [2]: https://github.com/ember-cli-deploy/ember-cli-deploy-build "ember-cli-deploy-build" 341 | [3]: https://github.com/ember-cli-deploy/ember-cli-deploy-gzip "ember-cli-deploy-gzip" 342 | [4]: https://github.com/ember-cli-deploy/ember-cli-deploy-manifest "ember-cli-deploy-manifest" 343 | [5]: https://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-configuring.html#Setting_AWS_Credentials "Setting AWS Credentials" 344 | [6]: http://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html "AWS Security Token Service guide" 345 | [7]: http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html "Amazon CORS guide" 346 | [8]: http://stackoverflow.com/questions/12229844/amazon-s3-cors-cross-origin-resource-sharing-and-firefox-cross-domain-font-loa?answertab=votes#tab-top "Stackoverflow" 347 | [9]: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-config-files "AWS Configuration" 348 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release 2 | 3 | The following steps should navigate you through the release process to ensure as few issues as possible. 4 | 5 | ## Steps 6 | 7 | ### Commit the changelog and publish to NPM 8 | 9 | 1. run `./bin/changelog` and add output to `CHANGELOG.md` 10 | 2. edit changelog output to be as user-friendly as possible (drop [INTERNAL] changes etc.) 11 | 3. bump package.json version 12 | 4. `./bin/prepare-release` 13 | 5. `git checkout master` 14 | 6. `git add` the modified `package.json` and `CHANGELOG.md` 15 | 7. `git commit -m "Release vx.y.z"` 16 | 8. `git push upstream master` 17 | 9. `git tag "vx.y.z"` 18 | 10. `git push upstream vx.y.z` 19 | 11. `npm publish ./ember-cli-deploy-s3-.tgz` 20 | 21 | ### Create a github release 22 | 23 | 1. under `Releases` on GitHub choose `Draft New Release` 24 | 2. enter the new version number created above as the tag, prefixed with v e.g. (v0.1.12) 25 | 3. for release title choose a great name, no pressure. 26 | 4. in the description paste the upgrade instructions from the previous release, followed by the new CHANGELOG entry 27 | 5. publish the release 28 | -------------------------------------------------------------------------------- /bin/changelog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | /* 5 | * This script generates the template a changelog by comparing a current version 6 | * with master. Run this, copy what's logged into the `CHANGELOG.md` and update 7 | * the top section based on the changes listed in "Community Contributions" 8 | * 9 | * Usage: 10 | * 11 | * bin/changelog 12 | */ 13 | 14 | var EOL = require('os').EOL; 15 | var multiline = require('multiline'); 16 | var RSVP = require('rsvp'); 17 | var GitHubApi = require('github'); 18 | 19 | var github = new GitHubApi({version: '3.0.0'}); 20 | var compareCommits = RSVP.denodeify(github.repos.compareCommits); 21 | var currentVersion = 'v' + require('../package').version; 22 | 23 | var user = 'ember-cli-deploy'; 24 | var repo = 'ember-cli-deploy-s3'; 25 | 26 | compareCommits({ 27 | user: user, 28 | repo: repo, 29 | base: currentVersion, 30 | head: 'master' 31 | }).then(function(res) { 32 | return res.commits.map(function(commitInfo) { 33 | return commitInfo.commit.message 34 | 35 | }).filter(function(message) { 36 | return message.indexOf('Merge pull request #') > -1; 37 | 38 | }).map(function(message) { 39 | var numAndAuthor = message.match(/#(\d+) from (.*)\//).slice(1,3); 40 | var title = message.split('\n\n')[1]; 41 | 42 | return { 43 | number: +numAndAuthor[0], 44 | author: numAndAuthor[1], 45 | title: title 46 | }; 47 | 48 | }).sort(function(a, b) { 49 | return a.number > b.number; 50 | }).map(function(pr) { 51 | var link = '[#' + pr.number + ']' + 52 | '(https://github.com/' + user + '/' + repo + '/pull/' + pr.number + ')'; 53 | var title = pr.title; 54 | var author = '[@' + pr.author + ']' + 55 | '(https://github.com/' + pr.author +')'; 56 | 57 | return '- ' + link + ' ' + title + ' ' + author; 58 | 59 | }).join('\n'); 60 | 61 | }).then(function(contributions) { 62 | var changelog = generateChangelog(contributions); 63 | 64 | console.log(changelog); 65 | }).catch(function(err) { 66 | console.error(err); 67 | }) 68 | 69 | function generateChangelog(contributions) { 70 | var header = multiline(function() {/* 71 | The following changes are required if you are upgrading from the previous 72 | version: 73 | - Users 74 | + Upgrade your project's ember-cli version - [docs](http://www.ember-cli.com/#project-update) 75 | - Addon Developers 76 | + No changes required 77 | - Core Contributors 78 | + No changes required 79 | #### Community Contributions 80 | */}); 81 | 82 | var footer = 'Thank you to all who took the time to contribute!'; 83 | 84 | return header + EOL + EOL + contributions + EOL + EOL + footer; 85 | } 86 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | module.exports = { 3 | scenarios: [ 4 | { 5 | name: 'ember-lts-2.4', 6 | bower: { 7 | dependencies: { 8 | 'ember': 'components/ember#lts-2-4' 9 | }, 10 | resolutions: { 11 | 'ember': 'lts-2-4' 12 | } 13 | } 14 | }, 15 | { 16 | name: 'ember-lts-2.8', 17 | bower: { 18 | dependencies: { 19 | 'ember': 'components/ember#lts-2-8' 20 | }, 21 | resolutions: { 22 | 'ember': 'lts-2-8' 23 | } 24 | } 25 | }, 26 | { 27 | name: 'ember-release', 28 | bower: { 29 | dependencies: { 30 | 'ember': 'components/ember#release' 31 | }, 32 | resolutions: { 33 | 'ember': 'release' 34 | } 35 | } 36 | }, 37 | { 38 | name: 'ember-beta', 39 | bower: { 40 | dependencies: { 41 | 'ember': 'components/ember#beta' 42 | }, 43 | resolutions: { 44 | 'ember': 'beta' 45 | } 46 | } 47 | }, 48 | { 49 | name: 'ember-canary', 50 | bower: { 51 | dependencies: { 52 | 'ember': 'components/ember#canary' 53 | }, 54 | resolutions: { 55 | 'ember': 'canary' 56 | } 57 | } 58 | } 59 | ] 60 | }; 61 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | module.exports = function(/* environment, appConfig */) { 5 | return { }; 6 | }; 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | 'use strict'; 3 | 4 | var RSVP = require('rsvp'); 5 | var minimatch = require('minimatch'); 6 | var DeployPluginBase = require('ember-cli-deploy-plugin'); 7 | var S3 = require('./lib/s3'); 8 | 9 | var EXPIRE_IN_2030 = new Date('2030'); 10 | var TWO_YEAR_CACHE_PERIOD_IN_SEC = 60 * 60 * 24 * 365 * 2; 11 | 12 | module.exports = { 13 | name: 'ember-cli-deploy-s3', 14 | 15 | createDeployPlugin: function(options) { 16 | var DeployPlugin = DeployPluginBase.extend({ 17 | name: options.name, 18 | defaultConfig: { 19 | filePattern: '**/*.{js,css,png,gif,ico,jpg,webp,map,xml,txt,svg,swf,eot,ttf,woff,woff2,otf,wasm,json}', 20 | fileIgnorePattern: null, 21 | prefix: '', 22 | profile: '', 23 | acl: 'public-read', 24 | cacheControl: 'max-age='+TWO_YEAR_CACHE_PERIOD_IN_SEC+', public, immutable', 25 | expires: EXPIRE_IN_2030, 26 | dotFolders: false, 27 | batchSize: 0, 28 | defaultMimeType: 'application/octet-stream', 29 | distDir: function(context) { 30 | return context.distDir; 31 | }, 32 | distFiles: function(context) { 33 | return context.distFiles || []; 34 | }, 35 | gzippedFiles: function(context) { 36 | return context.gzippedFiles || []; // e.g. from ember-cli-deploy-gzip 37 | }, 38 | brotliCompressedFiles: function(context) { 39 | return context.brotliCompressedFiles || []; // e.g. from ember-cli-deploy-gzip 40 | }, 41 | manifestPath: function(context) { 42 | return context.manifestPath; // e.g. from ember-cli-deploy-manifest 43 | }, 44 | uploadClient: function(context) { 45 | return context.uploadClient; // if you want to provide your own upload client to be used instead of one from this plugin 46 | }, 47 | s3Client: function(context) { 48 | return context.s3Client; // if you want to provide your own S3 client to be used instead of one from aws-sdk 49 | } 50 | }, 51 | requiredConfig: ['bucket', 'region'], 52 | 53 | upload: function() { 54 | var self = this; 55 | 56 | var filePattern = this.readConfig('filePattern'); 57 | var fileIgnorePattern = this.readConfig('fileIgnorePattern'); 58 | var distDir = this.readConfig('distDir'); 59 | var distFiles = this.readConfig('distFiles'); 60 | var gzippedFiles = this.readConfig('gzippedFiles'); 61 | var brotliCompressedFiles = this.readConfig('brotliCompressedFiles'); 62 | var bucket = this.readConfig('bucket'); 63 | var acl = this.readConfig('acl'); 64 | var prefix = this.readConfig('prefix'); 65 | var manifestPath = this.readConfig('manifestPath'); 66 | var cacheControl = this.readConfig('cacheControl'); 67 | var expires = this.readConfig('expires'); 68 | var dotFolders = this.readConfig('dotFolders'); 69 | var serverSideEncryption = this.readConfig('serverSideEncryption'); 70 | var batchSize = this.readConfig('batchSize'); 71 | var defaultMimeType = this.readConfig('defaultMimeType'); 72 | 73 | var filesToUpload = distFiles.filter(minimatch.filter(filePattern, { matchBase: true, dot: dotFolders })); 74 | if (fileIgnorePattern) { 75 | filesToUpload = filesToUpload.filter(function(path) { 76 | return !minimatch(path, fileIgnorePattern, { matchBase: true }); 77 | }); 78 | gzippedFiles = gzippedFiles.filter(function(path) { 79 | return !minimatch(path, fileIgnorePattern, { matchBase: true }); 80 | }); 81 | brotliCompressedFiles = brotliCompressedFiles.filter(function(path) { 82 | return !minimatch(path, fileIgnorePattern, { matchBase: true }); 83 | }); 84 | } 85 | 86 | var s3 = this.readConfig('uploadClient') || new S3({ 87 | plugin: this 88 | }); 89 | 90 | var options = { 91 | cwd: distDir, 92 | filePaths: filesToUpload, 93 | gzippedFilePaths: gzippedFiles, 94 | brotliCompressedFilePaths: brotliCompressedFiles, 95 | prefix: prefix, 96 | bucket: bucket, 97 | manifestPath: manifestPath, 98 | cacheControl: cacheControl, 99 | expires: expires, 100 | batchSize: batchSize, 101 | defaultMimeType: defaultMimeType 102 | }; 103 | 104 | if (acl) { 105 | options.acl = acl; 106 | } 107 | 108 | if (serverSideEncryption) { 109 | options.serverSideEncryption = serverSideEncryption; 110 | } 111 | 112 | this.log('preparing to upload to S3 bucket `' + bucket + '`', { verbose: true }); 113 | 114 | return s3.upload(options) 115 | .then(function(filesUploaded){ 116 | self.log('uploaded ' + filesUploaded.length + ' files ok', { verbose: true }); 117 | return { filesUploaded: filesUploaded }; 118 | }) 119 | .catch(this._errorMessage.bind(this)); 120 | }, 121 | _errorMessage: function(error) { 122 | this.log(error, { color: 'red' }); 123 | if (error) { 124 | this.log(error.stack, { color: 'red' }); 125 | } 126 | return RSVP.reject(error); 127 | } 128 | }); 129 | return new DeployPlugin(); 130 | } 131 | }; 132 | -------------------------------------------------------------------------------- /lib/s3.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | var CoreObject = require('core-object'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var mime = require('mime'); 6 | var RSVP = require('rsvp'); 7 | var _ = require('lodash'); 8 | 9 | module.exports = CoreObject.extend({ 10 | init: function(options) { 11 | this._super(options); 12 | 13 | const { 14 | fromIni 15 | } = require('@aws-sdk/credential-providers'); 16 | 17 | const { 18 | S3 19 | } = require('@aws-sdk/client-s3'); 20 | 21 | var s3Options = { 22 | region: this.plugin.readConfig('region') 23 | }; 24 | 25 | const proxy = this.plugin.readConfig('proxy'); 26 | if (proxy) { 27 | var agent; 28 | this._proxyAgent = this.plugin.readConfig('proxyAgent'); 29 | if (this._proxyAgent) { 30 | agent = this._proxyAgent(proxy); 31 | } else { 32 | const { ProxyAgent } = require('proxy-agent'); 33 | agent = new ProxyAgent(proxy); 34 | } 35 | s3Options.httpOptions = { 36 | agent 37 | }; 38 | } 39 | 40 | const accessKeyId = this.plugin.readConfig('accessKeyId'); 41 | const secretAccessKey = this.plugin.readConfig('secretAccessKey'); 42 | const sessionToken = this.plugin.readConfig('sessionToken'); 43 | const profile = this.plugin.readConfig('profile'); 44 | const signatureVersion = this.plugin.readConfig('signatureVersion'); 45 | const endpoint = this.plugin.readConfig('endpoint'); 46 | 47 | if (accessKeyId && secretAccessKey) { 48 | this.plugin.log('Using AWS access key id and secret access key from config', { verbose: true }); 49 | s3Options.credentials = { 50 | accessKeyId: accessKeyId, 51 | secretAccessKey: secretAccessKey, 52 | }; 53 | 54 | if (sessionToken) { 55 | this.plugin.log('Using AWS session token from config', { verbose: true }); 56 | s3Options.credentials.sessionToken = sessionToken; 57 | } 58 | } 59 | 60 | if (signatureVersion) { 61 | this.plugin.log('Using signature version from config', { verbose: true }); 62 | s3Options.signatureVersion = signatureVersion; 63 | } 64 | 65 | if (profile && !this.plugin.readConfig('s3Client')) { 66 | this.plugin.log('Using AWS profile from config', { verbose: true }); 67 | s3Options.credentials = fromIni({ profile: profile }); 68 | } 69 | 70 | if (endpoint) { 71 | this.plugin.log('Using endpoint from config', { verbose: true }); 72 | s3Options.endpoint = endpoint; 73 | } 74 | 75 | this._client = this.plugin.readConfig('s3Client') || new S3(s3Options); 76 | }, 77 | 78 | upload: function(options) { 79 | options = options || {}; 80 | return this._determineFilePaths(options).then(function(filePaths) { 81 | const allFilesUploaded = this._putObjects(filePaths, options); 82 | 83 | const manifestPath = options.manifestPath; 84 | if (manifestPath) { 85 | return allFilesUploaded.then(function(filesUploaded) { 86 | return this._putObject(manifestPath, options).then(function(manifestUploaded) { 87 | return filesUploaded.concat(manifestUploaded); 88 | }); 89 | }.bind(this)); 90 | } else { 91 | return allFilesUploaded; 92 | } 93 | }.bind(this)); 94 | }, 95 | 96 | _determineFilePaths: function(options) { 97 | var plugin = this.plugin; 98 | var filePaths = options.filePaths || []; 99 | if (typeof filePaths === 'string') { 100 | filePaths = [filePaths]; 101 | } 102 | var prefix = options.prefix; 103 | var manifestPath = options.manifestPath; 104 | if (manifestPath) { 105 | var key = prefix === '' ? manifestPath : [prefix, manifestPath].join('/'); 106 | plugin.log('Downloading manifest for differential deploy from `' + key + '`...', { verbose: true }); 107 | return new RSVP.Promise(function(resolve, reject){ 108 | var params = { Bucket: options.bucket, Key: key}; 109 | this._client.getObject(params, function(error, data) { 110 | if (error) { 111 | reject(error); 112 | } else { 113 | resolve(data.Body.toString().split('\n')); 114 | } 115 | }.bind(this)); 116 | }.bind(this)).then(function(manifestEntries){ 117 | plugin.log("Manifest found. Differential deploy will be applied.", { verbose: true }); 118 | return _.difference(filePaths, manifestEntries); 119 | }).catch(function(/* reason */){ 120 | plugin.log("Manifest not found. Disabling differential deploy.", { color: 'yellow', verbose: true }); 121 | return RSVP.resolve(filePaths); 122 | }); 123 | } else { 124 | return RSVP.resolve(filePaths); 125 | } 126 | }, 127 | 128 | _mimeCharsetsLookup: function(mimeType, fallback) { 129 | // the node-mime library removed this method in v 2.0. This is the replacement 130 | // code for what was formerly mime.charsets.lookup 131 | return (/^text\/|^application\/(javascript|json)/).test(mimeType) ? 'UTF-8' : fallback; 132 | }, 133 | 134 | _putObject: function(filePath, options, filePaths) { 135 | var plugin = this.plugin; 136 | var cwd = options.cwd; 137 | var bucket = options.bucket; 138 | var prefix = options.prefix; 139 | var acl = options.acl; 140 | var gzippedFilePaths = options.gzippedFilePaths || []; 141 | var brotliCompressedFilePaths = options.brotliCompressedFilePaths || []; 142 | var cacheControl = options.cacheControl; 143 | var expires = options.expires; 144 | var serverSideEncryption = options.serverSideEncryption; 145 | 146 | var defaultType = options.defaultMimeType || mime.getType('bin'); 147 | 148 | var basePath = path.join(cwd, filePath); 149 | var data = fs.readFileSync(basePath); 150 | var contentType = mime.getType(basePath) || defaultType; 151 | var encoding = this._mimeCharsetsLookup(contentType); 152 | var key = prefix === '' ? filePath : [prefix, filePath].join('/'); 153 | var isGzipped = gzippedFilePaths.indexOf(filePath) !== -1; 154 | var isBrotliCompressed = brotliCompressedFilePaths.indexOf(filePath) !== -1; 155 | 156 | if (isGzipped && path.extname(basePath) === '.gz') { 157 | var basePathUngzipped = filePath.slice(0, -3); 158 | if (filePaths && filePaths.indexOf(basePathUngzipped) !== -1) { 159 | contentType = mime.getType(basePathUngzipped) || defaultType; 160 | encoding = this._mimeCharsetsLookup(contentType); 161 | } 162 | } 163 | if (isBrotliCompressed) { 164 | var basePathUncompressed = filePath.slice(0, -3); 165 | if (filePaths && filePaths.indexOf(basePathUncompressed) !== -1) { 166 | contentType = mime.getType(basePathUncompressed) || defaultType; 167 | encoding = this._mimeCharsetsLookup(contentType); 168 | } 169 | } 170 | 171 | if (encoding) { 172 | contentType += '; charset='; 173 | contentType += encoding.toLowerCase(); 174 | } 175 | 176 | var params = { 177 | Bucket: bucket, 178 | ACL: acl, 179 | Body: data, 180 | ContentType: contentType, 181 | Key: key, 182 | CacheControl: cacheControl, 183 | Expires: expires 184 | }; 185 | 186 | if (serverSideEncryption) { 187 | params.ServerSideEncryption = serverSideEncryption; 188 | } 189 | 190 | if (isGzipped) { 191 | params.ContentEncoding = 'gzip'; 192 | } 193 | if (isBrotliCompressed) { 194 | params.ContentEncoding = 'br'; 195 | } 196 | 197 | return new RSVP.Promise(function(resolve, reject) { 198 | this._client.putObject(params, function(error) { 199 | if (error) { 200 | reject(error); 201 | } else { 202 | plugin.log('✔ ' + key, { verbose: true }); 203 | resolve(filePath); 204 | } 205 | }); 206 | }.bind(this)); 207 | }, 208 | 209 | _currentEnd: 0, 210 | _putObjectsBatch: function(filePaths, options) { 211 | var currentBatch = filePaths.slice(this._currentEnd, Math.min(this._currentEnd + options.batchSize, filePaths.length)); 212 | 213 | this._currentEnd += currentBatch.length; 214 | 215 | //Execute our current batch of promises 216 | return RSVP.all(currentBatch.map(function (filePath) { 217 | return this._putObject(filePath, options, filePaths); 218 | }.bind(this))) 219 | //Then check if we need to execute another batch 220 | .then(function () { 221 | if (this._currentEnd < filePaths.length) { 222 | return this._putObjectsBatch(filePaths, options); 223 | } 224 | 225 | return filePaths; 226 | }.bind(this)); 227 | }, 228 | 229 | _putObjects: function (filePaths, options) { 230 | if (options.batchSize > 0) { 231 | this._currentEnd = 0; 232 | return this._putObjectsBatch(filePaths, options); 233 | } 234 | 235 | return RSVP.all(filePaths.map(function (filePath) { 236 | return this._putObject(filePath, options, filePaths); 237 | }.bind(this))); 238 | } 239 | }); 240 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-deploy-s3", 3 | "version": "5.0.1", 4 | "description": "An ember-cli-deploy plugin to upload to s3", 5 | "keywords": [ 6 | "ember-addon", 7 | "ember-cli-deploy-plugin" 8 | ], 9 | "license": "MIT", 10 | "author": "Aaron Chambers and the ember-cli-deploy team", 11 | "directories": { 12 | "doc": "doc", 13 | "test": "tests" 14 | }, 15 | "repository": "https://github.com/ember-cli-deploy/ember-cli-deploy-s3", 16 | "scripts": { 17 | "release": "release-it", 18 | "test": "node tests/runner.js && ./node_modules/.bin/eslint index.js lib/* tests/**/*-test.js" 19 | }, 20 | "dependencies": { 21 | "@aws-sdk/client-s3": "^3.525.0", 22 | "@aws-sdk/credential-providers": "^3.525.0", 23 | "chalk": "^4.1.0", 24 | "core-object": "^3.1.5", 25 | "ember-cli-deploy-plugin": "^0.2.9", 26 | "lodash": "^4.17.21", 27 | "mime": "^3.0.0", 28 | "minimatch": "^3.0.3", 29 | "proxy-agent": "^6.3.0", 30 | "rsvp": "^4.8.5" 31 | }, 32 | "devDependencies": { 33 | "chai": "^4.3.7", 34 | "chai-as-promised": "^7.1.1", 35 | "ember-cli": "^3.28.6", 36 | "eslint": "^8.42.0", 37 | "github": "^14.0.0", 38 | "glob": "^10.2.6", 39 | "mocha": "^8.3.1", 40 | "multiline": "^2.0.0", 41 | "release-it": "14.4.1", 42 | "release-it-lerna-changelog": "^3.1.0" 43 | }, 44 | "engines": { 45 | "node": "14.x || 16.x || 18.x || >= 20.*" 46 | }, 47 | "ember-addon": { 48 | "before": "ember-cli-deploy-redis" 49 | }, 50 | "publishConfig": { 51 | "registry": "https://registry.npmjs.org/" 52 | }, 53 | "release-it": { 54 | "plugins": { 55 | "release-it-lerna-changelog": { 56 | "infile": "CHANGELOG.md", 57 | "launchEditor": false 58 | } 59 | }, 60 | "git": { 61 | "requireCleanWorkingDir": false 62 | }, 63 | "github": { 64 | "release": true 65 | } 66 | }, 67 | "volta": { 68 | "node": "16.20.0", 69 | "yarn": "1.22.17" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | "describe": true, 4 | "before": true, 5 | "beforeEach": true, 6 | "it": true 7 | }, 8 | env: { 9 | embertest: true 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /tests/fixtures/dist/app.css: -------------------------------------------------------------------------------- 1 | body: {} 2 | -------------------------------------------------------------------------------- /tests/fixtures/dist/app.css.br: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ember-cli-deploy/ember-cli-deploy-s3/7f56de370fd1e35f3945d2de6010b6e9dfc0439d/tests/fixtures/dist/app.css.br -------------------------------------------------------------------------------- /tests/fixtures/dist/app.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ember-cli-deploy/ember-cli-deploy-s3/7f56de370fd1e35f3945d2de6010b6e9dfc0439d/tests/fixtures/dist/app.css.gz -------------------------------------------------------------------------------- /tests/fixtures/dist/app.js: -------------------------------------------------------------------------------- 1 | some content here 2 | -------------------------------------------------------------------------------- /tests/fixtures/dist/index: -------------------------------------------------------------------------------- 1 |

Some HTML

2 | -------------------------------------------------------------------------------- /tests/fixtures/dist/manifest.txt: -------------------------------------------------------------------------------- 1 | app.css 2 | app.js 3 | -------------------------------------------------------------------------------- /tests/index-test.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | 'use strict'; 3 | 4 | var chai = require('chai'); 5 | var chaiAsPromised = require("chai-as-promised"); 6 | chai.use(chaiAsPromised); 7 | 8 | var assert = chai.assert; 9 | var RSVP = require('rsvp'); 10 | 11 | describe('s3 plugin', function() { 12 | var subject; 13 | var mockUi; 14 | var context; 15 | 16 | before(function() { 17 | subject = require('../index'); 18 | }); 19 | 20 | beforeEach(function() { 21 | mockUi = { 22 | verbose: true, 23 | messages: [], 24 | write: function() {}, 25 | writeLine: function(message) { 26 | this.messages.push(message); 27 | } 28 | }; 29 | 30 | context = { 31 | distDir: process.cwd() + '/tests/fixtures/dist', 32 | distFiles: ['app.css', 'app.js'], 33 | ui: mockUi, 34 | uploadClient: { 35 | upload: function(/* options */) { 36 | return RSVP.resolve(['app.css', 'app.js']); 37 | } 38 | }, 39 | config: { 40 | s3: { 41 | accessKeyId: 'aaaa', 42 | secretAccessKey: 'bbbb', 43 | sessionToken: 'eeee', 44 | bucket: 'cccc', 45 | region: 'dddd', 46 | profile: 'ffff', 47 | filePattern: '*.{css,js}', 48 | acl: 'authenticated-read', 49 | prefix: '', 50 | proxy: 'http://user:password@internal.proxy.com', 51 | distDir: function(context) { 52 | return context.distDir; 53 | }, 54 | distFiles: function(context) { 55 | return context.distFiles || []; 56 | }, 57 | gzippedFiles: function(context) { 58 | return context.gzippedFiles || []; // e.g. from ember-cli-deploy-gzip 59 | }, 60 | brotliCompressedFiles: function(context) { 61 | return context.brotliCompressedFiles || []; // e.g. from ember-cli-deploy-gzip 62 | }, 63 | manifestPath: function(context) { 64 | return context.manifestPath; // e.g. from ember-cli-deploy-manifest 65 | }, 66 | uploadClient: function(context) { 67 | return context.uploadClient; // if you want to provide your own upload client to be used instead of one from this addon 68 | }, 69 | s3Client: function(context) { 70 | return context.s3Client; // if you want to provide your own s3 client to be used instead of one from aws-sdk 71 | } 72 | } 73 | } 74 | }; 75 | }); 76 | 77 | it('has a name', function() { 78 | var plugin = subject.createDeployPlugin({ 79 | name: 's3' 80 | }); 81 | 82 | assert.equal(plugin.name, 's3'); 83 | }); 84 | 85 | it('implements the correct hooks', function() { 86 | var plugin = subject.createDeployPlugin({ 87 | name: 's3' 88 | }); 89 | 90 | assert.typeOf(plugin.configure, 'function'); 91 | assert.typeOf(plugin.upload, 'function'); 92 | }); 93 | 94 | describe('configure hook', function() { 95 | it('does not throw if config is ok', function() { 96 | var plugin = subject.createDeployPlugin({ 97 | name: 's3' 98 | }); 99 | plugin.beforeHook(context); 100 | plugin.configure(context); 101 | assert.ok(true); // it didn't throw 102 | }); 103 | 104 | it('throws if config is not valid', function() { 105 | var plugin = subject.createDeployPlugin({ 106 | name: 's3' 107 | }); 108 | 109 | context.config.s3 = {}; 110 | 111 | plugin.beforeHook(context); 112 | assert.throws(function(){ 113 | plugin.configure(context); 114 | }); 115 | }); 116 | 117 | it('warns about missing optional config', function() { 118 | delete context.config.s3.filePattern; 119 | delete context.config.s3.prefix; 120 | 121 | var plugin = subject.createDeployPlugin({ 122 | name: 's3' 123 | }); 124 | plugin.beforeHook(context); 125 | plugin.configure(context); 126 | var messages = mockUi.messages.reduce(function(previous, current) { 127 | if (/- Missing config:\s.*, using default:\s/.test(current)) { 128 | previous.push(current); 129 | } 130 | 131 | return previous; 132 | }, []); 133 | 134 | assert.equal(messages.length, 8); 135 | }); 136 | 137 | describe('required config', function() { 138 | it('warns about missing bucket', function() { 139 | delete context.config.s3.bucket; 140 | 141 | var plugin = subject.createDeployPlugin({ 142 | name: 's3' 143 | }); 144 | plugin.beforeHook(context); 145 | assert.throws(function(/* error */){ 146 | plugin.configure(context); 147 | }); 148 | var messages = mockUi.messages.reduce(function(previous, current) { 149 | if (/- Missing required config: `bucket`/.test(current)) { 150 | previous.push(current); 151 | } 152 | 153 | return previous; 154 | }, []); 155 | 156 | assert.equal(messages.length, 1); 157 | }); 158 | 159 | it('warns about missing region', function() { 160 | delete context.config.s3.region; 161 | 162 | var plugin = subject.createDeployPlugin({ 163 | name: 's3' 164 | }); 165 | plugin.beforeHook(context); 166 | assert.throws(function(/* error */){ 167 | plugin.configure(context); 168 | }); 169 | var messages = mockUi.messages.reduce(function(previous, current) { 170 | if (/- Missing required config: `region`/.test(current)) { 171 | previous.push(current); 172 | } 173 | 174 | return previous; 175 | }, []); 176 | 177 | assert.equal(messages.length, 1); 178 | }); 179 | }); 180 | 181 | it('adds default config to the config object', function() { 182 | delete context.config.s3.filePattern; 183 | delete context.config.s3.prefix; 184 | delete context.config.s3.cacheControl; 185 | delete context.config.s3.expires; 186 | 187 | assert.isUndefined(context.config.s3.filePattern); 188 | assert.isUndefined(context.config.s3.prefix); 189 | assert.isUndefined(context.config.s3.cacheControl); 190 | assert.isUndefined(context.config.s3.expires); 191 | 192 | var plugin = subject.createDeployPlugin({ 193 | name: 's3' 194 | }); 195 | plugin.beforeHook(context); 196 | plugin.configure(context); 197 | 198 | assert.equal(context.config.s3.filePattern, '**/*.{js,css,png,gif,ico,jpg,webp,map,xml,txt,svg,swf,eot,ttf,woff,woff2,otf,wasm,json}'); 199 | assert.equal(context.config.s3.prefix, ''); 200 | assert.equal(context.config.s3.cacheControl, 'max-age=63072000, public, immutable'); 201 | assert.equal(new Date(context.config.s3.expires).getTime(), new Date('Tue, 01 Jan 2030 00:00:00 GMT').getTime()); 202 | }); 203 | }); 204 | 205 | describe('#upload hook', function() { 206 | it('prints the begin message', function() { 207 | var plugin = subject.createDeployPlugin({ 208 | name: 's3' 209 | }); 210 | 211 | plugin.beforeHook(context); 212 | return assert.isFulfilled(plugin.upload(context)) 213 | .then(function() { 214 | assert.equal(mockUi.messages.length, 2); 215 | assert.match(mockUi.messages[0], /preparing to upload to S3 bucket `cccc`/); 216 | }); 217 | }); 218 | 219 | it('prints success message when files successully uploaded', function() { 220 | var plugin = subject.createDeployPlugin({ 221 | name: 's3' 222 | }); 223 | 224 | plugin.beforeHook(context); 225 | return assert.isFulfilled(plugin.upload(context)) 226 | .then(function() { 227 | assert.equal(mockUi.messages.length, 2); 228 | 229 | var messages = mockUi.messages.reduce(function(previous, current) { 230 | if (/- uploaded 2 files ok/.test(current)) { 231 | previous.push(current); 232 | } 233 | 234 | return previous; 235 | }, []); 236 | 237 | assert.equal(messages.length, 1); 238 | }); 239 | }); 240 | 241 | it('filters out ignored files via fileIgnorePattern', function() { 242 | var plugin = subject.createDeployPlugin({ 243 | name: 's3' 244 | }); 245 | 246 | context.config.s3.fileIgnorePattern = '*.css'; 247 | context.gzippedFiles = ['app.css', 'app.js']; 248 | context.brotliCompressedFiles = ['app.css', 'app.js']; 249 | context.config.s3.fileIgnorePattern = '*.css'; 250 | context.uploadClient.upload = function(options) { 251 | assert.equal(options.filePaths.length, 1, 'file paths are filtered'); 252 | assert.equal(options.gzippedFilePaths.length, 1, 'gzipped file paths are filtered'); 253 | assert.equal(options.brotliCompressedFilePaths.length, 1, 'brotli compressed file paths are filtered'); 254 | return RSVP.resolve(options.filePaths); 255 | }; 256 | plugin.beforeHook(context); 257 | 258 | return assert.isFulfilled(plugin.upload(context)) 259 | .then(function() { 260 | assert.equal(mockUi.messages.length, 2); 261 | 262 | var messages = mockUi.messages.reduce(function(previous, current) { 263 | if (/- uploaded 1 files ok/.test(current)) { 264 | previous.push(current); 265 | } 266 | 267 | return previous; 268 | }, []); 269 | 270 | assert.equal(messages.length, 1); 271 | }); 272 | }); 273 | 274 | it('prints an error message if the upload errors', function() { 275 | var plugin = subject.createDeployPlugin({ 276 | name: 's3' 277 | }); 278 | 279 | context.uploadClient = { 280 | upload: function(/* opts */) { 281 | return RSVP.reject(new Error('something bad went wrong')); 282 | } 283 | }; 284 | 285 | plugin.beforeHook(context); 286 | return assert.isRejected(plugin.upload(context)) 287 | .then(function() { 288 | assert.equal(mockUi.messages.length, 3); 289 | assert.match(mockUi.messages[1], /- Error: something bad went wrong/); 290 | }); 291 | }); 292 | 293 | it('calls proxy agent if a proxy is specified', function() { 294 | var plugin = subject.createDeployPlugin({ 295 | name: 's3' 296 | }); 297 | 298 | var assertionCount = 0; 299 | context.proxyAgent = function(/* proxy */) { 300 | assertionCount++; 301 | }; 302 | 303 | plugin.beforeHook(context); 304 | plugin.configure(context); 305 | 306 | assert.isFulfilled(plugin.upload(context)).then(function(){ 307 | assert.equal(assertionCount, 1); 308 | }); 309 | }); 310 | 311 | it('sets the appropriate header if the file is included in gzippedFiles list', function(done) { 312 | var plugin = subject.createDeployPlugin({ 313 | name: 's3' 314 | }); 315 | 316 | context.gzippedFiles = ['app.css']; 317 | var assertionCount = 0; 318 | context.uploadClient = null; 319 | context.s3Client = { 320 | putObject: function(params, cb) { 321 | if (params.Key === 'app.css') { 322 | assert.equal(params.ContentEncoding, 'gzip'); 323 | assertionCount++; 324 | } else { 325 | assert.isUndefined(params.ContentEncoding); 326 | assertionCount++; 327 | } 328 | cb(); 329 | }, 330 | getObject: function(params, cb){ 331 | cb(new Error("File not found")); 332 | } 333 | }; 334 | 335 | plugin.beforeHook(context); 336 | assert.isFulfilled(plugin.upload(context)).then(function(){ 337 | assert.equal(assertionCount, 2); 338 | done(); 339 | }).catch(function(reason){ 340 | done(reason); 341 | }); 342 | }); 343 | 344 | it('does not add an ACL', function() { 345 | context.config.s3.acl = false 346 | 347 | var plugin = subject.createDeployPlugin({ 348 | name: 's3' 349 | }); 350 | 351 | context.uploadClient.upload = function(options) { 352 | return RSVP.resolve(options); 353 | }; 354 | plugin.beforeHook(context); 355 | return assert.isFulfilled(plugin.upload(context)) 356 | .then(function(options) { 357 | assert.equal(options.acl, undefined, 'acl is not present'); 358 | }); 359 | }); 360 | }); 361 | }); 362 | -------------------------------------------------------------------------------- /tests/lib/s3-test.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | var os = require('os'); 3 | var chai = require('chai'); 4 | var chaiAsPromised = require("chai-as-promised"); 5 | chai.use(chaiAsPromised); 6 | 7 | var assert = chai.assert; 8 | describe('s3', function() { 9 | var S3, mockUi, s3Client, plugin, subject; 10 | 11 | before(function() { 12 | S3 = require('../../lib/s3'); 13 | }); 14 | 15 | beforeEach(function() { 16 | s3Client = { 17 | putObject: function(params, cb) { 18 | cb(); 19 | }, 20 | getObject: function(params, cb) { 21 | cb(new Error("File not found")); 22 | } 23 | }; 24 | mockUi = { 25 | messages: [], 26 | write: function() {}, 27 | writeLine: function(message) { 28 | this.messages.push(message); 29 | } 30 | }; 31 | plugin = { 32 | ui: mockUi, 33 | readConfig: function(propertyName) { 34 | if (propertyName === 's3Client') { 35 | return s3Client; 36 | } 37 | }, 38 | log: function(message/*, opts */) { 39 | this.ui.write('| '); 40 | this.ui.writeLine('- ' + message); 41 | } 42 | }; 43 | subject = new S3({ 44 | plugin: plugin 45 | }); 46 | }); 47 | 48 | describe('#upload', function() { 49 | it('resolves if all uploads succeed', function() { 50 | var options = { 51 | filePaths: ['app.js', 'app.css'], 52 | cwd: process.cwd() + '/tests/fixtures/dist', 53 | prefix: 'js-app' 54 | }; 55 | 56 | var promises = subject.upload(options); 57 | 58 | return assert.isFulfilled(promises) 59 | .then(function() { 60 | assert.equal(mockUi.messages.length, 2); 61 | 62 | var messages = mockUi.messages.reduce(function(previous, current) { 63 | if (/- ✔ {2}js-app\/app\.[js|css]/.test(current)) { 64 | previous.push(current); 65 | } 66 | 67 | return previous; 68 | }, []); 69 | 70 | assert.equal(messages.length, 2); 71 | }); 72 | }); 73 | 74 | it('rejects if an upload fails', function() { 75 | s3Client.putObject = function(params, cb) { 76 | cb('error uploading'); 77 | }; 78 | 79 | var options = { 80 | filePaths: ['app.js', 'app.css'], 81 | cwd: process.cwd() + '/tests/fixtures/dist', 82 | prefix: 'js-app' 83 | }; 84 | 85 | var promises = subject.upload(options); 86 | 87 | return assert.isRejected(promises) 88 | .then(function() { 89 | }); 90 | }); 91 | 92 | describe('sending the object to s3', function() { 93 | it('sends the correct params', function() { 94 | var s3Params; 95 | s3Client.putObject = function(params, cb) { 96 | s3Params = params; 97 | cb(); 98 | }; 99 | 100 | var options = { 101 | filePaths: ['app.css'], 102 | cwd: process.cwd() + '/tests/fixtures/dist', 103 | prefix: 'js-app', 104 | acl: 'public-read', 105 | bucket: 'some-bucket', 106 | cacheControl: 'max-age=1234, public', 107 | expires: '2010' 108 | }; 109 | 110 | var promises = subject.upload(options); 111 | 112 | return assert.isFulfilled(promises) 113 | .then(function() { 114 | assert.equal(s3Params.Bucket, 'some-bucket'); 115 | assert.equal(s3Params.ACL, 'public-read'); 116 | assert.equal(s3Params.Body.toString(), 'body: {}' + os.EOL); 117 | assert.equal(s3Params.ContentType, 'text/css; charset=utf-8'); 118 | assert.equal(s3Params.Key, 'js-app/app.css'); 119 | assert.equal(s3Params.CacheControl, 'max-age=1234, public'); 120 | assert.equal(s3Params.Expires, '2010'); 121 | assert.isUndefined(s3Params.ContentEncoding); 122 | assert.isUndefined(s3Params.ServerSideEncryption); 123 | }); 124 | }); 125 | 126 | it('sets ServerSideEncryption using serverSideEncryption', function() { 127 | var s3Params; 128 | s3Client.putObject = function(params, cb) { 129 | s3Params = params; 130 | cb(); 131 | }; 132 | 133 | var options = { 134 | filePaths: ['app.css'], 135 | cwd: process.cwd() + '/tests/fixtures/dist', 136 | prefix: 'js-app', 137 | acl: 'public-read', 138 | bucket: 'some-bucket', 139 | cacheControl: 'max-age=1234, public', 140 | expires: '2010', 141 | serverSideEncryption: 'AES256' 142 | }; 143 | 144 | var promise = subject.upload(options); 145 | 146 | return assert.isFulfilled(promise) 147 | .then(function() { 148 | assert.equal(s3Params.ServerSideEncryption, 'AES256', 'ServerSideEncryption passed correctly'); 149 | }); 150 | }); 151 | 152 | it('sends the correct content type params for gzipped files with .gz extension', function() { 153 | var s3Params; 154 | s3Client.putObject = function(params, cb) { 155 | s3Params = params; 156 | cb(); 157 | }; 158 | 159 | var options = { 160 | filePaths: ['app.css', 'app.css.gz'], 161 | gzippedFilePaths: ['app.css.gz'], 162 | cwd: process.cwd() + '/tests/fixtures/dist', 163 | prefix: 'js-app', 164 | acl: 'public-read', 165 | bucket: 'some-bucket', 166 | cacheControl: 'max-age=1234, public', 167 | expires: '2010' 168 | }; 169 | 170 | var promises = subject.upload(options); 171 | 172 | return assert.isFulfilled(promises) 173 | .then(function() { 174 | assert.equal(s3Params.ContentType, 'text/css; charset=utf-8'); 175 | assert.equal(s3Params.Key, 'js-app/app.css.gz'); 176 | assert.equal(s3Params.ContentEncoding, 'gzip'); 177 | assert.equal(s3Params.CacheControl, 'max-age=1234, public'); 178 | assert.equal(s3Params.Expires, '2010'); 179 | }); 180 | }); 181 | 182 | it('sends the correct content type params for brotli-compressed files with .br extension', function () { 183 | var s3Params; 184 | s3Client.putObject = function (params, cb) { 185 | s3Params = params; 186 | cb(); 187 | }; 188 | 189 | var options = { 190 | filePaths: ['app.css', 'app.css.br'], 191 | brotliCompressedFilePaths: ['app.css.br'], 192 | cwd: process.cwd() + '/tests/fixtures/dist', 193 | prefix: 'js-app', 194 | acl: 'public-read', 195 | bucket: 'some-bucket', 196 | cacheControl: 'max-age=1234, public', 197 | expires: '2010' 198 | }; 199 | 200 | var promises = subject.upload(options); 201 | 202 | return assert.isFulfilled(promises) 203 | .then(function () { 204 | assert.equal(s3Params.ContentType, 'text/css; charset=utf-8'); 205 | assert.equal(s3Params.Key, 'js-app/app.css.br'); 206 | assert.equal(s3Params.ContentEncoding, 'br'); 207 | assert.equal(s3Params.CacheControl, 'max-age=1234, public'); 208 | assert.equal(s3Params.Expires, '2010'); 209 | }); 210 | }); 211 | 212 | it('sets the content type using defaultMimeType', function() { 213 | var s3Params; 214 | s3Client.putObject = function(params, cb) { 215 | s3Params = params; 216 | cb(); 217 | }; 218 | 219 | var options = { 220 | filePaths: ['index'], 221 | cwd: process.cwd() + '/tests/fixtures/dist', 222 | defaultMimeType: 'text/html' 223 | }; 224 | 225 | var promises = subject.upload(options); 226 | 227 | return assert.isFulfilled(promises) 228 | .then(function() { 229 | assert.equal(s3Params.ContentType, 'text/html; charset=utf-8'); 230 | }); 231 | }); 232 | 233 | it('sets the content type to the default', function() { 234 | var s3Params; 235 | s3Client.putObject = function(params, cb) { 236 | s3Params = params; 237 | cb(); 238 | }; 239 | 240 | var options = { 241 | filePaths: ['index'], 242 | cwd: process.cwd() + '/tests/fixtures/dist' 243 | }; 244 | 245 | var promises = subject.upload(options); 246 | 247 | return assert.isFulfilled(promises) 248 | .then(function() { 249 | assert.equal(s3Params.ContentType, 'application/octet-stream'); 250 | }); 251 | }); 252 | 253 | it('returns a promise with an array of the files uploaded', function() { 254 | s3Client.putObject = function(params, cb) { 255 | cb(); 256 | }; 257 | 258 | var options = { 259 | filePaths: ['app.js', 'app.css'], 260 | cwd: process.cwd() + '/tests/fixtures/dist', 261 | prefix: 'js-app' 262 | }; 263 | 264 | var promises = subject.upload(options); 265 | 266 | return assert.isFulfilled(promises) 267 | .then(function(filesUploaded) { 268 | assert.deepEqual(filesUploaded, ['app.js', 'app.css']); 269 | }); 270 | }); 271 | }); 272 | 273 | describe('with a manifestPath specified', function () { 274 | it('uploads all files when manifest is missing from server', function () { 275 | var options = { 276 | filePaths: ['app.js', 'app.css'], 277 | cwd: process.cwd() + '/tests/fixtures/dist', 278 | prefix: 'js-app', 279 | manifestPath: 'manifest.txt' 280 | }; 281 | 282 | var promise = subject.upload(options); 283 | 284 | return assert.isFulfilled(promise) 285 | .then(function(filesUploaded) { 286 | assert.equal(mockUi.messages.length, 5); 287 | assert.match(mockUi.messages[0], /- Downloading manifest for differential deploy.../); 288 | assert.match(mockUi.messages[1], /- Manifest not found. Disabling differential deploy\./); 289 | assert.match(mockUi.messages[2], /- ✔ {2}js-app\/app\.js/); 290 | assert.match(mockUi.messages[3], /- ✔ {2}js-app\/app\.css/); 291 | assert.match(mockUi.messages[4], /- ✔ {2}js-app\/manifest\.txt/); 292 | assert.deepEqual(filesUploaded, ['app.js', 'app.css', 'manifest.txt']); 293 | }); 294 | }); 295 | 296 | it('only uploads missing files when manifest is present on server', function () { 297 | s3Client.getObject = function(params, cb) { 298 | cb(undefined, { 299 | Body: "app.js" 300 | }); 301 | }; 302 | 303 | var options = { 304 | filePaths: ['app.js', 'app.css'], 305 | cwd: process.cwd() + '/tests/fixtures/dist', 306 | prefix: 'js-app', 307 | manifestPath: 'manifest.txt' 308 | }; 309 | 310 | var promise = subject.upload(options); 311 | 312 | return assert.isFulfilled(promise) 313 | .then(function(filesUploaded) { 314 | assert.equal(mockUi.messages.length, 4); 315 | assert.match(mockUi.messages[0], /- Downloading manifest for differential deploy.../); 316 | assert.match(mockUi.messages[1], /- Manifest found. Differential deploy will be applied\./); 317 | assert.match(mockUi.messages[2], /- ✔ {2}js-app\/app\.css/); 318 | assert.match(mockUi.messages[3], /- ✔ {2}js-app\/manifest\.txt/); 319 | assert.deepEqual(filesUploaded, ['app.css', 'manifest.txt']); 320 | }); 321 | }); 322 | 323 | it('does not upload manifest.txt when one of the files does not succeed uploading', function() { 324 | s3Client.putObject = function(params, cb) { 325 | if (params.Key === 'js-app/app.css') { 326 | cb('error uploading'); 327 | } else { 328 | cb(); 329 | } 330 | }; 331 | 332 | var options = { 333 | filePaths: ['app.js', 'app.css'], 334 | cwd: process.cwd() + '/tests/fixtures/dist', 335 | prefix: 'js-app', 336 | manifestPath: 'manifest.txt' 337 | }; 338 | 339 | var promise = subject.upload(options); 340 | 341 | return assert.isRejected(promise) 342 | .then(function() { 343 | assert.equal(mockUi.messages.length, 3); 344 | assert.match(mockUi.messages[0], /- Downloading manifest for differential deploy.../); 345 | assert.match(mockUi.messages[1], /- Manifest not found. Disabling differential deploy\./); 346 | assert.match(mockUi.messages[2], /- ✔ {2}js-app\/app\.js/); 347 | }); 348 | }); 349 | }); 350 | 351 | describe('with an integer batchSize specified', function () { 352 | it('uploads all files', function () { 353 | var options = { 354 | filePaths: ['app.js', 'app.css'], 355 | cwd: process.cwd() + '/tests/fixtures/dist', 356 | prefix: 'js-app', 357 | batchSize: 10 358 | }; 359 | 360 | var promises = subject.upload(options); 361 | 362 | return assert.isFulfilled(promises) 363 | .then(function() { 364 | assert.equal(mockUi.messages.length, 2); 365 | 366 | var messages = mockUi.messages.reduce(function(previous, current) { 367 | if (/- ✔ {2}js-app\/app\.[js|css]/.test(current)) { 368 | previous.push(current); 369 | } 370 | 371 | return previous; 372 | }, []); 373 | 374 | assert.equal(messages.length, 2); 375 | }); 376 | }); 377 | 378 | it('returns a promise with an array of the files uploaded', function() { 379 | s3Client.putObject = function(params, cb) { 380 | cb(); 381 | }; 382 | 383 | var options = { 384 | filePaths: ['app.js', 'app.css'], 385 | cwd: process.cwd() + '/tests/fixtures/dist', 386 | prefix: 'js-app', 387 | batchSize: 10 388 | }; 389 | 390 | var promises = subject.upload(options); 391 | 392 | return assert.isFulfilled(promises) 393 | .then(function(filesUploaded) { 394 | assert.deepEqual(filesUploaded, ['app.js', 'app.css']); 395 | }); 396 | }); 397 | 398 | it('uploads the correct number of batches', function () { 399 | var requests = 0; 400 | 401 | s3Client.putObject = function (params, cb) { 402 | requests++; 403 | cb(); 404 | }; 405 | 406 | var oldPutObjectsBatch = subject._putObjectsBatch.bind(subject); 407 | var called = false; 408 | 409 | //Spy on _putObjectsBatch, making sure that after it has executed a batch it has created 2 requests 410 | subject._putObjectsBatch = function (paths, options) { 411 | if (!called) { 412 | called = true; 413 | return oldPutObjectsBatch(paths, options); 414 | } 415 | 416 | assert.equal(requests, 2); 417 | 418 | subject._putObjectsBatch = oldPutObjectsBatch; 419 | return subject._putObjectsBatch(paths, options); 420 | }; 421 | 422 | var options = { 423 | filePaths: ['app.js', 'app.css', 'app.css.gz', 'manifest.txt'], 424 | cwd: process.cwd() + '/tests/fixtures/dist', 425 | prefix: 'js-app', 426 | batchSize: 2 427 | }; 428 | 429 | return subject.upload(options) 430 | .then(function () { 431 | assert.equal(requests, 4); 432 | }); 433 | }); 434 | 435 | it('rejects if an upload fails', function () { 436 | s3Client.putObject = function(params, cb) { 437 | cb('error uploading'); 438 | }; 439 | 440 | var options = { 441 | filePaths: ['app.js', 'app.css'], 442 | cwd: process.cwd() + '/tests/fixtures/dist', 443 | prefix: 'js-app', 444 | batchSize: 10 445 | }; 446 | 447 | var promises = subject.upload(options); 448 | 449 | return assert.isRejected(promises); 450 | }); 451 | }); 452 | }); 453 | }); 454 | -------------------------------------------------------------------------------- /tests/runner.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | 'use strict'; 3 | 4 | var glob = require('glob'); 5 | var Mocha = require('mocha'); 6 | 7 | var mocha = new Mocha({ 8 | reporter: 'spec' 9 | }); 10 | 11 | var arg = process.argv[2]; 12 | var root = 'tests/'; 13 | 14 | function addFiles(mocha, files) { 15 | glob.sync(root + files).forEach(mocha.addFile.bind(mocha)); 16 | } 17 | 18 | addFiles(mocha, '/**/*-test.js'); 19 | 20 | if (arg === 'all') { 21 | addFiles(mocha, '/**/*-test-slow.js'); 22 | } 23 | 24 | mocha.run(function(failures) { 25 | process.on('exit', function() { 26 | process.exit(failures); 27 | }); 28 | }); 29 | --------------------------------------------------------------------------------