├── .gitignore ├── coverage ├── lcov-report │ ├── sort-arrow-sprite.png │ ├── prettify.css │ ├── index.html │ ├── alexa-skill-local │ │ ├── index.html │ │ └── authServer.js.html │ ├── sorter.js │ ├── base.css │ └── prettify.js ├── lcov.info └── coverage.json ├── .travis.yml ├── html ├── close.html └── index.html ├── test ├── endpointController.js └── authServer.js ├── authServer.js ├── .vscode └── launch.json ├── mockLambdaServer.js ├── LICENSE ├── package.json ├── README.md ├── bin └── start.js └── endpointController.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /.vscode/ 3 | -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itachiRedhair/alexa-skill-local/HEAD/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | 6 | install: 7 | - npm install 8 | 9 | script: 10 | - npm run cover 11 | 12 | after_script: "cat coverage/lcov.info | node_modules/coveralls/bin/coveralls.js" -------------------------------------------------------------------------------- /html/close.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | alexa-skill-local 7 | 8 | 9 | 10 |

Thank You! You can close this window now.

11 | 12 | 13 | -------------------------------------------------------------------------------- /coverage/lcov.info: -------------------------------------------------------------------------------- 1 | TN: 2 | SF:/Users/di/Desktop/akshay/alexa-skill-local/authServer.js 3 | FN:21,intiAuthServer 4 | FNF:1 5 | FNH:1 6 | FNDA:2,intiAuthServer 7 | DA:1,1 8 | DA:2,1 9 | DA:3,1 10 | DA:4,1 11 | DA:5,1 12 | DA:7,1 13 | DA:9,1 14 | DA:10,1 15 | DA:12,1 16 | DA:13,1 17 | DA:14,1 18 | DA:15,1 19 | DA:16,1 20 | DA:19,1 21 | DA:21,1 22 | DA:22,2 23 | DA:23,2 24 | DA:25,2 25 | LF:18 26 | LH:18 27 | BRF:0 28 | BRH:0 29 | end_of_record 30 | -------------------------------------------------------------------------------- /test/endpointController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const expect = require('chai').expect; 3 | const request = require('request'); 4 | const ngrok = require('ngrok'); 5 | 6 | const ngrokInit = require('./../endpointController').ngrokInit; 7 | 8 | describe("Testing ngrokInit function", function () { 9 | let url = ''; 10 | this.timeout(7000); 11 | 12 | before(async function () { 13 | url = await ngrokInit(3000); 14 | }); 15 | 16 | after(async function () { 17 | await ngrok.kill(); 18 | }); 19 | 20 | it('should generate ngrok url', function () { 21 | expect(url).to.contain('ngrok'); 22 | }); 23 | }); -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /authServer.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const http = require('http'); 3 | const bodyParser = require('body-parser') 4 | const app = express(); 5 | const nodemon = require('nodemon'); 6 | 7 | let accessToken = null; 8 | 9 | app.use(bodyParser.json()); 10 | app.use(express.static(__dirname + '/html')); 11 | 12 | app.get('/oauth2/callback', (req, res) => { 13 | accessToken = req.query.access_token; 14 | res.sendFile(__dirname + '/html/close.html'); 15 | httpServer.close(); 16 | httpServer.emit('access-token', accessToken); 17 | }); 18 | 19 | const httpServer = http.createServer(app); 20 | 21 | module.exports = function intiAuthServer() { 22 | httpServer.listen(3001, () => { 23 | console.log('Open in your browser and login with Amazon ==> http://localhost:3001') 24 | }); 25 | return httpServer; 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "attach", 10 | "name": "Node: Nodemon", 11 | "processId": "${command:PickProcess}", 12 | "restart": true, 13 | "protocol": "inspector", 14 | "port": 9229 15 | }, 16 | { 17 | "type": "node", 18 | "request": "launch", 19 | "name": "Launch Program", 20 | "program": "${workspaceFolder}\\bin\\start.js", 21 | "args": [ 22 | 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /mockLambdaServer.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin / env node 2 | 3 | const express = require("express"); 4 | const bodyParser = require("body-parser"); 5 | const path = require("path"); 6 | const colors = require("colors"); 7 | 8 | const server = express(); 9 | server.use(bodyParser.json()); 10 | 11 | const filePath = process.argv[2]; 12 | const port = process.argv[3]; 13 | 14 | const handler = require(filePath).handler; 15 | 16 | server.post("/", (req, res) => { 17 | // Create dummy lambda context with fail and succeed functions 18 | const context = { 19 | fail: () => { 20 | res.sendStatus(500); 21 | }, 22 | succeed: data => { 23 | res.send(data); 24 | } 25 | }; 26 | 27 | handler(req.body, context, (err, response) => { 28 | if (err) { 29 | console.log("data", err); 30 | throw new Error(err); 31 | } 32 | if (response) { 33 | res.send(response); 34 | } 35 | }); 36 | }); 37 | 38 | server.listen(port, function() { 39 | console.log(colors.green("Mock Lambda Service is running on port " + port)); 40 | }); 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Akshay Milmile 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alexa-skill-local", 3 | "version": "1.2.7", 4 | "description": "Develop Alexa skills locally", 5 | "scripts": { 6 | "start": "nodemon ./bin/start.js", 7 | "test": "NODE_ENV=test node_modules/.bin/mocha --reporter spec", 8 | "cover": 9 | "NODE_ENV=test node_modules/istanbul/lib/cli.js cover node_modules/mocha/bin/_mocha -- -R spec test/*" 10 | }, 11 | "bin": { 12 | "alexa-skill-local": "./bin/start.js" 13 | }, 14 | "engines": { 15 | "node": ">=6.0.0" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/itachiRedhair/alexa-skill-local" 20 | }, 21 | "author": "", 22 | "license": "MIT", 23 | "dependencies": { 24 | "argparse": "^1.0.10", 25 | "aws-lambda-mock-context": "^3.1.1", 26 | "body-parser": "^1.18.2", 27 | "colors": "^1.2.1", 28 | "express": "^4.16.3", 29 | "ngrok": "^3.0.1", 30 | "nodemon": "^1.17.3" 31 | }, 32 | "devDependencies": { 33 | "chai": "^4.1.2", 34 | "coveralls": "^3.0.0", 35 | "istanbul": "^0.4.5", 36 | "mocha": "^5.1.1", 37 | "request": "^2.85.0" 38 | }, 39 | "keywords": [ 40 | "alexa", 41 | "nodemon", 42 | "lambda", 43 | "ngrok", 44 | "alexa skill", 45 | "alexa skill local", 46 | "alexa local" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | alexa-skill-local 7 | 8 | 9 | 10 |

Welcome to alexa-skill-local

11 |

To continue Login With amazon

12 | 13 | Login with Amazon 15 | 16 | 24 | 25 |
26 | 27 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /coverage/coverage.json: -------------------------------------------------------------------------------- 1 | {"/Users/di/Desktop/akshay/alexa-skill-local/authServer.js":{"path":"/Users/di/Desktop/akshay/alexa-skill-local/authServer.js","s":{"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":2,"17":2,"18":2},"b":{},"f":{"1":2},"fnMap":{"1":{"name":"intiAuthServer","line":21,"loc":{"start":{"line":21,"column":17},"end":{"line":21,"column":43}}}},"statementMap":{"1":{"start":{"line":1,"column":0},"end":{"line":1,"column":35}},"2":{"start":{"line":2,"column":0},"end":{"line":2,"column":29}},"3":{"start":{"line":3,"column":0},"end":{"line":3,"column":41}},"4":{"start":{"line":4,"column":0},"end":{"line":4,"column":22}},"5":{"start":{"line":5,"column":0},"end":{"line":5,"column":35}},"6":{"start":{"line":7,"column":0},"end":{"line":7,"column":23}},"7":{"start":{"line":9,"column":0},"end":{"line":9,"column":27}},"8":{"start":{"line":10,"column":0},"end":{"line":10,"column":45}},"9":{"start":{"line":12,"column":0},"end":{"line":17,"column":3}},"10":{"start":{"line":13,"column":4},"end":{"line":13,"column":41}},"11":{"start":{"line":14,"column":4},"end":{"line":14,"column":49}},"12":{"start":{"line":15,"column":4},"end":{"line":15,"column":23}},"13":{"start":{"line":16,"column":4},"end":{"line":16,"column":49}},"14":{"start":{"line":19,"column":0},"end":{"line":19,"column":42}},"15":{"start":{"line":21,"column":0},"end":{"line":26,"column":1}},"16":{"start":{"line":22,"column":4},"end":{"line":24,"column":7}},"17":{"start":{"line":23,"column":8},"end":{"line":23,"column":91}},"18":{"start":{"line":25,"column":4},"end":{"line":25,"column":22}}},"branchMap":{}}} -------------------------------------------------------------------------------- /test/authServer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const expect = require('chai').expect; 3 | const request = require('request'); 4 | const fs = require('fs'); 5 | const path = require('path') 6 | 7 | const initAuthServer = require('./../authServer'); 8 | 9 | const indexHtml = fs.readFileSync(path.resolve(__dirname, './../html/index.html'), 'utf8'); 10 | const closeHtml = fs.readFileSync(path.resolve(__dirname, './../html/close.html'), 'utf8'); 11 | 12 | 13 | describe("Testing Login With Amazon Page", function () { 14 | let response = null; 15 | let body = null; 16 | let httpServer = null; 17 | 18 | before(function (done) { 19 | httpServer = initAuthServer(); 20 | request('http://localhost:3001', function (error, resp, respBody) { 21 | response = resp; 22 | body = respBody 23 | done(); 24 | }); 25 | }); 26 | 27 | after(function () { 28 | httpServer.close(); 29 | }); 30 | 31 | describe("Testing response statuscode and body", function () { 32 | it('should render the page with index.html', function () { 33 | expect(body).to.equal(indexHtml); 34 | }); 35 | 36 | it('should have status code 200', function () { 37 | expect(response.statusCode).to.equal(200); 38 | }); 39 | }) 40 | }) 41 | 42 | describe("Testing 'Successful Authentication and now you can close page'", function () { 43 | let response = null; 44 | let body = null; 45 | let httpServer = null; 46 | 47 | before(function (done) { 48 | httpServer = initAuthServer(); 49 | request('http://localhost:3001/oauth2/callback?access_token=your_login_with_amazon_access_token', function (error, resp, respBody) { 50 | response = resp; 51 | body = respBody 52 | done(); 53 | }); 54 | }); 55 | 56 | after(function () { 57 | httpServer.close(); 58 | }); 59 | 60 | describe("Testing response statuscode and body", function () { 61 | it('should render the page with close.html', function () { 62 | expect(body).to.equal(closeHtml); 63 | }); 64 | 65 | it('should have status code 200', function () { 66 | expect(response.statusCode).to.equal(200); 67 | }); 68 | }) 69 | }) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/itachiRedhair/alexa-skill-local.svg?branch=master)](https://travis-ci.org/itachiRedhair/alexa-skill-local) 2 | 3 | [![Coverage Status](https://coveralls.io/repos/github/itachiRedhair/alexa-skill-local/badge.svg?branch=master)](https://coveralls.io/github/itachiRedhair/alexa-skill-local?branch=master) 4 | 5 | # DEPRECATED 6 | This library is not maintained anymore. It might not work well for latest ASK SDK. Feel free to reach out if you liked the idea behind this library and are interested in maintaining it. 7 | 8 | # Update 9 | Now working with ASK SDK v2. 10 | 11 | # alexa-skill-local 12 | `alexa-skill-local` provides you local development environment for your Alexa Skill. It starts ngrok and mock lambda server on the same port. All the traffic on this port is tunneled through ngrok. You have to login with Amazon to update your skill endpoint with the ngrok url. Mock lambda server calls your lambda function the each time it gets request from Alexa. 13 | 14 | ### Requirements 15 | - Node and npm 16 | - Alexa Skill (Lambda Code) written in Node.js 17 | 18 | ### Installation 19 | 20 | Use [Node.js](https://nodejs.org/) v8.x.x to run. 21 | 22 | You can install alexa-skill-local globally (recommended) or in your project directory (in this case you many want to run it from npm scripts in package.json). 23 | 24 | ```sh 25 | $ npm install -g alexa-skill-local 26 | ``` 27 | 28 | ### Usage 29 | 30 | Run following command. When prompted open `http://localhost:3001` in your browser. Login with Amazon to grant `alexa-skill-local` an access to update your skill's endpoint. 31 | 32 | ```sh 33 | $ alexa-skill-local [-f|--file ] [-p|--port ] [-c|--config ] [--inspect-brk ] 34 | ``` 35 | `--file` : Optional. When run without `--file` argument alexa-skill-local searches for main entry in `package.json`. If not found, it searches for `index.js` in the root directory. 36 | 37 | `--port`: Optional. Specify port value for mock lambda server and ngrok. Default value is 3000. 38 | 39 | `--config`: Optional. If not specified, it searches for `asl-config.json` in the root directory. You need to have config file, otherwise skill endpoint update won't work. Config file has to be of following format (If you are not sure of the "stage", in most cases it is "development"): 40 | 41 | ```sh 42 | { 43 | "skillId" : "your_skill_id_here", 44 | "stage" : "stage_of_the_skill" 45 | } 46 | ``` 47 | 48 | `--inspect-brk`: Optional. Specify this flag if you want to attach debugger to mock lambda server process. If port is not specified debugger will listen to is 9229. 49 | 50 | 51 | Then you can use your favorite editor to attach debugger to this process. 52 | For example in VSCode you will need following configuration in your launch.json file: 53 | ```sh 54 | { 55 | "type": "node", 56 | "request": "attach", 57 | "name": "alexa-skill-local", 58 | "processId": "${command:PickProcess}", 59 | "restart": true, 60 | "protocol": "inspector", 61 | "port": 9229 62 | } 63 | ``` 64 | 65 | License 66 | ---- 67 | 68 | MIT 69 | 70 | Contact 71 | ---- 72 | email : akshay.milmile@gmail.com 73 | 74 | Links 75 | ---- 76 | npm : https://www.npmjs.com/package/alexa-skill-local 77 | Privacy Policy : https://sites.google.com/view/alexa-skill-local 78 | 79 | -------------------------------------------------------------------------------- /coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | / 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 18/18 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 18/18 41 |
42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
FileStatementsBranchesFunctionsLines
alexa-skill-local/
100%18/18100%0/0100%1/1100%18/18
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /coverage/lcov-report/alexa-skill-local/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for alexa-skill-local/ 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files alexa-skill-local/ 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 18/18 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 18/18 41 |
42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
FileStatementsBranchesFunctionsLines
authServer.js
100%18/18100%0/0100%1/1100%18/18
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /bin/start.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const colors = require('colors'); 3 | console.log(colors.magenta('alexa-skill-local is starting...')); 4 | const ArgumentParser = require('argparse').ArgumentParser; 5 | const argParser = new ArgumentParser(); 6 | const path = require('path'); 7 | const nodemon = require('nodemon'); 8 | 9 | const updateAlexaEndpoint = require('./../endpointController').updateAlexaEndpoint; 10 | const ngrokInit = require('./../endpointController').ngrokInit; 11 | const intiAuthServer = require('./../authServer'); 12 | 13 | argParser.addArgument( 14 | ['-f', '--file'], 15 | { 16 | help: 'Specify lambda entry file. By default it will search for index.js file in the current directory.' 17 | } 18 | ) 19 | 20 | argParser.addArgument( 21 | ['-p', '--port'], 22 | { 23 | help: 'Specify port for express and ngrok setup. Default: 3000.' 24 | } 25 | ) 26 | 27 | argParser.addArgument( 28 | '--inspect-brk', 29 | { 30 | help: 'Specify if you want to attach debugger.', 31 | action: 'store', 32 | nargs: '?', 33 | constant: 9229 34 | } 35 | ) 36 | 37 | argParser.addArgument( 38 | ['-c', '--config'], 39 | { 40 | help: 'Required. Load you config file with skillId and stage.', 41 | defaultValue: 'asl-config.json' 42 | // required: true 43 | } 44 | ) 45 | 46 | const httpsServer = intiAuthServer(); 47 | 48 | const args = argParser.parseArgs(); 49 | 50 | let filePath = path.resolve(args.file ? args.file : './'); 51 | 52 | const watchList = [ 53 | filePath 54 | ]; 55 | 56 | if (path.extname(filePath) === '.js') { 57 | let fileDirectory = path.dirname(filePath); 58 | watchList.push(fileDirectory); 59 | } else { 60 | try { 61 | let packageJson = require(filePath + '/package.json'); 62 | 63 | if (packageJson.main) { 64 | fileName = packageJson.main; 65 | console.log(colors.yellow('Taking ' + fileName + ' as an entry point from main field in package.json')); 66 | } else { 67 | fileName = 'index.js'; 68 | console.log(colors.yellow('Main is not defined in package.json. Taking index.js as an entry point')); 69 | } 70 | 71 | filePath += '/' + fileName; 72 | } catch (err) { 73 | console.log(colors.yellow('package.json not found. Taking index.js as an entry point')); 74 | filePath += '/' + 'index.js'; 75 | } 76 | } 77 | 78 | const port = args.port ? args.port : "3000"; 79 | 80 | const nodemonArgs = [] 81 | 82 | if (args.inspect_brk) { 83 | nodemonArgs.push(`--inspect-brk=${args.inspect_brk}`) 84 | } 85 | 86 | const serverArgs = [filePath, port]; 87 | 88 | httpsServer.on('access-token', async (accessToken) => { 89 | try { 90 | const config = require(path.resolve(args.config)); 91 | 92 | try { 93 | await updateAlexaEndpoint(port, accessToken, config); 94 | initMockLambdaServer(); 95 | } catch (err) { 96 | console.log(colors.red(err)); 97 | console.log(colors.green('Only Mock Lambda Server will run in case you can deal with it somehow.')); 98 | initMockLambdaServer(); 99 | } 100 | 101 | } catch (err) { 102 | console.log(colors.red('Error finding config file. Add asl-config.json file in root directory or specify JSON file path with --config argument .')); 103 | console.log(colors.yellow('Error updating Alexa Skill Endpoint. You have to do it manually.\n')) 104 | const url = await ngrokInit(port); 105 | console.log(colors.yellow('-----------------------------------------------------------------------------------------')); 106 | console.log(colors.yellow('| Enter this url as HTTPS endpoint in your Alexa console -->'), colors.cyan(url), colors.yellow(' |')); 107 | console.log(colors.yellow('-----------------------------------------------------------------------------------------\n')); 108 | initMockLambdaServer(); 109 | } 110 | 111 | }); 112 | 113 | function initMockLambdaServer() { 114 | try { 115 | const handler = require(filePath).handler; 116 | 117 | nodemon({ 118 | nodeArgs: nodemonArgs, 119 | script: __dirname + '/../mockLambdaServer.js', 120 | args: serverArgs, 121 | watch: watchList 122 | }); 123 | 124 | nodemon 125 | .on('quit', function () { 126 | console.log(colors.red('alexa-skill-local has stopped working.')); 127 | process.exit(); 128 | }).on('restart', function (files) { 129 | console.log(colors.green('Restarting due to changes in files:'), files); 130 | }); 131 | 132 | } catch (err) { 133 | console.error(colors.red('Error finding handler function in entry file.')); 134 | console.log(err); 135 | } 136 | } -------------------------------------------------------------------------------- /coverage/lcov-report/alexa-skill-local/authServer.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for alexa-skill-local/authServer.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / alexa-skill-local/ authServer.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 18/18 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 18/18 41 |
42 |
43 |
44 |
45 |

 46 | 
125 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 74 | 75 | 76 | 77 |   78 | 79 |   80 | 81 | 82 |   83 | 84 | 85 | 86 | 87 | 88 |   89 |   90 | 91 |   92 | 93 | 94 | 95 |   96 | 97 |   98 |  
const express = require('express');
 99 | const http = require('http');
100 | const bodyParser = require('body-parser')
101 | const app = express();
102 | const nodemon = require('nodemon');
103 |  
104 | let accessToken = null;
105 |  
106 | app.use(bodyParser.json());
107 | app.use(express.static(__dirname + '/html'));
108 |  
109 | app.get('/oauth2/callback', (req, res) => {
110 |     accessToken = req.query.access_token;
111 |     res.sendFile(__dirname + '/html/close.html');
112 |     httpServer.close();
113 |     httpServer.emit('access-token', accessToken);
114 | });
115 |  
116 | const httpServer = http.createServer(app);
117 |  
118 | module.exports = function intiAuthServer() {
119 |     httpServer.listen(3001, () => {
120 |         console.log('Open in your browser and login with Amazon ==> http://localhost:3001')
121 |     });
122 |     return httpServer;
123 | }
124 |  
126 |
127 |
128 | 132 | 133 | 134 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /endpointController.js: -------------------------------------------------------------------------------- 1 | 2 | const ngrok = require('ngrok'); 3 | const colors = require('colors'); 4 | var https = require("https"); 5 | 6 | const ngrokInit = async (port) => { 7 | try { 8 | const url = await ngrok.connect(port); 9 | console.log(colors.green('ngrok is listening on port ' + port)); 10 | return url; 11 | } catch (err) { 12 | throw new Error('Error starting ngrok.') 13 | } 14 | } 15 | 16 | 17 | // TODO: Handle expired access token condition 18 | const updateAlexaEndpoint = async (ngrokPort, accessToken, config) => { 19 | 20 | try { 21 | const url = await ngrokInit(ngrokPort); 22 | 23 | try { 24 | skillManifest = await getSkillManifest(accessToken, config); 25 | console.log(colors.green('Skill Manifest fetched successfully')); 26 | await putSkillManifest(skillManifest, accessToken, url, config); 27 | console.log(colors.green('Alexa Skill Endpoint is updated with the url ==> '), colors.cyan(url)); 28 | } catch (err) { 29 | console.log(colors.yellow('Error updating Alexa Skill Endpoint. You have to do it manually.\n')) 30 | console.log(colors.yellow('-----------------------------------------------------------------------------------------')); 31 | console.log(colors.yellow('| Enter this url as HTTPS endpoint in your Alexa console -->'), colors.cyan(url), colors.yellow(' |')); 32 | console.log(colors.yellow('-----------------------------------------------------------------------------------------\n')); 33 | } 34 | 35 | } catch (err) { 36 | throw new Error(err); 37 | } 38 | } 39 | 40 | const getSkillManifest = async (accessToken, config) => { 41 | let skillManifest = null; 42 | const skillId = config.skillId; 43 | const stage = config.stage; 44 | const options = { 45 | host: 'api.amazonalexa.com', 46 | path: `/v1/skills/${skillId}/stages/${stage}/manifest`, 47 | method: 'GET', 48 | headers: { 49 | 'Authorization': accessToken 50 | } 51 | } 52 | // httpsRequest(options, (data, statusCode) => { 53 | // skillManifest = data; 54 | // }) 55 | try { 56 | const response = await httpsGetRequest(options); 57 | if (response.statusCode == 200) { 58 | return response.body; 59 | } else { 60 | throw new Error(`Error fetching Skill Manifest. Response Status Code -> ${response.statusCode}`); 61 | } 62 | } catch (err) { 63 | throw new Error(err); 64 | } 65 | } 66 | 67 | const putSkillManifest = async (skillManifest, accessToken, url, config) => { 68 | const skillId = config.skillId; 69 | const stage = config.stage; 70 | 71 | skillManifest.manifest.apis.custom.endpoint.sslCertificateType = "Wildcard"; 72 | skillManifest.manifest.apis.custom.endpoint.uri = url; 73 | 74 | const options = { 75 | host: 'api.amazonalexa.com', 76 | path: `/v1/skills/${skillId}/stages/${stage}/manifest`, 77 | method: 'PUT', 78 | headers: { 79 | 'Authorization': accessToken, 80 | 'Content-Type': 'application/json' 81 | } 82 | } 83 | 84 | try { 85 | response = await httpsPutRequest(options, skillManifest); 86 | if (response.statusCode != 202) { 87 | throw new Error(`Error fetching Skill Manifest. Response Status Code -> ${response.statusCode}`); 88 | } 89 | } catch (err) { 90 | throw new Error(err); 91 | } 92 | } 93 | 94 | const httpsGetRequest = (options) => { 95 | return new Promise((resolve, reject) => { 96 | 97 | const req = https.get(options, function (res) { 98 | 99 | const bodyChunks = []; 100 | res.on('data', function (chunk) { 101 | bodyChunks.push(chunk); 102 | }).on('end', function () { 103 | const body = Buffer.concat(bodyChunks); 104 | resolve({ body: JSON.parse(body), statusCode: res.statusCode }) 105 | }) 106 | }); 107 | 108 | req.on('error', function (e) { 109 | // throw new Error(`Error getting Skill Manifest`); 110 | reject(e); 111 | }); 112 | 113 | req.end(); 114 | 115 | }) 116 | } 117 | 118 | const httpsPutRequest = (options, body) => { 119 | return new Promise((resolve, reject) => { 120 | const req = https.request(options, function (res) { 121 | res.setEncoding('utf8'); 122 | res.on('data', function (body) { 123 | resolve({ statusCode: res.statusCode }) 124 | }); 125 | }); 126 | req.on('error', function (e) { 127 | // throw new Error(`Error updating Alexa Skill Endpoint`); 128 | reject(e); 129 | }); 130 | // write data to request body 131 | req.write(JSON.stringify(body)); 132 | req.end(); 133 | }) 134 | } 135 | 136 | module.exports = { updateAlexaEndpoint, ngrokInit }; -------------------------------------------------------------------------------- /coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | // add the click event handler on the th so users 136 | // dont have to click on those tiny arrows 137 | el = getNthColumn(i).querySelector('.sorter').parentElement; 138 | if (el.addEventListener) { 139 | el.addEventListener('click', ithSorter(i)); 140 | } else { 141 | el.attachEvent('onclick', ithSorter(i)); 142 | } 143 | } 144 | } 145 | } 146 | // adds sorting functionality to the UI 147 | return function () { 148 | if (!getTable()) { 149 | return; 150 | } 151 | cols = loadColumns(); 152 | loadData(cols); 153 | addSortIndicators(); 154 | enableUI(); 155 | }; 156 | })(); 157 | 158 | window.addEventListener('load', addSorting); 159 | -------------------------------------------------------------------------------- /coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* dark red */ 156 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 157 | .low .chart { border:1px solid #C21F39 } 158 | /* medium red */ 159 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 160 | /* light red */ 161 | .low, .cline-no { background:#FCE1E5 } 162 | /* light green */ 163 | .high, .cline-yes { background:rgb(230,245,208) } 164 | /* medium green */ 165 | .cstat-yes { background:rgb(161,215,106) } 166 | /* dark green */ 167 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 168 | .high .chart { border:1px solid rgb(77,146,33) } 169 | /* dark yellow (gold) */ 170 | .medium .chart { border:1px solid #f9cd0b; } 171 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 172 | /* light yellow */ 173 | .medium { background: #fff4c2; } 174 | /* light gray */ 175 | span.cline-neutral { background: #eaeaea; } 176 | 177 | .cbranch-no { background: yellow !important; color: #111; } 178 | 179 | .cstat-skip { background: #ddd; color: #111; } 180 | .fstat-skip { background: #ddd; color: #111 !important; } 181 | .cbranch-skip { background: #ddd !important; color: #111; } 182 | 183 | 184 | .cover-fill, .cover-empty { 185 | display:inline-block; 186 | height: 12px; 187 | } 188 | .chart { 189 | line-height: 0; 190 | } 191 | .cover-empty { 192 | background: white; 193 | } 194 | .cover-full { 195 | border-right: none !important; 196 | } 197 | pre.prettyprint { 198 | border: none !important; 199 | padding: 0 !important; 200 | margin: 0 !important; 201 | } 202 | .com { color: #999 !important; } 203 | .ignore-none { color: #999; font-weight: normal; } 204 | 205 | .wrapper { 206 | min-height: 100%; 207 | height: auto !important; 208 | height: 100%; 209 | margin: 0 auto -48px; 210 | } 211 | .footer, .push { 212 | height: 48px; 213 | } 214 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.js: -------------------------------------------------------------------------------- 1 | window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); 2 | --------------------------------------------------------------------------------