├── .gitignore ├── README.md ├── diagrams ├── 20 │ ├── .gitkeep │ └── diagrams.xml ├── 21 │ ├── .gitkeep │ └── diagrams.xml ├── 22 │ └── .gitkeep ├── 23 │ └── .gitkeep ├── 01 │ ├── .gitkeep │ └── diagrams.xml ├── 02 │ └── .gitkeep ├── 03 │ └── .gitkeep ├── 04 │ └── .gitkeep ├── 05 │ └── .gitkeep ├── async │ ├── .gitkeep │ ├── reqs.xml │ └── requests.xml ├── ecomm │ ├── .gitkeep │ ├── diagrams.xml │ └── products.xml ├── maze │ ├── .gitkeep │ └── diagrams.xml ├── message │ ├── .gitkeep │ └── diagrams.xml ├── movies │ ├── .gitkeep │ ├── diagrams.xml │ └── part2.xml ├── node │ ├── .gitkeep │ ├── async.xml │ ├── diagrams.xml │ └── ls.xml ├── promises │ ├── .gitkeep │ └── promises.xml ├── requests │ ├── .gitkeep │ └── diagrams.xml ├── testing │ ├── .gitkeep │ └── testing.xml └── timer │ ├── .gitkeep │ └── diagrams.xml ├── ecomm ├── carts.json ├── index.js ├── package-lock.json ├── package.json ├── products.json ├── public │ ├── css │ │ └── main.css │ └── images │ │ └── banner.jpg ├── repositories │ ├── carts.js │ ├── products.js │ ├── repository.js │ └── users.js ├── routes │ ├── admin │ │ ├── auth.js │ │ ├── middlewares.js │ │ ├── products.js │ │ └── validators.js │ ├── carts.js │ └── products.js ├── users.json └── views │ ├── admin │ ├── auth │ │ ├── signin.js │ │ └── signup.js │ ├── layout.js │ └── products │ │ ├── edit.js │ │ ├── index.js │ │ └── new.js │ ├── carts │ └── show.js │ ├── helpers.js │ ├── layout.js │ └── products │ └── index.js ├── hidash ├── index.js └── index.test.js ├── list ├── index.js ├── package-lock.json ├── package.json └── test.js ├── maze ├── index.html └── index.js ├── message ├── index.html └── index.js ├── movies-testing ├── autocomplete.js ├── index.html ├── index.js ├── style.css ├── test │ ├── autocomplete.test.js │ └── test.html └── utils.js ├── movies ├── autocomplete.js ├── index.html ├── index.js ├── style.css └── utils.js ├── timer ├── index.html ├── index.js ├── style.css └── timer.js ├── tme ├── index.js ├── package-lock.json ├── package.json ├── render.js ├── runner.js ├── sampleproject │ ├── index.js │ └── test │ │ └── forEach.test.js └── samplewebproject │ ├── index.html │ ├── index.js │ └── test │ └── app.test.js └── watchit ├── index.js ├── package-lock.json ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-casts 2 | Companion repo to a course hosted on Udemy.com 3 | -------------------------------------------------------------------------------- /diagrams/01/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/js-casts/f5279e79c5f1d91125b9e8a76d53452960c667f0/diagrams/01/.gitkeep -------------------------------------------------------------------------------- /diagrams/01/diagrams.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /diagrams/02/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/js-casts/f5279e79c5f1d91125b9e8a76d53452960c667f0/diagrams/02/.gitkeep -------------------------------------------------------------------------------- /diagrams/03/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/js-casts/f5279e79c5f1d91125b9e8a76d53452960c667f0/diagrams/03/.gitkeep -------------------------------------------------------------------------------- /diagrams/04/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/js-casts/f5279e79c5f1d91125b9e8a76d53452960c667f0/diagrams/04/.gitkeep -------------------------------------------------------------------------------- /diagrams/05/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/20/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/21/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/21/diagrams.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /diagrams/22/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/23/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/async/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/ecomm/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/maze/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/message/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/movies/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/movies/part2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /diagrams/node/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/node/async.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /diagrams/promises/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/requests/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/testing/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /diagrams/timer/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ecomm/carts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "items": [], 4 | "id": "cafda8fb" 5 | }, 6 | { 7 | "items": [ 8 | { 9 | "id": "f6b1fbbc", 10 | "quantity": 1 11 | }, 12 | { 13 | "id": "ed1a0390", 14 | "quantity": 1 15 | } 16 | ], 17 | "id": "fb1e6e85" 18 | } 19 | ] -------------------------------------------------------------------------------- /ecomm/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const cookieSession = require('cookie-session'); 4 | const authRouter = require('./routes/admin/auth'); 5 | const adminProductsRouter = require('./routes/admin/products'); 6 | const productsRouter = require('./routes/products'); 7 | const cartsRouter = require('./routes/carts'); 8 | 9 | const app = express(); 10 | 11 | app.use(express.static('public')); 12 | app.use(bodyParser.urlencoded({ extended: true })); 13 | app.use( 14 | cookieSession({ 15 | keys: ['lkasld235j'] 16 | }) 17 | ); 18 | app.use(authRouter); 19 | app.use(productsRouter); 20 | app.use(adminProductsRouter); 21 | app.use(cartsRouter); 22 | 23 | app.listen(3000, () => { 24 | console.log('Listening'); 25 | }); 26 | -------------------------------------------------------------------------------- /ecomm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecomm", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon index.js --ignore *.json" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cookie-session": "^1.3.3", 14 | "express": "^4.17.1", 15 | "express-validator": "^6.2.0", 16 | "multer": "^1.4.2", 17 | "nodemon": "^1.19.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ecomm/public/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | --primary: #631d76; 3 | --secondary: #9e4770; 4 | --white: #fbfbfb; 5 | --dark: #2e2532; 6 | --black: #201a23; 7 | } 8 | 9 | .product-card figure { 10 | text-align: center; 11 | padding: 5px; 12 | } 13 | 14 | .navbar ul.social { 15 | display: flex; 16 | } 17 | .navbar ul.social li { 18 | margin-right: 20px; 19 | text-decoration: none; 20 | } 21 | .navbar ul.social li a { 22 | color: var(--white); 23 | } 24 | 25 | .banner { 26 | margin: 50px; 27 | } 28 | 29 | .columns.products { 30 | flex-wrap: wrap; 31 | } 32 | 33 | .card-footer-item i { 34 | margin-right: 10px; 35 | } 36 | 37 | .navbar-bottom.navbar { 38 | align-items: center; 39 | } 40 | 41 | .navbar-top.navbar { 42 | background-color: var(--primary); 43 | } 44 | 45 | .navbar-buttons { 46 | display: flex; 47 | } 48 | 49 | .navbar-top .container.navbar-container, 50 | .navbar-bottom .container.navbar-container { 51 | display: flex; 52 | justify-content: space-between; 53 | padding: 0px 5%; 54 | align-items: center; 55 | } 56 | 57 | #cart h3 { 58 | margin-top: 50px; 59 | } 60 | 61 | #cart .cart-item { 62 | margin-bottom: 10px; 63 | padding: 20px; 64 | } 65 | 66 | .product-card .card-footer { 67 | justify-content: center; 68 | } 69 | .product-card .card-footer form { 70 | display: block; 71 | width: 100%; 72 | } 73 | 74 | .admin > div.container { 75 | margin: 40px 10%; 76 | } 77 | 78 | .admin .control { 79 | display: flex; 80 | justify-content: space-between; 81 | align-items: center; 82 | } 83 | 84 | .admin table { 85 | width: 100%; 86 | } 87 | 88 | .admin table th:first-of-type { 89 | width: 50%; 90 | } 91 | .admin table td:first-of-type { 92 | width: 50%; 93 | } 94 | 95 | .admin table td { 96 | vertical-align: middle; 97 | } 98 | 99 | .product-card .card-footer button { 100 | border: none; 101 | width: 100%; 102 | height: 50px; 103 | } 104 | 105 | .product-card .card-footer button i { 106 | margin-right: 10px; 107 | } 108 | 109 | #cart .cart-item h3 { 110 | margin: 0; 111 | } 112 | 113 | .cart-item { 114 | display: flex; 115 | justify-content: space-between; 116 | align-items: center; 117 | } 118 | 119 | .quantity { 120 | position: relative; 121 | } 122 | 123 | .quantity:before { 124 | content: '#'; 125 | position: absolute; 126 | z-index: 10; 127 | top: 0; 128 | right: 5px; 129 | bottom: 0; 130 | display: flex; 131 | align-items: center; 132 | color: gray; 133 | } 134 | 135 | .cart-right .field { 136 | margin-bottom: 0 !important; 137 | } 138 | 139 | .price { 140 | margin: 0 20px; 141 | } 142 | 143 | .cart-right { 144 | display: flex; 145 | align-items: center; 146 | } 147 | -------------------------------------------------------------------------------- /ecomm/public/images/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/js-casts/f5279e79c5f1d91125b9e8a76d53452960c667f0/ecomm/public/images/banner.jpg -------------------------------------------------------------------------------- /ecomm/repositories/carts.js: -------------------------------------------------------------------------------- 1 | const Repository = require('./repository'); 2 | 3 | class CartsRepository extends Repository {} 4 | 5 | module.exports = new CartsRepository('carts.json'); 6 | -------------------------------------------------------------------------------- /ecomm/repositories/products.js: -------------------------------------------------------------------------------- 1 | const Repository = require('./repository'); 2 | 3 | class ProductsRepository extends Repository {} 4 | 5 | module.exports = new ProductsRepository('products.json'); 6 | -------------------------------------------------------------------------------- /ecomm/repositories/repository.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const crypto = require('crypto'); 3 | 4 | module.exports = class Repository { 5 | constructor(filename) { 6 | if (!filename) { 7 | throw new Error('Creating a repository requires a filename'); 8 | } 9 | 10 | this.filename = filename; 11 | try { 12 | fs.accessSync(this.filename); 13 | } catch (err) { 14 | fs.writeFileSync(this.filename, '[]'); 15 | } 16 | } 17 | 18 | async create(attrs) { 19 | attrs.id = this.randomId(); 20 | 21 | const records = await this.getAll(); 22 | records.push(attrs); 23 | await this.writeAll(records); 24 | 25 | return attrs; 26 | } 27 | 28 | async getAll() { 29 | return JSON.parse( 30 | await fs.promises.readFile(this.filename, { 31 | encoding: 'utf8' 32 | }) 33 | ); 34 | } 35 | 36 | async writeAll(records) { 37 | await fs.promises.writeFile( 38 | this.filename, 39 | JSON.stringify(records, null, 2) 40 | ); 41 | } 42 | 43 | randomId() { 44 | return crypto.randomBytes(4).toString('hex'); 45 | } 46 | 47 | async getOne(id) { 48 | const records = await this.getAll(); 49 | return records.find(record => record.id === id); 50 | } 51 | 52 | async delete(id) { 53 | const records = await this.getAll(); 54 | const filteredRecords = records.filter(record => record.id !== id); 55 | await this.writeAll(filteredRecords); 56 | } 57 | 58 | async update(id, attrs) { 59 | const records = await this.getAll(); 60 | const record = records.find(record => record.id === id); 61 | 62 | if (!record) { 63 | throw new Error(`Record with id ${id} not found`); 64 | } 65 | 66 | Object.assign(record, attrs); 67 | await this.writeAll(records); 68 | } 69 | 70 | async getOneBy(filters) { 71 | const records = await this.getAll(); 72 | 73 | for (let record of records) { 74 | let found = true; 75 | 76 | for (let key in filters) { 77 | if (record[key] !== filters[key]) { 78 | found = false; 79 | } 80 | } 81 | 82 | if (found) { 83 | return record; 84 | } 85 | } 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /ecomm/repositories/users.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const crypto = require('crypto'); 3 | const util = require('util'); 4 | const Repository = require('./repository'); 5 | 6 | const scrypt = util.promisify(crypto.scrypt); 7 | 8 | class UsersRepository extends Repository { 9 | async comparePasswords(saved, supplied) { 10 | // Saved -> password saved in our database. 'hashed.salt' 11 | // Supplied -> password given to us by a user trying sign in 12 | const [hashed, salt] = saved.split('.'); 13 | const hashedSuppliedBuf = await scrypt(supplied, salt, 64); 14 | 15 | return hashed === hashedSuppliedBuf.toString('hex'); 16 | } 17 | 18 | async create(attrs) { 19 | attrs.id = this.randomId(); 20 | 21 | const salt = crypto.randomBytes(8).toString('hex'); 22 | const buf = await scrypt(attrs.password, salt, 64); 23 | 24 | const records = await this.getAll(); 25 | const record = { 26 | ...attrs, 27 | password: `${buf.toString('hex')}.${salt}` 28 | }; 29 | records.push(record); 30 | 31 | await this.writeAll(records); 32 | 33 | return record; 34 | } 35 | } 36 | 37 | module.exports = new UsersRepository('users.json'); 38 | -------------------------------------------------------------------------------- /ecomm/routes/admin/auth.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const { handleErrors } = require('./middlewares'); 4 | const usersRepo = require('../../repositories/users'); 5 | const signupTemplate = require('../../views/admin/auth/signup'); 6 | const signinTemplate = require('../../views/admin/auth/signin'); 7 | const { 8 | requireEmail, 9 | requirePassword, 10 | requirePasswordConfirmation, 11 | requireEmailExists, 12 | requireValidPasswordForUser 13 | } = require('./validators'); 14 | 15 | const router = express.Router(); 16 | 17 | router.get('/signup', (req, res) => { 18 | res.send(signupTemplate({ req })); 19 | }); 20 | 21 | router.post( 22 | '/signup', 23 | [requireEmail, requirePassword, requirePasswordConfirmation], 24 | handleErrors(signupTemplate), 25 | async (req, res) => { 26 | const { email, password } = req.body; 27 | const user = await usersRepo.create({ email, password }); 28 | 29 | req.session.userId = user.id; 30 | 31 | res.redirect('/admin/products'); 32 | } 33 | ); 34 | 35 | router.get('/signout', (req, res) => { 36 | req.session = null; 37 | res.send('You are logged out'); 38 | }); 39 | 40 | router.get('/signin', (req, res) => { 41 | res.send(signinTemplate({})); 42 | }); 43 | 44 | router.post( 45 | '/signin', 46 | [requireEmailExists, requireValidPasswordForUser], 47 | handleErrors(signinTemplate), 48 | async (req, res) => { 49 | const { email } = req.body; 50 | 51 | const user = await usersRepo.getOneBy({ email }); 52 | 53 | req.session.userId = user.id; 54 | 55 | res.redirect('/admin/products'); 56 | } 57 | ); 58 | 59 | module.exports = router; 60 | -------------------------------------------------------------------------------- /ecomm/routes/admin/middlewares.js: -------------------------------------------------------------------------------- 1 | const { validationResult } = require('express-validator'); 2 | 3 | module.exports = { 4 | handleErrors(templateFunc, dataCb) { 5 | return async (req, res, next) => { 6 | const errors = validationResult(req); 7 | 8 | if (!errors.isEmpty()) { 9 | let data = {}; 10 | if (dataCb) { 11 | data = await dataCb(req); 12 | } 13 | 14 | return res.send(templateFunc({ errors, ...data })); 15 | } 16 | 17 | next(); 18 | }; 19 | }, 20 | requireAuth(req, res, next) { 21 | if (!req.session.userId) { 22 | return res.redirect('/signin'); 23 | } 24 | 25 | next(); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /ecomm/routes/admin/products.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const multer = require('multer'); 3 | 4 | const { handleErrors, requireAuth } = require('./middlewares'); 5 | const productsRepo = require('../../repositories/products'); 6 | const productsNewTemplate = require('../../views/admin/products/new'); 7 | const productsIndexTemplate = require('../../views/admin/products/index'); 8 | const productsEditTemplate = require('../../views/admin/products/edit'); 9 | const { requireTitle, requirePrice } = require('./validators'); 10 | 11 | const router = express.Router(); 12 | const upload = multer({ storage: multer.memoryStorage() }); 13 | 14 | router.get('/admin/products', requireAuth, async (req, res) => { 15 | const products = await productsRepo.getAll(); 16 | res.send(productsIndexTemplate({ products })); 17 | }); 18 | 19 | router.get('/admin/products/new', requireAuth, (req, res) => { 20 | res.send(productsNewTemplate({})); 21 | }); 22 | 23 | router.post( 24 | '/admin/products/new', 25 | requireAuth, 26 | upload.single('image'), 27 | [requireTitle, requirePrice], 28 | handleErrors(productsNewTemplate), 29 | async (req, res) => { 30 | const image = req.file.buffer.toString('base64'); 31 | const { title, price } = req.body; 32 | await productsRepo.create({ title, price, image }); 33 | 34 | res.redirect('/admin/products'); 35 | } 36 | ); 37 | 38 | router.get('/admin/products/:id/edit', requireAuth, async (req, res) => { 39 | const product = await productsRepo.getOne(req.params.id); 40 | 41 | if (!product) { 42 | return res.send('Product not found'); 43 | } 44 | 45 | res.send(productsEditTemplate({ product })); 46 | }); 47 | 48 | router.post( 49 | '/admin/products/:id/edit', 50 | requireAuth, 51 | upload.single('image'), 52 | [requireTitle, requirePrice], 53 | handleErrors(productsEditTemplate, async req => { 54 | const product = await productsRepo.getOne(req.params.id); 55 | return { product }; 56 | }), 57 | async (req, res) => { 58 | const changes = req.body; 59 | 60 | if (req.file) { 61 | changes.image = req.file.buffer.toString('base64'); 62 | } 63 | 64 | try { 65 | await productsRepo.update(req.params.id, changes); 66 | } catch (err) { 67 | return res.send('Could not find item'); 68 | } 69 | 70 | res.redirect('/admin/products'); 71 | } 72 | ); 73 | 74 | router.post('/admin/products/:id/delete', requireAuth, async (req, res) => { 75 | await productsRepo.delete(req.params.id); 76 | 77 | res.redirect('/admin/products'); 78 | }); 79 | 80 | module.exports = router; 81 | -------------------------------------------------------------------------------- /ecomm/routes/admin/validators.js: -------------------------------------------------------------------------------- 1 | const { check } = require('express-validator'); 2 | const usersRepo = require('../../repositories/users'); 3 | 4 | module.exports = { 5 | requireTitle: check('title') 6 | .trim() 7 | .isLength({ min: 5, max: 40 }) 8 | .withMessage('Must be between 5 and 40 characters'), 9 | requirePrice: check('price') 10 | .trim() 11 | .toFloat() 12 | .isFloat({ min: 1 }) 13 | .withMessage('Must be a number greater than 1'), 14 | requireEmail: check('email') 15 | .trim() 16 | .normalizeEmail() 17 | .isEmail() 18 | .withMessage('Must be a valid email') 19 | .custom(async email => { 20 | const existingUser = await usersRepo.getOneBy({ email }); 21 | if (existingUser) { 22 | throw new Error('Email in use'); 23 | } 24 | }), 25 | requirePassword: check('password') 26 | .trim() 27 | .isLength({ min: 4, max: 20 }) 28 | .withMessage('Must be between 4 and 20 characters'), 29 | requirePasswordConfirmation: check('passwordConfirmation') 30 | .trim() 31 | .isLength({ min: 4, max: 20 }) 32 | .withMessage('Must be between 4 and 20 characters') 33 | .custom(async (passwordConfirmation, { req }) => { 34 | if (passwordConfirmation !== req.body.password) { 35 | throw new Error('Passwords must match'); 36 | } 37 | }), 38 | requireEmailExists: check('email') 39 | .trim() 40 | .normalizeEmail() 41 | .isEmail() 42 | .withMessage('Must provide a valid email') 43 | .custom(async email => { 44 | const user = await usersRepo.getOneBy({ email }); 45 | if (!user) { 46 | throw new Error('Email not found!'); 47 | } 48 | }), 49 | requireValidPasswordForUser: check('password') 50 | .trim() 51 | .custom(async (password, { req }) => { 52 | const user = await usersRepo.getOneBy({ email: req.body.email }); 53 | if (!user) { 54 | throw new Error('Invalid password'); 55 | } 56 | 57 | const validPassword = await usersRepo.comparePasswords( 58 | user.password, 59 | password 60 | ); 61 | if (!validPassword) { 62 | throw new Error('Invalid password'); 63 | } 64 | }) 65 | }; 66 | -------------------------------------------------------------------------------- /ecomm/routes/carts.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cartsRepo = require('../repositories/carts'); 3 | const productsRepo = require('../repositories/products'); 4 | const cartShowTemplate = require('../views/carts/show'); 5 | 6 | const router = express.Router(); 7 | 8 | // Receive a post request to add an item to a cart 9 | router.post('/cart/products', async (req, res) => { 10 | // Figure out the cart! 11 | let cart; 12 | if (!req.session.cartId) { 13 | // We dont have a cart, we need to create one, 14 | // and store the cart id on the req.session.cartId 15 | // property 16 | cart = await cartsRepo.create({ items: [] }); 17 | req.session.cartId = cart.id; 18 | } else { 19 | // We have a cart! Lets get it from the repository 20 | cart = await cartsRepo.getOne(req.session.cartId); 21 | } 22 | 23 | const existingItem = cart.items.find(item => item.id === req.body.productId); 24 | if (existingItem) { 25 | // increment quantity and save cart 26 | existingItem.quantity++; 27 | } else { 28 | // add new product id to items array 29 | cart.items.push({ id: req.body.productId, quantity: 1 }); 30 | } 31 | await cartsRepo.update(cart.id, { 32 | items: cart.items 33 | }); 34 | 35 | res.redirect('/cart'); 36 | }); 37 | 38 | // Receive a GET request to show all items in cart 39 | router.get('/cart', async (req, res) => { 40 | if (!req.session.cartId) { 41 | return res.redirect('/'); 42 | } 43 | 44 | const cart = await cartsRepo.getOne(req.session.cartId); 45 | for (let item of cart.items) { 46 | const product = await productsRepo.getOne(item.id); 47 | 48 | item.product = product; 49 | } 50 | 51 | res.send(cartShowTemplate({ items: cart.items })); 52 | }); 53 | 54 | // Receive a post request to delete an item from a cart 55 | router.post('/cart/products/delete', async (req, res) => { 56 | const { itemId } = req.body; 57 | const cart = await cartsRepo.getOne(req.session.cartId); 58 | 59 | const items = cart.items.filter(item => item.id !== itemId); 60 | 61 | await cartsRepo.update(req.session.cartId, { items }); 62 | 63 | res.redirect('/cart'); 64 | }); 65 | 66 | module.exports = router; 67 | -------------------------------------------------------------------------------- /ecomm/routes/products.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const productsRepo = require('../repositories/products'); 3 | const productsIndexTemplate = require('../views/products/index'); 4 | 5 | const router = express.Router(); 6 | 7 | router.get('/', async (req, res) => { 8 | const products = await productsRepo.getAll(); 9 | res.send(productsIndexTemplate({ products })); 10 | }); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /ecomm/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "email": "test@test.com" 4 | }, 5 | { 6 | "email": "asdf@aslkdfj.com", 7 | "password": "password", 8 | "id": "4cad493a" 9 | }, 10 | { 11 | "email": "test1@test.com", 12 | "password": "password", 13 | "id": "a8a361ce" 14 | }, 15 | { 16 | "email": "test3@test.com", 17 | "password": "password", 18 | "id": "3ced88e5" 19 | }, 20 | { 21 | "email": "test5@test.com", 22 | "password": "ca43d32660d309b48ca04a03adaf4ebd6b877eab6110de20945669276bb6fc240a662205ebb0d7d2d05fa22fb329b35aaca3cb50356f3d4f355f8b89a084ab0a.2d21ade5cf256a9d", 23 | "id": "29aff662" 24 | }, 25 | { 26 | "email": "test6@test.com", 27 | "password": "6c976960f74031c397fbf9df460cbb774d4d1ec9db68d4fc7dcc247b721542996e8e0cd82c5792305d4a3356d16ceccd6ad44ade17dbeb19efa3d6f92350d389.b8b3b0f5c49ebca7", 28 | "id": "2952bf5c" 29 | }, 30 | { 31 | "email": "test10@test.com", 32 | "password": "4211bfcad8d3def24d04b9d88d4a08cdff7fefab77974c86e0e9394ae1ab9a6061f2dbf6d3389f3e9aa870f65e9070e88537f7f89af3d861705c89b46558a342.76e8d9f3f6b98cd7", 33 | "id": "7f49b927" 34 | }, 35 | { 36 | "email": "", 37 | "password": "3e1480674f7e589abf3eb8114ea11eb89256dda26d7af9e7c786b2d77f945f1c5ff47b52c601b4547bd1e050f84b3735547e1dd16cd62705c717e55e9eec81eb.500208f6a9ae8466", 38 | "id": "e99f8d2e" 39 | }, 40 | { 41 | "email": "test3@test.com", 42 | "password": "89ccc296b12e135729c9d2e2ac15f61550766e3e06e64642b85a2e3748f175b8b5e1a93881be99c6c5d1b933d41ba1d6cf01ca988b5a17fbc0dbb370f127c1fa.b0b86722b83f78c8", 43 | "id": "8525c8ba" 44 | }, 45 | { 46 | "email": "test3@test.com", 47 | "password": "0737da26a1fecf6cc61bb1c138158d4097ec2e4fbe57be135a0596f7eb88a15cb3bdff5af7a60cc90c50c9aacbb7529dda244c36b60e65e3d92203c6e717470c.a398fef03dcd63e0", 48 | "id": "c1934981" 49 | }, 50 | { 51 | "email": "test3@test.com", 52 | "password": "0a9741d02fde251d56c66ffd2cef9e8c792c83e7e763f31f60efd28b5c2f27cf4b29f28047f97bb128bc9d55518ff6f5a44b040463573a595805ef9d5e04e4be.e9657e687e491022", 53 | "id": "3abc2f01" 54 | }, 55 | { 56 | "email": "alskjdf@", 57 | "password": "61a689c8d20fdc3c0738e024a50c98e9589e250bab11cc07ebaf6134f15cf04df2600e1c07dd0ff81705250df92991675882f02fff5d8afa53298723b6c20c93.dd652bc403276b8f", 58 | "id": "1d42b5f0" 59 | }, 60 | { 61 | "email": "alskjdf@", 62 | "password": "a0ad5ce6b6b1df11457e705190294dbb39f43379de2ed46b2562125d9956c8548040dfcb21181463e49641e83acf706782f6732e51e7682437d3149af666bbf7.d62ca7e0b9553f70", 63 | "id": "3cdaa735" 64 | }, 65 | { 66 | "email": "alskjdf@", 67 | "password": "170b1ba39186e56f489a6298799778c8c39af3ea73eb2e08d2a37506725609aac512b2f349cdfdad124969adfa3834c123ca9e75fbc17a6fbeb3586853a3786a.b58292562636f913", 68 | "id": "64f909f7" 69 | }, 70 | { 71 | "email": "alksdfjlk@alskdjf.com", 72 | "password": "296f5fdfdb36cf989f8fff0654f3a5a870cee48456f6de6dc9521c94f8162054c0b958b64b4645d9663d4b4fa4522b4394d737d5de53afe4dd51d400a9f22a92.539498a95547a04d", 73 | "id": "a301383d" 74 | }, 75 | { 76 | "email": "alskjdf@alksdjf.com", 77 | "password": "a0f78630e3de6b1d7c44c7f91f0fe0c2ef51f1a39e094a4c5427904e2ffe27cf5d5ef5a2042cab528a906516074b94a37734d4651d642d1c1f0e3e35de23b8fd.637c5aa7b3388258", 78 | "id": "10774d9d" 79 | }, 80 | { 81 | "email": "alskdjf@alskdjf.com", 82 | "password": "489660662a0210cbf0f82b160030a075138deeacca0862eb8406db9624427c9d47546bfa8c577d03503a6128c386d281647a7998ac99b0180dad9b88e57cc958.4a4a343f5a6e0efa", 83 | "id": "b4214114" 84 | }, 85 | { 86 | "email": "test1935@test.com", 87 | "password": "695e1c471599ac9dcae7bba80ae65c3ad5bf0e3d57368047a0965f2c9be3020841e48e7f93873417a0b7799328614cca2699692235c38543575ef93cdb32adc1.c52032cb2ca6d17e", 88 | "id": "2e90d67b" 89 | }, 90 | { 91 | "email": "alsk@alsk.com", 92 | "password": "618df746279402e73a79cf75c5723c556bb5e7db62083d88448b4995da0ed915fed42dbd7a7b6cd268ecaeb479abce4a951ed5e858fc4d3d7da3b61d9f0b5138.bbc49cf62675c387", 93 | "id": "7b4d7485" 94 | }, 95 | { 96 | "email": "alskdfj@alskjdf.com", 97 | "password": "502670212f115561ff637096e984e425f1a5cc33748053f08aaf52a34430743d40158ee28206dd9813fc9fc81bd26a8a54e515c606ac3f97d20e9f1085bcbb9e.c34e6f08e5943cbd", 98 | "id": "f68a3efe" 99 | } 100 | ] -------------------------------------------------------------------------------- /ecomm/views/admin/auth/signin.js: -------------------------------------------------------------------------------- 1 | const layout = require('../layout'); 2 | const { getError } = require('../../helpers'); 3 | 4 | module.exports = ({ errors }) => { 5 | return layout({ 6 | content: ` 7 |
8 |
9 |
10 |
11 |

Sign in

12 |
13 | 14 | 15 |

${getError(errors, 'email')}

16 |
17 |
18 | 19 | 20 |

${getError(errors, 'password')}

21 |
22 | 23 |
24 | Need an account? Sign Up 25 |
26 |
27 |
28 | ` 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /ecomm/views/admin/auth/signup.js: -------------------------------------------------------------------------------- 1 | const layout = require('../layout'); 2 | const { getError } = require('../../helpers'); 3 | 4 | module.exports = ({ req, errors }) => { 5 | console.log(errors); 6 | return layout({ 7 | content: ` 8 |
9 |
10 |
11 |
12 |

Sign Up

13 |
14 | 15 | 16 |

${getError(errors, 'email')}

17 |
18 |
19 | 20 | 21 |

${getError(errors, 'password')}

22 |
23 |
24 | 25 | 26 |

${getError( 27 | errors, 28 | 'passwordConfirmation' 29 | )}

30 |
31 | 32 |
33 | Have an account? Sign In 34 |
35 |
36 |
37 | ` 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /ecomm/views/admin/layout.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ content }) => { 2 | return ` 3 | 4 | 5 | 6 | 7 | 8 | Shop 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 32 |
33 |
34 | ${content} 35 |
36 | 37 | 38 | `; 39 | }; 40 | -------------------------------------------------------------------------------- /ecomm/views/admin/products/edit.js: -------------------------------------------------------------------------------- 1 | const layout = require('../layout'); 2 | const { getError } = require('../../helpers'); 3 | 4 | module.exports = ({ product, errors }) => { 5 | return layout({ 6 | content: ` 7 |
8 |
9 |

Edit a Product

10 | 11 |
12 |
13 | 14 | 17 |

${getError(errors, 'title')}

18 |
19 | 20 |
21 | 22 | 25 |

${getError(errors, 'price')}

26 |
27 | 28 |
29 | 30 | 31 |
32 |
33 | 34 |
35 |
36 |
37 | ` 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /ecomm/views/admin/products/index.js: -------------------------------------------------------------------------------- 1 | const layout = require('../layout'); 2 | 3 | module.exports = ({ products }) => { 4 | const renderedProducts = products 5 | .map(product => { 6 | return ` 7 | 8 | ${product.title} 9 | ${product.price} 10 | 11 | 12 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | `; 24 | }) 25 | .join(''); 26 | 27 | return layout({ 28 | content: ` 29 |
30 |

Products

31 | New Product 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ${renderedProducts} 44 | 45 |
TitlePriceEditDelete
46 | ` 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /ecomm/views/admin/products/new.js: -------------------------------------------------------------------------------- 1 | const layout = require('../layout'); 2 | const { getError } = require('../../helpers'); 3 | 4 | module.exports = ({ errors }) => { 5 | return layout({ 6 | content: ` 7 |
8 |
9 |

Create a Product

10 | 11 |
12 |
13 | 14 | 15 |

${getError(errors, 'title')}

16 |
17 | 18 |
19 | 20 | 21 |

${getError(errors, 'price')}

22 |
23 | 24 |
25 | 26 | 27 |
28 |
29 | 30 |
31 |
32 |
33 | ` 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /ecomm/views/carts/show.js: -------------------------------------------------------------------------------- 1 | const layout = require('../layout'); 2 | 3 | module.exports = ({ items }) => { 4 | // let totalPrice = 0; 5 | // for (let item of items) { 6 | // totalPrice += item.quantity * item.product.price; 7 | // } 8 | 9 | const totalPrice = items.reduce((prev, item) => { 10 | return prev + item.quantity * item.product.price; 11 | }, 0); 12 | 13 | const renderedItems = items 14 | .map(item => { 15 | return ` 16 |
17 |

${item.product.title}

18 |
19 |
20 | $${item.product.price} X ${item.quantity} = 21 |
22 |
23 | $${item.product.price * item.quantity} 24 |
25 |
26 |
27 | 28 | 33 |
34 |
35 |
36 |
37 | `; 38 | }) 39 | .join(''); 40 | 41 | return layout({ 42 | content: ` 43 |
44 |
45 |
46 |
47 |

Shopping Cart

48 |
49 | ${renderedItems} 50 |
51 |
52 |
53 | Total 54 |
55 |

$${totalPrice}

56 | 57 |
58 |
59 |
60 |
61 |
62 | ` 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /ecomm/views/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getError(errors, prop) { 3 | try { 4 | return errors.mapped()[prop].msg; 5 | } catch (err) { 6 | return ''; 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /ecomm/views/layout.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ content }) => { 2 | return ` 3 | 4 | 5 | 6 | 7 | 8 | Shop 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 40 | 59 |
60 | 61 | ${content} 62 | 63 | 64 | `; 65 | }; 66 | -------------------------------------------------------------------------------- /ecomm/views/products/index.js: -------------------------------------------------------------------------------- 1 | const layout = require('../layout'); 2 | 3 | module.exports = ({ products }) => { 4 | const renderedProducts = products 5 | .map(product => { 6 | return ` 7 |
8 |
9 |
10 | 11 |
12 |
13 |

${product.title}

14 |
$${product.price}
15 |
16 | 24 |
25 |
26 | `; 27 | }) 28 | .join('\n'); 29 | 30 | return layout({ 31 | content: ` 32 | 39 | 40 |
41 |
42 |
43 |
44 |
45 |
46 |

Featured Items

47 |
48 | ${renderedProducts} 49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | ` 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /hidash/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | forEach(arr, fn) { 3 | // for (let i = 0; i < arr.length; i++) { 4 | // const value = arr[i]; 5 | // fn(value, i); 6 | // } 7 | 8 | for (let index in arr) { 9 | fn(arr[index], index); 10 | } 11 | }, 12 | map(arr, fn) { 13 | const result = []; 14 | 15 | for (let i = 0; i < arr.length; i++) { 16 | result.push(fn(arr[i], i)); 17 | } 18 | 19 | return result; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /hidash/index.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { forEach, map } = require('./index'); 3 | 4 | it('The forEach function', () => { 5 | let sum = 0; 6 | forEach([1, 2, 3], value => { 7 | sum += value; 8 | }); 9 | 10 | assert.strictEqual(sum, 6, 'Expected forEach to sum the array'); 11 | }); 12 | 13 | it('The map function', () => { 14 | const result = map([1, 2, 3], value => { 15 | return value * 2; 16 | }); 17 | 18 | assert.deepStrictEqual(result, [2, 4, 6]); 19 | }); 20 | -------------------------------------------------------------------------------- /list/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const util = require('util'); 5 | const chalk = require('chalk'); 6 | const path = require('path'); 7 | 8 | // Method #2 9 | // const lstat = util.promisify(fs.lstat); 10 | 11 | // Method #3 12 | const { lstat } = fs.promises; 13 | 14 | const targetDir = process.argv[2] || process.cwd(); 15 | 16 | fs.readdir(targetDir, async (err, filenames) => { 17 | if (err) { 18 | console.log(err); 19 | } 20 | 21 | const statPromises = filenames.map(filename => { 22 | return lstat(path.join(targetDir, filename)); 23 | }); 24 | 25 | const allStats = await Promise.all(statPromises); 26 | 27 | for (let stats of allStats) { 28 | const index = allStats.indexOf(stats); 29 | 30 | if (stats.isFile()) { 31 | console.log(filenames[index]); 32 | } else { 33 | console.log(chalk.bold(filenames[index])); 34 | } 35 | } 36 | }); 37 | 38 | // Method #1 39 | // const lstat = filename => { 40 | // return new Promise((resolve, reject) => { 41 | // fs.lstat(filename, (err, stats) => { 42 | // if (err) { 43 | // reject(err); 44 | // } 45 | 46 | // resolve(stats); 47 | // }); 48 | // }); 49 | // }; 50 | -------------------------------------------------------------------------------- /list/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "list", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-styles": { 8 | "version": "3.2.1", 9 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 10 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 11 | "requires": { 12 | "color-convert": "^1.9.0" 13 | } 14 | }, 15 | "chalk": { 16 | "version": "2.4.2", 17 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 18 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 19 | "requires": { 20 | "ansi-styles": "^3.2.1", 21 | "escape-string-regexp": "^1.0.5", 22 | "supports-color": "^5.3.0" 23 | } 24 | }, 25 | "color-convert": { 26 | "version": "1.9.3", 27 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 28 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 29 | "requires": { 30 | "color-name": "1.1.3" 31 | } 32 | }, 33 | "color-name": { 34 | "version": "1.1.3", 35 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 36 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 37 | }, 38 | "escape-string-regexp": { 39 | "version": "1.0.5", 40 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 41 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 42 | }, 43 | "has-flag": { 44 | "version": "3.0.0", 45 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 46 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 47 | }, 48 | "supports-color": { 49 | "version": "5.5.0", 50 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 51 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 52 | "requires": { 53 | "has-flag": "^3.0.0" 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "list", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "bin": { 13 | "nls": "index.js" 14 | }, 15 | "dependencies": { 16 | "chalk": "^2.4.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /list/test.js: -------------------------------------------------------------------------------- 1 | console.log('hi how are you?'); 2 | -------------------------------------------------------------------------------- /maze/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /maze/index.js: -------------------------------------------------------------------------------- 1 | const { Engine, Render, Runner, World, Bodies, Body, Events } = Matter; 2 | 3 | const cellsHorizontal = 16; 4 | const cellsVertical = 10; 5 | const width = window.innerWidth; 6 | const height = window.innerHeight; 7 | 8 | const unitLengthX = width / cellsHorizontal; 9 | const unitLengthY = height / cellsVertical; 10 | 11 | const engine = Engine.create(); 12 | engine.world.gravity.y = 0; 13 | const { world } = engine; 14 | const render = Render.create({ 15 | element: document.body, 16 | engine: engine, 17 | options: { 18 | wireframes: false, 19 | width, 20 | height 21 | } 22 | }); 23 | Render.run(render); 24 | Runner.run(Runner.create(), engine); 25 | 26 | // Walls 27 | const walls = [ 28 | Bodies.rectangle(width / 2, 0, width, 2, { isStatic: true }), 29 | Bodies.rectangle(width / 2, height, width, 2, { isStatic: true }), 30 | Bodies.rectangle(0, height / 2, 2, height, { isStatic: true }), 31 | Bodies.rectangle(width, height / 2, 2, height, { isStatic: true }) 32 | ]; 33 | World.add(world, walls); 34 | 35 | // Maze generation 36 | 37 | const shuffle = arr => { 38 | let counter = arr.length; 39 | 40 | while (counter > 0) { 41 | const index = Math.floor(Math.random() * counter); 42 | 43 | counter--; 44 | 45 | const temp = arr[counter]; 46 | arr[counter] = arr[index]; 47 | arr[index] = temp; 48 | } 49 | 50 | return arr; 51 | }; 52 | 53 | const grid = Array(cellsVertical) 54 | .fill(null) 55 | .map(() => Array(cellsHorizontal).fill(false)); 56 | 57 | const verticals = Array(cellsVertical) 58 | .fill(null) 59 | .map(() => Array(cellsHorizontal - 1).fill(false)); 60 | 61 | const horizontals = Array(cellsVertical - 1) 62 | .fill(null) 63 | .map(() => Array(cellsHorizontal).fill(false)); 64 | 65 | const startRow = Math.floor(Math.random() * cellsVertical); 66 | const startColumn = Math.floor(Math.random() * cellsHorizontal); 67 | 68 | const stepThroughCell = (row, column) => { 69 | // If i have visted the cell at [row, column], then return 70 | if (grid[row][column]) { 71 | return; 72 | } 73 | 74 | // Mark this cell as being visited 75 | grid[row][column] = true; 76 | 77 | // Assemble randomly-ordered list of neighbors 78 | const neighbors = shuffle([ 79 | [row - 1, column, 'up'], 80 | [row, column + 1, 'right'], 81 | [row + 1, column, 'down'], 82 | [row, column - 1, 'left'] 83 | ]); 84 | // For each neighbor.... 85 | for (let neighbor of neighbors) { 86 | const [nextRow, nextColumn, direction] = neighbor; 87 | 88 | // See if that neighbor is out of bounds 89 | if ( 90 | nextRow < 0 || 91 | nextRow >= cellsVertical || 92 | nextColumn < 0 || 93 | nextColumn >= cellsHorizontal 94 | ) { 95 | continue; 96 | } 97 | 98 | // If we have visited that neighbor, continue to next neighbor 99 | if (grid[nextRow][nextColumn]) { 100 | continue; 101 | } 102 | 103 | // Remove a wall from either horizontals or verticals 104 | if (direction === 'left') { 105 | verticals[row][column - 1] = true; 106 | } else if (direction === 'right') { 107 | verticals[row][column] = true; 108 | } else if (direction === 'up') { 109 | horizontals[row - 1][column] = true; 110 | } else if (direction === 'down') { 111 | horizontals[row][column] = true; 112 | } 113 | 114 | stepThroughCell(nextRow, nextColumn); 115 | } 116 | }; 117 | 118 | stepThroughCell(startRow, startColumn); 119 | 120 | horizontals.forEach((row, rowIndex) => { 121 | row.forEach((open, columnIndex) => { 122 | if (open) { 123 | return; 124 | } 125 | 126 | const wall = Bodies.rectangle( 127 | columnIndex * unitLengthX + unitLengthX / 2, 128 | rowIndex * unitLengthY + unitLengthY, 129 | unitLengthX, 130 | 5, 131 | { 132 | label: 'wall', 133 | isStatic: true, 134 | render: { 135 | fillStyle: 'red' 136 | } 137 | } 138 | ); 139 | World.add(world, wall); 140 | }); 141 | }); 142 | 143 | verticals.forEach((row, rowIndex) => { 144 | row.forEach((open, columnIndex) => { 145 | if (open) { 146 | return; 147 | } 148 | 149 | const wall = Bodies.rectangle( 150 | columnIndex * unitLengthX + unitLengthX, 151 | rowIndex * unitLengthY + unitLengthY / 2, 152 | 5, 153 | unitLengthY, 154 | { 155 | label: 'wall', 156 | isStatic: true, 157 | render: { 158 | fillStyle: 'red' 159 | } 160 | } 161 | ); 162 | World.add(world, wall); 163 | }); 164 | }); 165 | 166 | // Goal 167 | 168 | const goal = Bodies.rectangle( 169 | width - unitLengthX / 2, 170 | height - unitLengthY / 2, 171 | unitLengthX * 0.7, 172 | unitLengthY * 0.7, 173 | { 174 | label: 'goal', 175 | isStatic: true, 176 | render: { 177 | fillStyle: 'green' 178 | } 179 | } 180 | ); 181 | World.add(world, goal); 182 | 183 | // Ball 184 | 185 | const ballRadius = Math.min(unitLengthX, unitLengthY) / 4; 186 | const ball = Bodies.circle(unitLengthX / 2, unitLengthY / 2, ballRadius, { 187 | label: 'ball', 188 | render: { 189 | fillStyle: 'blue' 190 | } 191 | }); 192 | World.add(world, ball); 193 | 194 | document.addEventListener('keydown', event => { 195 | const { x, y } = ball.velocity; 196 | 197 | if (event.keyCode === 87) { 198 | Body.setVelocity(ball, { x, y: y - 5 }); 199 | } 200 | 201 | if (event.keyCode === 68) { 202 | Body.setVelocity(ball, { x: x + 5, y }); 203 | } 204 | 205 | if (event.keyCode === 83) { 206 | Body.setVelocity(ball, { x, y: y + 5 }); 207 | } 208 | 209 | if (event.keyCode === 65) { 210 | Body.setVelocity(ball, { x: x - 5, y }); 211 | } 212 | }); 213 | 214 | // Win Condition 215 | 216 | Events.on(engine, 'collisionStart', event => { 217 | event.pairs.forEach(collision => { 218 | const labels = ['ball', 'goal']; 219 | 220 | if ( 221 | labels.includes(collision.bodyA.label) && 222 | labels.includes(collision.bodyB.label) 223 | ) { 224 | document.querySelector('.winner').classList.remove('hidden'); 225 | world.gravity.y = 1; 226 | world.bodies.forEach(body => { 227 | if (body.label === 'wall') { 228 | Body.setStatic(body, false); 229 | } 230 | }); 231 | } 232 | }); 233 | }); 234 | -------------------------------------------------------------------------------- /message/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |

24 |
25 | Create your own secret message 26 |
27 |
28 | 29 |
30 |
31 | 32 | 33 | 34 |
35 |
36 | 37 | 43 |
44 |
45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /message/index.js: -------------------------------------------------------------------------------- 1 | const { hash } = window.location; 2 | 3 | const message = atob(hash.replace('#', '')); 4 | 5 | if (message) { 6 | document.querySelector('#message-form').classList.add('hide'); 7 | document.querySelector('#message-show').classList.remove('hide'); 8 | 9 | document.querySelector('h1').innerHTML = message; 10 | } 11 | 12 | document.querySelector('form').addEventListener('submit', event => { 13 | event.preventDefault(); 14 | 15 | document.querySelector('#message-form').classList.add('hide'); 16 | document.querySelector('#link-form').classList.remove('hide'); 17 | 18 | const input = document.querySelector('#message-input'); 19 | const encrypted = btoa(input.value); 20 | 21 | const linkInput = document.querySelector('#link-input'); 22 | linkInput.value = `${window.location}#${encrypted}`; 23 | linkInput.select(); 24 | }); 25 | -------------------------------------------------------------------------------- /movies-testing/autocomplete.js: -------------------------------------------------------------------------------- 1 | const createAutoComplete = ({ 2 | root, 3 | renderOption, 4 | onOptionSelect, 5 | inputValue, 6 | fetchData 7 | }) => { 8 | root.innerHTML = ` 9 | 10 | 11 | 16 | `; 17 | 18 | const input = root.querySelector('input'); 19 | const dropdown = root.querySelector('.dropdown'); 20 | const resultsWrapper = root.querySelector('.results'); 21 | 22 | const onInput = async event => { 23 | const items = await fetchData(event.target.value); 24 | 25 | if (!items.length) { 26 | dropdown.classList.remove('is-active'); 27 | return; 28 | } 29 | 30 | resultsWrapper.innerHTML = ''; 31 | dropdown.classList.add('is-active'); 32 | for (let item of items) { 33 | const option = document.createElement('a'); 34 | 35 | option.classList.add('dropdown-item'); 36 | option.innerHTML = renderOption(item); 37 | option.addEventListener('click', () => { 38 | dropdown.classList.remove('is-active'); 39 | input.value = inputValue(item); 40 | onOptionSelect(item); 41 | }); 42 | 43 | resultsWrapper.appendChild(option); 44 | } 45 | }; 46 | input.addEventListener('input', debounce(onInput, 500)); 47 | 48 | document.addEventListener('click', event => { 49 | if (!root.contains(event.target)) { 50 | dropdown.classList.remove('is-active'); 51 | } 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /movies-testing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

21 | Movie Fight 22 | 23 | 24 | 25 |

26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |

Search For a Movie on Both Sides

44 |

We will tell you which is best!

45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /movies-testing/index.js: -------------------------------------------------------------------------------- 1 | const autoCompleteConfig = { 2 | renderOption(movie) { 3 | const imgSrc = movie.Poster === 'N/A' ? '' : movie.Poster; 4 | return ` 5 | 6 | ${movie.Title} (${movie.Year}) 7 | `; 8 | }, 9 | inputValue(movie) { 10 | return movie.Title; 11 | }, 12 | async fetchData(searchTerm) { 13 | const response = await axios.get('http://www.omdbapi.com/', { 14 | params: { 15 | apikey: 'd9835cc5', 16 | s: searchTerm 17 | } 18 | }); 19 | 20 | if (response.data.Error) { 21 | return []; 22 | } 23 | 24 | return response.data.Search; 25 | } 26 | }; 27 | 28 | createAutoComplete({ 29 | ...autoCompleteConfig, 30 | root: document.querySelector('#left-autocomplete'), 31 | onOptionSelect(movie) { 32 | document.querySelector('.tutorial').classList.add('is-hidden'); 33 | onMovieSelect(movie, document.querySelector('#left-summary'), 'left'); 34 | } 35 | }); 36 | createAutoComplete({ 37 | ...autoCompleteConfig, 38 | root: document.querySelector('#right-autocomplete'), 39 | onOptionSelect(movie) { 40 | document.querySelector('.tutorial').classList.add('is-hidden'); 41 | onMovieSelect(movie, document.querySelector('#right-summary'), 'right'); 42 | } 43 | }); 44 | 45 | let leftMovie; 46 | let rightMovie; 47 | const onMovieSelect = async (movie, summaryElement, side) => { 48 | const response = await axios.get('http://www.omdbapi.com/', { 49 | params: { 50 | apikey: 'd9835cc5', 51 | i: movie.imdbID 52 | } 53 | }); 54 | 55 | summaryElement.innerHTML = movieTemplate(response.data); 56 | 57 | if (side === 'left') { 58 | leftMovie = response.data; 59 | } else { 60 | rightMovie = response.data; 61 | } 62 | 63 | if (leftMovie && rightMovie) { 64 | runComparison(); 65 | } 66 | }; 67 | 68 | const runComparison = () => { 69 | const leftSideStats = document.querySelectorAll( 70 | '#left-summary .notification' 71 | ); 72 | const rightSideStats = document.querySelectorAll( 73 | '#right-summary .notification' 74 | ); 75 | 76 | leftSideStats.forEach((leftStat, index) => { 77 | const rightStat = rightSideStats[index]; 78 | 79 | const leftSideValue = leftStat.dataset.value; 80 | const rightSideValue = rightStat.dataset.value; 81 | 82 | if (rightSideValue > leftSideValue) { 83 | leftStat.classList.remove('is-primary'); 84 | leftStat.classList.add('is-warning'); 85 | } else { 86 | rightStat.classList.remove('is-primary'); 87 | rightStat.classList.add('is-warning'); 88 | } 89 | }); 90 | }; 91 | 92 | const movieTemplate = movieDetail => { 93 | const dollars = parseInt( 94 | movieDetail.BoxOffice.replace(/\$/g, '').replace(/,/g, '') 95 | ); 96 | const metascore = parseInt(movieDetail.Metascore); 97 | const imdbRating = parseFloat(movieDetail.imdbRating); 98 | const imdbVotes = parseInt(movieDetail.imdbVotes.replace(/,/g, '')); 99 | const awards = movieDetail.Awards.split(' ').reduce((prev, word) => { 100 | const value = parseInt(word); 101 | 102 | if (isNaN(value)) { 103 | return prev; 104 | } else { 105 | return prev + value; 106 | } 107 | }, 0); 108 | 109 | return ` 110 |
111 |
112 |

113 | 114 |

115 |
116 |
117 |
118 |

${movieDetail.Title}

119 |

${movieDetail.Genre}

120 |

${movieDetail.Plot}

121 |
122 |
123 |
124 | 125 |
126 |

${movieDetail.Awards}

127 |

Awards

128 |
129 |
130 |

${movieDetail.BoxOffice}

131 |

Box Office

132 |
133 |
134 |

${movieDetail.Metascore}

135 |

Metascore

136 |
137 |
138 |

${movieDetail.imdbRating}

139 |

IMDB Rating

140 |
141 |
142 |

${movieDetail.imdbVotes}

143 |

IMDB Votes

144 |
145 | `; 146 | }; 147 | -------------------------------------------------------------------------------- /movies-testing/style.css: -------------------------------------------------------------------------------- 1 | .tutorial { 2 | width: 600px; 3 | text-align: center; 4 | margin: auto; 5 | } 6 | 7 | figure.image { 8 | display: flex; 9 | justify-content: center; 10 | background-color: black; 11 | } 12 | 13 | figure .image img { 14 | width: 128px; 15 | } 16 | 17 | .notification { 18 | margin-top: 20px !important; 19 | } 20 | 21 | .title .icon { 22 | margin-left: 15px; 23 | } 24 | 25 | .hero { 26 | margin-bottom: 20px; 27 | } 28 | 29 | .forms { 30 | display: flex; 31 | justify-content: space-around; 32 | } 33 | 34 | .results { 35 | max-height: 500px; 36 | overflow-y: scroll; 37 | } 38 | 39 | .dropdown-item { 40 | display: flex; 41 | align-items: center; 42 | height: 60px; 43 | } 44 | 45 | .dropdown-item img { 46 | height: 50px; 47 | margin-right: 10px; 48 | } 49 | -------------------------------------------------------------------------------- /movies-testing/test/autocomplete.test.js: -------------------------------------------------------------------------------- 1 | const waitFor = selector => { 2 | return new Promise((resolve, reject) => { 3 | const interval = setInterval(() => { 4 | if (document.querySelector(selector)) { 5 | clearInterval(interval); 6 | clearTimeout(timeout); 7 | resolve(); 8 | } 9 | }, 30); 10 | 11 | const timeout = setTimeout(() => { 12 | clearInterval(interval); 13 | reject(); 14 | }, 2000); 15 | }); 16 | }; 17 | 18 | beforeEach(() => { 19 | document.querySelector('#target').innerHTML = ''; 20 | createAutoComplete({ 21 | root: document.querySelector('#target'), 22 | fetchData() { 23 | return [ 24 | { Title: 'Avengers' }, 25 | { Title: 'Not Avengers' }, 26 | { Title: 'Some other movie' } 27 | ]; 28 | }, 29 | renderOption(movie) { 30 | return movie.Title; 31 | } 32 | }); 33 | }); 34 | 35 | it('Dropdown starts closed', () => { 36 | const dropdown = document.querySelector('.dropdown'); 37 | 38 | expect(dropdown.className).not.to.include('is-active'); 39 | }); 40 | 41 | it('After searching, dropdown opens up', async () => { 42 | const input = document.querySelector('input'); 43 | input.value = 'avengers'; 44 | input.dispatchEvent(new Event('input')); 45 | 46 | await waitFor('.dropdown-item'); 47 | 48 | const dropdown = document.querySelector('.dropdown'); 49 | expect(dropdown.className).to.include('is-active'); 50 | }); 51 | 52 | it('After searching, displays some results', async () => { 53 | const input = document.querySelector('input'); 54 | input.value = 'avengers'; 55 | input.dispatchEvent(new Event('input')); 56 | 57 | await waitFor('.dropdown-item'); 58 | 59 | const items = document.querySelectorAll('.dropdown-item'); 60 | 61 | expect(items.length).to.equal(3); 62 | }); 63 | -------------------------------------------------------------------------------- /movies-testing/test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Tests 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /movies-testing/utils.js: -------------------------------------------------------------------------------- 1 | const debounce = (func, delay = 1000) => { 2 | let timeoutId; 3 | return (...args) => { 4 | if (timeoutId) { 5 | clearTimeout(timeoutId); 6 | } 7 | timeoutId = setTimeout(() => { 8 | func.apply(null, args); 9 | }, delay); 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /movies/autocomplete.js: -------------------------------------------------------------------------------- 1 | const createAutoComplete = ({ 2 | root, 3 | renderOption, 4 | onOptionSelect, 5 | inputValue, 6 | fetchData 7 | }) => { 8 | root.innerHTML = ` 9 | 10 | 11 | 16 | `; 17 | 18 | const input = root.querySelector('input'); 19 | const dropdown = root.querySelector('.dropdown'); 20 | const resultsWrapper = root.querySelector('.results'); 21 | 22 | const onInput = async event => { 23 | const items = await fetchData(event.target.value); 24 | 25 | if (!items.length) { 26 | dropdown.classList.remove('is-active'); 27 | return; 28 | } 29 | 30 | resultsWrapper.innerHTML = ''; 31 | dropdown.classList.add('is-active'); 32 | for (let item of items) { 33 | const option = document.createElement('a'); 34 | 35 | option.classList.add('dropdown-item'); 36 | option.innerHTML = renderOption(item); 37 | option.addEventListener('click', () => { 38 | dropdown.classList.remove('is-active'); 39 | input.value = inputValue(item); 40 | onOptionSelect(item); 41 | }); 42 | 43 | resultsWrapper.appendChild(option); 44 | } 45 | }; 46 | input.addEventListener('input', debounce(onInput, 500)); 47 | 48 | document.addEventListener('click', event => { 49 | if (!root.contains(event.target)) { 50 | dropdown.classList.remove('is-active'); 51 | } 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /movies/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

21 | Movie Fight 22 | 23 | 24 | 25 |

26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |

Search For a Movie on Both Sides

44 |

We will tell you which is best!

45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /movies/index.js: -------------------------------------------------------------------------------- 1 | const autoCompleteConfig = { 2 | renderOption(movie) { 3 | const imgSrc = movie.Poster === 'N/A' ? '' : movie.Poster; 4 | return ` 5 | 6 | ${movie.Title} (${movie.Year}) 7 | `; 8 | }, 9 | inputValue(movie) { 10 | return movie.Title; 11 | }, 12 | async fetchData(searchTerm) { 13 | const response = await axios.get('http://www.omdbapi.com/', { 14 | params: { 15 | apikey: 'd9835cc5', 16 | s: searchTerm 17 | } 18 | }); 19 | 20 | if (response.data.Error) { 21 | return []; 22 | } 23 | 24 | return response.data.Search; 25 | } 26 | }; 27 | 28 | createAutoComplete({ 29 | ...autoCompleteConfig, 30 | root: document.querySelector('#left-autocomplete'), 31 | onOptionSelect(movie) { 32 | document.querySelector('.tutorial').classList.add('is-hidden'); 33 | onMovieSelect(movie, document.querySelector('#left-summary'), 'left'); 34 | } 35 | }); 36 | createAutoComplete({ 37 | ...autoCompleteConfig, 38 | root: document.querySelector('#right-autocomplete'), 39 | onOptionSelect(movie) { 40 | document.querySelector('.tutorial').classList.add('is-hidden'); 41 | onMovieSelect(movie, document.querySelector('#right-summary'), 'right'); 42 | } 43 | }); 44 | 45 | let leftMovie; 46 | let rightMovie; 47 | const onMovieSelect = async (movie, summaryElement, side) => { 48 | const response = await axios.get('http://www.omdbapi.com/', { 49 | params: { 50 | apikey: 'd9835cc5', 51 | i: movie.imdbID 52 | } 53 | }); 54 | 55 | summaryElement.innerHTML = movieTemplate(response.data); 56 | 57 | if (side === 'left') { 58 | leftMovie = response.data; 59 | } else { 60 | rightMovie = response.data; 61 | } 62 | 63 | if (leftMovie && rightMovie) { 64 | runComparison(); 65 | } 66 | }; 67 | 68 | const runComparison = () => { 69 | const leftSideStats = document.querySelectorAll( 70 | '#left-summary .notification' 71 | ); 72 | const rightSideStats = document.querySelectorAll( 73 | '#right-summary .notification' 74 | ); 75 | 76 | leftSideStats.forEach((leftStat, index) => { 77 | const rightStat = rightSideStats[index]; 78 | 79 | const leftSideValue = leftStat.dataset.value; 80 | const rightSideValue = rightStat.dataset.value; 81 | 82 | if (rightSideValue > leftSideValue) { 83 | leftStat.classList.remove('is-primary'); 84 | leftStat.classList.add('is-warning'); 85 | } else { 86 | rightStat.classList.remove('is-primary'); 87 | rightStat.classList.add('is-warning'); 88 | } 89 | }); 90 | }; 91 | 92 | const movieTemplate = movieDetail => { 93 | const dollars = parseInt( 94 | movieDetail.BoxOffice.replace(/\$/g, '').replace(/,/g, '') 95 | ); 96 | const metascore = parseInt(movieDetail.Metascore); 97 | const imdbRating = parseFloat(movieDetail.imdbRating); 98 | const imdbVotes = parseInt(movieDetail.imdbVotes.replace(/,/g, '')); 99 | const awards = movieDetail.Awards.split(' ').reduce((prev, word) => { 100 | const value = parseInt(word); 101 | 102 | if (isNaN(value)) { 103 | return prev; 104 | } else { 105 | return prev + value; 106 | } 107 | }, 0); 108 | 109 | return ` 110 |
111 |
112 |

113 | 114 |

115 |
116 |
117 |
118 |

${movieDetail.Title}

119 |

${movieDetail.Genre}

120 |

${movieDetail.Plot}

121 |
122 |
123 |
124 | 125 |
126 |

${movieDetail.Awards}

127 |

Awards

128 |
129 |
130 |

${movieDetail.BoxOffice}

131 |

Box Office

132 |
133 |
134 |

${movieDetail.Metascore}

135 |

Metascore

136 |
137 |
138 |

${movieDetail.imdbRating}

139 |

IMDB Rating

140 |
141 |
142 |

${movieDetail.imdbVotes}

143 |

IMDB Votes

144 |
145 | `; 146 | }; 147 | -------------------------------------------------------------------------------- /movies/style.css: -------------------------------------------------------------------------------- 1 | .tutorial { 2 | width: 600px; 3 | text-align: center; 4 | margin: auto; 5 | } 6 | 7 | figure.image { 8 | display: flex; 9 | justify-content: center; 10 | background-color: black; 11 | } 12 | 13 | figure .image img { 14 | width: 128px; 15 | } 16 | 17 | .notification { 18 | margin-top: 20px !important; 19 | } 20 | 21 | .title .icon { 22 | margin-left: 15px; 23 | } 24 | 25 | .hero { 26 | margin-bottom: 20px; 27 | } 28 | 29 | .forms { 30 | display: flex; 31 | justify-content: space-around; 32 | } 33 | 34 | .results { 35 | max-height: 500px; 36 | overflow-y: scroll; 37 | } 38 | 39 | .dropdown-item { 40 | display: flex; 41 | align-items: center; 42 | height: 60px; 43 | } 44 | 45 | .dropdown-item img { 46 | height: 50px; 47 | margin-right: 10px; 48 | } 49 | -------------------------------------------------------------------------------- /movies/utils.js: -------------------------------------------------------------------------------- 1 | const debounce = (func, delay = 1000) => { 2 | let timeoutId; 3 | return (...args) => { 4 | if (timeoutId) { 5 | clearTimeout(timeoutId); 6 | } 7 | timeoutId = setTimeout(() => { 8 | func.apply(null, args); 9 | }, delay); 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /timer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 |
13 |
14 | 15 |
16 | 17 | 18 |
19 |
20 | 21 | 30 | 31 |
32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /timer/index.js: -------------------------------------------------------------------------------- 1 | const durationInput = document.querySelector('#duration'); 2 | const startButton = document.querySelector('#start'); 3 | const pauseButton = document.querySelector('#pause'); 4 | const circle = document.querySelector('circle'); 5 | 6 | const perimeter = circle.getAttribute('r') * 2 * Math.PI; 7 | circle.setAttribute('stroke-dasharray', perimeter); 8 | 9 | let duration; 10 | const timer = new Timer(durationInput, startButton, pauseButton, { 11 | onStart(totalDuration) { 12 | duration = totalDuration; 13 | }, 14 | onTick(timeRemaining) { 15 | circle.setAttribute( 16 | 'stroke-dashoffset', 17 | (perimeter * timeRemaining) / duration - perimeter 18 | ); 19 | }, 20 | onComplete() { 21 | console.log('Timer is completed'); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /timer/style.css: -------------------------------------------------------------------------------- 1 | .dial { 2 | height: 400px; 3 | width: 400px; 4 | } 5 | 6 | .timer { 7 | position: relative; 8 | display: inline-block; 9 | } 10 | 11 | * { 12 | font: inherit; 13 | } 14 | 15 | body { 16 | font-family: 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 17 | 'Droid Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 18 | } 19 | 20 | .timer input { 21 | display: block; 22 | border: none; 23 | width: 240px; 24 | font-size: 90px; 25 | text-align: center; 26 | } 27 | 28 | .timer button { 29 | border: none; 30 | font-size: 36px; 31 | cursor: pointer; 32 | } 33 | 34 | .timer button:focus { 35 | outline: none; 36 | } 37 | 38 | .timer input:focus { 39 | outline: none; 40 | } 41 | 42 | body { 43 | display: flex; 44 | justify-content: center; 45 | align-items: center; 46 | height: 100vh; 47 | } 48 | 49 | .controls { 50 | position: absolute; 51 | top: 0; 52 | left: 0; 53 | right: 0; 54 | bottom: 0; 55 | display: flex; 56 | justify-content: center; 57 | align-items: center; 58 | flex-direction: column; 59 | } 60 | -------------------------------------------------------------------------------- /timer/timer.js: -------------------------------------------------------------------------------- 1 | class Timer { 2 | constructor(durationInput, startButton, pauseButton, callbacks) { 3 | this.durationInput = durationInput; 4 | this.startButton = startButton; 5 | this.pauseButton = pauseButton; 6 | if (callbacks) { 7 | this.onStart = callbacks.onStart; 8 | this.onTick = callbacks.onTick; 9 | this.onComplete = callbacks.onComplete; 10 | } 11 | 12 | this.startButton.addEventListener('click', this.start); 13 | this.pauseButton.addEventListener('click', this.pause); 14 | } 15 | 16 | start = () => { 17 | if (this.onStart) { 18 | this.onStart(this.timeRemaining); 19 | } 20 | this.tick(); 21 | this.interval = setInterval(this.tick, 20); 22 | }; 23 | 24 | pause = () => { 25 | clearInterval(this.interval); 26 | }; 27 | 28 | tick = () => { 29 | if (this.timeRemaining <= 0) { 30 | this.pause(); 31 | if (this.onComplete) { 32 | this.onComplete(); 33 | } 34 | } else { 35 | this.timeRemaining = this.timeRemaining - 0.02; 36 | if (this.onTick) { 37 | this.onTick(this.timeRemaining); 38 | } 39 | } 40 | }; 41 | 42 | get timeRemaining() { 43 | return parseFloat(this.durationInput.value); 44 | } 45 | 46 | set timeRemaining(time) { 47 | this.durationInput.value = time.toFixed(2); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tme/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const Runner = require('./runner'); 4 | const runner = new Runner(); 5 | 6 | const run = async () => { 7 | await runner.collectFiles(process.cwd()); 8 | runner.runTests(); 9 | }; 10 | 11 | run(); 12 | -------------------------------------------------------------------------------- /tme/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tme", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/color-name": { 8 | "version": "1.1.1", 9 | "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", 10 | "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" 11 | }, 12 | "abab": { 13 | "version": "2.0.3", 14 | "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", 15 | "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" 16 | }, 17 | "acorn": { 18 | "version": "7.1.0", 19 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", 20 | "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==" 21 | }, 22 | "acorn-globals": { 23 | "version": "4.3.4", 24 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", 25 | "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", 26 | "requires": { 27 | "acorn": "^6.0.1", 28 | "acorn-walk": "^6.0.1" 29 | }, 30 | "dependencies": { 31 | "acorn": { 32 | "version": "6.4.0", 33 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", 34 | "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==" 35 | } 36 | } 37 | }, 38 | "acorn-walk": { 39 | "version": "6.2.0", 40 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", 41 | "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" 42 | }, 43 | "ajv": { 44 | "version": "6.10.2", 45 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", 46 | "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", 47 | "requires": { 48 | "fast-deep-equal": "^2.0.1", 49 | "fast-json-stable-stringify": "^2.0.0", 50 | "json-schema-traverse": "^0.4.1", 51 | "uri-js": "^4.2.2" 52 | } 53 | }, 54 | "ansi-styles": { 55 | "version": "4.2.0", 56 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.0.tgz", 57 | "integrity": "sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==", 58 | "requires": { 59 | "@types/color-name": "^1.1.1", 60 | "color-convert": "^2.0.1" 61 | } 62 | }, 63 | "array-equal": { 64 | "version": "1.0.0", 65 | "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", 66 | "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" 67 | }, 68 | "asn1": { 69 | "version": "0.2.4", 70 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 71 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 72 | "requires": { 73 | "safer-buffer": "~2.1.0" 74 | } 75 | }, 76 | "assert-plus": { 77 | "version": "1.0.0", 78 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 79 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 80 | }, 81 | "async-limiter": { 82 | "version": "1.0.1", 83 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 84 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" 85 | }, 86 | "asynckit": { 87 | "version": "0.4.0", 88 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 89 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 90 | }, 91 | "aws-sign2": { 92 | "version": "0.7.0", 93 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 94 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 95 | }, 96 | "aws4": { 97 | "version": "1.9.0", 98 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", 99 | "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==" 100 | }, 101 | "bcrypt-pbkdf": { 102 | "version": "1.0.2", 103 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 104 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 105 | "requires": { 106 | "tweetnacl": "^0.14.3" 107 | } 108 | }, 109 | "browser-process-hrtime": { 110 | "version": "0.1.3", 111 | "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", 112 | "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" 113 | }, 114 | "caseless": { 115 | "version": "0.12.0", 116 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 117 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 118 | }, 119 | "chalk": { 120 | "version": "3.0.0", 121 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", 122 | "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", 123 | "requires": { 124 | "ansi-styles": "^4.1.0", 125 | "supports-color": "^7.1.0" 126 | } 127 | }, 128 | "color-convert": { 129 | "version": "2.0.1", 130 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 131 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 132 | "requires": { 133 | "color-name": "~1.1.4" 134 | } 135 | }, 136 | "color-name": { 137 | "version": "1.1.4", 138 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 139 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 140 | }, 141 | "combined-stream": { 142 | "version": "1.0.8", 143 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 144 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 145 | "requires": { 146 | "delayed-stream": "~1.0.0" 147 | } 148 | }, 149 | "core-util-is": { 150 | "version": "1.0.2", 151 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 152 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 153 | }, 154 | "cssom": { 155 | "version": "0.4.4", 156 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", 157 | "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" 158 | }, 159 | "cssstyle": { 160 | "version": "2.0.0", 161 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.0.0.tgz", 162 | "integrity": "sha512-QXSAu2WBsSRXCPjvI43Y40m6fMevvyRm8JVAuF9ksQz5jha4pWP1wpaK7Yu5oLFc6+XAY+hj8YhefyXcBB53gg==", 163 | "requires": { 164 | "cssom": "~0.3.6" 165 | }, 166 | "dependencies": { 167 | "cssom": { 168 | "version": "0.3.8", 169 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", 170 | "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" 171 | } 172 | } 173 | }, 174 | "dashdash": { 175 | "version": "1.14.1", 176 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 177 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 178 | "requires": { 179 | "assert-plus": "^1.0.0" 180 | } 181 | }, 182 | "data-urls": { 183 | "version": "1.1.0", 184 | "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", 185 | "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", 186 | "requires": { 187 | "abab": "^2.0.0", 188 | "whatwg-mimetype": "^2.2.0", 189 | "whatwg-url": "^7.0.0" 190 | } 191 | }, 192 | "deep-is": { 193 | "version": "0.1.3", 194 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 195 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" 196 | }, 197 | "delayed-stream": { 198 | "version": "1.0.0", 199 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 200 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 201 | }, 202 | "domexception": { 203 | "version": "1.0.1", 204 | "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", 205 | "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", 206 | "requires": { 207 | "webidl-conversions": "^4.0.2" 208 | } 209 | }, 210 | "ecc-jsbn": { 211 | "version": "0.1.2", 212 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 213 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 214 | "requires": { 215 | "jsbn": "~0.1.0", 216 | "safer-buffer": "^2.1.0" 217 | } 218 | }, 219 | "escodegen": { 220 | "version": "1.12.0", 221 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", 222 | "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", 223 | "requires": { 224 | "esprima": "^3.1.3", 225 | "estraverse": "^4.2.0", 226 | "esutils": "^2.0.2", 227 | "optionator": "^0.8.1", 228 | "source-map": "~0.6.1" 229 | } 230 | }, 231 | "esprima": { 232 | "version": "3.1.3", 233 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", 234 | "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" 235 | }, 236 | "estraverse": { 237 | "version": "4.3.0", 238 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 239 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" 240 | }, 241 | "esutils": { 242 | "version": "2.0.3", 243 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 244 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" 245 | }, 246 | "extend": { 247 | "version": "3.0.2", 248 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 249 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 250 | }, 251 | "extsprintf": { 252 | "version": "1.3.0", 253 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 254 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 255 | }, 256 | "fast-deep-equal": { 257 | "version": "2.0.1", 258 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 259 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 260 | }, 261 | "fast-json-stable-stringify": { 262 | "version": "2.0.0", 263 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 264 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 265 | }, 266 | "fast-levenshtein": { 267 | "version": "2.0.6", 268 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 269 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" 270 | }, 271 | "forever-agent": { 272 | "version": "0.6.1", 273 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 274 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 275 | }, 276 | "form-data": { 277 | "version": "2.3.3", 278 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 279 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 280 | "requires": { 281 | "asynckit": "^0.4.0", 282 | "combined-stream": "^1.0.6", 283 | "mime-types": "^2.1.12" 284 | } 285 | }, 286 | "getpass": { 287 | "version": "0.1.7", 288 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 289 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 290 | "requires": { 291 | "assert-plus": "^1.0.0" 292 | } 293 | }, 294 | "har-schema": { 295 | "version": "2.0.0", 296 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 297 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 298 | }, 299 | "har-validator": { 300 | "version": "5.1.3", 301 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 302 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 303 | "requires": { 304 | "ajv": "^6.5.5", 305 | "har-schema": "^2.0.0" 306 | } 307 | }, 308 | "has-flag": { 309 | "version": "4.0.0", 310 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 311 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 312 | }, 313 | "html-encoding-sniffer": { 314 | "version": "1.0.2", 315 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", 316 | "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", 317 | "requires": { 318 | "whatwg-encoding": "^1.0.1" 319 | } 320 | }, 321 | "http-signature": { 322 | "version": "1.2.0", 323 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 324 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 325 | "requires": { 326 | "assert-plus": "^1.0.0", 327 | "jsprim": "^1.2.2", 328 | "sshpk": "^1.7.0" 329 | } 330 | }, 331 | "iconv-lite": { 332 | "version": "0.4.24", 333 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 334 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 335 | "requires": { 336 | "safer-buffer": ">= 2.1.2 < 3" 337 | } 338 | }, 339 | "ip-regex": { 340 | "version": "2.1.0", 341 | "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", 342 | "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" 343 | }, 344 | "is-typedarray": { 345 | "version": "1.0.0", 346 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 347 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 348 | }, 349 | "isstream": { 350 | "version": "0.1.2", 351 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 352 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 353 | }, 354 | "jsbn": { 355 | "version": "0.1.1", 356 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 357 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 358 | }, 359 | "jsdom": { 360 | "version": "15.2.1", 361 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", 362 | "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", 363 | "requires": { 364 | "abab": "^2.0.0", 365 | "acorn": "^7.1.0", 366 | "acorn-globals": "^4.3.2", 367 | "array-equal": "^1.0.0", 368 | "cssom": "^0.4.1", 369 | "cssstyle": "^2.0.0", 370 | "data-urls": "^1.1.0", 371 | "domexception": "^1.0.1", 372 | "escodegen": "^1.11.1", 373 | "html-encoding-sniffer": "^1.0.2", 374 | "nwsapi": "^2.2.0", 375 | "parse5": "5.1.0", 376 | "pn": "^1.1.0", 377 | "request": "^2.88.0", 378 | "request-promise-native": "^1.0.7", 379 | "saxes": "^3.1.9", 380 | "symbol-tree": "^3.2.2", 381 | "tough-cookie": "^3.0.1", 382 | "w3c-hr-time": "^1.0.1", 383 | "w3c-xmlserializer": "^1.1.2", 384 | "webidl-conversions": "^4.0.2", 385 | "whatwg-encoding": "^1.0.5", 386 | "whatwg-mimetype": "^2.3.0", 387 | "whatwg-url": "^7.0.0", 388 | "ws": "^7.0.0", 389 | "xml-name-validator": "^3.0.0" 390 | } 391 | }, 392 | "json-schema": { 393 | "version": "0.2.3", 394 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 395 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 396 | }, 397 | "json-schema-traverse": { 398 | "version": "0.4.1", 399 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 400 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 401 | }, 402 | "json-stringify-safe": { 403 | "version": "5.0.1", 404 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 405 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 406 | }, 407 | "jsprim": { 408 | "version": "1.4.1", 409 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 410 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 411 | "requires": { 412 | "assert-plus": "1.0.0", 413 | "extsprintf": "1.3.0", 414 | "json-schema": "0.2.3", 415 | "verror": "1.10.0" 416 | } 417 | }, 418 | "levn": { 419 | "version": "0.3.0", 420 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 421 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 422 | "requires": { 423 | "prelude-ls": "~1.1.2", 424 | "type-check": "~0.3.2" 425 | } 426 | }, 427 | "lodash": { 428 | "version": "4.17.15", 429 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 430 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" 431 | }, 432 | "lodash.sortby": { 433 | "version": "4.7.0", 434 | "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", 435 | "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" 436 | }, 437 | "mime-db": { 438 | "version": "1.42.0", 439 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", 440 | "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" 441 | }, 442 | "mime-types": { 443 | "version": "2.1.25", 444 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", 445 | "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", 446 | "requires": { 447 | "mime-db": "1.42.0" 448 | } 449 | }, 450 | "nwsapi": { 451 | "version": "2.2.0", 452 | "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", 453 | "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" 454 | }, 455 | "oauth-sign": { 456 | "version": "0.9.0", 457 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 458 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 459 | }, 460 | "optionator": { 461 | "version": "0.8.3", 462 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", 463 | "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", 464 | "requires": { 465 | "deep-is": "~0.1.3", 466 | "fast-levenshtein": "~2.0.6", 467 | "levn": "~0.3.0", 468 | "prelude-ls": "~1.1.2", 469 | "type-check": "~0.3.2", 470 | "word-wrap": "~1.2.3" 471 | } 472 | }, 473 | "parse5": { 474 | "version": "5.1.0", 475 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", 476 | "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" 477 | }, 478 | "performance-now": { 479 | "version": "2.1.0", 480 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 481 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 482 | }, 483 | "pn": { 484 | "version": "1.1.0", 485 | "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", 486 | "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" 487 | }, 488 | "prelude-ls": { 489 | "version": "1.1.2", 490 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 491 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" 492 | }, 493 | "psl": { 494 | "version": "1.4.0", 495 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", 496 | "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" 497 | }, 498 | "punycode": { 499 | "version": "2.1.1", 500 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 501 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 502 | }, 503 | "qs": { 504 | "version": "6.5.2", 505 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 506 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 507 | }, 508 | "request": { 509 | "version": "2.88.0", 510 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 511 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 512 | "requires": { 513 | "aws-sign2": "~0.7.0", 514 | "aws4": "^1.8.0", 515 | "caseless": "~0.12.0", 516 | "combined-stream": "~1.0.6", 517 | "extend": "~3.0.2", 518 | "forever-agent": "~0.6.1", 519 | "form-data": "~2.3.2", 520 | "har-validator": "~5.1.0", 521 | "http-signature": "~1.2.0", 522 | "is-typedarray": "~1.0.0", 523 | "isstream": "~0.1.2", 524 | "json-stringify-safe": "~5.0.1", 525 | "mime-types": "~2.1.19", 526 | "oauth-sign": "~0.9.0", 527 | "performance-now": "^2.1.0", 528 | "qs": "~6.5.2", 529 | "safe-buffer": "^5.1.2", 530 | "tough-cookie": "~2.4.3", 531 | "tunnel-agent": "^0.6.0", 532 | "uuid": "^3.3.2" 533 | }, 534 | "dependencies": { 535 | "punycode": { 536 | "version": "1.4.1", 537 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 538 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 539 | }, 540 | "tough-cookie": { 541 | "version": "2.4.3", 542 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 543 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 544 | "requires": { 545 | "psl": "^1.1.24", 546 | "punycode": "^1.4.1" 547 | } 548 | } 549 | } 550 | }, 551 | "request-promise-core": { 552 | "version": "1.1.3", 553 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", 554 | "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", 555 | "requires": { 556 | "lodash": "^4.17.15" 557 | } 558 | }, 559 | "request-promise-native": { 560 | "version": "1.0.8", 561 | "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", 562 | "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", 563 | "requires": { 564 | "request-promise-core": "1.1.3", 565 | "stealthy-require": "^1.1.1", 566 | "tough-cookie": "^2.3.3" 567 | }, 568 | "dependencies": { 569 | "tough-cookie": { 570 | "version": "2.5.0", 571 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 572 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 573 | "requires": { 574 | "psl": "^1.1.28", 575 | "punycode": "^2.1.1" 576 | } 577 | } 578 | } 579 | }, 580 | "safe-buffer": { 581 | "version": "5.2.0", 582 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 583 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 584 | }, 585 | "safer-buffer": { 586 | "version": "2.1.2", 587 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 588 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 589 | }, 590 | "saxes": { 591 | "version": "3.1.11", 592 | "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", 593 | "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", 594 | "requires": { 595 | "xmlchars": "^2.1.1" 596 | } 597 | }, 598 | "source-map": { 599 | "version": "0.6.1", 600 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 601 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 602 | "optional": true 603 | }, 604 | "sshpk": { 605 | "version": "1.16.1", 606 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 607 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 608 | "requires": { 609 | "asn1": "~0.2.3", 610 | "assert-plus": "^1.0.0", 611 | "bcrypt-pbkdf": "^1.0.0", 612 | "dashdash": "^1.12.0", 613 | "ecc-jsbn": "~0.1.1", 614 | "getpass": "^0.1.1", 615 | "jsbn": "~0.1.0", 616 | "safer-buffer": "^2.0.2", 617 | "tweetnacl": "~0.14.0" 618 | } 619 | }, 620 | "stealthy-require": { 621 | "version": "1.1.1", 622 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 623 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 624 | }, 625 | "supports-color": { 626 | "version": "7.1.0", 627 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", 628 | "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", 629 | "requires": { 630 | "has-flag": "^4.0.0" 631 | } 632 | }, 633 | "symbol-tree": { 634 | "version": "3.2.4", 635 | "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", 636 | "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" 637 | }, 638 | "tough-cookie": { 639 | "version": "3.0.1", 640 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", 641 | "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", 642 | "requires": { 643 | "ip-regex": "^2.1.0", 644 | "psl": "^1.1.28", 645 | "punycode": "^2.1.1" 646 | } 647 | }, 648 | "tr46": { 649 | "version": "1.0.1", 650 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", 651 | "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", 652 | "requires": { 653 | "punycode": "^2.1.0" 654 | } 655 | }, 656 | "tunnel-agent": { 657 | "version": "0.6.0", 658 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 659 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 660 | "requires": { 661 | "safe-buffer": "^5.0.1" 662 | } 663 | }, 664 | "tweetnacl": { 665 | "version": "0.14.5", 666 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 667 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 668 | }, 669 | "type-check": { 670 | "version": "0.3.2", 671 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 672 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 673 | "requires": { 674 | "prelude-ls": "~1.1.2" 675 | } 676 | }, 677 | "uri-js": { 678 | "version": "4.2.2", 679 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 680 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 681 | "requires": { 682 | "punycode": "^2.1.0" 683 | } 684 | }, 685 | "uuid": { 686 | "version": "3.3.3", 687 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", 688 | "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" 689 | }, 690 | "verror": { 691 | "version": "1.10.0", 692 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 693 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 694 | "requires": { 695 | "assert-plus": "^1.0.0", 696 | "core-util-is": "1.0.2", 697 | "extsprintf": "^1.2.0" 698 | } 699 | }, 700 | "w3c-hr-time": { 701 | "version": "1.0.1", 702 | "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", 703 | "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", 704 | "requires": { 705 | "browser-process-hrtime": "^0.1.2" 706 | } 707 | }, 708 | "w3c-xmlserializer": { 709 | "version": "1.1.2", 710 | "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", 711 | "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", 712 | "requires": { 713 | "domexception": "^1.0.1", 714 | "webidl-conversions": "^4.0.2", 715 | "xml-name-validator": "^3.0.0" 716 | } 717 | }, 718 | "webidl-conversions": { 719 | "version": "4.0.2", 720 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", 721 | "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" 722 | }, 723 | "whatwg-encoding": { 724 | "version": "1.0.5", 725 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", 726 | "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", 727 | "requires": { 728 | "iconv-lite": "0.4.24" 729 | } 730 | }, 731 | "whatwg-mimetype": { 732 | "version": "2.3.0", 733 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", 734 | "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" 735 | }, 736 | "whatwg-url": { 737 | "version": "7.1.0", 738 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", 739 | "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", 740 | "requires": { 741 | "lodash.sortby": "^4.7.0", 742 | "tr46": "^1.0.1", 743 | "webidl-conversions": "^4.0.2" 744 | } 745 | }, 746 | "word-wrap": { 747 | "version": "1.2.3", 748 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 749 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" 750 | }, 751 | "ws": { 752 | "version": "7.2.0", 753 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.0.tgz", 754 | "integrity": "sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==", 755 | "requires": { 756 | "async-limiter": "^1.0.0" 757 | } 758 | }, 759 | "xml-name-validator": { 760 | "version": "3.0.0", 761 | "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", 762 | "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" 763 | }, 764 | "xmlchars": { 765 | "version": "2.2.0", 766 | "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", 767 | "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" 768 | } 769 | } 770 | } 771 | -------------------------------------------------------------------------------- /tme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tme", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "bin": { 13 | "tme": "index.js" 14 | }, 15 | "dependencies": { 16 | "chalk": "^3.0.0", 17 | "jsdom": "^15.2.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tme/render.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const jsdom = require('jsdom'); 3 | const { JSDOM } = jsdom; 4 | 5 | const render = async filename => { 6 | const filePath = path.join(process.cwd(), filename); 7 | 8 | const dom = await JSDOM.fromFile(filePath, { 9 | runScripts: 'dangerously', 10 | resources: 'usable' 11 | }); 12 | 13 | return new Promise((resolve, reject) => { 14 | dom.window.document.addEventListener('DOMContentLoaded', () => { 15 | resolve(dom); 16 | }); 17 | }); 18 | }; 19 | 20 | module.exports = render; 21 | -------------------------------------------------------------------------------- /tme/runner.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const chalk = require('chalk'); 4 | const render = require('./render'); 5 | 6 | const forbiddenDirs = ['node_modules']; 7 | 8 | class Runner { 9 | constructor() { 10 | this.testFiles = []; 11 | } 12 | 13 | async runTests() { 14 | for (let file of this.testFiles) { 15 | console.log(chalk.gray(`---- ${file.shortName}`)); 16 | const beforeEaches = []; 17 | global.render = render; 18 | global.beforeEach = fn => { 19 | beforeEaches.push(fn); 20 | }; 21 | global.it = async (desc, fn) => { 22 | beforeEaches.forEach(func => func()); 23 | try { 24 | await fn(); 25 | console.log(chalk.green(`\tOK - ${desc}`)); 26 | } catch (err) { 27 | const message = err.message.replace(/\n/g, '\n\t\t'); 28 | console.log(chalk.red(`\tX - ${desc}`)); 29 | console.log(chalk.red('\t', message)); 30 | } 31 | }; 32 | 33 | try { 34 | require(file.name); 35 | } catch (err) { 36 | console.log(chalk.red(err)); 37 | } 38 | } 39 | } 40 | 41 | async collectFiles(targetPath) { 42 | const files = await fs.promises.readdir(targetPath); 43 | 44 | for (let file of files) { 45 | const filepath = path.join(targetPath, file); 46 | const stats = await fs.promises.lstat(filepath); 47 | 48 | if (stats.isFile() && file.includes('.test.js')) { 49 | this.testFiles.push({ name: filepath, shortName: file }); 50 | } else if (stats.isDirectory() && !forbiddenDirs.includes(file)) { 51 | const childFiles = await fs.promises.readdir(filepath); 52 | 53 | files.push(...childFiles.map(f => path.join(file, f))); 54 | } 55 | } 56 | } 57 | } 58 | 59 | module.exports = Runner; 60 | -------------------------------------------------------------------------------- /tme/sampleproject/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | forEach(arr, fn) { 3 | for (let element of arr) { 4 | fn(element); 5 | } 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /tme/sampleproject/test/forEach.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { forEach } = require('../index'); 3 | 4 | let numbers; 5 | beforeEach(() => { 6 | numbers = [1, 2, 3]; 7 | }); 8 | 9 | it('should sum an array', () => { 10 | let total = 0; 11 | forEach(numbers, value => { 12 | total += value; 13 | }); 14 | 15 | assert.strictEqual(total, 6); 16 | numbers.push(3); 17 | numbers.push(3); 18 | numbers.push(3); 19 | numbers.push(3); 20 | }); 21 | 22 | it('beforeEach is ran each time', () => { 23 | assert.strictEqual(numbers.length, 4); 24 | }); 25 | -------------------------------------------------------------------------------- /tme/samplewebproject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 |
10 |

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tme/samplewebproject/index.js: -------------------------------------------------------------------------------- 1 | document.querySelector('form').addEventListener('submit', event => { 2 | event.preventDefault(); 3 | 4 | const { value } = document.querySelector('input'); 5 | 6 | const header = document.querySelector('h1'); 7 | if (value.includes('@')) { 8 | header.innerHTML = 'Looks good!'; 9 | } else { 10 | header.innerHTML = 'Invalid email'; 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /tme/samplewebproject/test/app.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | it('has a text input', async () => { 4 | const dom = await render('index.html'); 5 | 6 | const input = dom.window.document.querySelector('input'); 7 | 8 | assert(input); 9 | }); 10 | 11 | it('shows a success message with a valid email', async () => { 12 | const dom = await render('index.html'); 13 | 14 | const input = dom.window.document.querySelector('input'); 15 | input.value = 'alskdjf@aslkdjf.com'; 16 | dom.window.document 17 | .querySelector('form') 18 | .dispatchEvent(new dom.window.Event('submit')); 19 | 20 | const h1 = dom.window.document.querySelector('h1'); 21 | 22 | assert.strictEqual(h1.innerHTML, 'Looks good!'); 23 | }); 24 | 25 | it('shows a fail message with a invalid email', async () => { 26 | const dom = await render('index.html'); 27 | 28 | const input = dom.window.document.querySelector('input'); 29 | input.value = 'aasdfsdf'; 30 | dom.window.document 31 | .querySelector('form') 32 | .dispatchEvent(new dom.window.Event('submit')); 33 | 34 | const h1 = dom.window.document.querySelector('h1'); 35 | 36 | assert.strictEqual(h1.innerHTML, 'Invalid email'); 37 | }); 38 | -------------------------------------------------------------------------------- /watchit/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const debounce = require('lodash.debounce'); 4 | const chokidar = require('chokidar'); 5 | const program = require('caporal'); 6 | const fs = require('fs'); 7 | const { spawn } = require('child_process'); 8 | const chalk = require('chalk'); 9 | 10 | program 11 | .version('0.0.1') 12 | .argument('[filename]', 'Name of a file to execute') 13 | .action(async ({ filename }) => { 14 | const name = filename || 'index.js'; 15 | 16 | try { 17 | await fs.promises.access(name); 18 | } catch (err) { 19 | throw new Error(`Could not find the file ${name}`); 20 | } 21 | 22 | let proc; 23 | const start = debounce(() => { 24 | if (proc) { 25 | proc.kill(); 26 | } 27 | console.log(chalk.blue('>>>> Starting process...')); 28 | proc = spawn('node', [name], { stdio: 'inherit' }); 29 | }, 100); 30 | 31 | chokidar 32 | .watch('.') 33 | .on('add', start) 34 | .on('change', start) 35 | .on('unlink', start); 36 | }); 37 | 38 | program.parse(process.argv); 39 | -------------------------------------------------------------------------------- /watchit/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "watchit", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi": { 8 | "version": "0.3.1", 9 | "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", 10 | "integrity": "sha1-DELU+xcWDVqa8eSEus4cZpIsGyE=" 11 | }, 12 | "ansi-escapes": { 13 | "version": "1.4.0", 14 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", 15 | "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" 16 | }, 17 | "ansi-regex": { 18 | "version": "3.0.0", 19 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 20 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" 21 | }, 22 | "ansi-styles": { 23 | "version": "4.1.0", 24 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.1.0.tgz", 25 | "integrity": "sha512-Qts4KCLKG+waHc9C4m07weIY8qyeixoS0h6RnbsNVD6Fw+pEZGW3vTyObL3WXpE09Mq4Oi7/lBEyLmOiLtlYWQ==", 26 | "requires": { 27 | "color-convert": "^2.0.1" 28 | } 29 | }, 30 | "anymatch": { 31 | "version": "3.1.1", 32 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", 33 | "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", 34 | "requires": { 35 | "normalize-path": "^3.0.0", 36 | "picomatch": "^2.0.4" 37 | } 38 | }, 39 | "are-we-there-yet": { 40 | "version": "1.1.5", 41 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 42 | "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 43 | "requires": { 44 | "delegates": "^1.0.0", 45 | "readable-stream": "^2.0.6" 46 | } 47 | }, 48 | "async": { 49 | "version": "1.0.0", 50 | "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", 51 | "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" 52 | }, 53 | "binary-extensions": { 54 | "version": "2.0.0", 55 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", 56 | "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" 57 | }, 58 | "bluebird": { 59 | "version": "3.7.1", 60 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", 61 | "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==" 62 | }, 63 | "braces": { 64 | "version": "3.0.2", 65 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 66 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 67 | "requires": { 68 | "fill-range": "^7.0.1" 69 | } 70 | }, 71 | "buffer-from": { 72 | "version": "1.1.1", 73 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 74 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 75 | }, 76 | "caporal": { 77 | "version": "1.3.0", 78 | "resolved": "https://registry.npmjs.org/caporal/-/caporal-1.3.0.tgz", 79 | "integrity": "sha512-4bj21UXbEu5cF+1gVjhwwRqMhY3lR7CUTlBr6YX3uzftL4l/sbu8EoHfOLZWHr+HeiosW9fTWkQCS2UZMAk5lw==", 80 | "requires": { 81 | "bluebird": "^3.4.7", 82 | "cli-table3": "^0.5.0", 83 | "colorette": "^1.0.1", 84 | "fast-levenshtein": "^2.0.6", 85 | "lodash": "^4.17.14", 86 | "micromist": "1.1.0", 87 | "prettyjson": "^1.2.1", 88 | "tabtab": "^2.2.2", 89 | "winston": "^2.3.1" 90 | } 91 | }, 92 | "chalk": { 93 | "version": "3.0.0", 94 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", 95 | "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", 96 | "requires": { 97 | "ansi-styles": "^4.1.0", 98 | "supports-color": "^7.1.0" 99 | } 100 | }, 101 | "chokidar": { 102 | "version": "3.3.0", 103 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", 104 | "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", 105 | "requires": { 106 | "anymatch": "~3.1.1", 107 | "braces": "~3.0.2", 108 | "fsevents": "~2.1.1", 109 | "glob-parent": "~5.1.0", 110 | "is-binary-path": "~2.1.0", 111 | "is-glob": "~4.0.1", 112 | "normalize-path": "~3.0.0", 113 | "readdirp": "~3.2.0" 114 | } 115 | }, 116 | "cli-cursor": { 117 | "version": "1.0.2", 118 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", 119 | "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", 120 | "requires": { 121 | "restore-cursor": "^1.0.1" 122 | } 123 | }, 124 | "cli-table3": { 125 | "version": "0.5.1", 126 | "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", 127 | "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", 128 | "requires": { 129 | "colors": "^1.1.2", 130 | "object-assign": "^4.1.0", 131 | "string-width": "^2.1.1" 132 | } 133 | }, 134 | "cli-width": { 135 | "version": "2.2.0", 136 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", 137 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" 138 | }, 139 | "code-point-at": { 140 | "version": "1.1.0", 141 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 142 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 143 | }, 144 | "color-convert": { 145 | "version": "2.0.1", 146 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 147 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 148 | "requires": { 149 | "color-name": "~1.1.4" 150 | } 151 | }, 152 | "color-name": { 153 | "version": "1.1.4", 154 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 155 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 156 | }, 157 | "colorette": { 158 | "version": "1.1.0", 159 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz", 160 | "integrity": "sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg==" 161 | }, 162 | "colors": { 163 | "version": "1.4.0", 164 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 165 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 166 | }, 167 | "concat-stream": { 168 | "version": "1.6.2", 169 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 170 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 171 | "requires": { 172 | "buffer-from": "^1.0.0", 173 | "inherits": "^2.0.3", 174 | "readable-stream": "^2.2.2", 175 | "typedarray": "^0.0.6" 176 | } 177 | }, 178 | "core-util-is": { 179 | "version": "1.0.2", 180 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 181 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 182 | }, 183 | "cycle": { 184 | "version": "1.0.3", 185 | "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", 186 | "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" 187 | }, 188 | "debug": { 189 | "version": "2.6.9", 190 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 191 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 192 | "requires": { 193 | "ms": "2.0.0" 194 | } 195 | }, 196 | "delegates": { 197 | "version": "1.0.0", 198 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 199 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 200 | }, 201 | "escape-string-regexp": { 202 | "version": "1.0.5", 203 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 204 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 205 | }, 206 | "exit-hook": { 207 | "version": "1.1.1", 208 | "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", 209 | "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" 210 | }, 211 | "extend": { 212 | "version": "3.0.2", 213 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 214 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 215 | }, 216 | "external-editor": { 217 | "version": "1.1.1", 218 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", 219 | "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", 220 | "requires": { 221 | "extend": "^3.0.0", 222 | "spawn-sync": "^1.0.15", 223 | "tmp": "^0.0.29" 224 | } 225 | }, 226 | "eyes": { 227 | "version": "0.1.8", 228 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 229 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" 230 | }, 231 | "fast-levenshtein": { 232 | "version": "2.0.6", 233 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 234 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" 235 | }, 236 | "figures": { 237 | "version": "1.7.0", 238 | "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", 239 | "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", 240 | "requires": { 241 | "escape-string-regexp": "^1.0.5", 242 | "object-assign": "^4.1.0" 243 | } 244 | }, 245 | "fill-range": { 246 | "version": "7.0.1", 247 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 248 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 249 | "requires": { 250 | "to-regex-range": "^5.0.1" 251 | } 252 | }, 253 | "fsevents": { 254 | "version": "2.1.1", 255 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.1.tgz", 256 | "integrity": "sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw==", 257 | "optional": true 258 | }, 259 | "gauge": { 260 | "version": "1.2.7", 261 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz", 262 | "integrity": "sha1-6c7FSD09TuDvRLYKfZnkk14TbZM=", 263 | "requires": { 264 | "ansi": "^0.3.0", 265 | "has-unicode": "^2.0.0", 266 | "lodash.pad": "^4.1.0", 267 | "lodash.padend": "^4.1.0", 268 | "lodash.padstart": "^4.1.0" 269 | } 270 | }, 271 | "glob-parent": { 272 | "version": "5.1.0", 273 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", 274 | "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", 275 | "requires": { 276 | "is-glob": "^4.0.1" 277 | } 278 | }, 279 | "has-ansi": { 280 | "version": "2.0.0", 281 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 282 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 283 | "requires": { 284 | "ansi-regex": "^2.0.0" 285 | }, 286 | "dependencies": { 287 | "ansi-regex": { 288 | "version": "2.1.1", 289 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 290 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 291 | } 292 | } 293 | }, 294 | "has-flag": { 295 | "version": "4.0.0", 296 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 297 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 298 | }, 299 | "has-unicode": { 300 | "version": "2.0.1", 301 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 302 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 303 | }, 304 | "inherits": { 305 | "version": "2.0.4", 306 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 307 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 308 | }, 309 | "inquirer": { 310 | "version": "1.2.3", 311 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", 312 | "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", 313 | "requires": { 314 | "ansi-escapes": "^1.1.0", 315 | "chalk": "^1.0.0", 316 | "cli-cursor": "^1.0.1", 317 | "cli-width": "^2.0.0", 318 | "external-editor": "^1.1.0", 319 | "figures": "^1.3.5", 320 | "lodash": "^4.3.0", 321 | "mute-stream": "0.0.6", 322 | "pinkie-promise": "^2.0.0", 323 | "run-async": "^2.2.0", 324 | "rx": "^4.1.0", 325 | "string-width": "^1.0.1", 326 | "strip-ansi": "^3.0.0", 327 | "through": "^2.3.6" 328 | }, 329 | "dependencies": { 330 | "ansi-regex": { 331 | "version": "2.1.1", 332 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 333 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 334 | }, 335 | "ansi-styles": { 336 | "version": "2.2.1", 337 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 338 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" 339 | }, 340 | "chalk": { 341 | "version": "1.1.3", 342 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 343 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 344 | "requires": { 345 | "ansi-styles": "^2.2.1", 346 | "escape-string-regexp": "^1.0.2", 347 | "has-ansi": "^2.0.0", 348 | "strip-ansi": "^3.0.0", 349 | "supports-color": "^2.0.0" 350 | } 351 | }, 352 | "is-fullwidth-code-point": { 353 | "version": "1.0.0", 354 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 355 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 356 | "requires": { 357 | "number-is-nan": "^1.0.0" 358 | } 359 | }, 360 | "string-width": { 361 | "version": "1.0.2", 362 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 363 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 364 | "requires": { 365 | "code-point-at": "^1.0.0", 366 | "is-fullwidth-code-point": "^1.0.0", 367 | "strip-ansi": "^3.0.0" 368 | } 369 | }, 370 | "strip-ansi": { 371 | "version": "3.0.1", 372 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 373 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 374 | "requires": { 375 | "ansi-regex": "^2.0.0" 376 | } 377 | }, 378 | "supports-color": { 379 | "version": "2.0.0", 380 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 381 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" 382 | } 383 | } 384 | }, 385 | "is-binary-path": { 386 | "version": "2.1.0", 387 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 388 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 389 | "requires": { 390 | "binary-extensions": "^2.0.0" 391 | } 392 | }, 393 | "is-extglob": { 394 | "version": "2.1.1", 395 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 396 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" 397 | }, 398 | "is-fullwidth-code-point": { 399 | "version": "2.0.0", 400 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 401 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 402 | }, 403 | "is-glob": { 404 | "version": "4.0.1", 405 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 406 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 407 | "requires": { 408 | "is-extglob": "^2.1.1" 409 | } 410 | }, 411 | "is-number": { 412 | "version": "7.0.0", 413 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 414 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 415 | }, 416 | "is-promise": { 417 | "version": "2.1.0", 418 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 419 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" 420 | }, 421 | "isarray": { 422 | "version": "1.0.0", 423 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 424 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 425 | }, 426 | "isstream": { 427 | "version": "0.1.2", 428 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 429 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 430 | }, 431 | "lodash": { 432 | "version": "4.17.15", 433 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 434 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" 435 | }, 436 | "lodash.camelcase": { 437 | "version": "4.3.0", 438 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 439 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" 440 | }, 441 | "lodash.debounce": { 442 | "version": "4.0.8", 443 | "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", 444 | "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" 445 | }, 446 | "lodash.difference": { 447 | "version": "4.5.0", 448 | "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", 449 | "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" 450 | }, 451 | "lodash.pad": { 452 | "version": "4.5.1", 453 | "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", 454 | "integrity": "sha1-QzCUmoM6fI2iLMIPaibE1Z3runA=" 455 | }, 456 | "lodash.padend": { 457 | "version": "4.6.1", 458 | "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", 459 | "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=" 460 | }, 461 | "lodash.padstart": { 462 | "version": "4.6.1", 463 | "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", 464 | "integrity": "sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=" 465 | }, 466 | "lodash.uniq": { 467 | "version": "4.5.0", 468 | "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", 469 | "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" 470 | }, 471 | "micromist": { 472 | "version": "1.1.0", 473 | "resolved": "https://registry.npmjs.org/micromist/-/micromist-1.1.0.tgz", 474 | "integrity": "sha512-+CQ76pabE9egniSEdmDuH+j2cYyIBKP97kujG8ZLZyLCRq5ExwtIy4DPHPFrq4jVbhMRBnyjuH50KU9Ohs8QCg==", 475 | "requires": { 476 | "lodash.camelcase": "^4.3.0" 477 | } 478 | }, 479 | "minimist": { 480 | "version": "1.2.0", 481 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 482 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 483 | }, 484 | "mkdirp": { 485 | "version": "0.5.1", 486 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 487 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 488 | "requires": { 489 | "minimist": "0.0.8" 490 | }, 491 | "dependencies": { 492 | "minimist": { 493 | "version": "0.0.8", 494 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 495 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 496 | } 497 | } 498 | }, 499 | "ms": { 500 | "version": "2.0.0", 501 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 502 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 503 | }, 504 | "mute-stream": { 505 | "version": "0.0.6", 506 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", 507 | "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=" 508 | }, 509 | "normalize-path": { 510 | "version": "3.0.0", 511 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 512 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" 513 | }, 514 | "npmlog": { 515 | "version": "2.0.4", 516 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-2.0.4.tgz", 517 | "integrity": "sha1-mLUlMPJRTKkNCexbIsiEZyI3VpI=", 518 | "requires": { 519 | "ansi": "~0.3.1", 520 | "are-we-there-yet": "~1.1.2", 521 | "gauge": "~1.2.5" 522 | } 523 | }, 524 | "number-is-nan": { 525 | "version": "1.0.1", 526 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 527 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 528 | }, 529 | "object-assign": { 530 | "version": "4.1.1", 531 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 532 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 533 | }, 534 | "onetime": { 535 | "version": "1.1.0", 536 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", 537 | "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" 538 | }, 539 | "os-shim": { 540 | "version": "0.1.3", 541 | "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", 542 | "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=" 543 | }, 544 | "os-tmpdir": { 545 | "version": "1.0.2", 546 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 547 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 548 | }, 549 | "picomatch": { 550 | "version": "2.1.1", 551 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", 552 | "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==" 553 | }, 554 | "pinkie": { 555 | "version": "2.0.4", 556 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 557 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" 558 | }, 559 | "pinkie-promise": { 560 | "version": "2.0.1", 561 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 562 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 563 | "requires": { 564 | "pinkie": "^2.0.0" 565 | } 566 | }, 567 | "prettyjson": { 568 | "version": "1.2.1", 569 | "resolved": "https://registry.npmjs.org/prettyjson/-/prettyjson-1.2.1.tgz", 570 | "integrity": "sha1-/P+rQdGcq0365eV15kJGYZsS0ok=", 571 | "requires": { 572 | "colors": "^1.1.2", 573 | "minimist": "^1.2.0" 574 | } 575 | }, 576 | "process-nextick-args": { 577 | "version": "2.0.1", 578 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 579 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 580 | }, 581 | "readable-stream": { 582 | "version": "2.3.6", 583 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 584 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 585 | "requires": { 586 | "core-util-is": "~1.0.0", 587 | "inherits": "~2.0.3", 588 | "isarray": "~1.0.0", 589 | "process-nextick-args": "~2.0.0", 590 | "safe-buffer": "~5.1.1", 591 | "string_decoder": "~1.1.1", 592 | "util-deprecate": "~1.0.1" 593 | } 594 | }, 595 | "readdirp": { 596 | "version": "3.2.0", 597 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", 598 | "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", 599 | "requires": { 600 | "picomatch": "^2.0.4" 601 | } 602 | }, 603 | "restore-cursor": { 604 | "version": "1.0.1", 605 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", 606 | "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", 607 | "requires": { 608 | "exit-hook": "^1.0.0", 609 | "onetime": "^1.0.0" 610 | } 611 | }, 612 | "run-async": { 613 | "version": "2.3.0", 614 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", 615 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", 616 | "requires": { 617 | "is-promise": "^2.1.0" 618 | } 619 | }, 620 | "rx": { 621 | "version": "4.1.0", 622 | "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", 623 | "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" 624 | }, 625 | "safe-buffer": { 626 | "version": "5.1.2", 627 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 628 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 629 | }, 630 | "spawn-sync": { 631 | "version": "1.0.15", 632 | "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", 633 | "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", 634 | "requires": { 635 | "concat-stream": "^1.4.7", 636 | "os-shim": "^0.1.2" 637 | } 638 | }, 639 | "stack-trace": { 640 | "version": "0.0.10", 641 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 642 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 643 | }, 644 | "string-width": { 645 | "version": "2.1.1", 646 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 647 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 648 | "requires": { 649 | "is-fullwidth-code-point": "^2.0.0", 650 | "strip-ansi": "^4.0.0" 651 | } 652 | }, 653 | "string_decoder": { 654 | "version": "1.1.1", 655 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 656 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 657 | "requires": { 658 | "safe-buffer": "~5.1.0" 659 | } 660 | }, 661 | "strip-ansi": { 662 | "version": "4.0.0", 663 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 664 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 665 | "requires": { 666 | "ansi-regex": "^3.0.0" 667 | } 668 | }, 669 | "supports-color": { 670 | "version": "7.1.0", 671 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", 672 | "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", 673 | "requires": { 674 | "has-flag": "^4.0.0" 675 | } 676 | }, 677 | "tabtab": { 678 | "version": "2.2.2", 679 | "resolved": "https://registry.npmjs.org/tabtab/-/tabtab-2.2.2.tgz", 680 | "integrity": "sha1-egR/FDsBC0y9MfhX6ClhUSy/ThQ=", 681 | "requires": { 682 | "debug": "^2.2.0", 683 | "inquirer": "^1.0.2", 684 | "lodash.difference": "^4.5.0", 685 | "lodash.uniq": "^4.5.0", 686 | "minimist": "^1.2.0", 687 | "mkdirp": "^0.5.1", 688 | "npmlog": "^2.0.3", 689 | "object-assign": "^4.1.0" 690 | } 691 | }, 692 | "through": { 693 | "version": "2.3.8", 694 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 695 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 696 | }, 697 | "tmp": { 698 | "version": "0.0.29", 699 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", 700 | "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", 701 | "requires": { 702 | "os-tmpdir": "~1.0.1" 703 | } 704 | }, 705 | "to-regex-range": { 706 | "version": "5.0.1", 707 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 708 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 709 | "requires": { 710 | "is-number": "^7.0.0" 711 | } 712 | }, 713 | "typedarray": { 714 | "version": "0.0.6", 715 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 716 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 717 | }, 718 | "util-deprecate": { 719 | "version": "1.0.2", 720 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 721 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 722 | }, 723 | "winston": { 724 | "version": "2.4.4", 725 | "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.4.tgz", 726 | "integrity": "sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q==", 727 | "requires": { 728 | "async": "~1.0.0", 729 | "colors": "1.0.x", 730 | "cycle": "1.0.x", 731 | "eyes": "0.1.x", 732 | "isstream": "0.1.x", 733 | "stack-trace": "0.0.x" 734 | }, 735 | "dependencies": { 736 | "colors": { 737 | "version": "1.0.3", 738 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 739 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" 740 | } 741 | } 742 | } 743 | } 744 | } 745 | -------------------------------------------------------------------------------- /watchit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "watchit", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "bin": { 13 | "watchit": "index.js" 14 | }, 15 | "dependencies": { 16 | "caporal": "^1.3.0", 17 | "chalk": "^3.0.0", 18 | "chokidar": "^3.3.0", 19 | "lodash.debounce": "^4.0.8" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /watchit/test.js: -------------------------------------------------------------------------------- 1 | setInterval(() => { 2 | console.log('bye there!'); 3 | }, 1000); 4 | --------------------------------------------------------------------------------