├── .editorconfig ├── .eslintrc.js ├── .github ├── PULL_REQUEST_TEMPLATE └── workflows │ └── ci.yaml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── index.js ├── lib └── redis.js ├── package.json ├── tests ├── .eslintrc.js ├── helpers │ ├── assert.js │ ├── fake-redis-client.js │ └── fake-redis-lib.js ├── index.html ├── runner.js └── unit │ ├── .gitkeep │ ├── index-test.js │ └── lib │ └── redis-test.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 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 10, 5 | sourceType: 'module' 6 | }, 7 | extends: 'eslint:recommended', 8 | env: { 9 | node: 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.yaml: -------------------------------------------------------------------------------- 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@v2 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@v2 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 http://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 | tests/ 3 | tmp/ 4 | 5 | .bowerrc 6 | .editorconfig 7 | .ember-cli 8 | .travis.yml 9 | .npmignore 10 | **/.gitkeep 11 | bower.json 12 | Brocfile.js 13 | testem.json 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version 9 of Highlight.js has reached EOL and is no longer supported. 2 | Please upgrade or ask whatever dependency you are using to upgrade. 3 | https://github.com/highlightjs/highlight.js/issues/2877 4 | 5 | Version 9 of Highlight.js has reached EOL and is no longer supported. 6 | Please upgrade or ask whatever dependency you are using to upgrade. 7 | https://github.com/highlightjs/highlight.js/issues/2877 8 | 9 | ## v4.0.0 (2023-06-04) 10 | 11 | #### :boom: Breaking Change 12 | * [#140](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/140) Update dependencies and node versions ([@lukemelia](https://github.com/lukemelia)) 13 | 14 | #### Committers: 2 15 | - Jon Johnson ([@jrjohnson](https://github.com/jrjohnson)) 16 | - Luke Melia ([@lukemelia](https://github.com/lukemelia)) 17 | 18 | ## v3.0.0 (2021-06-08) 19 | 20 | #### :boom: Breaking Change 21 | * [#119](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/119) Update node version to 12 or higher ([@lukemelia](https://github.com/lukemelia)) 22 | 23 | #### :bug: Bug Fix 24 | * [#113](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/113) Allow rediss:// style urls for TLS support ([@wongpeiyi](https://github.com/wongpeiyi)) 25 | 26 | #### :house: Internal 27 | * [#116](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/116) Update dependencies ([@lukemelia](https://github.com/lukemelia)) 28 | 29 | #### Committers: 2 30 | - Luke Melia ([@lukemelia](https://github.com/lukemelia)) 31 | - Wong Pei Yi ([@wongpeiyi](https://github.com/wongpeiyi)) 32 | 33 | ### Changelog 34 | 35 | All notable changes to this project will be documented in this file. Dates are displayed in UTC. 36 | 37 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 38 | 39 | ### [v2.0.0](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v1.0.3...v2.0.0) 40 | 41 | > 16 May 2020 42 | 43 | - Configure release-it changelog [`#99`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/99) 44 | - Bump handlebars from 4.0.6 to 4.7.6 [`#92`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/92) 45 | - Bump js-yaml from 3.8.2 to 3.13.1 [`#97`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/97) 46 | - Bump mixin-deep from 1.3.1 to 1.3.2 [`#91`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/91) 47 | - Bump lodash from 4.17.10 to 4.17.15 [`#94`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/94) 48 | - Update ioredis and ioredismock [`#98`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/98) 49 | - Drop unused dependencies: `redis` and `github` [`#96`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/96) 50 | - Update to more modern Javascript syntax [`#95`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/95) 51 | - Update some dependencies, and switch to release-it [`#93`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/93) 52 | - bump ember-cli to latest 2.x, this allows use with node 8 [`#87`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/87) 53 | - Upgrade ember-cli to 3.18 [`8b7ee89`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/8b7ee89eada201e8473747018fa04ad4eb386d8a) 54 | - Update tests to use async/await [`e153fd3`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/e153fd3ef138693ed3a3ed43cf5cbb9aab9d5582) 55 | - Update implementation to use async/await [`5942721`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/594272103001b9cbab66b050d4b4c53b77f96f65) 56 | 57 | #### [v1.0.3](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v1.0.2...v1.0.3) 58 | 59 | > 1 September 2018 60 | 61 | - Replace then-redis with ioredis [`#86`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/86) 62 | - Better Error Message for Bad Redis URLs [`#83`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/83) 63 | - Fix links in README [`#81`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/81) 64 | - Replace then-redis with ioredis. [`#51`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/issues/51) [`#74`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/issues/74) 65 | - Better Error Message for Bad Redis URLs [`#82`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/issues/82) 66 | - Released v1.0.3 [`1436c0b`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/1436c0b38fec44f4b8483d647facec506ad31c07) 67 | - Test against Node 8 and 10 [`efc86ce`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/efc86ce1b23632b2dc70e4f90fdd1cf4ffca588e) 68 | 69 | #### [v1.0.2](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v1.0.1...v1.0.2) 70 | 71 | > 4 May 2017 72 | 73 | - Revert "replaces then-redis with redis. closes #74" [`#78`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/78) 74 | - Merge pull request #78 from ember-cli-deploy/revert-76 [`#74`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/issues/74) 75 | - Revert "replaces then-redis with redis. closes #74" [`#74`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/issues/74) 76 | - Released v1.0.2 [`d1fd35c`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/d1fd35c515bb0785351b367015bb79d859f1ac22) 77 | 78 | #### [v1.0.1](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v1.0.0...v1.0.1) 79 | 80 | > 4 May 2017 81 | 82 | - replaces then-redis with redis. closes #74 [`#76`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/76) 83 | - Merge pull request #76 from maprules1000/master [`#74`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/issues/74) 84 | - replaces then-redis with redis. closes #74 [`#74`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/issues/74) 85 | - Released v1.0.1 [`8da894f`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/8da894f0cf39584cfc25fdefa36f9ea7e61ad9a1) 86 | 87 | #### [v1.0.0](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v1.0.0-beta.2...v1.0.0) 88 | 89 | > 6 April 2017 90 | 91 | - [DOC] Update CHANGELOG [`b27275e`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/b27275e57227457ea03f82398243aa6d97b23eab) 92 | - Released v1.0.0 [`f0afde8`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/f0afde8fdc179501208ef72e55fd4ab06234af8d) 93 | 94 | #### [v1.0.0-beta.2](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v1.0.0-beta.1...v1.0.0-beta.2) 95 | 96 | > 29 March 2017 97 | 98 | - release with travis [`#73`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/73) 99 | - travis release prototype [`1e601cc`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/1e601cc01766da650896addc1506b1245f6a797f) 100 | - Released v1.0.0-beta.2 [`32287b8`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/32287b899f02f2c5a56b7175259abbed2279aba0) 101 | 102 | #### [v1.0.0-beta.1](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v1.0.0-beta.0...v1.0.0-beta.1) 103 | 104 | > 25 March 2017 105 | 106 | - Eliminate usage triggering ember-cli deprecation [`#72`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/72) 107 | - Released v1.0.0-beta.1 [`44c51d8`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/44c51d86f4df6c04a3dae40deca8957760c869e6) 108 | 109 | #### [v1.0.0-beta.0](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v0.4.2...v1.0.0-beta.0) 110 | 111 | > 25 March 2017 112 | 113 | - Upgrade ember-cli & embrace being a node-only ember-cli addon [`#71`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/71) 114 | - [DOC] updated readme with ssh-tunnel Redis port details [`#70`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/70) 115 | - [DOC] Update revisionKey default documentation [`#68`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/68) 116 | - Released v1.0.0-beta.0 [`bbeee2a`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/bbeee2a8ac43e4d631ae11965683eb2f5a1a80fe) 117 | 118 | #### [v0.4.2](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v0.4.1...v0.4.2) 119 | 120 | > 21 November 2016 121 | 122 | - Update dependencies [`#66`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/66) 123 | - Fix readConfig bug [`#65`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/65) 124 | - previousRevisionKey returns an unresolved promise [`#63`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/63) 125 | - Released v0.4.2 [`c1500a5`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/c1500a5a20ab31b79c1afd914108303a83b01720) 126 | 127 | #### [v0.4.1](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v0.4.0...v0.4.1) 128 | 129 | > 26 July 2016 130 | 131 | - Add additional dependencies to install instructions [`#57`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/57) 132 | - Add 'Listing Revisions' section to README [`0624736`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/06247363aff6a72e706370e2eec101af30b190a8) 133 | - Use 'activationSuffix' in fetchRevision hooks #61 (@zzarcon) [`6f23ca4`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/6f23ca4dd7bf5d5ceb33a4ec28ac031d7c31b0a3) 134 | - Released v0.4.1 [`78cefbb`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/78cefbb026ff1781353872513c2406b0f5079521) 135 | 136 | #### [v0.4.0](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v0.3.0...v0.4.0) 137 | 138 | > 12 May 2016 139 | 140 | - Strip Heroku-style usernames from config url [`#58`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/58) 141 | - Released v0.4.0 [`06a8341`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/06a8341725e6f71e93d09697fa7fa4690c144b48) 142 | 143 | #### [v0.3.0](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v0.2.0...v0.3.0) 144 | 145 | > 1 April 2016 146 | 147 | - add support for caching revision data [`#56`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/56) 148 | - Released v0.3.0 [`7eb606b`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/7eb606bc45741f41fd1118279245fa88063393de) 149 | - ghedamat is terrible, sorry [`6f693e6`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/6f693e61e8383e0971ad6a7f7988c71fbf637b3f) 150 | - add revisionData to settings [`59fa599`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/59fa599083fc28bb1b3e43a53c56ef4369fa5b3a) 151 | 152 | #### [v0.2.0](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v0.1.1...v0.2.0) 153 | 154 | > 6 February 2016 155 | 156 | - add fetchInitialRevisions [`#50`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/50) 157 | - update ember-cli-deploy-plugin to 0.2.1 [`#49`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/49) 158 | - [DOC] update changelog [`ffc62eb`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/ffc62eba8b7fd0311c59d914078939281dbde72c) 159 | - Released v0.2.0 [`1e7bf35`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/1e7bf35b39c31096e10004fc4f698adeb3104bc1) 160 | - update ember-cli-deploy-plugin [`75f9774`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/75f9774973bae257e8c5283e68ec8fea5311aa56) 161 | 162 | #### [v0.1.1](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v0.1.0...v0.1.1) 163 | 164 | > 10 December 2015 165 | 166 | - Add functionality to copy revision on activation. [`#43`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/43) 167 | - Add maxRecentUploads configuration option [`#46`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/46) 168 | - use ssh-tunnel port if present [`#38`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/38) 169 | - set previousRevisionKey before activating a new revision [`#47`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/47) 170 | - Fix Readme [`#44`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/44) 171 | - Feature/activation suffix [`#41`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/41) 172 | - Add functionality to copy revision on activation. This enables nginx to do the lookup as redis support is very limited. [`bf3ecac`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/bf3ecac8fbf56c83e65ba1759280aba3ac5b2f1b) 173 | - Update tests for activationSuffix [`db29dda`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/db29ddae2b1afd7adf3f146fe4963164ec1a4ee1) 174 | - Use activationSuffix from config instead of :current [`822c42b`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/822c42bba6e374fbb5c85af449012df20e530378) 175 | 176 | #### [v0.1.0](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/0.1.0-beta.4...v0.1.0) 177 | 178 | > 25 October 2015 179 | 180 | - Update to use new verbose option for logging [`#39`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/39) 181 | - Release v0.1.0. [`f156dcc`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/f156dcc3e0635455e00e82200ddd9dc186dedad9) 182 | 183 | #### [0.1.0-beta.4](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v0.1.0-beta.3...0.1.0-beta.4) 184 | 185 | > 16 September 2015 186 | 187 | - store revision list in `keyPrefix:revisions` [`#32`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/32) 188 | - Minor Readme Updates [`#31`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/31) 189 | - Update repo url [`#30`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/30) 190 | - Update repository [`#29`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/29) 191 | - store revision list in `keyPrefix:revisions` [`#28`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/issues/28) 192 | - Adds `ssh-tunnel`/Elasticache details to README.md [`83de031`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/83de03168d980aa87df1013babe5c433f75f7ed0) 193 | - Released 0.1.0-beta.4 [`166164c`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/166164c0611ba680a0ccdc3cfbe9ed96fd602cfe) 194 | - Shifts section ordering [`b508822`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/b508822020d1980b0c6e41b341aab8a62749d447) 195 | 196 | #### [v0.1.0-beta.3](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v0.1.0-beta.2...v0.1.0-beta.3) 197 | 198 | > 13 September 2015 199 | 200 | - Release 0.1.0-beta.3 (beta.2 was a botched npm release) [`a1d3e25`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/a1d3e2570b75d60254dd1d2b7f40e0cd9980d13b) 201 | 202 | #### [v0.1.0-beta.2](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/compare/v0.1.0-beta.1...v0.1.0-beta.2) 203 | 204 | > 13 September 2015 205 | 206 | - Reference revisionKey from new revisionData object [`#27`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/27) 207 | - use a sorted set to store the list of revisions [`#24`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/24) 208 | - add fetchRevisions to lib/redis and deployPlugin [`#23`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/23) 209 | - Specify allowOverwrite option in README [`#20`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/20) 210 | - improved trim of old revisions [`eee05fe`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/eee05fead1cfa126443b4727f3fdf7b7bee1cea9) 211 | - dry stubbing of fake-redis-client [`61bba36`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/61bba36ead72b3746c8cef66f0a4bee3a660236e) 212 | - fix tests after rebase [`be6d96b`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/be6d96bb34fac50bf53dea83844a4a57f51dfd0c) 213 | 214 | #### v0.1.0-beta.1 215 | 216 | > 8 August 2015 217 | 218 | - Fixed typo [`#22`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/22) 219 | - Very small typo [`#21`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/21) 220 | - Read config from configured name path instead of static 'redis' key [`#18`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/18) 221 | - Pass through database number [`#19`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/19) 222 | - Update ember-cli-deploy-plugin version for bugfix. [`#16`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/16) 223 | - Update for ember-cli-deploy CLI changes [`#17`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/17) 224 | - Update README for v0.5.0 [`#14`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/14) 225 | - Make filePattern config relative to distDir [`#15`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/15) 226 | - Fixed a bug in activate hook logging [`#13`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/13) 227 | - Make filePattern relative to context.distDir [`#12`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/12) 228 | - Plugin base class restructure [`#11`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/11) 229 | - Added didDeploy hook [`#9`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/9) 230 | - [WIP] Implement `activate` hook [`#8`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/8) 231 | - Use new `configure` hook instead of `willDeploy` [`#6`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/6) 232 | - Allow keyPrefix to be configurable. Defaults to "[project name]:index" [`#5`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/5) 233 | - Update description and repo in package.json [`#4`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/4) 234 | - Allow configuration of redis using a url property instead of host/port/etc [`#3`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/3) 235 | - Now we're using user defined config over context [`#2`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/2) 236 | - Deploy to Redis [`#1`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/1) 237 | - Restructure to use ember-cli-deploy-plugin [`ee9af93`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/ee9af930971efc908798415129dee5238750f63f) 238 | - Initial Commit from Ember CLI v0.2.3 [`d098d75`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/d098d75fd414a1912cb641611231874b8a5f5ce3) 239 | - Now we can upload `index` to Redis. [`ae3e8f9`](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/commit/ae3e8f9546b8d08845583cad1c5797fc25d88eb5) 240 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 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-redis 2 | 3 | > An ember-cli-deploy plugin to upload index.html to a Redis store 4 | 5 | [![](https://ember-cli-deploy.github.io/ember-cli-deploy-version-badges/plugins/ember-cli-deploy-redis.svg)](http://ember-cli-deploy.github.io/ember-cli-deploy-version-badges/) 6 | 7 | This plugin uploads a file, presumably index.html, to a specified Redis store. 8 | 9 | More often than not this plugin will be used in conjunction with the [lightning method of deployment][1] where the ember application assets will be served from S3 and the index.html file will be served from Redis. However, it can be used to upload any file to a Redis store. 10 | 11 | ## What is an ember-cli-deploy plugin? 12 | 13 | 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. 14 | 15 | For more information on what plugins are and how they work, please refer to the [Plugin Documentation][2]. 16 | 17 | ## Quick Start 18 | To get up and running quickly, do the following: 19 | 20 | - Ensure [ember-cli-deploy-build][4] is installed and configured. 21 | 22 | - Install this plugin 23 | 24 | ```bash 25 | $ ember install ember-cli-deploy-redis 26 | ``` 27 | 28 | - Place the following configuration into `config/deploy.js` 29 | 30 | ```javascript 31 | ENV.redis = { 32 | host: '', 33 | port: , 34 | password: '' 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-redis 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][2]. 54 | 55 | - `configure` 56 | - `upload` 57 | - `willActivate` 58 | - `activate` 59 | - `didDeploy` 60 | 61 | ## Configuration Options 62 | 63 | For detailed information on how configuration of plugins works, please refer to the [Plugin Documentation][2]. 64 | 65 | ### host 66 | 67 | The Redis host. If [url](#url) is defined, then this option is not needed. 68 | 69 | *Default:* `'localhost'` 70 | 71 | ### port 72 | 73 | The Redis port. If [url](#url) is defined, then this option is not needed. 74 | 75 | *Default:* `6379` 76 | 77 | This option must not be overwritten if you're using [ember-cli-deploy-ssh-tunnel][7], where the default is `context.tunnel.srcPort`. 78 | 79 | ### database 80 | 81 | The Redis database number. If [url](#url) is defined, then this option is not needed. 82 | 83 | *Default:* `undefined` 84 | 85 | ### password 86 | 87 | The Redis password. If [url](#url) is defined, then this option is not needed. 88 | 89 | *Default:* `null` 90 | 91 | ### url 92 | 93 | A Redis connection url to the Redis store 94 | 95 | *Example:* 'redis://some-user:some-password@some-host.com:1234' 96 | 97 | ### redisOptions 98 | 99 | Options to be passed to the redis client. 100 | 101 | *Example:* { tls: { rejectUnauthorized: false } } 102 | ### filePattern 103 | 104 | A file matching this pattern will be uploaded to Redis. 105 | 106 | *Default:* `'index.html'` 107 | 108 | ### distDir 109 | 110 | The root directory where the file matching `filePattern` will be searched for. By default, this option will use the `distDir` property of the deployment context. 111 | 112 | *Default:* `context.distDir` 113 | 114 | ### keyPrefix 115 | 116 | The prefix to be used for the Redis key under which file will be uploaded to Redis. The Redis key will be a combination of the `keyPrefix` and the `revisionKey`. By default this option will use the `project.name()` property from the deployment context. 117 | 118 | *Default:* `context.project.name() + ':index'` 119 | 120 | ### activationSuffix 121 | 122 | The suffix to be used for the Redis key under which the activated revision will be stored in Redis. By default this option will be `"current"`. This makes the default activated revision key in Redis looks like: `project.name() + ':index:current'` 123 | 124 | *Default:* `current` 125 | 126 | ### revisionKey 127 | 128 | The unique revision number for the version of the file being uploaded to Redis. The Redis key will be a combination of the `keyPrefix` and the `revisionKey`. By default this option will use either the `revision` passed in from the command line or the `revisionData.revisionKey` property from the deployment context. 129 | 130 | *Default:* `context.commandOptions.revision || context.revisionData.revisionKey` 131 | 132 | ### activeContentSuffix 133 | 134 | The suffix to be used for the Redis key under which the activated revision content will be stored in Redis. By default this option will be `"current-content"`. This makes the default activated revision in Redis looks like: `project.name() + ':index:current-content'` 135 | 136 | This makes it possible to serve content completely from within NGINX using the [redis](https://www.nginx.com/resources/wiki/modules/redis/) module without doing a primary key lookup. 137 | 138 | ``` 139 | server { 140 | location / { 141 | set $redis_key project-name:index:current-content; 142 | redis_pass name:6379; 143 | default_type text/html; 144 | } 145 | } 146 | ``` 147 | 148 | *Default:* `current-content` 149 | 150 | ### allowOverwrite 151 | 152 | A flag to specify whether the revision should be overwritten if it already exists in Redis. 153 | 154 | *Default:* `false` 155 | 156 | ### redisDeployClient 157 | 158 | The Redis client to be used to upload files to the Redis store. By default this option will use a new instance of the [Redis][3] client. This allows for injection of a mock client for testing purposes. 159 | 160 | *Default:* `return new Redis(options)` 161 | 162 | ### didDeployMessage 163 | 164 | A message that will be displayed after the file has been successfully uploaded to Redis. By default this message will only display if the revision for `revisionData.revisionKey` of the deployment context has been activated. 165 | 166 | *Default:* 167 | 168 | ```javascript 169 | if (context.revisionData.revisionKey && !context.revisionData.activatedRevisionKey) { 170 | return "Deployed but did not activate revision " + context.revisionData.revisionKey + ". " 171 | + "To activate, run: " 172 | + "ember deploy:activate " + context.revisionData.revisionKey + " --environment=" + context.deployEnvironment + "\n"; 173 | } 174 | ``` 175 | 176 | ### maxRecentUploads 177 | 178 | The maximum number of recent revisions to keep in Redis. 179 | 180 | *Default:* `10` 181 | 182 | ### revisionData 183 | 184 | Metadata about the revision being uploaded. (normally provided by a plugin like [ember-cli-deploy-revision-data][6]) 185 | 186 | ## Activation 187 | 188 | As well as uploading a file to Redis, *ember-cli-deploy-redis* has the ability to mark a revision of a deployed file as `current`. This is most commonly used in the [lightning method of deployment][1] whereby an index.html file is pushed to Redis and then served to the user by a web server. The web server could be configured to return any existing revision of the index.html file as requested by a query parameter. However, the revision marked as the currently `active` revision would be returned if no query paramter is present. For more detailed information on this method of deployment please refer to the [ember-cli-deploy-lightning-pack README][1]. 189 | 190 | ### How do I activate a revision? 191 | 192 | A user can activate a revision by either: 193 | 194 | - Passing a command line argument to the `deploy` command: 195 | 196 | ```bash 197 | $ ember deploy --activate=true 198 | ``` 199 | 200 | - Running the `deploy:activate` command: 201 | 202 | ```bash 203 | $ ember deploy:activate 204 | ``` 205 | 206 | - Setting the `activateOnDeploy` flag in `deploy.js` 207 | 208 | ```javascript 209 | ENV.pipeline = { 210 | activateOnDeploy: true 211 | } 212 | ``` 213 | 214 | ### What does activation do? 215 | 216 | When *ember-cli-deploy-redis* uploads a file to Redis, it uploads it under the key defined by a combination of the two config properties `keyPrefix` and `revisionKey`. 217 | 218 | So, if the `keyPrefix` was configured to be `my-app:index` and there had been 3 revisons deployed, then Redis might look something like this: 219 | 220 | ```bash 221 | $ redis-cli 222 | 223 | 127.0.0.1:6379> KEYS * 224 | 1) my-app:index:revisions 225 | 2) my-app:index:9ab2021411f0cbc5ebd5ef8ddcd85cef 226 | 3) my-app:index:499f5ac793551296aaf7f1ec74b2ca79 227 | 4) my-app:index:f769d3afb67bd20ccdb083549048c86c 228 | ``` 229 | 230 | Activating a revison would add a new entry to Redis pointing to the currently active revision: 231 | 232 | ```bash 233 | $ ember deploy:activate f769d3afb67bd20ccdb083549048c86c 234 | 235 | $ redis-cli 236 | 237 | 127.0.0.1:6379> KEYS * 238 | 1) my-app:index:revisions 239 | 2) my-app:index:9ab2021411f0cbc5ebd5ef8ddcd85cef 240 | 3) my-app:index:499f5ac793551296aaf7f1ec74b2ca79 241 | 4) my-app:index:f769d3afb67bd20ccdb083549048c86c 242 | 5) my-app:index:current 243 | 244 | 127.0.0.1:6379> GET my-app:index:current 245 | "f769d3afb67bd20ccdb083549048c86c" 246 | ``` 247 | 248 | ### When does activation occur? 249 | 250 | Activation occurs during the `activate` hook of the pipeline. By default, activation is turned off and must be explicitly enabled by one of the 3 methods above. 251 | 252 | ## Listing Revisions 253 | 254 | Another helpful part of the [lightning method of deployment][1] is using [ember-cli-deploy-display-revisions][8] to quickly review previously deployed revisions to your redis instance. 255 | 256 | ### How do I display the deployed revisions on my redis instance? 257 | 258 | First, install the [ember-cli-deploy-display-revisions][8] plugin: 259 | 260 | ``` 261 | ember install ember-cli-deploy-display-revisions 262 | ``` 263 | 264 | Then use the following command: 265 | 266 | ``` 267 | $ ember deploy:list 268 | 269 | - Listing revisions for key: `my-app` 270 | RevisionKey Commit User Branch 271 | > 8af596f af596fbb email@example.com staging 272 | 18cf1a6 8cf1a6c9 email@example.com staging 273 | 82be0d2 2be0d26c email@example.com staging 274 | 7dee0a0 dee0a0b3 email@example.com staging 275 | 937899e 37899eb6 email@example.com staging 276 | f4cfc1f 4cfc1f0b email@example.com staging 277 | d748d1b 748d1bc4 email@example.com staging 278 | c6d9fb1 6d9fb155 email@example.com staging 279 | 128a967 28a96772 email@example.com staging 280 | bfb5e46 fb5e46dc email@example.com staging 281 | ``` 282 | 283 | ## What if my Redis server isn't publicly accessible? 284 | 285 | Not to worry! Just install the handy-dandy `ember-cli-deploy-ssh-tunnel` plugin: 286 | 287 | ``` 288 | ember install ember-cli-deploy-ssh-tunnel 289 | ``` 290 | 291 | Add set up your `deploy.js` similar to the following: 292 | 293 | ```js 294 | 'redis': { 295 | host: "localhost", 296 | }, 297 | 'ssh-tunnel': { 298 | username: "your-ssh-username", 299 | host: "remote-redis-host" 300 | } 301 | ``` 302 | 303 | ### What if my Redis server is only accessible *from* my remote server? 304 | 305 | Sometimes you need to SSH into a server (a "bastion" server) and then run 306 | `redis-cli` or similar from there. This is really common if you're using 307 | Elasticache on AWS, for instance. We've got you covered there too - just 308 | set your SSH tunnel host to the bastion server, and tell the tunnel to use 309 | your Redis host as the destination host, like so: 310 | 311 | ```js 312 | 'redis': { 313 | host: "localhost", 314 | }, 315 | 'ssh-tunnel': { 316 | username: "your-ssh-username", 317 | host: "remote-redis-host" 318 | dstHost: "location-of-your-elasticache-node-or-remote-redis" 319 | } 320 | ``` 321 | 322 | ## Prerequisites 323 | 324 | The following properties are expected to be present on the deployment `context` object: 325 | 326 | - `distDir` (provided by [ember-cli-deploy-build][4]) 327 | - `project.name()` (provided by [ember-cli-deploy][5]) 328 | - `revisionData.revisionKey` (provided by [ember-cli-deploy-revision-data][6]) 329 | - `commandLineArgs.revisionKey` (provided by [ember-cli-deploy][5]) 330 | - `deployEnvironment` (provided by [ember-cli-deploy][5]) 331 | 332 | The following properties are used if present on the deployment `context` object: 333 | 334 | - `tunnel.srcPort` (provided by [ember-cli-deploy-ssh-tunnel][7]) 335 | 336 | ## Running Tests 337 | 338 | * yarn test 339 | 340 | ## Why `ember build` and `ember test` don't work 341 | 342 | 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. 343 | 344 | [1]: https://github.com/ember-cli-deploy/ember-cli-deploy-lightning-pack "ember-cli-deploy-lightning-pack" 345 | [2]: http://ember-cli-deploy.com/docs/v1.0.x/using-plugins/ "Plugin Documentation" 346 | [3]: https://www.npmjs.com/package/redis "Redis Client" 347 | [4]: https://github.com/ember-cli-deploy/ember-cli-deploy-build "ember-cli-deploy-build" 348 | [5]: https://github.com/ember-cli-deploy/ember-cli-deploy "ember-cli-deploy" 349 | [6]: https://github.com/ember-cli-deploy/ember-cli-deploy-revision-data "ember-cli-deploy-revision-data" 350 | [7]: https://github.com/ember-cli-deploy/ember-cli-deploy-ssh-tunnel "ember-cli-deploy-ssh-tunnel" 351 | [8]: https://github.com/ember-cli-deploy/ember-cli-deploy-display-revisions "ember-cli-deploy-display-revisions" 352 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release 2 | 3 | This project uses release-it to release new versions. 4 | 5 | ## Steps 6 | 7 | ### Commit the changelog, bump version, create a release on Github, and publish to NPM 8 | 9 | 1. run `yarn release [patch|minor|major]` 10 | 2. review the release created on Github and edit if needed 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | let path = require('path'); 5 | let fs = require('fs'); 6 | 7 | let DeployPluginBase = require('ember-cli-deploy-plugin'); 8 | 9 | module.exports = { 10 | name: 'ember-cli-deploy-redis', 11 | 12 | createDeployPlugin(options) { 13 | var Redis = require('./lib/redis'); 14 | 15 | var DeployPlugin = DeployPluginBase.extend({ 16 | name: options.name, 17 | defaultConfig: { 18 | host: 'localhost', 19 | port(context) { 20 | if (context.tunnel && context.tunnel.srcPort) { 21 | return context.tunnel.srcPort; 22 | } else { 23 | return 6379; 24 | } 25 | }, 26 | filePattern: 'index.html', 27 | maxRecentUploads: 10, 28 | distDir(context) { 29 | return context.distDir; 30 | }, 31 | keyPrefix(context){ 32 | return `${context.project.name()}:index`; 33 | }, 34 | activationSuffix: 'current', 35 | activeContentSuffix: 'current-content', 36 | didDeployMessage(context){ 37 | var revisionKey = context.revisionData && context.revisionData.revisionKey; 38 | var activatedRevisionKey = context.revisionData && context.revisionData.activatedRevisionKey; 39 | if (revisionKey && !activatedRevisionKey) { 40 | return `Deployed but did not activate revision ${revisionKey}. ` 41 | + `To activate, run: ` 42 | + `ember deploy:activate ${context.deployTarget} --revision=${revisionKey}` + "\n"; 43 | } 44 | }, 45 | revisionKey(context) { 46 | return context.commandOptions.revision || (context.revisionData && context.revisionData.revisionKey); 47 | }, 48 | redisDeployClient(context, pluginHelper) { 49 | let redisLib = context._redisLib; 50 | let libOptions = { 51 | url: pluginHelper.readConfig('url'), 52 | host: pluginHelper.readConfig('host'), 53 | port: pluginHelper.readConfig('port'), 54 | password: pluginHelper.readConfig('password'), 55 | database: pluginHelper.readConfig('database'), 56 | redisOptions: pluginHelper.readConfig('redisOptions'), 57 | maxRecentUploads: pluginHelper.readConfig('maxRecentUploads'), 58 | allowOverwrite: pluginHelper.readConfig('allowOverwrite'), 59 | activationSuffix: pluginHelper.readConfig('activationSuffix') 60 | }; 61 | 62 | return new Redis(libOptions, redisLib); 63 | }, 64 | 65 | revisionData(context) { 66 | return context.revisionData; 67 | } 68 | }, 69 | configure(/* context */) { 70 | this.log('validating config', { verbose: true }); 71 | 72 | if (!this.pluginConfig.url) { 73 | ['host', 'port'].forEach(this.applyDefaultConfigProperty.bind(this)); 74 | } else { 75 | var redisUrlRegexp = new RegExp('^rediss?://'); 76 | 77 | if (!this.pluginConfig.url.match(redisUrlRegexp)) { 78 | throw new Error(`Your Redis URL appears to be missing the "redis://" protocol. Update your URL to: redis://${this.pluginConfig.url}`); 79 | } 80 | } 81 | 82 | ['filePattern', 'distDir', 'keyPrefix', 'activationSuffix', 'activeContentSuffix', 'revisionKey', 'didDeployMessage', 'redisDeployClient', 'maxRecentUploads', 'revisionData'].forEach(this.applyDefaultConfigProperty.bind(this)); 83 | 84 | this.log('config ok', { verbose: true }); 85 | }, 86 | 87 | async upload(/* context */) { 88 | let redisDeployClient = this.readConfig('redisDeployClient'); 89 | let revisionKey = this.readConfig('revisionKey'); 90 | let distDir = this.readConfig('distDir'); 91 | let filePattern = this.readConfig('filePattern'); 92 | let keyPrefix = this.readConfig('keyPrefix'); 93 | let filePath = path.join(distDir, filePattern); 94 | 95 | this.log(`Uploading \`${filePath}\``, { verbose: true }); 96 | try { 97 | let fileContents = await this._readFileContents(filePath); 98 | let key = await redisDeployClient.upload(keyPrefix, revisionKey, this.readConfig('revisionData'), fileContents); 99 | this._logUploadSuccessMessage(key); 100 | return { redisKey: key }; 101 | } catch(e) { 102 | this._logErrorMessage(e); 103 | throw e; 104 | } 105 | }, 106 | 107 | async willActivate(/* context */) { 108 | let redisDeployClient = this.readConfig('redisDeployClient'); 109 | let keyPrefix = this.readConfig('keyPrefix'); 110 | 111 | let previousRevisionKey = await redisDeployClient.activeRevision(keyPrefix); 112 | return { 113 | revisionData: { 114 | previousRevisionKey 115 | } 116 | }; 117 | }, 118 | 119 | async activate(/* context */) { 120 | let redisDeployClient = this.readConfig('redisDeployClient'); 121 | let revisionKey = this.readConfig('revisionKey'); 122 | let keyPrefix = this.readConfig('keyPrefix'); 123 | let activationSuffix = this.readConfig('activationSuffix'); 124 | let activeContentSuffix = this.readConfig('activeContentSuffix'); 125 | 126 | this.log(`Activating revision \`${revisionKey}\``, { verbose: true }); 127 | try { 128 | await redisDeployClient.activate(keyPrefix, revisionKey, activationSuffix, activeContentSuffix); 129 | this.log(`✔ Activated revision \`${revisionKey}\``, {}); 130 | return { 131 | revisionData: { 132 | activatedRevisionKey: revisionKey 133 | } 134 | }; 135 | } catch(e) { 136 | this._logErrorMessage(e); 137 | throw e; 138 | } 139 | }, 140 | 141 | didDeploy(/* context */){ 142 | var didDeployMessage = this.readConfig('didDeployMessage'); 143 | if (didDeployMessage) { 144 | this.log(didDeployMessage); 145 | } 146 | }, 147 | 148 | async fetchInitialRevisions(/* context */) { 149 | let redisDeployClient = this.readConfig('redisDeployClient'); 150 | let keyPrefix = this.readConfig('keyPrefix'); 151 | 152 | this.log(`Fetching initial revisions for key: \`${keyPrefix}\``, { verbose: true }); 153 | try { 154 | let initialRevisions = await redisDeployClient.fetchRevisions(keyPrefix); 155 | return { 156 | initialRevisions 157 | }; 158 | } catch(e) { 159 | this._logErrorMessage(e); 160 | throw e; 161 | } 162 | }, 163 | 164 | async fetchRevisions(/* context */) { 165 | let redisDeployClient = this.readConfig('redisDeployClient'); 166 | let keyPrefix = this.readConfig('keyPrefix'); 167 | this.log(`Fetching revisions for key: \`${keyPrefix}\``, { verbose: true }); 168 | try { 169 | let revisions = await redisDeployClient.fetchRevisions(keyPrefix); 170 | return { 171 | revisions 172 | }; 173 | } catch(e) { 174 | this._logErrorMessage(e); 175 | throw e; 176 | } 177 | }, 178 | 179 | async _readFileContents(path) { 180 | let buffer = await fs.promises.readFile(path); 181 | return buffer.toString(); 182 | }, 183 | 184 | _logUploadSuccessMessage(key) { 185 | this.log(`Uploaded with key \`${key}\``, { verbose: true }); 186 | }, 187 | 188 | _logErrorMessage(error) { 189 | this.log(error, { color: 'red' }); 190 | } 191 | }); 192 | 193 | return new DeployPlugin(); 194 | } 195 | }; 196 | -------------------------------------------------------------------------------- /lib/redis.js: -------------------------------------------------------------------------------- 1 | var CoreObject = require('core-object'); 2 | var RSVP = require('rsvp'); 3 | 4 | module.exports = CoreObject.extend({ 5 | 6 | init(options, lib) { 7 | this._super(); 8 | let libOptions = {}; 9 | let RedisLib = lib; 10 | if (options.url) { 11 | libOptions = { url: this._stripUsernameFromConfigUrl(options.url) }; 12 | } else { 13 | libOptions = { 14 | host: options.host, 15 | port: options.port 16 | }; 17 | 18 | if (options.password) { 19 | libOptions.password = options.password; 20 | } 21 | 22 | if (options.database) { 23 | libOptions.db = options.database; 24 | } 25 | } 26 | if (options.redisOptions) { 27 | libOptions = Object.assign(libOptions, options.redisOptions); 28 | } 29 | 30 | if (!RedisLib) { 31 | RedisLib = require('ioredis'); 32 | } 33 | 34 | if (libOptions.url) { 35 | let url = libOptions.url; 36 | delete libOptions.url; 37 | this._client = new RedisLib(url, libOptions); 38 | } else { 39 | this._client = new RedisLib(libOptions); 40 | } 41 | this._maxRecentUploads = options.maxRecentUploads || 10; 42 | this._allowOverwrite = options.allowOverwrite || false; 43 | this._activationSuffix = options.activationSuffix || 'current'; 44 | }, 45 | 46 | async upload(/*keyPrefix, revisionKey, value*/) { 47 | let args = Array.prototype.slice.call(arguments); 48 | 49 | let keyPrefix = args.shift(); 50 | let value = args.pop(); 51 | let revisionKey = args[0] || 'default'; 52 | let revisionData = args[1]; 53 | let redisKey = `${keyPrefix}:${revisionKey}`; 54 | let maxEntries = this._maxRecentUploads; 55 | 56 | await this._uploadIfKeyDoesNotExist(redisKey, value); 57 | if (revisionData) { 58 | await this._uploadRevisionData(keyPrefix, revisionKey, revisionData); 59 | } 60 | await this._updateRecentUploadsList(keyPrefix, revisionKey); 61 | await this._trimRecentUploadsList(keyPrefix, maxEntries); 62 | return redisKey; 63 | }, 64 | 65 | async activate(keyPrefix, revisionKey, activationSuffix, activeContentSuffix) { 66 | let revisions = await this._listRevisions(keyPrefix); 67 | this._validateRevisionKey(revisionKey, revisions); 68 | await this._activateRevision(keyPrefix, revisionKey, activationSuffix, activeContentSuffix); 69 | }, 70 | 71 | async fetchRevisions(keyPrefix) { 72 | let revisions = await this._listRevisions(keyPrefix); 73 | let current = await this.activeRevision(keyPrefix); 74 | let revisionData = await this._revisionData(keyPrefix, revisions); 75 | return revisions.map(function(revision, i) { 76 | let hash = { 77 | revision: revision, 78 | active: revision === current, 79 | }; 80 | if (revisionData) { 81 | hash.revisionData = revisionData[i]; 82 | } 83 | return hash; 84 | }); 85 | }, 86 | 87 | activeRevision(keyPrefix) { 88 | let currentKey = keyPrefix + ':' + this._activationSuffix; 89 | return this._client.get(currentKey); 90 | }, 91 | 92 | async _revisionData(keyPrefix, revisions) { 93 | if (revisions.length === 0) { 94 | return; 95 | } 96 | let dataKeys = revisions.map((rev) => `${keyPrefix}:revision-data:${rev}`); 97 | 98 | let data = await this._client.mget(dataKeys); 99 | if (!data) { 100 | return; 101 | } 102 | return data.map((d) => JSON.parse(d)); 103 | }, 104 | 105 | _listRevisions(keyPrefix) { 106 | let client = this._client; 107 | let listKey = `${keyPrefix}:revisions`; 108 | return client.zrevrange(listKey, 0, -1); 109 | }, 110 | 111 | _validateRevisionKey(revisionKey, revisions) { 112 | if (revisions.indexOf(revisionKey) === -1) { 113 | throw new Error(`\`${revisionKey}\` is not a valid revision key`); 114 | } 115 | return; 116 | }, 117 | 118 | _activateRevisionKey(keyPrefix, revisionKey, activationSuffix) { 119 | let currentKey = `${keyPrefix}:${activationSuffix}`; 120 | return this._client.set(currentKey, revisionKey); 121 | }, 122 | 123 | _activateRevision(keyPrefix, revisionKey, activationSuffix, activeContentSuffix) { 124 | if (activeContentSuffix) { 125 | return this._copyRevisionAndActivateRevisionKey(keyPrefix, revisionKey, activationSuffix, activeContentSuffix); 126 | } 127 | 128 | return this._activateRevisionKey(keyPrefix, revisionKey, activationSuffix); 129 | }, 130 | 131 | async _copyRevisionAndActivateRevisionKey(keyPrefix, revisionKey, activationSuffix, activeContentSuffix) { 132 | let client = this._client; 133 | let activeContentKey = `${keyPrefix}:${activeContentSuffix}`; 134 | let revisionContentKey = `${keyPrefix}:${revisionKey}`; 135 | 136 | let value = await client.get(revisionContentKey); 137 | await client.set(activeContentKey, value); 138 | await this._activateRevisionKey(keyPrefix, revisionKey, activationSuffix); 139 | }, 140 | 141 | async _uploadIfKeyDoesNotExist(redisKey, value) { 142 | let client = this._client; 143 | let allowOverwrite = !!this._allowOverwrite; 144 | 145 | let existingValue = await client.get(redisKey); 146 | if (existingValue && !allowOverwrite) { 147 | throw new Error(`Value already exists for key: ${redisKey}`); 148 | } 149 | return client.set(redisKey, value); 150 | }, 151 | 152 | async _uploadRevisionData(keyPrefix, revisionKey, revisionData) { 153 | let client = this._client; 154 | let redisKey = `${keyPrefix}:revision-data:${revisionKey}`; 155 | await client.set(redisKey, JSON.stringify(revisionData)); 156 | }, 157 | 158 | async _updateRecentUploadsList(keyPrefix, revisionKey) { 159 | let client = this._client; 160 | let score = new Date().getTime(); 161 | let listKey = `${keyPrefix}:revisions`; 162 | await client.zadd(listKey, score, revisionKey); 163 | }, 164 | 165 | async _trimRecentUploadsList(keyPrefix, maxEntries) { 166 | let client = this._client; 167 | let listKey = `${keyPrefix}:revisions`; 168 | 169 | let revisionCount = await client.zcard(listKey); 170 | let revisionsToBeRemoved; 171 | if (revisionCount > maxEntries) { 172 | revisionsToBeRemoved = await client.zrange(listKey, 0, revisionCount - maxEntries - 1); 173 | } 174 | if (!revisionsToBeRemoved) { 175 | return; 176 | } 177 | let current = await this.activeRevision(keyPrefix); 178 | let promises = []; 179 | revisionsToBeRemoved.forEach(function(revision) { 180 | if (revision !== current) { 181 | promises.push(client.del(`${keyPrefix}:${revision}`)); 182 | promises.push(client.del(`${keyPrefix}:revision-data:${revision}`)); 183 | promises.push(client.zrem(listKey, revision)); 184 | } 185 | }); 186 | await RSVP.all(promises); 187 | }, 188 | 189 | _stripUsernameFromConfigUrl(configUrl) { 190 | var regex = /redis:\/\/(\w+):(\w+)(.*)/; 191 | var matches = configUrl.match(regex); 192 | 193 | if (matches) { 194 | configUrl = 'redis://:' + matches[2] + matches[3]; 195 | } 196 | 197 | return configUrl; 198 | } 199 | }); 200 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-deploy-redis", 3 | "version": "4.0.0", 4 | "description": "Ember CLI Deploy plugin to deploy file(s) into redis.", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "release": "release-it", 11 | "test": "node tests/runner.js && ./node_modules/.bin/eslint index.js lib/* tests/**/*.js" 12 | }, 13 | "repository": "https://github.com/ember-cli-deploy/ember-cli-deploy-redis", 14 | "engines": { 15 | "node": "14.* || 16.* || 18.* || >= 20" 16 | }, 17 | "author": "Aaron Chambers and the ember-cli-deploy team", 18 | "license": "MIT", 19 | "dependencies": { 20 | "chalk": "^4.1.1", 21 | "core-object": "^3.1.5", 22 | "ember-cli-deploy-plugin": "^0.2.9", 23 | "ioredis": "^4.28.5", 24 | "rsvp": "^4.8.5" 25 | }, 26 | "devDependencies": { 27 | "chai": "^4.3.7", 28 | "chai-as-promised": "^7.1.1", 29 | "ember-cli": "^3.28.6", 30 | "eslint": "^8.42.0", 31 | "glob": "^10.2.6", 32 | "ioredis-mock": "^5.9.1", 33 | "lerna-changelog": "^1.0.1", 34 | "mocha": "^8.4.0", 35 | "release-it": "14.8.0", 36 | "release-it-lerna-changelog": "^3.1.0", 37 | "sinon": "^11.1.2" 38 | }, 39 | "keywords": [ 40 | "ember-addon", 41 | "ember-cli-deploy-plugin" 42 | ], 43 | "ember-addon": { 44 | "configPath": "tests/dummy/config" 45 | }, 46 | "publishConfig": { 47 | "registry": "https://registry.npmjs.org/" 48 | }, 49 | "release-it": { 50 | "git": { 51 | "tagName": "v${version}", 52 | "commitMessage": "v${version}", 53 | "pushArgs": "--follow-tags --no-verify" 54 | }, 55 | "npm": { 56 | "publish": true 57 | }, 58 | "github": { 59 | "release": true 60 | }, 61 | "plugins": { 62 | "release-it-lerna-changelog": { 63 | "infile": "CHANGELOG.md" 64 | } 65 | } 66 | }, 67 | "volta": { 68 | "node": "14.18.1", 69 | "yarn": "1.22.17" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | "describe": true, 4 | "beforeEach": true, 5 | "it": true 6 | }, 7 | env: { 8 | mocha: true 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /tests/helpers/assert.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var chaiAsPromised = require("chai-as-promised"); 3 | chai.use(chaiAsPromised); 4 | 5 | module.exports = chai.assert; 6 | -------------------------------------------------------------------------------- /tests/helpers/fake-redis-client.js: -------------------------------------------------------------------------------- 1 | var RSVP = require('rsvp'); 2 | var CoreObject = require('core-object'); 3 | 4 | module.exports = CoreObject.extend({ 5 | init: function (options) { 6 | this._super(); 7 | this.recentRevisions = []; 8 | this.options = options; 9 | }, 10 | get: function(/* key */) { 11 | return RSVP.resolve('some-other-value'); 12 | }, 13 | set: function() { }, 14 | del: function() { }, 15 | zadd: function(key, score , tag) { 16 | var prefix = key.replace(':revisions',''); 17 | this.recentRevisions.push(prefix + ':' + tag); 18 | }, 19 | zrem: function(key, revision) { 20 | var i = this.recentRevisions.indexOf(revision); 21 | this.recentRevisions.splice(i,1); 22 | }, 23 | zrange: function() { 24 | }, 25 | zrevrange: function() { 26 | return RSVP.resolve(this.recentRevisions); 27 | }, 28 | mget: function() { 29 | return RSVP.resolve(); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /tests/helpers/fake-redis-lib.js: -------------------------------------------------------------------------------- 1 | var FakeClient = require('./fake-redis-client'); 2 | 3 | var CoreObject = require('core-object'); 4 | 5 | module.exports = CoreObject.extend({ 6 | init: function(clientClass) { 7 | this._super(); 8 | this.clientClass = clientClass || FakeClient; 9 | }, 10 | 11 | createClient: function(options) { 12 | this.options = options; 13 | this.createdClient = new this.clientClass(options); 14 | return this.createdClient; 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for 'head-footer'}} 18 | {{content-for 'test-head-footer'}} 19 | 20 | 21 | 22 | {{content-for 'body'}} 23 | {{content-for 'test-body'}} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for 'body-footer'}} 31 | {{content-for 'test-body-footer'}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ember-cli-deploy/ember-cli-deploy-redis/d596a8b307200b40c58aad9e5309d21046ca5ceb/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/index-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var RSVP = require("rsvp"); 4 | var assert = require("../helpers/assert"); 5 | var IoRedis = require("ioredis"); 6 | var sandbox = require("sinon").createSandbox(); 7 | 8 | var stubProject = { 9 | name() { 10 | return "my-project"; 11 | } 12 | }; 13 | 14 | describe("redis plugin", function() { 15 | var subject, mockUi; 16 | 17 | beforeEach(function() { 18 | subject = require("../../index"); 19 | mockUi = { 20 | verbose: true, 21 | messages: [], 22 | write() {}, 23 | writeLine(message) { 24 | this.messages.push(message); 25 | } 26 | }; 27 | }); 28 | 29 | afterEach(function() { 30 | sandbox.restore(); 31 | }); 32 | 33 | it("has a name", function() { 34 | var result = subject.createDeployPlugin({ 35 | name: "test-plugin" 36 | }); 37 | 38 | assert.equal(result.name, "test-plugin"); 39 | }); 40 | 41 | it("implements the correct hooks", function() { 42 | var plugin = subject.createDeployPlugin({ 43 | name: "test-plugin" 44 | }); 45 | assert.ok(plugin.configure); 46 | assert.ok(plugin.upload); 47 | assert.ok(plugin.activate); 48 | assert.ok(plugin.didDeploy); 49 | }); 50 | 51 | describe("configure hook", function() { 52 | it("runs without error if config is ok", function() { 53 | var plugin = subject.createDeployPlugin({ 54 | name: "redis" 55 | }); 56 | 57 | var context = { 58 | ui: mockUi, 59 | project: stubProject, 60 | config: { 61 | redis: { 62 | host: "somehost", 63 | port: 1234, 64 | database: 4 65 | } 66 | } 67 | }; 68 | plugin.beforeHook(context); 69 | plugin.configure(context); 70 | assert.ok(true); // didn't throw an error 71 | }); 72 | 73 | it("passes through config options", function() { 74 | var plugin = subject.createDeployPlugin({ 75 | name: "redis" 76 | }); 77 | 78 | var redisLibStub = sandbox.stub(IoRedis, "constructor"); 79 | 80 | var context = { 81 | ui: mockUi, 82 | project: stubProject, 83 | config: { 84 | redis: { 85 | host: "somehost", 86 | port: 1234, 87 | database: 4, 88 | redisOptions: { tls: { rejectUnauthorized: false }} 89 | } 90 | }, 91 | _redisLib: redisLibStub 92 | }; 93 | plugin.beforeHook(context); 94 | plugin.configure(context); 95 | plugin.readConfig("redisDeployClient"); 96 | 97 | assert.deepEqual( 98 | redisLibStub.lastCall.args, 99 | [ 100 | { 101 | host: "somehost", 102 | port: 1234, 103 | db: 4, 104 | tls: { rejectUnauthorized: false } 105 | } 106 | ] 107 | ); 108 | }); 109 | 110 | describe("handles redis urls appropriately", function() { 111 | it("handles pre-stripped urls without a username", function() { 112 | var plugin = subject.createDeployPlugin({ 113 | name: "redis" 114 | }); 115 | 116 | var redisLibStub = sandbox.stub(IoRedis, "constructor"); 117 | 118 | var context = { 119 | ui: mockUi, 120 | project: stubProject, 121 | config: { 122 | redis: { 123 | url: "redis://:password@host.amazonaws.com:6379/4" 124 | } 125 | }, 126 | _redisLib: redisLibStub 127 | }; 128 | plugin.beforeHook(context); 129 | plugin.configure(context); 130 | plugin.readConfig("redisDeployClient"); 131 | 132 | assert.deepEqual( 133 | redisLibStub.lastCall.args, 134 | ["redis://:password@host.amazonaws.com:6379/4", {}], 135 | ); 136 | }); 137 | 138 | it("strips Redis username from a Heroku url to work with our upstream redis library", function() { 139 | var plugin = subject.createDeployPlugin({ 140 | name: "redis" 141 | }); 142 | 143 | var redisLibStub = sandbox.stub(IoRedis, "constructor"); 144 | 145 | var context = { 146 | ui: mockUi, 147 | project: stubProject, 148 | config: { 149 | redis: { 150 | url: "redis://username:password@host.amazonaws.com:6379/4" 151 | } 152 | }, 153 | _redisLib: redisLibStub 154 | }; 155 | plugin.beforeHook(context); 156 | plugin.configure(context); 157 | plugin.readConfig("redisDeployClient"); 158 | 159 | assert.isTrue( 160 | redisLibStub.calledWith("redis://:password@host.amazonaws.com:6379/4") 161 | ); 162 | }); 163 | 164 | it('throws if the Redis URL is missing the "redis://" protocol', function() { 165 | var plugin = subject.createDeployPlugin({ 166 | name: "redis" 167 | }); 168 | 169 | var redisLibStub = sandbox.stub(IoRedis, "constructor"); 170 | 171 | var context = { 172 | ui: mockUi, 173 | project: stubProject, 174 | config: { 175 | redis: { 176 | url: "host.amazonaws.com:6379/4" 177 | } 178 | }, 179 | _redisLib: redisLibStub 180 | }; 181 | 182 | plugin.beforeHook(context); 183 | 184 | assert.throws(function() { 185 | plugin.configure(context); 186 | }, 'Your Redis URL appears to be missing the "redis://" protocol. Update your URL to: redis://host.amazonaws.com:6379/4'); 187 | }); 188 | }); 189 | 190 | describe("resolving port from the pipeline", function() { 191 | it("uses the config data if it already exists", function() { 192 | var plugin = subject.createDeployPlugin({ 193 | name: "redis" 194 | }); 195 | 196 | var config = { 197 | host: "somehost", 198 | port: 1234 199 | }; 200 | var context = { 201 | ui: mockUi, 202 | project: stubProject, 203 | config: { 204 | redis: config 205 | }, 206 | tunnel: { 207 | srcPort: "2345" 208 | } 209 | }; 210 | 211 | plugin.beforeHook(context); 212 | plugin.configure(context); 213 | assert.equal(plugin.readConfig("port"), "1234"); 214 | }); 215 | 216 | it("uses the context value if it exists and config doesn't", function() { 217 | var plugin = subject.createDeployPlugin({ 218 | name: "redis" 219 | }); 220 | 221 | var config = { 222 | host: "somehost" 223 | }; 224 | var context = { 225 | ui: mockUi, 226 | project: stubProject, 227 | config: { 228 | redis: config 229 | }, 230 | tunnel: { 231 | srcPort: "2345" 232 | } 233 | }; 234 | 235 | plugin.beforeHook(context); 236 | plugin.configure(context); 237 | assert.equal(plugin.readConfig("port"), "2345"); 238 | }); 239 | 240 | it("uses the default port if config and context don't exist", function() { 241 | var plugin = subject.createDeployPlugin({ 242 | name: "redis" 243 | }); 244 | 245 | var config = { 246 | host: "somehost" 247 | }; 248 | var context = { 249 | ui: mockUi, 250 | project: stubProject, 251 | config: { 252 | redis: config 253 | } 254 | }; 255 | 256 | plugin.beforeHook(context); 257 | plugin.configure(context); 258 | assert.equal(plugin.readConfig("port"), "6379"); 259 | }); 260 | }); 261 | 262 | describe("resolving revisionKey from the pipeline", function() { 263 | it("uses the config data if it already exists", function() { 264 | var plugin = subject.createDeployPlugin({ 265 | name: "redis" 266 | }); 267 | 268 | var config = { 269 | host: "somehost", 270 | port: 1234, 271 | revisionKey: "12345" 272 | }; 273 | var context = { 274 | ui: mockUi, 275 | project: stubProject, 276 | config: { 277 | redis: config 278 | }, 279 | revisionData: { 280 | revisionKey: "something-else" 281 | } 282 | }; 283 | 284 | plugin.beforeHook(context); 285 | plugin.configure(context); 286 | assert.equal(plugin.readConfig("revisionKey"), "12345"); 287 | }); 288 | 289 | it("uses the commandOptions value if it exists", function() { 290 | var plugin = subject.createDeployPlugin({ 291 | name: "redis" 292 | }); 293 | 294 | var config = { 295 | host: "somehost", 296 | port: 1234 297 | }; 298 | var context = { 299 | ui: mockUi, 300 | project: stubProject, 301 | config: { 302 | redis: config 303 | }, 304 | commandOptions: { 305 | revision: "abcd" 306 | }, 307 | revisionData: { 308 | revisionKey: "something-else" 309 | } 310 | }; 311 | 312 | plugin.beforeHook(context); 313 | plugin.configure(context); 314 | assert.typeOf(config.revisionKey, "function"); 315 | assert.equal(config.revisionKey(context), "abcd"); 316 | }); 317 | 318 | it("uses the context value if it exists and commandOptions doesn't", function() { 319 | var plugin = subject.createDeployPlugin({ 320 | name: "redis" 321 | }); 322 | 323 | var config = { 324 | host: "somehost", 325 | port: 1234 326 | }; 327 | var context = { 328 | ui: mockUi, 329 | project: stubProject, 330 | config: { 331 | redis: config 332 | }, 333 | commandOptions: {}, 334 | revisionData: { 335 | revisionKey: "something-else" 336 | } 337 | }; 338 | 339 | plugin.beforeHook(context); 340 | plugin.configure(context); 341 | assert.typeOf(config.revisionKey, "function"); 342 | assert.equal(config.revisionKey(context), "something-else"); 343 | }); 344 | }); 345 | describe("without providing config", function() { 346 | var config, plugin, context; 347 | beforeEach(function() { 348 | config = {}; 349 | plugin = subject.createDeployPlugin({ 350 | name: "redis" 351 | }); 352 | context = { 353 | ui: mockUi, 354 | project: stubProject, 355 | config: config 356 | }; 357 | plugin.beforeHook(context); 358 | }); 359 | it("warns about missing optional config", function() { 360 | plugin.configure(context); 361 | var messages = mockUi.messages.reduce(function(previous, current) { 362 | if (/- Missing config:\s.*, using default:\s/.test(current)) { 363 | previous.push(current); 364 | } 365 | 366 | return previous; 367 | }, []); 368 | assert.equal(messages.length, 12); 369 | }); 370 | it("adds default config to the config object", function() { 371 | plugin.configure(context); 372 | assert.isDefined(config.redis.host); 373 | assert.isDefined(config.redis.port); 374 | assert.isDefined(config.redis.keyPrefix); 375 | assert.isDefined(config.redis.activationSuffix); 376 | assert.isDefined(config.redis.didDeployMessage); 377 | }); 378 | }); 379 | 380 | describe("with a keyPrefix provided", function() { 381 | var config, plugin, context; 382 | beforeEach(function() { 383 | config = { 384 | redis: { 385 | keyPrefix: "proj:home" 386 | } 387 | }; 388 | plugin = subject.createDeployPlugin({ 389 | name: "redis" 390 | }); 391 | context = { 392 | ui: mockUi, 393 | project: stubProject, 394 | config: config 395 | }; 396 | plugin.beforeHook(context); 397 | }); 398 | it("warns about missing optional filePattern, distDir, activationSuffix, revisionKey, didDeployMessage, maxNumberOfRecentUploads, and connection info", function() { 399 | plugin.configure(context); 400 | var messages = mockUi.messages.reduce(function(previous, current) { 401 | if (/- Missing config:\s.*, using default:\s/.test(current)) { 402 | previous.push(current); 403 | } 404 | 405 | return previous; 406 | }, []); 407 | assert.equal(messages.length, 11); 408 | }); 409 | it("does not add default config to the config object", function() { 410 | plugin.configure(context); 411 | assert.isDefined(config.redis.host); 412 | assert.isDefined(config.redis.port); 413 | assert.isDefined(config.redis.filePattern); 414 | assert.isDefined(config.redis.activationSuffix); 415 | assert.isDefined(config.redis.didDeployMessage); 416 | assert.equal(config.redis.keyPrefix, "proj:home"); 417 | }); 418 | }); 419 | 420 | describe("with an activationSuffix provided", function() { 421 | var config, plugin, context; 422 | beforeEach(function() { 423 | config = { 424 | redis: { 425 | activationSuffix: "special:suffix" 426 | } 427 | }; 428 | plugin = subject.createDeployPlugin({ 429 | name: "redis" 430 | }); 431 | context = { 432 | ui: mockUi, 433 | project: stubProject, 434 | config: config 435 | }; 436 | plugin.beforeHook(context); 437 | }); 438 | it("warns about missing optional filePattern, distDir, keyPrefix, revisionKey, didDeployMessage, maxNumberOfRecentUploads, and connection info", function() { 439 | plugin.configure(context); 440 | var messages = mockUi.messages.reduce(function(previous, current) { 441 | if (/- Missing config:\s.*, using default:\s/.test(current)) { 442 | previous.push(current); 443 | } 444 | 445 | return previous; 446 | }, []); 447 | assert.equal(messages.length, 11); 448 | }); 449 | it("does not add default config to the config object", function() { 450 | plugin.configure(context); 451 | assert.isDefined(config.redis.host); 452 | assert.isDefined(config.redis.port); 453 | assert.isDefined(config.redis.filePattern); 454 | assert.isDefined(config.redis.keyPrefix); 455 | assert.isDefined(config.redis.didDeployMessage); 456 | assert.equal(config.redis.activationSuffix, "special:suffix"); 457 | }); 458 | }); 459 | 460 | describe("with a url provided", function() { 461 | var config, plugin, context; 462 | beforeEach(function() { 463 | config = { 464 | redis: { 465 | url: "redis://localhost:6379" 466 | } 467 | }; 468 | plugin = subject.createDeployPlugin({ 469 | name: "redis" 470 | }); 471 | context = { 472 | ui: mockUi, 473 | project: stubProject, 474 | config: config 475 | }; 476 | plugin.beforeHook(context); 477 | }); 478 | it("warns about missing optional filePattern, distDir, keyPrefix, activationSuffix, revisionKey, maxNumberOfRecentUploads, and didDeployMessage only", function() { 479 | plugin.configure(context); 480 | var messages = mockUi.messages.reduce(function(previous, current) { 481 | if (/- Missing config:\s.*, using default:\s/.test(current)) { 482 | previous.push(current); 483 | } 484 | 485 | return previous; 486 | }, []); 487 | assert.equal(messages.length, 10); 488 | }); 489 | 490 | it("does not add default config to the config object", function() { 491 | plugin.configure(context); 492 | assert.isUndefined(config.redis.host); 493 | assert.isUndefined(config.redis.port); 494 | assert.isDefined(config.redis.filePattern); 495 | assert.isDefined(config.redis.didDeployMessage); 496 | }); 497 | }); 498 | 499 | describe("with aliases", function() { 500 | it("passes config for specified alias to redis", function() { 501 | var plugin = subject.createDeployPlugin({ 502 | name: "foobar" 503 | }); 504 | 505 | var redisLibStub = sandbox.stub(IoRedis, "constructor"); 506 | 507 | var config = { 508 | database: 7 509 | }; 510 | var context = { 511 | ui: mockUi, 512 | project: stubProject, 513 | config: { 514 | foobar: config 515 | }, 516 | _redisLib: redisLibStub 517 | }; 518 | 519 | plugin.beforeHook(context); 520 | plugin.configure(context); 521 | plugin.readConfig("redisDeployClient"); 522 | 523 | assert.isTrue(redisLibStub.calledWithMatch({ db: 7 })); 524 | }); 525 | }); 526 | }); 527 | 528 | describe("upload hook", function() { 529 | var plugin; 530 | var context; 531 | 532 | it("uploads the index", async function() { 533 | plugin = subject.createDeployPlugin({ 534 | name: "redis" 535 | }); 536 | 537 | context = { 538 | ui: mockUi, 539 | project: stubProject, 540 | config: { 541 | redis: { 542 | keyPrefix: "test-prefix", 543 | filePattern: "index.html", 544 | distDir: "tests", 545 | revisionKey: "123abc", 546 | redisDeployClient(/* context */) { 547 | return { 548 | upload(keyPrefix, revisionKey) { 549 | return RSVP.resolve(keyPrefix + ":" + revisionKey); 550 | } 551 | }; 552 | } 553 | } 554 | } 555 | }; 556 | plugin.beforeHook(context); 557 | plugin.configure(context); 558 | 559 | let result = await assert.isFulfilled(plugin.upload(context)); 560 | assert.deepEqual(result, { redisKey: "test-prefix:123abc" }); 561 | }); 562 | }); 563 | 564 | describe("activate hook", function() { 565 | it("activates revision", async function() { 566 | var activateCalled = false; 567 | 568 | var plugin = subject.createDeployPlugin({ 569 | name: "redis" 570 | }); 571 | 572 | var context = { 573 | ui: mockUi, 574 | project: stubProject, 575 | config: { 576 | redis: { 577 | keyPrefix: "test-prefix", 578 | filePattern: "index.html", 579 | distDir: "tests", 580 | revisionKey: "123abc", 581 | redisDeployClient(/* context */) { 582 | return { 583 | activate() { 584 | activateCalled = true; 585 | } 586 | }; 587 | } 588 | } 589 | } 590 | }; 591 | plugin.beforeHook(context); 592 | 593 | let result = await assert.isFulfilled(plugin.activate(context)); 594 | assert.ok(activateCalled); 595 | assert.equal(result.revisionData.activatedRevisionKey, "123abc"); 596 | }); 597 | 598 | it("rejects if an error is thrown when activating", async function() { 599 | var plugin = subject.createDeployPlugin({ 600 | name: "redis" 601 | }); 602 | 603 | var context = { 604 | ui: mockUi, 605 | project: stubProject, 606 | config: { 607 | redis: { 608 | keyPrefix: "test-prefix", 609 | filePattern: "index.html", 610 | distDir: "tests", 611 | revisionKey: "123abc", 612 | redisDeployClient(/* context */) { 613 | return { 614 | activate() { 615 | return RSVP.reject("some-error"); 616 | } 617 | }; 618 | } 619 | } 620 | } 621 | }; 622 | 623 | plugin.beforeHook(context); 624 | let error = await assert.isRejected(plugin.activate(context)); 625 | assert.equal(error, "some-error"); 626 | }); 627 | }); 628 | describe("didDeploy hook", function() { 629 | it("prints default message about lack of activation when revision has not been activated", function() { 630 | var messageOutput = ""; 631 | 632 | var plugin = subject.createDeployPlugin({ 633 | name: "redis" 634 | }); 635 | plugin.upload = function() {}; 636 | plugin.activate = function() {}; 637 | 638 | var context = { 639 | deployTarget: "qa", 640 | ui: { 641 | write(message) { 642 | messageOutput = messageOutput + message; 643 | }, 644 | writeLine(message) { 645 | messageOutput = messageOutput + message + "\n"; 646 | } 647 | }, 648 | project: stubProject, 649 | config: { 650 | redis: {} 651 | }, 652 | revisionData: { 653 | revisionKey: "123abc" 654 | } 655 | }; 656 | plugin.beforeHook(context); 657 | plugin.configure(context); 658 | plugin.beforeHook(context); 659 | plugin.didDeploy(context); 660 | assert.match( 661 | messageOutput, 662 | /Deployed but did not activate revision 123abc./ 663 | ); 664 | assert.match(messageOutput, /To activate, run/); 665 | assert.match(messageOutput, /ember deploy:activate qa --revision=123abc/); 666 | }); 667 | }); 668 | 669 | describe("fetchInitialRevisions hook", function() { 670 | it("fills the initialRevisions variable on context", async function() { 671 | var plugin; 672 | var context; 673 | 674 | plugin = subject.createDeployPlugin({ 675 | name: "redis" 676 | }); 677 | 678 | context = { 679 | ui: mockUi, 680 | project: stubProject, 681 | config: { 682 | redis: { 683 | keyPrefix: "test-prefix", 684 | filePattern: "index.html", 685 | distDir: "tests", 686 | revisionKey: "123abc", 687 | redisDeployClient(/* context */) { 688 | return { 689 | fetchRevisions(/* keyPrefix, revisionKey */) { 690 | return RSVP.resolve([ 691 | { 692 | revision: "a", 693 | active: false 694 | } 695 | ]); 696 | } 697 | }; 698 | } 699 | } 700 | } 701 | }; 702 | plugin.beforeHook(context); 703 | plugin.configure(context); 704 | 705 | let result = await assert.isFulfilled(plugin.fetchInitialRevisions(context)); 706 | assert.deepEqual(result, { 707 | initialRevisions: [ 708 | { 709 | active: false, 710 | revision: "a" 711 | } 712 | ] 713 | }); 714 | }); 715 | }); 716 | 717 | describe("fetchRevisions hook", function() { 718 | it("fills the revisions variable on context", async function() { 719 | var plugin; 720 | var context; 721 | 722 | plugin = subject.createDeployPlugin({ 723 | name: "redis" 724 | }); 725 | 726 | context = { 727 | ui: mockUi, 728 | project: stubProject, 729 | config: { 730 | redis: { 731 | keyPrefix: "test-prefix", 732 | filePattern: "index.html", 733 | distDir: "tests", 734 | revisionKey: "123abc", 735 | redisDeployClient(/* context */) { 736 | return { 737 | fetchRevisions(/* keyPrefix, revisionKey */) { 738 | return RSVP.resolve([ 739 | { 740 | revision: "a", 741 | active: false 742 | } 743 | ]); 744 | } 745 | }; 746 | } 747 | } 748 | } 749 | }; 750 | plugin.beforeHook(context); 751 | plugin.configure(context); 752 | 753 | let result = await assert.isFulfilled(plugin.fetchRevisions(context)); 754 | assert.deepEqual(result, { 755 | revisions: [ 756 | { 757 | active: false, 758 | revision: "a" 759 | } 760 | ] 761 | }); 762 | }); 763 | }); 764 | }); 765 | -------------------------------------------------------------------------------- /tests/unit/lib/redis-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var IoredisMock = require('ioredis-mock'); 4 | 5 | var RSVP = require('rsvp'); 6 | var assert = require('../../helpers/assert'); 7 | var sandbox = require('sinon').createSandbox(); 8 | 9 | describe('redis', function () { 10 | var Redis; 11 | 12 | before(function () { 13 | Redis = require('../../../lib/redis'); 14 | }); 15 | 16 | afterEach(function () { 17 | sandbox.restore(); 18 | }); 19 | 20 | describe('#upload', function () { 21 | it('rejects if the key already exists in redis', async function () { 22 | var redis = new Redis({}, IoredisMock); 23 | 24 | await redis.upload('key', 'value'); 25 | let promise = redis.upload('key', 'value'); 26 | assert.isRejected(promise, /^Value already exists for key: key:default$/); 27 | }); 28 | 29 | it('uploads the contents if the key does not already exist', async function () { 30 | var redis = new Redis({}, IoredisMock); 31 | 32 | let promise = redis.upload('key', 'value', 'filecontents'); 33 | await assert.isFulfilled(promise); 34 | let value = await redis._client.get('key:value'); 35 | assert.equal(value, 'filecontents'); 36 | }); 37 | 38 | it('uploads the contents if the key already exists but allowOverwrite is true', async function () { 39 | var redis = new Redis({ 40 | allowOverwrite: true 41 | }, IoredisMock); 42 | 43 | await redis.upload('key', 'value', 'firstfilecontents'); 44 | await redis.upload('key', 'value', 'secondfilecontents'); 45 | let value = await redis._client.get('key:value'); 46 | assert.equal(value, 'secondfilecontents'); 47 | }); 48 | 49 | it('trims the list of recent uploads and removes the index key and the revisionData', async function () { 50 | let redis = new Redis({ 51 | maxRecentUploads: 2 52 | }, IoredisMock); 53 | 54 | await redis.upload('key', 1, '1value'); 55 | await redis.upload('key', 2, '2value'); 56 | await redis.upload('key', 3, '3value'); 57 | let values = await redis._client.mget('key:1', 'key:revision-data:1') 58 | assert.equal(values.filter(Boolean).length, 0, 'Expected key:1 and key:revision-data:1 to be deleted.'); 59 | let value = await redis._client.zrange('key:revisions', 0, -1); 60 | assert.deepEqual(value, ['2', '3']); 61 | }); 62 | 63 | it('trims the list of recent uploads but leaves the active one', async function () { 64 | let redis = new Redis({ 65 | maxRecentUploads: 2 66 | }, IoredisMock); 67 | 68 | await redis.upload('key', 1, '1value'); 69 | await redis._client.set('key:current', '1'); 70 | await redis.upload('key', 2, '2value'); 71 | await redis.upload('key', 3, '3value'); 72 | await redis.upload('key', 4, '4value'); 73 | let values = await redis._client.keys('*'); 74 | assert.deepEqual(values, [ 75 | 'key:1', 76 | 'key:revisions', 77 | 'key:current', 78 | 'key:3', // key 2 was trimmed 79 | 'key:4' 80 | ]); 81 | }); 82 | 83 | describe('generating the redis key', function () { 84 | it('will use just the default tag if the tag is not provided', async function () { 85 | let redis = new Redis({}, IoredisMock); 86 | 87 | await redis.upload('key', undefined, 'filecontents'); 88 | let value = await redis._client.get('key:default'); 89 | assert.equal(value, 'filecontents'); 90 | }); 91 | 92 | it('will use the key and the tag if the tag is provided', async function () { 93 | let redis = new Redis({}, IoredisMock); 94 | 95 | await redis.upload('key', 'tag', 'filecontents'); 96 | let value = await redis._client.get('key:tag'); 97 | assert.equal(value, 'filecontents'); 98 | }); 99 | }); 100 | }); 101 | 102 | describe('#willActivate', function () { 103 | it('sets the previous revision to the current revision', async function () { 104 | let redis = new Redis({}, IoredisMock); 105 | 106 | await redis.upload('key', '1', 'filecontents1'); 107 | await redis.upload('key', '2', 'filecontents2'); 108 | await redis.activate('key', '1', 'current'); 109 | let activeRevision = await redis.activeRevision('key'); 110 | assert.equal(activeRevision, '1'); 111 | }); 112 | }), 113 | 114 | describe('#activate', function () { 115 | it('rejects if the revisionKey doesn\'t exist in list of uploaded revisions', async function () { 116 | let redis = new Redis({}, IoredisMock); 117 | 118 | await redis.upload('key', '1', 'filecontents1'); 119 | await redis.upload('key', '2', 'filecontents2'); 120 | let promise = redis.activate('key', '3', 'current'); 121 | await assert.isRejected(promise); 122 | }); 123 | 124 | it('resolves and sets the current revision to the revision key provided', async function () { 125 | let redis = new Redis({}, IoredisMock); 126 | 127 | await redis.upload('key', '1', 'filecontents1'); 128 | await redis.upload('key', '2', 'filecontents2'); 129 | await redis.activate('key', '1', 'current'); 130 | let activeRevision = await redis.activeRevision('key'); 131 | assert.equal(activeRevision, '1'); 132 | let keyContents = await redis._client.get('key:1'); 133 | assert.equal(keyContents, 'filecontents1'); 134 | }); 135 | 136 | it('copies revision to the activeContentSuffix', async function () { 137 | let redis = new Redis({}, IoredisMock); 138 | 139 | await redis.upload('key', '1', 'filecontents1'); 140 | await redis.upload('key', '2', 'filecontents2'); 141 | await redis.upload('key', '3', 'filecontents3'); 142 | await redis.activate('key', '1', 'current-id', 'current-content'); 143 | let currentContent = await redis._client.get('key:current-content'); 144 | assert.equal(currentContent, 'filecontents1'); 145 | let currentId = await redis._client.get('key:current-id'); 146 | assert.equal(currentId, '1'); 147 | }); 148 | }); 149 | 150 | describe('#fetchRevisions', function () { 151 | it('lists the last existing revisions', async function () { 152 | let redis = new Redis({}, IoredisMock); 153 | 154 | await redis.upload('key', '1', 'filecontents1'); 155 | await redis.upload('key', '2', 'filecontents2'); 156 | await redis.upload('key', '3', 'filecontents3'); 157 | let recentRevisions = await redis.fetchRevisions('key'); 158 | assert.deepEqual(recentRevisions, [{ 159 | revision: '3', 160 | active: false, 161 | revisionData: null 162 | }, 163 | { 164 | revision: '2', 165 | active: false, 166 | revisionData: null 167 | }, 168 | { 169 | revision: '1', 170 | active: false, 171 | revisionData: null 172 | } 173 | ]); 174 | }); 175 | 176 | it('lists revisions and marks the active one', async function () { 177 | let redis = new Redis({}, IoredisMock); 178 | 179 | await redis.upload('key', '1', 'filecontents1'); 180 | await redis.activate('key', '1', 'current'); 181 | await redis.upload('key', '2', 'filecontents2'); 182 | await redis.upload('key', '3', 'filecontents3'); 183 | let recentRevisions = await redis.fetchRevisions('key'); 184 | assert.deepEqual(recentRevisions, [{ 185 | revision: '3', 186 | active: false, 187 | revisionData: null 188 | }, 189 | { 190 | revision: '2', 191 | active: false, 192 | revisionData: null 193 | }, 194 | { 195 | revision: '1', 196 | active: true, 197 | revisionData: null 198 | } 199 | ]); 200 | }); 201 | 202 | it('retrieves revisionData', async function () { 203 | let redis = new Redis({}, IoredisMock); 204 | let revisionData = '{"revisionKey":"a","timestamp":"2016-03-13T14:25:40.563Z","scm":{"sha":"9101968710f18a6720c48bf032fd82efd5743b7d","email":"mattia@mail.com","name":"Mattia Gheda","timestamp":"2015-12-22T12:44:48.000Z","branch":"master"}}'; 205 | 206 | await redis.upload('key', '1', revisionData, 'filecontents1'); 207 | let revisions = await redis.fetchRevisions('key'); 208 | assert.deepEqual(revisions, [{ 209 | revision: '1', 210 | active: false, 211 | revisionData: revisionData 212 | }]); 213 | }); 214 | 215 | it('uses activationSuffix in order to get the right activeRevision', async function () { 216 | let redis = new Redis({ 217 | activationSuffix: 'active-key' 218 | }, IoredisMock); 219 | 220 | let redisGetStub = sandbox.stub(redis._client, 'get').returns(RSVP.Promise.resolve()); 221 | 222 | await redis.activeRevision('key-prefix'); 223 | assert.isTrue(redisGetStub.calledWith('key-prefix:active-key')); 224 | }); 225 | }); 226 | }); 227 | --------------------------------------------------------------------------------