├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── AcademicHistoryAcornAPI.ts ├── AcornAPI.ts ├── AcornError.ts ├── BasicAcornAPI.ts ├── CourseAcornAPI.ts ├── CourseInterfaces.ts └── index.ts ├── test ├── AcademicHistoryAcornAPI.spec.ts ├── BasicAcornAPI.spec.ts ├── CourseAcornAPI.spec.ts └── TestHelper.ts ├── test_script.pl └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # IDE 61 | /.idea 62 | 63 | # distribution js code generated by tsc 64 | /dist 65 | 66 | # Test Configurations 67 | /test/test_config.json 68 | 69 | # Test logs 70 | /test/logs/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### macOS template 3 | *.DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must end with two \r 8 | Icon 9 | 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | ### Example user template template 30 | ### Example user template 31 | 32 | # IntelliJ project files 33 | .idea 34 | *.iml 35 | out 36 | gen 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chenlei Hu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Acorn API (typescript) 2 | This project is inspired by [AcornAPI](https://github.com/LesterLyu/AcornAPI) 3 | which is an Acorn API written in Java. 4 | 5 | ## Installation 6 | ```bash 7 | npm install acorn-api --save 8 | ``` 9 | 10 | ## Example 11 | 12 | ### Login 13 | ```javascript 14 | import { Acorn } from 'acorn-api-js'; 15 | const example = new Acorn(); 16 | example.basic.login('user', 'pass'); 17 | ``` 18 | 19 | ### Get Registrations 20 | ```javascript 21 | example.course.getEligibleRegistrations(); 22 | ``` 23 | 24 | ### Get Student Courses 25 | ```javascript 26 | example.course.getEnrolledCourses(); 27 | example.course.getCartedCourses(); 28 | ``` 29 | 30 | ### Get Course Info (Can also use it to get waiting list rank for a waitlisted course) 31 | ```javascript 32 | int registrationIndex = 0; 33 | const courseCode = "CSC373H1", sectionCode = "Y", courseSessionCode = "20175"; 34 | const course = example.getExtraCourseInfo(registrationIndex, courseCode, courseSessionCode, sectionCode); 35 | ``` 36 | 37 | ### Enroll a Course (Not yet tested) 38 | ```javascript 39 | int registrationIndex = 0; 40 | const courseCode = "CSC373H1", sectionCode = "Y", lecSection = "LEC,5101"; 41 | const result = example.course.enroll(registrationIndex, courseCode, sectionCode, lecSection); 42 | ``` 43 | 44 | ### Get Current Transcript 45 | ```javascript 46 | const academicReport = example.academic.getAcademicHistory(); 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acorn-api", 3 | "version": "1.0.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/bluebird": { 8 | "version": "3.5.11", 9 | "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.11.tgz", 10 | "integrity": "sha512-tL3ySMpaih/6mJqP8NLfIhQ+qxXvU1Lb+AP+fQ46ZA6/gAQm09GfDiS+C32UgP05kbA2tA4M3B6+oXfkRDpKMQ==" 11 | }, 12 | "@types/chai": { 13 | "version": "4.0.4", 14 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.4.tgz", 15 | "integrity": "sha512-cvU0HomQ7/aGDQJZsbtJXqBQ7w4J4TqLB0Z/h8mKrpRjfeZEvTbygkfJEb7fWdmwpIeDeFmIVwAEqS0OYuUv3Q==", 16 | "dev": true 17 | }, 18 | "@types/chai-as-promised": { 19 | "version": "7.1.0", 20 | "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz", 21 | "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", 22 | "dev": true, 23 | "requires": { 24 | "@types/chai": "4.0.4" 25 | } 26 | }, 27 | "@types/form-data": { 28 | "version": "2.2.0", 29 | "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.0.tgz", 30 | "integrity": "sha512-vm5OGsKc61Sx/GTRMQ9d0H0PYCDebT78/bdIBPCoPEHdgp0etaH1RzMmkDygymUmyXTj3rdWQn0sRUpYKZzljA==", 31 | "requires": { 32 | "@types/node": "8.0.30" 33 | } 34 | }, 35 | "@types/libxmljs": { 36 | "version": "0.14.30", 37 | "resolved": "https://registry.npmjs.org/@types/libxmljs/-/libxmljs-0.14.30.tgz", 38 | "integrity": "sha1-TKyPbr3nJShXJu90bz1eXawXj08=", 39 | "requires": { 40 | "@types/node": "8.0.30" 41 | } 42 | }, 43 | "@types/lodash": { 44 | "version": "4.14.77", 45 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.77.tgz", 46 | "integrity": "sha512-sRCTcVQkIiQqRoQcazgN2PvRLS7d9BnSl8elRZR5UYlpm6XgU8F4j/0csz8WoaKKTUqa6rSuOy3Vph7AHfX7KQ==" 47 | }, 48 | "@types/mocha": { 49 | "version": "2.2.43", 50 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.43.tgz", 51 | "integrity": "sha512-xNlAmH+lRJdUMXClMTI9Y0pRqIojdxfm7DHsIxoB2iTzu3fnPmSMEN8SsSx0cdwV36d02PWCWaDUoZPDSln+xw==", 52 | "dev": true 53 | }, 54 | "@types/node": { 55 | "version": "8.0.30", 56 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.30.tgz", 57 | "integrity": "sha512-IaQtG3DWe9gRsmk1DqNnYyRVjGDVcBdZywkRVF2f62Boe8XKmlR7lNcwC6pk4V4W8nk+Zu+vdGMsOdRTDj1JPA==" 58 | }, 59 | "@types/request": { 60 | "version": "2.0.3", 61 | "resolved": "https://registry.npmjs.org/@types/request/-/request-2.0.3.tgz", 62 | "integrity": "sha512-cIvnyFRARxwE4OHpCyYue7H+SxaKFPpeleRCHJicft8QhyTNbVYsMwjvEzEPqG06D2LGHZ+sN5lXc8+bTu6D8A==", 63 | "requires": { 64 | "@types/form-data": "2.2.0", 65 | "@types/node": "8.0.30" 66 | } 67 | }, 68 | "@types/request-promise": { 69 | "version": "4.1.37", 70 | "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.37.tgz", 71 | "integrity": "sha512-yshslce5sZZ5bqwf8teepkdq1lio7kHziPQDcLet6dXHeWjWGuTlpFHPMjw4TI9sJhFhlxhq3LVFVU5Tq8c22g==", 72 | "requires": { 73 | "@types/bluebird": "3.5.11", 74 | "@types/request": "2.0.3" 75 | } 76 | }, 77 | "@types/tough-cookie": { 78 | "version": "2.3.1", 79 | "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.1.tgz", 80 | "integrity": "sha512-Ei8DBGk7P2BNGMglIqnFJKYY//0pKV1ctXCbf+hjdVF/2wxYgfuUSZOonVrYu3sy8wToKtnDQWE5rSpNgWpmrw==" 81 | }, 82 | "abbrev": { 83 | "version": "1.1.0", 84 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", 85 | "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=" 86 | }, 87 | "ajv": { 88 | "version": "5.2.2", 89 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.2.tgz", 90 | "integrity": "sha1-R8aNaehvXZUxA7AHSpQw3GPaXjk=", 91 | "requires": { 92 | "co": "4.6.0", 93 | "fast-deep-equal": "1.0.0", 94 | "json-schema-traverse": "0.3.1", 95 | "json-stable-stringify": "1.0.1" 96 | } 97 | }, 98 | "ansi-regex": { 99 | "version": "2.1.1", 100 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 101 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 102 | }, 103 | "ansi-styles": { 104 | "version": "3.2.0", 105 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", 106 | "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", 107 | "dev": true, 108 | "requires": { 109 | "color-convert": "1.9.0" 110 | } 111 | }, 112 | "aproba": { 113 | "version": "1.2.0", 114 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 115 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 116 | }, 117 | "are-we-there-yet": { 118 | "version": "1.1.4", 119 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", 120 | "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", 121 | "requires": { 122 | "delegates": "1.0.0", 123 | "readable-stream": "2.3.3" 124 | } 125 | }, 126 | "arrify": { 127 | "version": "1.0.1", 128 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 129 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", 130 | "dev": true 131 | }, 132 | "asn1": { 133 | "version": "0.2.3", 134 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 135 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 136 | }, 137 | "assert-plus": { 138 | "version": "1.0.0", 139 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 140 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 141 | }, 142 | "assertion-error": { 143 | "version": "1.0.2", 144 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", 145 | "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", 146 | "dev": true 147 | }, 148 | "asynckit": { 149 | "version": "0.4.0", 150 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 151 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 152 | }, 153 | "aws-sign2": { 154 | "version": "0.7.0", 155 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 156 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 157 | }, 158 | "aws4": { 159 | "version": "1.6.0", 160 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", 161 | "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" 162 | }, 163 | "balanced-match": { 164 | "version": "1.0.0", 165 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 166 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 167 | }, 168 | "bcrypt-pbkdf": { 169 | "version": "1.0.1", 170 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 171 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 172 | "optional": true, 173 | "requires": { 174 | "tweetnacl": "0.14.5" 175 | } 176 | }, 177 | "bindings": { 178 | "version": "1.2.1", 179 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", 180 | "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" 181 | }, 182 | "block-stream": { 183 | "version": "0.0.9", 184 | "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", 185 | "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", 186 | "requires": { 187 | "inherits": "2.0.3" 188 | } 189 | }, 190 | "bluebird": { 191 | "version": "3.5.0", 192 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", 193 | "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" 194 | }, 195 | "boom": { 196 | "version": "4.3.1", 197 | "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", 198 | "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", 199 | "requires": { 200 | "hoek": "4.2.0" 201 | } 202 | }, 203 | "brace-expansion": { 204 | "version": "1.1.8", 205 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 206 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 207 | "requires": { 208 | "balanced-match": "1.0.0", 209 | "concat-map": "0.0.1" 210 | } 211 | }, 212 | "browser-stdout": { 213 | "version": "1.3.0", 214 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", 215 | "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", 216 | "dev": true 217 | }, 218 | "caseless": { 219 | "version": "0.12.0", 220 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 221 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 222 | }, 223 | "chai": { 224 | "version": "4.1.2", 225 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", 226 | "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", 227 | "dev": true, 228 | "requires": { 229 | "assertion-error": "1.0.2", 230 | "check-error": "1.0.2", 231 | "deep-eql": "3.0.1", 232 | "get-func-name": "2.0.0", 233 | "pathval": "1.1.0", 234 | "type-detect": "4.0.3" 235 | } 236 | }, 237 | "chai-as-promised": { 238 | "version": "7.1.1", 239 | "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", 240 | "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", 241 | "dev": true, 242 | "requires": { 243 | "check-error": "1.0.2" 244 | } 245 | }, 246 | "chalk": { 247 | "version": "2.1.0", 248 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", 249 | "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", 250 | "dev": true, 251 | "requires": { 252 | "ansi-styles": "3.2.0", 253 | "escape-string-regexp": "1.0.5", 254 | "supports-color": "4.4.0" 255 | }, 256 | "dependencies": { 257 | "has-flag": { 258 | "version": "2.0.0", 259 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 260 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", 261 | "dev": true 262 | }, 263 | "supports-color": { 264 | "version": "4.4.0", 265 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", 266 | "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", 267 | "dev": true, 268 | "requires": { 269 | "has-flag": "2.0.0" 270 | } 271 | } 272 | } 273 | }, 274 | "check-error": { 275 | "version": "1.0.2", 276 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 277 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 278 | "dev": true 279 | }, 280 | "co": { 281 | "version": "4.6.0", 282 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 283 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 284 | }, 285 | "code-point-at": { 286 | "version": "1.1.0", 287 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 288 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 289 | }, 290 | "color-convert": { 291 | "version": "1.9.0", 292 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", 293 | "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", 294 | "dev": true, 295 | "requires": { 296 | "color-name": "1.1.3" 297 | } 298 | }, 299 | "color-name": { 300 | "version": "1.1.3", 301 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 302 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 303 | "dev": true 304 | }, 305 | "combined-stream": { 306 | "version": "1.0.5", 307 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", 308 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", 309 | "requires": { 310 | "delayed-stream": "1.0.0" 311 | } 312 | }, 313 | "commander": { 314 | "version": "2.9.0", 315 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", 316 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", 317 | "dev": true, 318 | "requires": { 319 | "graceful-readlink": "1.0.1" 320 | } 321 | }, 322 | "concat-map": { 323 | "version": "0.0.1", 324 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 325 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 326 | }, 327 | "console-control-strings": { 328 | "version": "1.1.0", 329 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 330 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 331 | }, 332 | "core-util-is": { 333 | "version": "1.0.2", 334 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 335 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 336 | }, 337 | "cryptiles": { 338 | "version": "3.1.2", 339 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", 340 | "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", 341 | "requires": { 342 | "boom": "5.2.0" 343 | }, 344 | "dependencies": { 345 | "boom": { 346 | "version": "5.2.0", 347 | "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", 348 | "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", 349 | "requires": { 350 | "hoek": "4.2.0" 351 | } 352 | } 353 | } 354 | }, 355 | "dashdash": { 356 | "version": "1.14.1", 357 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 358 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 359 | "requires": { 360 | "assert-plus": "1.0.0" 361 | } 362 | }, 363 | "debug": { 364 | "version": "2.6.9", 365 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 366 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 367 | "requires": { 368 | "ms": "2.0.0" 369 | } 370 | }, 371 | "deep-eql": { 372 | "version": "3.0.1", 373 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", 374 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", 375 | "dev": true, 376 | "requires": { 377 | "type-detect": "4.0.3" 378 | } 379 | }, 380 | "deep-extend": { 381 | "version": "0.4.2", 382 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", 383 | "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" 384 | }, 385 | "delayed-stream": { 386 | "version": "1.0.0", 387 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 388 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 389 | }, 390 | "delegates": { 391 | "version": "1.0.0", 392 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 393 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 394 | }, 395 | "diff": { 396 | "version": "3.2.0", 397 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", 398 | "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", 399 | "dev": true 400 | }, 401 | "ecc-jsbn": { 402 | "version": "0.1.1", 403 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 404 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 405 | "optional": true, 406 | "requires": { 407 | "jsbn": "0.1.1" 408 | } 409 | }, 410 | "escape-string-regexp": { 411 | "version": "1.0.5", 412 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 413 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 414 | "dev": true 415 | }, 416 | "extend": { 417 | "version": "3.0.1", 418 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 419 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 420 | }, 421 | "extsprintf": { 422 | "version": "1.3.0", 423 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 424 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 425 | }, 426 | "fast-deep-equal": { 427 | "version": "1.0.0", 428 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", 429 | "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" 430 | }, 431 | "forever-agent": { 432 | "version": "0.6.1", 433 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 434 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 435 | }, 436 | "form-data": { 437 | "version": "2.3.1", 438 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", 439 | "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", 440 | "requires": { 441 | "asynckit": "0.4.0", 442 | "combined-stream": "1.0.5", 443 | "mime-types": "2.1.17" 444 | } 445 | }, 446 | "fs.realpath": { 447 | "version": "1.0.0", 448 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 449 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 450 | }, 451 | "fstream": { 452 | "version": "1.0.11", 453 | "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", 454 | "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", 455 | "requires": { 456 | "graceful-fs": "4.1.11", 457 | "inherits": "2.0.3", 458 | "mkdirp": "0.5.1", 459 | "rimraf": "2.6.2" 460 | } 461 | }, 462 | "fstream-ignore": { 463 | "version": "1.0.5", 464 | "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", 465 | "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", 466 | "requires": { 467 | "fstream": "1.0.11", 468 | "inherits": "2.0.3", 469 | "minimatch": "3.0.4" 470 | } 471 | }, 472 | "gauge": { 473 | "version": "2.7.4", 474 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 475 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 476 | "requires": { 477 | "aproba": "1.2.0", 478 | "console-control-strings": "1.1.0", 479 | "has-unicode": "2.0.1", 480 | "object-assign": "4.1.1", 481 | "signal-exit": "3.0.2", 482 | "string-width": "1.0.2", 483 | "strip-ansi": "3.0.1", 484 | "wide-align": "1.1.2" 485 | } 486 | }, 487 | "get-func-name": { 488 | "version": "2.0.0", 489 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 490 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 491 | "dev": true 492 | }, 493 | "getpass": { 494 | "version": "0.1.7", 495 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 496 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 497 | "requires": { 498 | "assert-plus": "1.0.0" 499 | } 500 | }, 501 | "glob": { 502 | "version": "7.1.2", 503 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 504 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 505 | "requires": { 506 | "fs.realpath": "1.0.0", 507 | "inflight": "1.0.6", 508 | "inherits": "2.0.3", 509 | "minimatch": "3.0.4", 510 | "once": "1.4.0", 511 | "path-is-absolute": "1.0.1" 512 | } 513 | }, 514 | "graceful-fs": { 515 | "version": "4.1.11", 516 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 517 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" 518 | }, 519 | "graceful-readlink": { 520 | "version": "1.0.1", 521 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", 522 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", 523 | "dev": true 524 | }, 525 | "growl": { 526 | "version": "1.9.2", 527 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", 528 | "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", 529 | "dev": true 530 | }, 531 | "har-schema": { 532 | "version": "2.0.0", 533 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 534 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 535 | }, 536 | "har-validator": { 537 | "version": "5.0.3", 538 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", 539 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", 540 | "requires": { 541 | "ajv": "5.2.2", 542 | "har-schema": "2.0.0" 543 | } 544 | }, 545 | "has-flag": { 546 | "version": "1.0.0", 547 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", 548 | "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", 549 | "dev": true 550 | }, 551 | "has-unicode": { 552 | "version": "2.0.1", 553 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 554 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 555 | }, 556 | "hawk": { 557 | "version": "6.0.2", 558 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", 559 | "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", 560 | "requires": { 561 | "boom": "4.3.1", 562 | "cryptiles": "3.1.2", 563 | "hoek": "4.2.0", 564 | "sntp": "2.0.2" 565 | } 566 | }, 567 | "he": { 568 | "version": "1.1.1", 569 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 570 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 571 | "dev": true 572 | }, 573 | "hoek": { 574 | "version": "4.2.0", 575 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", 576 | "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" 577 | }, 578 | "http-signature": { 579 | "version": "1.2.0", 580 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 581 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 582 | "requires": { 583 | "assert-plus": "1.0.0", 584 | "jsprim": "1.4.1", 585 | "sshpk": "1.13.1" 586 | } 587 | }, 588 | "inflight": { 589 | "version": "1.0.6", 590 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 591 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 592 | "requires": { 593 | "once": "1.4.0", 594 | "wrappy": "1.0.2" 595 | } 596 | }, 597 | "inherits": { 598 | "version": "2.0.3", 599 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 600 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 601 | }, 602 | "ini": { 603 | "version": "1.3.4", 604 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", 605 | "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" 606 | }, 607 | "is-fullwidth-code-point": { 608 | "version": "1.0.0", 609 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 610 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 611 | "requires": { 612 | "number-is-nan": "1.0.1" 613 | } 614 | }, 615 | "is-typedarray": { 616 | "version": "1.0.0", 617 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 618 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 619 | }, 620 | "isarray": { 621 | "version": "1.0.0", 622 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 623 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 624 | }, 625 | "isstream": { 626 | "version": "0.1.2", 627 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 628 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 629 | }, 630 | "jsbn": { 631 | "version": "0.1.1", 632 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 633 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 634 | "optional": true 635 | }, 636 | "json-schema": { 637 | "version": "0.2.3", 638 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 639 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 640 | }, 641 | "json-schema-traverse": { 642 | "version": "0.3.1", 643 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 644 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 645 | }, 646 | "json-stable-stringify": { 647 | "version": "1.0.1", 648 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 649 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", 650 | "requires": { 651 | "jsonify": "0.0.0" 652 | } 653 | }, 654 | "json-stringify-safe": { 655 | "version": "5.0.1", 656 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 657 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 658 | }, 659 | "json3": { 660 | "version": "3.3.2", 661 | "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", 662 | "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", 663 | "dev": true 664 | }, 665 | "jsonify": { 666 | "version": "0.0.0", 667 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 668 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" 669 | }, 670 | "jsprim": { 671 | "version": "1.4.1", 672 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 673 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 674 | "requires": { 675 | "assert-plus": "1.0.0", 676 | "extsprintf": "1.3.0", 677 | "json-schema": "0.2.3", 678 | "verror": "1.10.0" 679 | } 680 | }, 681 | "libxmljs": { 682 | "version": "0.18.7", 683 | "resolved": "https://registry.npmjs.org/libxmljs/-/libxmljs-0.18.7.tgz", 684 | "integrity": "sha1-NnPrF8dMuv/e+fM8uD3dgqJQVbc=", 685 | "requires": { 686 | "bindings": "1.2.1", 687 | "nan": "2.5.1", 688 | "node-pre-gyp": "0.6.38" 689 | } 690 | }, 691 | "lodash": { 692 | "version": "4.17.4", 693 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 694 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 695 | }, 696 | "lodash._baseassign": { 697 | "version": "3.2.0", 698 | "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", 699 | "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", 700 | "dev": true, 701 | "requires": { 702 | "lodash._basecopy": "3.0.1", 703 | "lodash.keys": "3.1.2" 704 | } 705 | }, 706 | "lodash._basecopy": { 707 | "version": "3.0.1", 708 | "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", 709 | "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", 710 | "dev": true 711 | }, 712 | "lodash._basecreate": { 713 | "version": "3.0.3", 714 | "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", 715 | "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", 716 | "dev": true 717 | }, 718 | "lodash._getnative": { 719 | "version": "3.9.1", 720 | "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", 721 | "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", 722 | "dev": true 723 | }, 724 | "lodash._isiterateecall": { 725 | "version": "3.0.9", 726 | "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", 727 | "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", 728 | "dev": true 729 | }, 730 | "lodash.create": { 731 | "version": "3.1.1", 732 | "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", 733 | "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", 734 | "dev": true, 735 | "requires": { 736 | "lodash._baseassign": "3.2.0", 737 | "lodash._basecreate": "3.0.3", 738 | "lodash._isiterateecall": "3.0.9" 739 | } 740 | }, 741 | "lodash.isarguments": { 742 | "version": "3.1.0", 743 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 744 | "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", 745 | "dev": true 746 | }, 747 | "lodash.isarray": { 748 | "version": "3.0.4", 749 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", 750 | "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", 751 | "dev": true 752 | }, 753 | "lodash.keys": { 754 | "version": "3.1.2", 755 | "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", 756 | "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", 757 | "dev": true, 758 | "requires": { 759 | "lodash._getnative": "3.9.1", 760 | "lodash.isarguments": "3.1.0", 761 | "lodash.isarray": "3.0.4" 762 | } 763 | }, 764 | "make-error": { 765 | "version": "1.3.0", 766 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.0.tgz", 767 | "integrity": "sha1-Uq06M5zPEM5itAQLcI/nByRLi5Y=", 768 | "dev": true 769 | }, 770 | "mime-db": { 771 | "version": "1.30.0", 772 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", 773 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" 774 | }, 775 | "mime-types": { 776 | "version": "2.1.17", 777 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", 778 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", 779 | "requires": { 780 | "mime-db": "1.30.0" 781 | } 782 | }, 783 | "minimatch": { 784 | "version": "3.0.4", 785 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 786 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 787 | "requires": { 788 | "brace-expansion": "1.1.8" 789 | } 790 | }, 791 | "minimist": { 792 | "version": "0.0.8", 793 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 794 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 795 | }, 796 | "mkdirp": { 797 | "version": "0.5.1", 798 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 799 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 800 | "requires": { 801 | "minimist": "0.0.8" 802 | } 803 | }, 804 | "mocha": { 805 | "version": "3.5.3", 806 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", 807 | "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", 808 | "dev": true, 809 | "requires": { 810 | "browser-stdout": "1.3.0", 811 | "commander": "2.9.0", 812 | "debug": "2.6.8", 813 | "diff": "3.2.0", 814 | "escape-string-regexp": "1.0.5", 815 | "glob": "7.1.1", 816 | "growl": "1.9.2", 817 | "he": "1.1.1", 818 | "json3": "3.3.2", 819 | "lodash.create": "3.1.1", 820 | "mkdirp": "0.5.1", 821 | "supports-color": "3.1.2" 822 | }, 823 | "dependencies": { 824 | "debug": { 825 | "version": "2.6.8", 826 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 827 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 828 | "dev": true, 829 | "requires": { 830 | "ms": "2.0.0" 831 | } 832 | }, 833 | "glob": { 834 | "version": "7.1.1", 835 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", 836 | "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", 837 | "dev": true, 838 | "requires": { 839 | "fs.realpath": "1.0.0", 840 | "inflight": "1.0.6", 841 | "inherits": "2.0.3", 842 | "minimatch": "3.0.4", 843 | "once": "1.4.0", 844 | "path-is-absolute": "1.0.1" 845 | } 846 | } 847 | } 848 | }, 849 | "ms": { 850 | "version": "2.0.0", 851 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 852 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 853 | }, 854 | "nan": { 855 | "version": "2.5.1", 856 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.5.1.tgz", 857 | "integrity": "sha1-1bAWkSUzJql6K77p5hxV2NYDUeI=" 858 | }, 859 | "node-pre-gyp": { 860 | "version": "0.6.38", 861 | "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz", 862 | "integrity": "sha1-6Sog+DQWQVu0CG9tH7eLPac9ET0=", 863 | "requires": { 864 | "hawk": "3.1.3", 865 | "mkdirp": "0.5.1", 866 | "nopt": "4.0.1", 867 | "npmlog": "4.1.2", 868 | "rc": "1.2.1", 869 | "request": "2.81.0", 870 | "rimraf": "2.6.2", 871 | "semver": "5.4.1", 872 | "tar": "2.2.1", 873 | "tar-pack": "3.4.0" 874 | }, 875 | "dependencies": { 876 | "ajv": { 877 | "version": "4.11.8", 878 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", 879 | "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", 880 | "requires": { 881 | "co": "4.6.0", 882 | "json-stable-stringify": "1.0.1" 883 | } 884 | }, 885 | "assert-plus": { 886 | "version": "0.2.0", 887 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", 888 | "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" 889 | }, 890 | "aws-sign2": { 891 | "version": "0.6.0", 892 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", 893 | "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" 894 | }, 895 | "boom": { 896 | "version": "2.10.1", 897 | "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", 898 | "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", 899 | "requires": { 900 | "hoek": "2.16.3" 901 | } 902 | }, 903 | "cryptiles": { 904 | "version": "2.0.5", 905 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", 906 | "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", 907 | "requires": { 908 | "boom": "2.10.1" 909 | } 910 | }, 911 | "form-data": { 912 | "version": "2.1.4", 913 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", 914 | "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", 915 | "requires": { 916 | "asynckit": "0.4.0", 917 | "combined-stream": "1.0.5", 918 | "mime-types": "2.1.17" 919 | } 920 | }, 921 | "har-schema": { 922 | "version": "1.0.5", 923 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", 924 | "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" 925 | }, 926 | "har-validator": { 927 | "version": "4.2.1", 928 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", 929 | "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", 930 | "requires": { 931 | "ajv": "4.11.8", 932 | "har-schema": "1.0.5" 933 | } 934 | }, 935 | "hawk": { 936 | "version": "3.1.3", 937 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", 938 | "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", 939 | "requires": { 940 | "boom": "2.10.1", 941 | "cryptiles": "2.0.5", 942 | "hoek": "2.16.3", 943 | "sntp": "1.0.9" 944 | } 945 | }, 946 | "hoek": { 947 | "version": "2.16.3", 948 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", 949 | "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" 950 | }, 951 | "http-signature": { 952 | "version": "1.1.1", 953 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", 954 | "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", 955 | "requires": { 956 | "assert-plus": "0.2.0", 957 | "jsprim": "1.4.1", 958 | "sshpk": "1.13.1" 959 | } 960 | }, 961 | "performance-now": { 962 | "version": "0.2.0", 963 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", 964 | "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" 965 | }, 966 | "qs": { 967 | "version": "6.4.0", 968 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 969 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 970 | }, 971 | "request": { 972 | "version": "2.81.0", 973 | "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", 974 | "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", 975 | "requires": { 976 | "aws-sign2": "0.6.0", 977 | "aws4": "1.6.0", 978 | "caseless": "0.12.0", 979 | "combined-stream": "1.0.5", 980 | "extend": "3.0.1", 981 | "forever-agent": "0.6.1", 982 | "form-data": "2.1.4", 983 | "har-validator": "4.2.1", 984 | "hawk": "3.1.3", 985 | "http-signature": "1.1.1", 986 | "is-typedarray": "1.0.0", 987 | "isstream": "0.1.2", 988 | "json-stringify-safe": "5.0.1", 989 | "mime-types": "2.1.17", 990 | "oauth-sign": "0.8.2", 991 | "performance-now": "0.2.0", 992 | "qs": "6.4.0", 993 | "safe-buffer": "5.1.1", 994 | "stringstream": "0.0.5", 995 | "tough-cookie": "2.3.3", 996 | "tunnel-agent": "0.6.0", 997 | "uuid": "3.1.0" 998 | } 999 | }, 1000 | "sntp": { 1001 | "version": "1.0.9", 1002 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", 1003 | "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", 1004 | "requires": { 1005 | "hoek": "2.16.3" 1006 | } 1007 | } 1008 | } 1009 | }, 1010 | "nopt": { 1011 | "version": "4.0.1", 1012 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", 1013 | "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", 1014 | "requires": { 1015 | "abbrev": "1.1.0", 1016 | "osenv": "0.1.4" 1017 | } 1018 | }, 1019 | "npmlog": { 1020 | "version": "4.1.2", 1021 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 1022 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 1023 | "requires": { 1024 | "are-we-there-yet": "1.1.4", 1025 | "console-control-strings": "1.1.0", 1026 | "gauge": "2.7.4", 1027 | "set-blocking": "2.0.0" 1028 | } 1029 | }, 1030 | "number-is-nan": { 1031 | "version": "1.0.1", 1032 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1033 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 1034 | }, 1035 | "oauth-sign": { 1036 | "version": "0.8.2", 1037 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 1038 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 1039 | }, 1040 | "object-assign": { 1041 | "version": "4.1.1", 1042 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1043 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1044 | }, 1045 | "once": { 1046 | "version": "1.4.0", 1047 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1048 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1049 | "requires": { 1050 | "wrappy": "1.0.2" 1051 | } 1052 | }, 1053 | "os-homedir": { 1054 | "version": "1.0.2", 1055 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1056 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" 1057 | }, 1058 | "os-tmpdir": { 1059 | "version": "1.0.2", 1060 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1061 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 1062 | }, 1063 | "osenv": { 1064 | "version": "0.1.4", 1065 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", 1066 | "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", 1067 | "requires": { 1068 | "os-homedir": "1.0.2", 1069 | "os-tmpdir": "1.0.2" 1070 | } 1071 | }, 1072 | "path-is-absolute": { 1073 | "version": "1.0.1", 1074 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1075 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1076 | }, 1077 | "pathval": { 1078 | "version": "1.1.0", 1079 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 1080 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 1081 | "dev": true 1082 | }, 1083 | "performance-now": { 1084 | "version": "2.1.0", 1085 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 1086 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 1087 | }, 1088 | "process-nextick-args": { 1089 | "version": "1.0.7", 1090 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 1091 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" 1092 | }, 1093 | "punycode": { 1094 | "version": "1.4.1", 1095 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 1096 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 1097 | }, 1098 | "qs": { 1099 | "version": "6.5.1", 1100 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 1101 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 1102 | }, 1103 | "rc": { 1104 | "version": "1.2.1", 1105 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", 1106 | "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", 1107 | "requires": { 1108 | "deep-extend": "0.4.2", 1109 | "ini": "1.3.4", 1110 | "minimist": "1.2.0", 1111 | "strip-json-comments": "2.0.1" 1112 | }, 1113 | "dependencies": { 1114 | "minimist": { 1115 | "version": "1.2.0", 1116 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1117 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 1118 | } 1119 | } 1120 | }, 1121 | "readable-stream": { 1122 | "version": "2.3.3", 1123 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 1124 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 1125 | "requires": { 1126 | "core-util-is": "1.0.2", 1127 | "inherits": "2.0.3", 1128 | "isarray": "1.0.0", 1129 | "process-nextick-args": "1.0.7", 1130 | "safe-buffer": "5.1.1", 1131 | "string_decoder": "1.0.3", 1132 | "util-deprecate": "1.0.2" 1133 | } 1134 | }, 1135 | "request": { 1136 | "version": "2.82.0", 1137 | "resolved": "https://registry.npmjs.org/request/-/request-2.82.0.tgz", 1138 | "integrity": "sha512-/QWqfmyTfQ4OYs6EhB1h2wQsX9ZxbuNePCvCm0Mdz/mxw73mjdg0D4QdIl0TQBFs35CZmMXLjk0iCGK395CUDg==", 1139 | "requires": { 1140 | "aws-sign2": "0.7.0", 1141 | "aws4": "1.6.0", 1142 | "caseless": "0.12.0", 1143 | "combined-stream": "1.0.5", 1144 | "extend": "3.0.1", 1145 | "forever-agent": "0.6.1", 1146 | "form-data": "2.3.1", 1147 | "har-validator": "5.0.3", 1148 | "hawk": "6.0.2", 1149 | "http-signature": "1.2.0", 1150 | "is-typedarray": "1.0.0", 1151 | "isstream": "0.1.2", 1152 | "json-stringify-safe": "5.0.1", 1153 | "mime-types": "2.1.17", 1154 | "oauth-sign": "0.8.2", 1155 | "performance-now": "2.1.0", 1156 | "qs": "6.5.1", 1157 | "safe-buffer": "5.1.1", 1158 | "stringstream": "0.0.5", 1159 | "tough-cookie": "2.3.3", 1160 | "tunnel-agent": "0.6.0", 1161 | "uuid": "3.1.0" 1162 | } 1163 | }, 1164 | "request-debug": { 1165 | "version": "0.2.0", 1166 | "resolved": "https://registry.npmjs.org/request-debug/-/request-debug-0.2.0.tgz", 1167 | "integrity": "sha1-/AVOyBcYGwTKQaBSwTb2HEirr3g=", 1168 | "dev": true, 1169 | "requires": { 1170 | "stringify-clone": "1.1.1" 1171 | } 1172 | }, 1173 | "request-promise": { 1174 | "version": "4.2.2", 1175 | "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz", 1176 | "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", 1177 | "requires": { 1178 | "bluebird": "3.5.0", 1179 | "request-promise-core": "1.1.1", 1180 | "stealthy-require": "1.1.1", 1181 | "tough-cookie": "2.3.3" 1182 | } 1183 | }, 1184 | "request-promise-core": { 1185 | "version": "1.1.1", 1186 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", 1187 | "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", 1188 | "requires": { 1189 | "lodash": "4.17.4" 1190 | } 1191 | }, 1192 | "rimraf": { 1193 | "version": "2.6.2", 1194 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 1195 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 1196 | "requires": { 1197 | "glob": "7.1.2" 1198 | } 1199 | }, 1200 | "safe-buffer": { 1201 | "version": "5.1.1", 1202 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 1203 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 1204 | }, 1205 | "semver": { 1206 | "version": "5.4.1", 1207 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", 1208 | "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" 1209 | }, 1210 | "set-blocking": { 1211 | "version": "2.0.0", 1212 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1213 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1214 | }, 1215 | "signal-exit": { 1216 | "version": "3.0.2", 1217 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 1218 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" 1219 | }, 1220 | "sntp": { 1221 | "version": "2.0.2", 1222 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz", 1223 | "integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=", 1224 | "requires": { 1225 | "hoek": "4.2.0" 1226 | } 1227 | }, 1228 | "source-map": { 1229 | "version": "0.5.7", 1230 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 1231 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 1232 | "dev": true 1233 | }, 1234 | "source-map-support": { 1235 | "version": "0.4.18", 1236 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", 1237 | "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", 1238 | "dev": true, 1239 | "requires": { 1240 | "source-map": "0.5.7" 1241 | } 1242 | }, 1243 | "sshpk": { 1244 | "version": "1.13.1", 1245 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", 1246 | "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", 1247 | "requires": { 1248 | "asn1": "0.2.3", 1249 | "assert-plus": "1.0.0", 1250 | "bcrypt-pbkdf": "1.0.1", 1251 | "dashdash": "1.14.1", 1252 | "ecc-jsbn": "0.1.1", 1253 | "getpass": "0.1.7", 1254 | "jsbn": "0.1.1", 1255 | "tweetnacl": "0.14.5" 1256 | } 1257 | }, 1258 | "stealthy-require": { 1259 | "version": "1.1.1", 1260 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 1261 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 1262 | }, 1263 | "string-width": { 1264 | "version": "1.0.2", 1265 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1266 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1267 | "requires": { 1268 | "code-point-at": "1.1.0", 1269 | "is-fullwidth-code-point": "1.0.0", 1270 | "strip-ansi": "3.0.1" 1271 | } 1272 | }, 1273 | "string_decoder": { 1274 | "version": "1.0.3", 1275 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 1276 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 1277 | "requires": { 1278 | "safe-buffer": "5.1.1" 1279 | } 1280 | }, 1281 | "stringify-clone": { 1282 | "version": "1.1.1", 1283 | "resolved": "https://registry.npmjs.org/stringify-clone/-/stringify-clone-1.1.1.tgz", 1284 | "integrity": "sha1-MJojX7Ts/M19OI2+GLqQT6yvQzs=", 1285 | "dev": true 1286 | }, 1287 | "stringstream": { 1288 | "version": "0.0.5", 1289 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", 1290 | "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" 1291 | }, 1292 | "strip-ansi": { 1293 | "version": "3.0.1", 1294 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1295 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1296 | "requires": { 1297 | "ansi-regex": "2.1.1" 1298 | } 1299 | }, 1300 | "strip-bom": { 1301 | "version": "3.0.0", 1302 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1303 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 1304 | "dev": true 1305 | }, 1306 | "strip-json-comments": { 1307 | "version": "2.0.1", 1308 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1309 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" 1310 | }, 1311 | "supports-color": { 1312 | "version": "3.1.2", 1313 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", 1314 | "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", 1315 | "dev": true, 1316 | "requires": { 1317 | "has-flag": "1.0.0" 1318 | } 1319 | }, 1320 | "tar": { 1321 | "version": "2.2.1", 1322 | "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", 1323 | "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", 1324 | "requires": { 1325 | "block-stream": "0.0.9", 1326 | "fstream": "1.0.11", 1327 | "inherits": "2.0.3" 1328 | } 1329 | }, 1330 | "tar-pack": { 1331 | "version": "3.4.0", 1332 | "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", 1333 | "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", 1334 | "requires": { 1335 | "debug": "2.6.9", 1336 | "fstream": "1.0.11", 1337 | "fstream-ignore": "1.0.5", 1338 | "once": "1.4.0", 1339 | "readable-stream": "2.3.3", 1340 | "rimraf": "2.6.2", 1341 | "tar": "2.2.1", 1342 | "uid-number": "0.0.6" 1343 | } 1344 | }, 1345 | "tough-cookie": { 1346 | "version": "2.3.3", 1347 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", 1348 | "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", 1349 | "requires": { 1350 | "punycode": "1.4.1" 1351 | } 1352 | }, 1353 | "ts-node": { 1354 | "version": "3.3.0", 1355 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-3.3.0.tgz", 1356 | "integrity": "sha1-wTxqMCTjC+EYDdUwOPwgkonUv2k=", 1357 | "dev": true, 1358 | "requires": { 1359 | "arrify": "1.0.1", 1360 | "chalk": "2.1.0", 1361 | "diff": "3.2.0", 1362 | "make-error": "1.3.0", 1363 | "minimist": "1.2.0", 1364 | "mkdirp": "0.5.1", 1365 | "source-map-support": "0.4.18", 1366 | "tsconfig": "6.0.0", 1367 | "v8flags": "3.0.0", 1368 | "yn": "2.0.0" 1369 | }, 1370 | "dependencies": { 1371 | "minimist": { 1372 | "version": "1.2.0", 1373 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1374 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 1375 | "dev": true 1376 | } 1377 | } 1378 | }, 1379 | "tsconfig": { 1380 | "version": "6.0.0", 1381 | "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-6.0.0.tgz", 1382 | "integrity": "sha1-aw6DdgA9evGGT434+J3QBZ/80DI=", 1383 | "dev": true, 1384 | "requires": { 1385 | "strip-bom": "3.0.0", 1386 | "strip-json-comments": "2.0.1" 1387 | } 1388 | }, 1389 | "tunnel-agent": { 1390 | "version": "0.6.0", 1391 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1392 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1393 | "requires": { 1394 | "safe-buffer": "5.1.1" 1395 | } 1396 | }, 1397 | "tweetnacl": { 1398 | "version": "0.14.5", 1399 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1400 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 1401 | "optional": true 1402 | }, 1403 | "type-detect": { 1404 | "version": "4.0.3", 1405 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", 1406 | "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", 1407 | "dev": true 1408 | }, 1409 | "typescript": { 1410 | "version": "2.5.2", 1411 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz", 1412 | "integrity": "sha1-A4qV99m7tCCxvzW6MdTFwd0//jQ=" 1413 | }, 1414 | "uid-number": { 1415 | "version": "0.0.6", 1416 | "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", 1417 | "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" 1418 | }, 1419 | "user-home": { 1420 | "version": "1.1.1", 1421 | "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", 1422 | "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", 1423 | "dev": true 1424 | }, 1425 | "util-deprecate": { 1426 | "version": "1.0.2", 1427 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1428 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1429 | }, 1430 | "uuid": { 1431 | "version": "3.1.0", 1432 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 1433 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 1434 | }, 1435 | "v8flags": { 1436 | "version": "3.0.0", 1437 | "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.0.0.tgz", 1438 | "integrity": "sha512-AGl+C+4qpeSu2g3JxCD/mGFFOs/vVZ3XREkD3ibQXEqr4Y4zgIrPWW124/IKJFHOIVFIoH8miWrLf0o84HYjwA==", 1439 | "dev": true, 1440 | "requires": { 1441 | "user-home": "1.1.1" 1442 | } 1443 | }, 1444 | "verror": { 1445 | "version": "1.10.0", 1446 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1447 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 1448 | "requires": { 1449 | "assert-plus": "1.0.0", 1450 | "core-util-is": "1.0.2", 1451 | "extsprintf": "1.3.0" 1452 | } 1453 | }, 1454 | "wide-align": { 1455 | "version": "1.1.2", 1456 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", 1457 | "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", 1458 | "requires": { 1459 | "string-width": "1.0.2" 1460 | } 1461 | }, 1462 | "wrappy": { 1463 | "version": "1.0.2", 1464 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1465 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1466 | }, 1467 | "yn": { 1468 | "version": "2.0.0", 1469 | "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", 1470 | "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", 1471 | "dev": true 1472 | } 1473 | } 1474 | } 1475 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acorn-api", 3 | "version": "1.0.2", 4 | "description": "University of Toronto Acorn API", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "scripts": { 8 | "test": "perl test_script.pl", 9 | "prepublishOnly": "tsc -p ./ --outDir dist/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/huchenlei/acorn-api-js.git" 14 | }, 15 | "keywords": [ 16 | "Acorn" 17 | ], 18 | "author": "Chenlei Hu", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/huchenlei/acorn-api-js/issues" 22 | }, 23 | "homepage": "https://github.com/huchenlei/acorn-api-js#readme", 24 | "dependencies": { 25 | "@types/libxmljs": "^0.14.30", 26 | "@types/lodash": "^4.14.77", 27 | "@types/request": "^2.0.3", 28 | "@types/request-promise": "^4.1.37", 29 | "@types/tough-cookie": "^2.3.1", 30 | "libxmljs": "^0.18.7", 31 | "lodash": "^4.17.4", 32 | "request": "^2.82.0", 33 | "request-promise": "^4.2.2", 34 | "tough-cookie": "^2.3.3", 35 | "typescript": "^2.5.2" 36 | }, 37 | "devDependencies": { 38 | "@types/chai": "^4.0.4", 39 | "@types/chai-as-promised": "^7.1.0", 40 | "@types/mocha": "^2.2.43", 41 | "chai": "^4.1.2", 42 | "chai-as-promised": "^7.1.1", 43 | "mocha": "^3.5.3", 44 | "request-debug": "^0.2.0", 45 | "ts-node": "^3.3.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/AcademicHistoryAcornAPI.ts: -------------------------------------------------------------------------------- 1 | import {BaseAcornAPI, needLogin} from "./AcornAPI"; 2 | import rp = require('request-promise'); 3 | import libxmljs = require('libxmljs'); 4 | import _ = require('lodash'); 5 | import {isUndefined} from "util"; 6 | import {AcornError} from "./AcornError"; 7 | import {isNull} from "util"; 8 | import assert = require('assert'); 9 | 10 | /** 11 | * This class is responsible for all academic history operations 12 | * Created by Charlie on 2017-10-05. 13 | */ 14 | 15 | export namespace Acorn { 16 | export interface Score { 17 | code: string; 18 | title: string; 19 | weight: string; 20 | other?: string; 21 | score?: string; 22 | rank?: string; 23 | classRank?: string; 24 | } 25 | 26 | export interface SessionalAcademicHistory { 27 | header: string; 28 | scores: Score[]; 29 | extraInfo?: string; 30 | } 31 | } 32 | 33 | function getText(elements: Array): Array { 34 | return _.map(elements, (element: libxmljs.Element) => { 35 | return element.text(); 36 | }); 37 | } 38 | 39 | export class AcademicHistoryAcornAPI extends BaseAcornAPI { 40 | @needLogin 41 | public async getAcademicHistory(): Promise> { 42 | const page = await rp.get({ 43 | uri: 'https://acorn.utoronto.ca/sws/transcript/academic/main.do?main.dispatch&mode=complete', 44 | jar: this.state.cookieJar 45 | }); 46 | 47 | // const page = require('fs').readFileSync('./sample_original.html', 'utf-8'); 48 | const doc = libxmljs.parseHtml(page); 49 | const infoNode = doc.get("//div[@class='academic-history']//div[@class='academic-history-report row']"); 50 | if (isUndefined(infoNode)) throw new AcornError("Unable to locate academic info div!"); 51 | 52 | // [WARNING]: here only considered the case all session proceed case 53 | const headers = getText(infoNode.find("./h3[@class='sessionHeader']")); 54 | const scores = _.map(getText(infoNode.find("./div[@class='courses blok']")), 55 | sessionScore => { 56 | return _.map(_.filter(sessionScore.split('\n'), 57 | courseScore => { // Remove empty lines 58 | return !(/^[ \t\n]*$/.test(courseScore)); 59 | }), 60 | courseScore => { 61 | let match = /(\w{3,4}\d{3,4}\w\d) (.+?) (\d\.\d\d) (.+)/ 62 | .exec(courseScore); 63 | if (isNull(match)) { 64 | throw new AcornError("Unexpected course score format: " + courseScore); 65 | } 66 | match.shift(); // Remove the first match which is not a capture 67 | _.map(match, _.trim); 68 | const scoreRegex = /(\d{1,3})\s+(\w[+\-]?)\s+(\w[+\-]?)/; 69 | const mustFields = ["code", "title", "weight"]; 70 | const lastField = match[match.length - 1]; 71 | if (scoreRegex.test(lastField)) { 72 | match.pop(); 73 | const scoreMatch = scoreRegex.exec(lastField); 74 | if (isNull(scoreMatch)) throw new AcornError("Severe. This should never happen"); 75 | scoreMatch.shift(); 76 | return _.zipObject(mustFields.concat(["score", "rank", "classRank"]), 77 | match.concat(scoreMatch)); 78 | } else { 79 | return _.zipObject(mustFields.concat(["other"]), match); 80 | } 81 | } 82 | ); 83 | } 84 | ); 85 | const extraInfos = _.chunk(getText(infoNode.find("./div[@class='emph gpa-listing']")), 3); 86 | assert(headers.length === scores.length); 87 | let result = []; 88 | for (let i = 0; i < headers.length; i++) { 89 | const extraInfo = (extraInfos.length > i) ? extraInfos[i] : undefined; 90 | result.push({ 91 | header: headers[i], 92 | scores: scores[i], 93 | extraInfo 94 | }); 95 | } 96 | return result; 97 | } 98 | } -------------------------------------------------------------------------------- /src/AcornAPI.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Charlie on 2017-09-24. 3 | */ 4 | 5 | 'use strict'; 6 | import request = require('request'); 7 | import {AcornError} from "./AcornError"; 8 | 9 | /** 10 | * Share the state information among all API components 11 | */ 12 | 13 | export class AcornStateManager { 14 | private _cookieJar: request.CookieJar; 15 | 16 | get cookieJar(): request.CookieJar { 17 | return this._cookieJar; 18 | } 19 | 20 | public isLoggedIn: boolean; 21 | 22 | public constructor(cookieJar: request.CookieJar) { 23 | this.isLoggedIn = false; 24 | this._cookieJar = cookieJar; 25 | } 26 | 27 | // TODO add login expire check 28 | } 29 | 30 | /** 31 | * Every API components must have field AcornStateManager 32 | */ 33 | export interface AcornAPI { 34 | state: AcornStateManager; 35 | } 36 | 37 | /** 38 | * Base class for every Acorn API class 39 | */ 40 | export class BaseAcornAPI implements AcornAPI { 41 | public state: AcornStateManager; 42 | 43 | constructor(state: AcornStateManager = new AcornStateManager(request.jar())) { 44 | this.state = state; 45 | } 46 | } 47 | 48 | /** 49 | * Decorator to wrap member functions of BaseAcornAPI and it's ascendants. 50 | * the decorated member functions would first check the login state and then 51 | * proceed. 52 | * 53 | * The return type of decorated function should be a Promise 54 | * @param target BaseAcornAPI instance 55 | * @param propertyKey decorated method name 56 | * @param descriptor method descriptor 57 | * @return {PropertyDescriptor} 58 | */ 59 | export function needLogin(target: any, propertyKey: string, descriptor: any) { 60 | // save a reference to the original method this way we keep the values currently in the 61 | // descriptor and don't overwrite what another decorator might have done to the descriptor. 62 | if (descriptor === undefined) { 63 | descriptor = Object.getOwnPropertyDescriptor(target, propertyKey); 64 | } 65 | let originalMethod = descriptor.value; 66 | // editing the descriptor/value parameter 67 | descriptor.value = async function (...args: any[]) { 68 | if (!(this).state.isLoggedIn) { 69 | throw new AcornError('Need to first login to proceed with ' + propertyKey); 70 | } else { 71 | return originalMethod.apply(this, args); 72 | } 73 | }; 74 | return descriptor; 75 | } -------------------------------------------------------------------------------- /src/AcornError.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Charlie on 2017-09-22. 3 | */ 4 | 5 | export class AcornError implements Error { 6 | name: string = 'AcornError'; 7 | message: string; 8 | public constructor(message: string) { 9 | this.message = message; 10 | } 11 | } -------------------------------------------------------------------------------- /src/BasicAcornAPI.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This Class provides basic acorn actions 3 | * 4 | * Created by Charlie on 2017-09-22. 5 | */ 6 | 7 | import tough = require('tough-cookie'); 8 | import request = require('request'); 9 | import rp = require('request-promise'); 10 | 11 | import libxmljs = require('libxmljs'); 12 | 13 | import {AcornError} from './AcornError'; 14 | import {BaseAcornAPI} from "./AcornAPI"; 15 | 16 | const ACORN_HOST = "https://acorn.utoronto.ca"; 17 | const urlTable = { 18 | "authURL1": ACORN_HOST + "/sws", 19 | "authURL2": "https://weblogin.utoronto.ca/", 20 | "authURL3": "https://idp.utorauth.utoronto.ca/PubCookie.reply", 21 | "acornURL": ACORN_HOST + "/spACS" 22 | }; 23 | 24 | const formHeader = { 25 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36', 26 | 'content-type': 'application/x-www-form-urlencoded', 27 | 'Accept': 'text/html' 28 | }; 29 | 30 | interface LooseObj { 31 | [key: string]: string; 32 | } 33 | 34 | export class BasicAcornAPI extends BaseAcornAPI { 35 | /** 36 | * Login to Acorn 37 | * @throws AcornError throw AcornError if login failed 38 | * @returns Boolean will be true if all processes goes properly 39 | */ 40 | public async login(user: string, pass: string): Promise { 41 | let body: string = await rp.get({ 42 | uri: urlTable.authURL1, 43 | jar: this.state.cookieJar 44 | }); 45 | body = await rp.post({ 46 | uri: urlTable.authURL2, 47 | jar: this.state.cookieJar, 48 | headers: formHeader, 49 | form: BasicAcornAPI.extractFormData(body), 50 | }); 51 | let loginInfo = BasicAcornAPI.extractFormData(body); 52 | loginInfo['user'] = user; 53 | loginInfo['pass'] = pass; 54 | body = await rp.post({ 55 | uri: urlTable.authURL2, 56 | jar: this.state.cookieJar, 57 | headers: formHeader, 58 | form: loginInfo, 59 | }); 60 | 61 | if (body.search('Authentication failed') > -1) 62 | throw new AcornError('Invalid Identity'); 63 | 64 | body = await rp.post({ 65 | uri: urlTable.authURL3, 66 | jar: this.state.cookieJar, 67 | headers: formHeader, 68 | form: BasicAcornAPI.extractFormData(body), 69 | followAllRedirects: true 70 | }); 71 | 72 | if (body.search('

A problem has occurred

') > -1) 73 | throw new AcornError('A problem has occurred'); 74 | 75 | body = await rp.post({ 76 | uri: urlTable.acornURL, 77 | jar: this.state.cookieJar, 78 | headers: formHeader, 79 | form: BasicAcornAPI.extractFormData(body), 80 | followAllRedirects: true 81 | }); 82 | 83 | if (!(body.search('ACORN') > -1)) 84 | throw new AcornError('Acorn Unavailable Now'); 85 | 86 | // TODO check cookie to verify whether logged in 87 | this.state.isLoggedIn = true; 88 | return true; 89 | } 90 | 91 | /** 92 | * Extract data from fields of all existing forms from HTML string or dom 93 | * Helper method to facilitate auth process 94 | * @param doc HTML Document or HTML string 95 | * @return LooseObj loose javascript object 96 | */ 97 | private static extractFormData(doc: libxmljs.HTMLDocument | string): LooseObj { 98 | let sanctifiedDoc: libxmljs.HTMLDocument; 99 | if (typeof doc === 'string') { 100 | sanctifiedDoc = libxmljs.parseHtml(doc); 101 | } else { 102 | sanctifiedDoc = doc; 103 | } 104 | const inputs: Array = sanctifiedDoc.find('//form//input[@type="hidden"]'); 105 | let result: LooseObj = {}; 106 | for (let input of inputs) { 107 | result[input.attr('name').value()] = input.attr('value') ? input.attr('value').value() : ""; 108 | } 109 | return result; 110 | } 111 | } -------------------------------------------------------------------------------- /src/CourseAcornAPI.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 'use strict'; 3 | import {BaseAcornAPI, needLogin} from "./AcornAPI"; 4 | import rp = require('request-promise'); 5 | import querystring = require('querystring'); 6 | import {AcornError} from "./AcornError"; 7 | import _ = require('lodash'); 8 | import assert = require('assert'); 9 | 10 | /** 11 | * This class handles all course related actions on acorn 12 | * Created by Charlie on 2017-09-23. 13 | */ 14 | 15 | 16 | function needRegistration(target: any, propertyKey: string, descriptor: any) { 17 | if (descriptor === undefined) { 18 | descriptor = Object.getOwnPropertyDescriptor(target, propertyKey); 19 | } 20 | let originalMethod = descriptor.value; 21 | descriptor.value = async function (registrationIndex: number = 0, ...args: any[]) { 22 | if ((this).cachedRegistrations.length === 0) { 23 | await (this).getEligibleRegistrations(); 24 | } 25 | const regisNum = (this).cachedRegistrations.length; 26 | if (!(regisNum > registrationIndex)) { 27 | throw new AcornError(`Registration IndexOutOfBound! no enough registrations(need ${registrationIndex + 1}, but got ${regisNum})`); 28 | } 29 | args.unshift(registrationIndex); // add registration index at the front of args 30 | return originalMethod.apply(this, args); 31 | }; 32 | return descriptor; 33 | } 34 | 35 | interface CookieValue { 36 | key: string; 37 | value: string; 38 | [key: string]: any; 39 | } 40 | 41 | export class CourseAcornAPI extends BaseAcornAPI { 42 | cachedRegistrations: Array = []; 43 | 44 | /** 45 | * Get user registration status 46 | * @return {TRequest} 47 | */ 48 | @needLogin 49 | public async getEligibleRegistrations(): Promise> { 50 | const res = await rp.get({ 51 | uri: 'https://acorn.utoronto.ca/sws/rest/enrolment/eligible-registrations', 52 | jar: this.state.cookieJar, 53 | json: true 54 | }); 55 | this.cachedRegistrations = res; 56 | return res; 57 | } 58 | 59 | /** 60 | * Get list of courses that are currently enrolled(APP), waitlisted(WAIT), dropped(DROP) 61 | * @param registrationIndex 62 | * @return {TRequest} 63 | */ 64 | @needLogin 65 | @needRegistration 66 | public async getEnrolledCourses(registrationIndex: number = 0): Promise { 67 | const getQueryStr = querystring.stringify(this.cachedRegistrations[registrationIndex].registrationParams); 68 | return await rp.get({ 69 | uri: 'https://acorn.utoronto.ca/sws/rest/enrolment/course/enrolled-courses?' + getQueryStr, 70 | jar: this.state.cookieJar, 71 | json: true 72 | }); 73 | } 74 | 75 | /** 76 | * Get list of courses that are in enrollment cart 77 | * @param registrationIndex 78 | * @return {TRequest} 79 | */ 80 | @needLogin 81 | @needRegistration 82 | public async getCartedCourses(registrationIndex: number = 0): Promise> { 83 | const getQueryStr = querystring.stringify(_.pick(this.cachedRegistrations[registrationIndex], 84 | ['candidacyPostCode', 'candidacySessionCode', 'sessionCode'])); 85 | return await rp.get({ 86 | uri: 'https://acorn.utoronto.ca/sws/rest/enrolment/plan?' + getQueryStr, 87 | jar: this.state.cookieJar, 88 | json: true 89 | }); 90 | } 91 | 92 | /** 93 | * Normally registrationIndex = 1 is summer course 94 | * {"course":{"code":"CSC236H1","sectionCode":"Y","primaryTeachMethod":"LEC","enroled":false},"lecture":{"sectionNo":"LEC,5101"},"tutorial":{},"practical":{}} 95 | * 96 | * [WARNING] this method has not been tested yet; 97 | * Currently its logic is directly copied from Acorn API(Java) 98 | * @param registrationIndex 99 | * @param code courseCode 100 | * @param sectionCode 101 | * @param lectureSectionNo 102 | */ 103 | @needLogin 104 | @needRegistration 105 | public async enroll(registrationIndex: number, 106 | code: string, 107 | sectionCode: string, 108 | lectureSectionNo: string): Promise { 109 | const payload = { 110 | activeCourse: { 111 | course: { 112 | code, 113 | sectionCode: sectionCode.toUpperCase(), 114 | primaryTeachMethod: "LEC", 115 | enrolled: "false" 116 | }, 117 | lecture: { 118 | sectionNo: lectureSectionNo.toUpperCase() 119 | }, 120 | tutorial: {}, 121 | practical: {} 122 | }, 123 | eligRegParams: this.cachedRegistrations[registrationIndex].registrationParams 124 | }; 125 | let xsrfToken = ""; 126 | this.state.cookieJar.getCookies('https://acorn.utoronto.ca').forEach(cookie => { 127 | const cv = JSON.parse(JSON.stringify(cookie)); 128 | if (cv.key === 'XSRF-TOKEN') { 129 | xsrfToken = cv.value; 130 | } 131 | }); 132 | 133 | assert(xsrfToken !== "", "unable to locate xsrf-token in cookies"); 134 | const res = await rp.post({ 135 | uri: 'https://acorn.utoronto.ca/sws/rest/enrolment/course/modify', 136 | body: payload, 137 | headers: { 138 | "X-XSRF-TOKEN": xsrfToken 139 | } 140 | }); 141 | return true; 142 | } 143 | 144 | /** 145 | * This method loads some extra information on courses 146 | * @param registrationIndex 147 | * @param courseCode 148 | * @param courseSessionCode 149 | * @param sectionCode 150 | * @return {TRequest} 151 | */ 152 | @needLogin 153 | @needRegistration 154 | public async getExtraCourseInfo(registrationIndex: number, 155 | courseCode: string, 156 | courseSessionCode: string, 157 | sectionCode: string): Promise { 158 | const getQueryStr = 159 | querystring.stringify(this.cachedRegistrations[registrationIndex].registrationParams) + '&' + 160 | querystring.stringify({ 161 | activityApprovedInd: "", 162 | activityApprovedOrg: "", 163 | courseCode, 164 | courseSessionCode, 165 | sectionCode 166 | }); 167 | 168 | return await rp.get({ 169 | uri: 'https://acorn.utoronto.ca/sws/rest/enrolment/course/view?' + getQueryStr, 170 | jar: this.state.cookieJar, 171 | json: true 172 | }); 173 | } 174 | } -------------------------------------------------------------------------------- /src/CourseInterfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file describes course structure 3 | * Created by Charlie on 2017-10-03. 4 | */ 5 | namespace Acorn { 6 | export interface Post { 7 | code: string; 8 | description: string; 9 | acpDuration: number; 10 | } 11 | 12 | export interface RegistrationStatuses { 13 | [key: string]: { 14 | registrationStatusCode: string; 15 | yearOfStudy: string; 16 | }; 17 | } 18 | 19 | export interface RegistrationParams { 20 | postCode: string; 21 | postDescription: string; 22 | sessionCode: string; 23 | sessionDescription: string; 24 | status: string; 25 | assocOrgCode: string; 26 | acpDuration: string; 27 | levelOfInstruction: string; 28 | typeOfProgram: string; 29 | subjectCode1: string; 30 | designationCode1: string; 31 | primaryOrgCode: string; 32 | secondaryOrgCode: string; 33 | collaborativeOrgCode: string; 34 | adminOrgCode: string; 35 | coSecondaryOrgCode: string; 36 | yearOfStudy: string; 37 | postAcpDuration: string; 38 | useSws: string; 39 | } 40 | 41 | export interface Registration { 42 | post: Post; 43 | sessionCode: string; 44 | personId: number; 45 | acpDuration: number; 46 | status: string; 47 | yearOfStudy: string; 48 | assocOrgCode: string; 49 | levelOfInstruction: string; 50 | typeOfProgram: string; 51 | subjectCode1: string; 52 | designationCode1: string; 53 | primaryOrgCode: string; 54 | secondaryOrgCode: string; 55 | collaborativeOrgCode: string; 56 | adminOrgCode: string; 57 | coSecondaryOrgCode: string; 58 | overrideSubjectPostPrimaryOrg: string; 59 | candidacyPostCode: string; 60 | candidacySessionCode: string; 61 | postOfferCncLimit: string; 62 | postOfferCncAllowed: string; 63 | activityApprovedInd: string; 64 | activityApprovedOrg: string; 65 | academicStatusDesc?: any; 66 | exchangeTitle?: any; 67 | associatedOrgName?: any; 68 | attendanceClassDesc: string; 69 | gradFundingIndicator: string; 70 | useSws: string; 71 | sessionDescription: string; 72 | studentCampusCode: string; 73 | maxCourseLoad?: any; 74 | registrationSessions: string[]; 75 | registrationStatuses: RegistrationStatuses; 76 | registrationParams: RegistrationParams; 77 | currentRegistration: boolean; 78 | postInfo: string; 79 | sessionInfo: string; 80 | } 81 | 82 | export interface Day { 83 | dayCode: string; 84 | dayName: string; 85 | index: number; 86 | weekDay: boolean; 87 | gregorianCalendarDayOfWeek: number; 88 | } 89 | 90 | export interface Time { 91 | day: Day; 92 | startTime: string; 93 | endTime: string; 94 | buildingCode: string; 95 | room: string; 96 | instructors: string[]; 97 | commaSeparatedInstructorNames: string; 98 | } 99 | 100 | export interface MeetingResource { 101 | sessionCode: string; 102 | activityCode: string; 103 | sectionCode: string; 104 | teachMethod: string; 105 | sectionNumber: string; 106 | sequence: number; 107 | instructorOrgUnit?: any; 108 | teachPercent?: any; 109 | employeeNumber?: any; 110 | logCounter: number; 111 | employeeType?: any; 112 | instructorSurname: string; 113 | instructorFirstName: string; 114 | instructorInitials: string; 115 | } 116 | 117 | export interface Meeting { 118 | sectionNo: string; 119 | sessionCode: string; 120 | teachMethod: string; 121 | enrollSpace: number; 122 | enrollmentSpaceAvailable: number; 123 | totalSpace: number; 124 | cancelled: boolean; 125 | closed: boolean; 126 | waitlistable: boolean; 127 | subTitle1: string; 128 | subTitle2: string; 129 | subTitle3: string; 130 | waitlistRank: number; 131 | waitlistLookupMethod?: any; 132 | times: Time[]; 133 | displayTime: string; 134 | action: string; 135 | full: boolean; 136 | enrollmentControlFull: boolean; 137 | enrollmentControlMissing: boolean; 138 | enrolmentIndicator: string; 139 | enrolmentIndicatorDisplay?: any; 140 | enrolmentIndicatorMsg?: any; 141 | enrolmentIndicatorDates: any[]; 142 | deliveryMode: string; 143 | waitlistableForAll: boolean; 144 | message?: any; 145 | professorApprovalReq: string; 146 | meetingResources: MeetingResource[]; 147 | displayName: string; 148 | subTitle: string; 149 | hasSubTitle: boolean; 150 | commaSeparatedInstructorNames: string; 151 | } 152 | 153 | export interface Course { 154 | code: string; 155 | sectionCode: string; 156 | title: string; 157 | status: string; 158 | primaryTeachMethod: string; 159 | secondaryTeachMethod1: string; 160 | secondaryTeachMethod2: string; 161 | primarySectionNo: string; 162 | secondarySectionNo1: string; 163 | secondarySectionNo2: string; 164 | deliveryMode: string; 165 | waitlistTeachMethod: string; 166 | waitlistSectionNo: string; 167 | waitlistMeetings?: any; 168 | meetings: Meeting[]; 169 | enroled: boolean; 170 | attendanceStatus: string; 171 | sessionCode: string; 172 | courseCredits: string; 173 | markApprovedDate?: any; 174 | mark: string; 175 | regApprovedDate?: any; 176 | regApprovedTime?: any; 177 | subSessionCode: string; 178 | currentCncCreditsBalance?: any; 179 | cncAllowed: string; 180 | postCode?: any; 181 | activityPrimaryOrgCode?: any; 182 | activitySecondaryOrgCode?: any; 183 | activityCoSecondaryOrgCode?: any; 184 | courseStatusIfEnroling?: any; 185 | cancelled: boolean; 186 | regSessionCode1: string; 187 | regSessionCode2: string; 188 | regSessionCode3: string; 189 | enrolmentErrorCode: number; 190 | enrolmentErrorMessage?: any; 191 | planErrorCode: number; 192 | planningAllowed: boolean; 193 | planningStartDate?: any; 194 | planningEndDate?: any; 195 | enrolEnded: boolean; 196 | enrolNotStarted: boolean; 197 | retrieveCancelledCourses: boolean; 198 | displayName: string; 199 | meetingList: string[]; 200 | waitlistable: boolean; 201 | approved: boolean; 202 | dropped: boolean; 203 | refused: boolean; 204 | requested: boolean; 205 | interim: boolean; 206 | waitlisted: boolean; 207 | enroledInPrimary: boolean; 208 | enroledInSecondary1: boolean; 209 | enroledInSecondary2: boolean; 210 | isEligibleForCnc: boolean; 211 | isWithinSessionalDatesForCnc: boolean; 212 | enroledInAll: boolean; 213 | enroledMeetingSections: string[]; 214 | teachMethods: string[]; 215 | secondaryTeachMethods: string[]; 216 | deliveryModeDisplay: string; 217 | } 218 | 219 | export interface EnrolledCourses{ 220 | APP?: Course[]; 221 | WAIT?: Course[]; 222 | DROP?: Course[]; 223 | } 224 | 225 | export interface Info { 226 | primaryActivities: any[]; 227 | secondaryActivities: any[]; 228 | thirdActivities: any[]; 229 | } 230 | 231 | export interface Message { 232 | title: string; 233 | value: string; 234 | } 235 | 236 | export interface CartedCourse { 237 | _id: string; 238 | courseCode: string; 239 | sectionCode: string; 240 | primaryActivityId: string; 241 | secondaryActivityId: string; 242 | thirdActivityId: string; 243 | courseTitle: string; 244 | cancelled: boolean; 245 | regSessionCode1: string; 246 | regSessionCode2: string; 247 | regSessionCode3: string; 248 | messages: Message[]; 249 | info: Info; 250 | } 251 | } 252 | 253 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Charlie on 2017-09-24. 3 | */ 4 | 5 | 'use strict'; 6 | import {AcornStateManager} from "./AcornAPI"; 7 | import request = require("request"); 8 | import {CourseAcornAPI} from "./CourseAcornAPI"; 9 | import {BasicAcornAPI} from "./BasicAcornAPI"; 10 | import {AcademicHistoryAcornAPI} from "./AcademicHistoryAcornAPI"; 11 | 12 | export class Acorn { 13 | private state: AcornStateManager; 14 | basic: BasicAcornAPI; 15 | course: CourseAcornAPI; 16 | academic: AcademicHistoryAcornAPI; 17 | public constructor() { 18 | this.state = new AcornStateManager(request.jar()); 19 | this.basic = new BasicAcornAPI(this.state); 20 | this.course = new CourseAcornAPI(this.state); 21 | this.academic = new AcademicHistoryAcornAPI(this.state); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/AcademicHistoryAcornAPI.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Charlie on 2017-10-05. 3 | */ 4 | import {expect} from "chai"; 5 | import {AcademicHistoryAcornAPI} from "../src/AcademicHistoryAcornAPI"; 6 | import {BasicAcornAPI} from "../src/BasicAcornAPI"; 7 | import {AcornStateManager} from "../src/AcornAPI"; 8 | import request = require("request"); 9 | import {config, logToFileSync} from "./TestHelper"; 10 | 11 | require('chai').use(require('chai-as-promised')).should(); 12 | 13 | describe('AcademicHistoryAcornAPI', function () { 14 | this.timeout(15000); // set timeout to be 15s instead of default 2 15 | let academicAPI: AcademicHistoryAcornAPI; 16 | 17 | it('should be created', async function () { 18 | let state = new AcornStateManager(request.jar()); 19 | let basicAPI = new BasicAcornAPI(state); 20 | academicAPI = new AcademicHistoryAcornAPI(state); 21 | expect(academicAPI).to.not.be.null; 22 | await basicAPI.login(config.data.user, config.data.pass); 23 | }); 24 | 25 | it('should get academic history', async function () { 26 | logToFileSync('academic_history', await academicAPI.getAcademicHistory()); 27 | }); 28 | }); -------------------------------------------------------------------------------- /test/BasicAcornAPI.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Charlie on 2017-09-24. 3 | */ 4 | import {expect} from "chai"; 5 | import {BasicAcornAPI} from "../src/BasicAcornAPI"; 6 | import {config} from "./TestHelper"; 7 | 8 | require('chai').use(require('chai-as-promised')).should(); 9 | 10 | 11 | describe('BasicAcornAPI', function () { 12 | this.timeout(15000); // set timeout to be 15s instead of default 2 13 | let basicAPI: BasicAcornAPI; 14 | it('should be created', function () { 15 | basicAPI = new BasicAcornAPI(); 16 | expect(basicAPI).to.not.be.null; 17 | }); 18 | 19 | it('should throw Acorn Error when user/pass pair is incorrect', function () { 20 | expect(basicAPI.login('user', 'pass')).to.be.rejected; 21 | }); 22 | 23 | // High Delay 24 | it('should login the user', async function () { 25 | // console.log(basicAPI); 26 | let result = await basicAPI.login(config.data.user, config.data.pass); 27 | expect(result).to.be.true; 28 | expect(basicAPI.state.isLoggedIn).to.be.true; 29 | }); 30 | }); -------------------------------------------------------------------------------- /test/CourseAcornAPI.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Charlie on 2017-09-24. 3 | */ 4 | import {expect} from "chai"; 5 | import {CourseAcornAPI} from "../src/CourseAcornAPI"; 6 | import {BasicAcornAPI} from "../src/BasicAcornAPI"; 7 | import {AcornStateManager} from "../src/AcornAPI"; 8 | import {AcornError} from '../src/AcornError'; 9 | import request = require("request"); 10 | import assert = require('assert'); 11 | import _ = require("lodash"); 12 | import {config, logToFileSync} from "./TestHelper"; 13 | 14 | 15 | require('chai').use(require('chai-as-promised')).should(); 16 | 17 | describe('CourseAcornAPI', async function () { 18 | this.timeout(15000); // set timeout to be 15s instead of default 2 19 | let basicAPI: BasicAcornAPI; 20 | let courseAPI: CourseAcornAPI; 21 | it('should be created', function () { 22 | const state = new AcornStateManager(request.jar()); 23 | basicAPI = new BasicAcornAPI(state); 24 | courseAPI = new CourseAcornAPI(state); 25 | 26 | expect(courseAPI).to.not.be.null; 27 | expect(courseAPI.state).to.not.be.undefined; 28 | }); 29 | 30 | it('should share state with other AcornAPI instances', function () { 31 | expect(courseAPI.state).to.deep.equal(basicAPI.state); 32 | }); 33 | 34 | /** 35 | * Test for @needLogin decorator 36 | */ 37 | it('should not get registration if not logged in', function () { 38 | courseAPI.getEligibleRegistrations().should.be.rejected; 39 | }); 40 | 41 | it('should get registration if logged in', async function () { 42 | await basicAPI.login(config.data.user, config.data.pass); 43 | expect(basicAPI.state.isLoggedIn).to.be.true; 44 | 45 | let res = await courseAPI.getEligibleRegistrations(); 46 | res.should.be.a.instanceof(Array); 47 | (>res)[0].should.haveOwnProperty('registrationParams'); 48 | logToFileSync('registrations', res); 49 | }); 50 | 51 | it('should get enrolled courses if logged in', async function () { 52 | logToFileSync('enrolled_courses', await courseAPI.getEnrolledCourses()); 53 | }); 54 | 55 | let cartedCourse: Acorn.CartedCourse | null = null; 56 | it('should get carted courses if logged in', async function () { 57 | const cartedCourses = await courseAPI.getCartedCourses(); 58 | // For later testing use 59 | if (cartedCourses.length > 0) { 60 | cartedCourse = cartedCourses[0]; 61 | } 62 | logToFileSync('carted_courses', cartedCourses); 63 | }); 64 | 65 | // TODO test when course registration is open 66 | // it('should enroll course if logged in', async function () { 67 | // await courseAPI.enroll(0, "CSC467", "F", "LEC,0101"); 68 | // }); 69 | 70 | /** 71 | * To test this part 72 | * The tester's acorn account must have at least one course in enrollment cart 73 | */ 74 | it('should get extra course info if logged in', async function () { 75 | if (cartedCourse === null) { 76 | throw new AcornError('No course in course cart, unable to find extra info'); 77 | } 78 | const sessionCodes = _.compact(_.values(_.pick(cartedCourse, 79 | ['regSessionCode1', 'regSessionCode2', 'regSessionCode3']))); 80 | assert(sessionCodes.length > 0, 'no session code available'); // need at least one sessionCode 81 | const res = await courseAPI.getExtraCourseInfo( 82 | 0, cartedCourse.courseCode, sessionCodes[0], cartedCourse.sectionCode); 83 | logToFileSync('extra_course_info', res); 84 | }); 85 | }); -------------------------------------------------------------------------------- /test/TestHelper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Charlie on 2017-10-15. 3 | */ 4 | 5 | import fs = require('fs'); 6 | 7 | const LOG_DIR = './test/logs/'; 8 | if (!fs.existsSync(LOG_DIR)) { 9 | fs.mkdirSync(LOG_DIR); 10 | } 11 | 12 | export const config = 13 | JSON.parse(fs.readFileSync('./test/test_config.json', 'utf-8')); 14 | 15 | export function logToFileSync(file_name:string, data: object):void{ 16 | fs.writeFileSync(`${LOG_DIR}${file_name}.json`, JSON.stringify(data)); 17 | } -------------------------------------------------------------------------------- /test_script.pl: -------------------------------------------------------------------------------- 1 | # Used by package.json test script 2 | use strict; 3 | use warnings; 4 | 5 | # to run all test, invoke `npm test -- "*"` 6 | my $module_name = $ARGV[0]; 7 | system "mocha -r ts-node/register ./test/$module_name.spec.ts"; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", 5 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ 6 | "module": "commonjs", 7 | /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 8 | // "lib": [], /* Specify library files to be included in the compilation: */ 9 | // "allowJs": true, /* Allow javascript files to be compiled. */ 10 | // "checkJs": true, /* Report errors in .js files. */ 11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 12 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 13 | "sourceMap": true, 14 | /* Generates corresponding '.map' file. */ 15 | // "outFile": "./dist/", /* Concatenate and emit output to single file. */ 16 | "outDir": "./dist", 17 | /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, 27 | /* Enable all strict type-checking options. */ 28 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 29 | // "strictNullChecks": true, /* Enable strict null checks. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | "sourceRoot": "./", 51 | /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ 59 | }, 60 | "files": [ 61 | "./node_modules/@types/request/index.d.ts", 62 | "./node_modules/@types/request-promise/index.d.ts", 63 | "./node_modules/@types/tough-cookie/index.d.ts", 64 | "./node_modules/@types/libxmljs/index.d.ts", 65 | "./node_modules/@types/mocha/index.d.ts", 66 | "./node_modules/@types/chai/index.d.ts", 67 | "./node_modules/@types/lodash/index.d.ts", 68 | ], 69 | "include": [ 70 | "src/**/*.ts", 71 | "./main.ts" 72 | ], 73 | "exclude": [ 74 | "node_modules" 75 | ] 76 | } --------------------------------------------------------------------------------