diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b9c62..dfa7232 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### 3.0.1 (Next) +* [#71](https://github.com/alexa-js/alexa-app-server/pull/71), [#68](https://github.com/alexa-js/alexa-app-server/issues/68): Fixed log output containing multiple slashes - [@tejashah88](https://github.com/tejashah88). * [#72](https://github.com/alexa-js/alexa-app-server/pull/72): Use `path.join` for constructing relative paths - [@dblock](https://github.com/dblock). * [#74](https://github.com/alexa-js/alexa-app-server/pull/74): Added locale selector to test page - [@siedi](https://github.com/siedi). * Your contribution here. diff --git a/RELEASING.md b/RELEASING.md index ddf5b1c..f53d648 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -27,13 +27,13 @@ Modify the "Stable Release" section in [README.md](README.md). Change the text t ``` ## Stable Release -You're reading the documentation for the stable release of alexa-app-server, 2.4.0. +You're reading the documentation for the stable release of alexa-app-server, 3.0.1. ``` Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version. ``` -### 2.4.0 (Feb 4, 2017) +### 3.0.1 (Feb 7, 2017) ``` Remove the line with "Your contribution here.", since there will be no more contributions to this release. @@ -42,13 +42,13 @@ Commit your changes. ``` git add README.md CHANGELOG.md -git commit -m "Preparing for release, 2.4.0." +git commit -m "Preparing for release, 3.0.1." ``` Tag the release. ``` -git tag v2.4.0 +git tag v3.0.1 ``` Release. @@ -70,14 +70,14 @@ Modify the "Stable Release" section in [README.md](README.md). Change the text t ``` ## Stable Release -You're reading the documentation for the next release of alexa-app-server, which should be 2.4.1. -The current stable release is 2.4.0. +You're reading the documentation for the next release of alexa-app-server, which should be 3.0.2. +The current stable release is 3.0.1. ``` Add the next release to [CHANGELOG.md](CHANGELOG.md). ``` -#### 2.4.1 (Next) +#### 3.0.2 (Next) * Your contribution here. ``` @@ -88,6 +88,6 @@ Commit your changes. ``` git add CHANGELOG.md README.md package.json -git commit -m "Preparing for next development iteration, 2.4.1." +git commit -m "Preparing for next development iteration, 3.0.2." git push origin master -``` +``` \ No newline at end of file diff --git a/UPGRADING.md b/UPGRADING.md index 05679ee..e642009 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -6,7 +6,7 @@ This is the first release out of the [alexa-js Github org](https://github.com/al #### Alexa-app minimum version -A minimum version 2.5.0 of `alexa-app` is now required. +A minimum version 3.0.0 of `alexa-app` is now required. See [alexa-app CHANGELOG](https://github.com/alexa-js/alexa-app/blob/master/CHANGELOG.md) and [UPGRADING](https://github.com/alexa-js/alexa-app/blob/master/UPGRADING.md) for more information. @@ -16,4 +16,4 @@ The `.urlencoded` body-parser has been removed. If your application requires it, The `.json` body-parser is only used for Alexa applications when `alexa-app-server` is started with `verify: false`, because `verifier-middleware`, which is now mounted inside `alexa-app` acts as a body-parser as well. -See [#35](https://github.com/alexa-js/alexa-app-server/issues/35) and [#64](https://github.com/alexa-js/alexa-app-server/pull/64) for more information. +See [#35](https://github.com/alexa-js/alexa-app-server/issues/35) and [#64](https://github.com/alexa-js/alexa-app-server/pull/64) for more information. \ No newline at end of file diff --git a/index.js b/index.js index 8fe575b..2ec2a44 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,13 @@ var hotswap = require('hotswap'); var fs = require('fs'); -var path = path = require('path'); +var path = require('path'); var http = require('http'); var https = require('https'); var express = require('express'); var alexa = require('alexa-app'); var Promise = require('bluebird'); -var defaults = require('lodash.defaults'); +var defaults = require("lodash.defaults"); +var utils = require("./utils"); var appServer = function(config) { var self = {}; @@ -32,7 +33,7 @@ var appServer = function(config) { throw new Error("invalid configuration: the verify and debug options cannot be both enabled"); } - if (self.config.httpEnabled == false && self.config.httpsEnabled == false) { + if (!self.config.httpEnabled && !self.config.httpsEnabled) { throw new Error("invalid configuration: either http or https must be enabled"); } @@ -69,30 +70,30 @@ var appServer = function(config) { // set up a router to hang all alexa apps off of var alexaRouter = express.Router(); - var normalizedRoot = root.indexOf('/') === 0 ? root : '/' + root; + var normalizedRoot = utils.normalizeApiPath(root); self.express.use(normalizedRoot, alexaRouter); var app_directories = function(srcpath) { return fs.readdirSync(srcpath).filter(function(file) { - return fs.statSync(path.join(srcpath, file)).isDirectory(); + return utils.isValidDirectory(path.join(srcpath, file)); }); }; app_directories(app_dir).forEach(function(dir) { - var package_json = path.join(app_dir, dir, "/package.json"); - if (!fs.existsSync(package_json) || !fs.statSync(package_json).isFile()) { + var package_json = path.join(app_dir, dir, "package.json"); + if (!utils.isValidFile(package_json)) { self.error(" package.json not found in directory " + dir); return; } - var pkg = JSON.parse(fs.readFileSync(package_json, 'utf8')); + var pkg = utils.readJsonFile(package_json); if (!pkg || !pkg.main || !pkg.name) { self.error(" failed to load " + package_json); return; } var main = fs.realpathSync(path.join(app_dir, dir, pkg.main)); - if (!fs.existsSync(main) || !fs.statSync(main).isFile()) { + if (!utils.isValidFile(main)) { self.error(" main file not found for app [" + pkg.name + "]: " + main); return; } @@ -127,7 +128,8 @@ var appServer = function(config) { postRequest: self.config.postRequest }); - self.log(" loaded app [" + pkg.name + "] at endpoint: " + normalizedRoot + "/" + pkg.name); + var endpoint = path.posix.join(normalizedRoot, pkg.name); + self.log(" loaded app [" + pkg.name + "] at endpoint: " + endpoint); }); return self.apps; @@ -137,7 +139,7 @@ var appServer = function(config) { self.load_server_modules = function(server_dir) { var server_files = function(srcpath) { return fs.readdirSync(srcpath).filter(function(file) { - return fs.statSync(path.join(srcpath, file)).isFile(); + return utils.isValidFile(path.join(srcpath, file)); }); }; server_files(server_dir).forEach(function(file) { @@ -164,7 +166,7 @@ var appServer = function(config) { // serve static content var static_dir = path.join(self.config.server_root, self.config.public_html); - if (fs.existsSync(static_dir) && fs.statSync(static_dir).isDirectory()) { + if (utils.isValidDirectory(static_dir)) { self.log("serving static content from: " + static_dir); self.express.use(express.static(static_dir)); } else { @@ -173,7 +175,7 @@ var appServer = function(config) { // find any server-side processing modules and let them hook in var server_dir = path.join(self.config.server_root, self.config.server_dir); - if (fs.existsSync(server_dir) && fs.statSync(server_dir).isDirectory()) { + if (utils.isValidDirectory(server_dir)) { self.log("loading server-side modules from: " + server_dir); self.load_server_modules(server_dir); } else { @@ -182,14 +184,14 @@ var appServer = function(config) { // find and load alexa-app modules var app_dir = path.join(self.config.server_root, self.config.app_dir); - if (fs.existsSync(app_dir) && fs.statSync(app_dir).isDirectory()) { + if (utils.isValidDirectory(app_dir)) { self.log("loading apps from: " + app_dir); self.load_apps(app_dir, self.config.app_root); } else { self.log("apps not loaded because directory [" + app_dir + "] does not exist"); } - if (self.config.httpsEnabled == true) { + if (self.config.httpsEnabled) { self.log("enabling https"); if (self.config.privateKey != undefined && self.config.certificate != undefined && self.config.httpsPort != undefined) { @@ -198,21 +200,21 @@ var appServer = function(config) { var certificateFile = path.join(sslCertRoot, self.config.certificate); var chainFile = (self.config.chain != undefined) ? path.join(sslCertRoot, self.config.chain) : undefined; - if (fs.existsSync(privateKeyFile) && fs.existsSync(certificateFile)) { - var privateKey = fs.readFileSync(privateKeyFile, 'utf8'); - var certificate = fs.readFileSync(certificateFile, 'utf8'); + if (utils.isValidFile(privateKeyFile) && utils.isValidFile(certificateFile)) { + var privateKey = utils.readFile(privateKeyFile); + var certificate = utils.readFile(certificateFile); var chain = undefined; if (chainFile != undefined) { - if (fs.existsSync(chainFile)) { - chain = fs.readFileSync(chainFile, 'utf8'); + if (utils.isValidFile(chainFile)) { + chain = utils.readFile(chainFile); } else { - self.error("chain: '" + self.config.chain + "' does not exist in /sslcert"); + self.error("chain: '" + self.config.chain + "' does not exist in " + sslCertRoot); } } if (chain == undefined && chainFile != undefined) { - self.error("failed to load chain from /sslcert, https will not be enabled"); + self.error("failed to load chain from " + sslCertRoot + ", https will not be enabled"); } else if (privateKey != undefined && certificate != undefined) { var credentials = { key: privateKey, @@ -225,7 +227,7 @@ var appServer = function(config) { if (chain != undefined) { credentials.ca = chain; - self.log("using chain certificate from /sslcert"); + self.log("using chain certificate from " + sslCertRoot); } try { @@ -243,10 +245,10 @@ var appServer = function(config) { self.error("failed to listen via https: " + error); } } else { - self.error("failed to load privateKey or certificate from /sslcert, https will not be enabled"); + self.error("failed to load privateKey or certificate from " + sslCertRoot + ", https will not be enabled"); } } else { - self.error("privateKey: '" + self.config.privateKey + "' or certificate: '" + self.config.certificate + "' do not exist in /sslcert, https will not be enabled"); + self.error("privateKey: '" + self.config.privateKey + "' or certificate: '" + self.config.certificate + "' do not exist in " + sslCertRoot + ", https will not be enabled"); } } else { self.error("httpsPort, privateKey or certificate parameter is not set in config, https will not be enabled"); diff --git a/invalid_examples/apps/hello_world/index.js b/invalid_examples/apps/hello_world/index.js new file mode 100644 index 0000000..aeed421 --- /dev/null +++ b/invalid_examples/apps/hello_world/index.js @@ -0,0 +1,26 @@ +var alexa = require('alexa-app'); + +// Allow this module to be reloaded by hotswap when changed +module.change_code = 1; + +// Define an alexa-app +var app = new alexa.app('hello_world'); +app.launch(function(req, res) { + res.say("Hello World!!"); +}); + +app.intent('NameIntent', { + "slots": { "NAME": "LITERAL", "AGE": "NUMBER" }, + "utterances": ["{My name is|my name's} {matt|bob|bill|jake|nancy|mary|jane|NAME} and I am {1-100|AGE}{ years old|}"] +}, function(req, res) { + res.say('Your name is ' + req.slot('NAME') + ' and you are ' + req.slot('AGE') + ' years old'); +}); + +app.intent('AgeIntent', { + "slots": { "AGE": "NUMBER" }, + "utterances": ["My age is {1-100|AGE}"] +}, function(req, res) { + res.say('Your age is ' + req.slot('AGE')); +}); + +module.exports = app; diff --git a/invalid_examples/apps/hello_world/package.json b/invalid_examples/apps/hello_world/package.json new file mode 100644 index 0000000..72c3561 --- /dev/null +++ b/invalid_examples/apps/hello_world/package.json @@ -0,0 +1,14 @@ +{ + "name": "hello_world", + "version": "1.0.0", + "description": "A sample Alexa app", + "main": "index.js", + "author": "Matt Kruse (http://mattkruse.com/)", + "license": "ISC", + "alexa": { + "applicationId": "amzn1.echo-sdk-ams.app.999999-d0ed-9999-ad00-999999d00ebe" + }, + "dependencies": { + "alexa-app": "^2.1.0" + } +} diff --git a/package.json b/package.json index b61d151..d9f443c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "license": "MIT", "dependencies": { "alexa-app": "^3.0.0", - "alexa-verifier-middleware": "^0.1.8", "bluebird": "^3.4.7", "body-parser": "~1.16.0", "ejs": "~2.5.5", @@ -29,12 +28,12 @@ "lodash.defaults": "^4.2.0" }, "devDependencies": { - "mocha": "^2.3.4", - "chai": "^3.4.1", - "supertest": "^2.0.1", + "mocha": "^3.2.0", + "chai": "^3.5.0", + "supertest": "^3.0.0", "istanbul": "0.4.5", "coveralls": "^2.11.15", - "danger": "0.11.2", + "danger": "0.11.4", "tcp-port-used": "0.1.2" } } diff --git a/test/example.json b/test/example.json new file mode 100644 index 0000000..448f9f0 --- /dev/null +++ b/test/example.json @@ -0,0 +1,3 @@ +{ + "pangram": "The quick brown fox jumps over the lazy dog" +} \ No newline at end of file diff --git a/test/example.txt b/test/example.txt new file mode 100644 index 0000000..ff3bb63 --- /dev/null +++ b/test/example.txt @@ -0,0 +1 @@ +The quick brown fox jumps over the lazy dog \ No newline at end of file diff --git a/test/test-examples-server-https-support-extended.js b/test/test-examples-server-https-support-extended.js index 4781633..ea48a62 100644 --- a/test/test-examples-server-https-support-extended.js +++ b/test/test-examples-server-https-support-extended.js @@ -7,13 +7,14 @@ var request = require("supertest"); var alexaAppServer = require("../index"); var fs = require("fs"); var tcpPortUsed = require('tcp-port-used'); +var utils = require("../utils"); describe("Alexa App Server with Examples & more HTTPS support", function() { var testServer; var sampleLaunchReq; before(function() { - sampleLaunchReq = JSON.parse(fs.readFileSync("test/sample-launch-req.json", 'utf8')); + sampleLaunchReq = utils.readJsonFile("test/sample-launch-req.json"); }); afterEach(function() { diff --git a/test/test-examples-server-https-support.js b/test/test-examples-server-https-support.js index 9a436f8..51c2912 100644 --- a/test/test-examples-server-https-support.js +++ b/test/test-examples-server-https-support.js @@ -6,6 +6,7 @@ chai.config.includeStack = true; var request = require("supertest"); var alexaAppServer = require("../index"); var fs = require("fs"); +var utils = require("../utils"); describe("Alexa App Server with Examples & HTTPS support", function() { var testServer; @@ -13,16 +14,17 @@ describe("Alexa App Server with Examples & HTTPS support", function() { before(function() { testServer = alexaAppServer.start({ - port: 6000, + port: 3000, server_root: 'examples', - https: true, + httpsEnabled: true, + httpsPort: 6000, privateKey: 'private-key.pem', certificate: 'cert.cer', chain: 'cert.ca_bundle', passphrase: "test123" }); - sampleLaunchReq = JSON.parse(fs.readFileSync("test/sample-launch-req.json", 'utf8')); + sampleLaunchReq = utils.readJsonFile("test/sample-launch-req.json"); }); after(function() { diff --git a/test/test-examples-server-pre-post-functions.js b/test/test-examples-server-pre-post-functions.js index 00c2622..23f2c3e 100644 --- a/test/test-examples-server-pre-post-functions.js +++ b/test/test-examples-server-pre-post-functions.js @@ -6,6 +6,7 @@ chai.config.includeStack = true; var request = require("supertest"); var alexaAppServer = require("../index"); var fs = require("fs"); +var utils = require("../utils"); describe("Alexa App Server with Examples & Pre/Post functions", function() { var testServer, fired; @@ -30,7 +31,7 @@ describe("Alexa App Server with Examples & Pre/Post functions", function() { } }); - sampleLaunchReq = JSON.parse(fs.readFileSync("test/sample-launch-req.json", 'utf8')); + sampleLaunchReq = utils.readJsonFile("test/sample-launch-req.json"); }); after(function() { diff --git a/test/test-examples-server-verification.js b/test/test-examples-server-verification.js index 63cb8ab..93082fd 100644 --- a/test/test-examples-server-verification.js +++ b/test/test-examples-server-verification.js @@ -6,6 +6,7 @@ chai.config.includeStack = true; var request = require("supertest"); var alexaAppServer = require("../index"); var fs = require("fs"); +var utils = require("../utils"); describe("Alexa App Server with Examples & Verification", function() { var testServer; @@ -19,7 +20,7 @@ describe("Alexa App Server with Examples & Verification", function() { verify: true }); - sampleLaunchReq = JSON.parse(fs.readFileSync("test/sample-launch-req.json", 'utf8')); + sampleLaunchReq = utils.readJsonFile("test/sample-launch-req.json"); }); after(function() { diff --git a/test/test-examples-server.js b/test/test-examples-server.js index 93c28a9..ed4980a 100644 --- a/test/test-examples-server.js +++ b/test/test-examples-server.js @@ -6,6 +6,7 @@ chai.config.includeStack = true; var request = require("supertest"); var alexaAppServer = require("../index"); var fs = require("fs"); +var utils = require("../utils"); describe("Alexa App Server with Examples", function() { var testServer; @@ -17,9 +18,9 @@ describe("Alexa App Server with Examples", function() { server_root: 'examples' }); - sampleLaunchReq = JSON.parse(fs.readFileSync("test/sample-launch-req.json", 'utf8')); - sampleUtterances = fs.readFileSync("test/sample-utterances.txt", 'utf8'); - sampleSchema = fs.readFileSync("test/sample-schema.txt", 'utf8'); + sampleLaunchReq = utils.readJsonFile("test/sample-launch-req.json"); + sampleUtterances = utils.readFile("test/sample-utterances.txt"); + sampleSchema = utils.readFile("test/sample-schema.txt"); }); after(function() { diff --git a/test/test-utils.js b/test/test-utils.js new file mode 100644 index 0000000..32dfe8f --- /dev/null +++ b/test/test-utils.js @@ -0,0 +1,83 @@ +/*jshint expr: true*/ +"use strict"; +var chai = require("chai"); +var expect = chai.expect; +chai.config.includeStack = true; +var utils = require("../utils"); + +describe("Utils", function() { + describe("#isValidFile", function() { + it("verifies that 'README.md' does exists and is a file", function() { + expect(utils.isValidFile('README.md')).to.be.true; + }); + + it("verifies that 'RANDOM.md' does not exists", function() { + expect(utils.isValidFile('RANDOM.md')).to.be.false; + }); + + it("verifies that 'examples' is not a file", function() { + expect(utils.isValidFile('examples')).to.be.false; + }); + }); + + describe("#isValidDirectory", function() { + it("verifies that 'examples' does exists and is a directory", function() { + expect(utils.isValidDirectory('examples')).to.be.true; + }); + + it("verifies that 'random' does not exists", function() { + expect(utils.isValidDirectory('random')).to.be.false; + }); + + it("verifies that 'README.md' is not a directory", function() { + expect(utils.isValidDirectory('README.md')).to.be.false; + }); + }); + + describe("#readFile", function() { + it("successfully reads 'example.txt'", function() { + expect(utils.readFile('test/example.txt')).to.equal("The quick brown fox jumps over the lazy dog"); + }); + + it("throws an error when trying to read 'example.text'", function() { + expect(function() { + utils.readFile('test/example.text'); + }).to.throw(Error); + }); + }); + + describe("#readJsonFile", function() { + it("successfully reads 'example.json'", function() { + expect(utils.readJsonFile('test/example.json')).to.deep.equal({ + "pangram": "The quick brown fox jumps over the lazy dog" + }); + }); + + it("throws an error when trying to read 'example.jason'", function() { + expect(function() { + utils.readJsonFile('test/example.jason'); + }).to.throw(Error); + }); + }); + + describe("#normalizeApiPath", function() { + var tests = [ + { original: "alexa", final: "/alexa" }, + { original: "/alexa", final: "/alexa" }, + { original: "//alexa", final: "/alexa" }, + { original: "///alexa", final: "/alexa" }, + { original: "alexa/", final: "/alexa/" }, + { original: "alexa//", final: "/alexa/" }, + { original: "alexa///", final: "/alexa/" }, + { original: "/alexa/", final: "/alexa/" }, + { original: "//alexa//", final: "/alexa/" }, + { original: "///alexa///", final: "/alexa/" }, + ]; + + tests.forEach(function(test) { + it('correctly normalizes ' + test.original + ' into ' + test.final, function() { + expect(utils.normalizeApiPath(test.original)).to.equal(test.final); + }); + }); + }); +}); \ No newline at end of file diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..c110eb8 --- /dev/null +++ b/utils.js @@ -0,0 +1,30 @@ +var fs = require('fs'); +var path = require('path'); + +var isValidDirectory = function(dir) { + return fs.existsSync(dir) && fs.statSync(dir).isDirectory(); +}; + +var isValidFile = function(file) { + return fs.existsSync(file) && fs.statSync(file).isFile(); +}; + +var readFile = function(file) { + return fs.readFileSync(file, 'utf8'); +}; + +var readJsonFile = function(file) { + return JSON.parse(readFile(file)); +}; + +var normalizeApiPath = function(apiPath) { + return path.posix.normalize(path.posix.join('/', apiPath)); +}; + +module.exports = { + isValidDirectory : isValidDirectory, + isValidFile : isValidFile, + readFile : readFile, + readJsonFile : readJsonFile, + normalizeApiPath : normalizeApiPath +}; \ No newline at end of file