Update the codebase to use ES6 and support Node v4.3 on Lambda (#13)

Fixes #6

This required a few changes to the code base. First of all, we need to update our lambda function to use Node v4.3. This can be done in the Lambda console on AWS, and is also done in the default.env file. Next, since Node 4.3 doesn't offer full ES6 support yet, we need to run our source code through babel to transform it to be ES5-friendly. The ES6 source now lives in the src/ directory, and the transformed js is output into a dist/ directory. The dist/ package is what ends up getting uploaded to lambda.

One last issue is that node-lambda doesn't work with ES6 either, so we need to make sure it runs against the files in the dist/ directory instead. You can use the test-lambda npm script to do this automatically for you by executing npm run test-lambda in the terminal. This will trigger running babel to transform the js and then running node-lambda against the newly transformed files in dist/.
This commit is contained in:
Joseph J. Schmitt 2016-10-29 22:04:44 -04:00 committed by GitHub
parent e09ee66d1c
commit e31ac662a6
13 changed files with 206 additions and 183 deletions

4
.babelrc Normal file
View File

@ -0,0 +1,4 @@
{
"presets": ["es2015"],
"plugins": ["transform-runtime"]
}

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ deploy.env
lambda.zip
node_modules/
.env
dist/

View File

@ -23,8 +23,8 @@ should point to your Couch Potato server, and `CP_API_KEY` which should have you
You can use [node-lambda](https://github.com/motdotla/node-lambda) to test this skill locally. In
the `test_events` directory are several event files you can use for testing, and they should map
pretty well to each Intent. To test an intent, simply copy the contents of one of the json files in
that directory and overwrite the contents of `event.json`. Then run `node-lambda run` from the
command line.
that directory and overwrite the contents of `event.json`. Make sure you run `npm install` from the
command line to get the the latest npm packages, and then run `npm run test-lambda`.
## Setting up the Skill

View File

@ -7,6 +7,6 @@ AWS_MODE=event
AWS_MEMORY_SIZE=128
AWS_TIMEOUT=10
AWS_DESCRIPTION=
AWS_RUNTIME=nodejs
AWS_RUNTIME=nodejs4.3
CP_URL=http://url-to-couch-potato-server
CP_API_KEY=APIKEY

View File

@ -26,6 +26,9 @@
},
{
"intent": "AMAZON.CancelIntent"
},
{
"intent": "AMAZON.HelpIntent"
}
]
}

View File

@ -1,132 +0,0 @@
'use strict';
var CouchPotato = require('node-couchpotato');
var utils = require('./utils.js');
var WELCOME_DESCRIPTION = 'This skill allows you to manage your Couch Potato movie list.';
var HELP_RESPONSE = ['You can ask Couch Potato about the movies in your queue or add new movies',
'to it. Try asking "is The Godfather on the list?". If it\'s not and you want to add it, try',
'saying "add The Godfather".'].join(' ');
var CANCEL_RESPONSE = 'Exiting Couch Potato';
var config = require('dotenv').config();
var cp = new CouchPotato({
url: config.CP_URL,
apikey: config.CP_API_KEY,
debug:true
});
function handleLaunchIntent(req, resp) {
resp
.say(WELCOME_DESCRIPTION)
.say(HELP_RESPONSE)
.send();
}
function handleFindMovieIntent(req, resp) {
var movieName = req.slot('movieName');
cp.movie.list({search: movieName, limit_offset: 5}).then(function (searchResp) {
var movies = searchResp.movies;
var result;
if (!movies || !movies.length) {
resp.say('Couldn\'t find ' + movieName + ' queued for download. ');
cp.movie.search(movieName).then(function (searchResults) {
utils.sendSearchResponse(searchResults, resp);
});
}
else {
result = movies[0].info;
resp
.say([
'It looks like', result.original_title,
'(' + result.year + ')', 'is already on your list.'
].join(' '))
.send();
}
});
//Async response
return false;
}
function handleAddMovieIntent(req, resp) {
var movieName = req.slot('movieName');
cp.movie.search(movieName,5).then(function (movies) {
movies = utils.formatSearchResults(movies);
utils.sendSearchResponse(movies, movieName, resp);
});
//Async response
return false;
}
function handleYesIntent(req, resp) {
var promptData = req.session('promptData');
var movie;
if (!promptData) {
console.log('Got a AMAZON.YesIntent but no promptData. Ending session.');
resp.send();
}
else if (promptData.yesAction === 'addMovie') {
movie = promptData.searchResults[0];
cp.movie.add({
title: movie.titles[0],
identifier: movie.imdb
}).then(function () {
resp
.say(promptData.yesResponse)
.send();
});
//Async response
return false;
}
else {
console.log("Got an unexpected yesAction. PromptData:");
console.log(promptData);
resp.send();
}
}
function handleNoIntent(req, resp) {
var promptData = req.session('promptData');
if (!promptData) {
console.log('Got a AMAZON.YesIntent but no promptData. Ending session.');
resp.send();
}
else if (promptData.noAction === 'endSession') {
resp.say(promptData.noResponse).send();
}
else if (promptData.noAction === 'suggestNextMovie') {
var movies = promptData.searchResults;
resp
.say(promptData.noResponse)
.session('promptData', utils.buildPrompt(movies.slice(1)))
.shouldEndSession(false)
.send();
}
else {
console.log("Got an unexpected noAction. PromptData:");
console.log(promptData);
resp.send();
}
}
function handleCancelIntent(req, resp) {
resp.say(CANCEL_RESPONSE).shouldEndSession(true).send();
}
module.exports = {
handleFindMovieIntent: handleFindMovieIntent,
handleAddMovieIntent: handleAddMovieIntent,
handleYesIntent: handleYesIntent,
handleNoIntent: handleNoIntent,
handleCancelIntent: handleCancelIntent
};

View File

@ -1,15 +0,0 @@
'use strict';
var handlers = require('./handlers.js');
var alexa = require('alexa-app');
var app = new alexa.app('couchPotato');
app.launch(handlers.handleLaunchIntent);
app.intent('FindMovie', handlers.handleFindMovieIntent);
app.intent('AddMovie', handlers.handleAddMovieIntent);
app.intent('AMAZON.YesIntent', handlers.handleYesIntent);
app.intent('AMAZON.NoIntent', handlers.handleNoIntent);
app.intent('AMAZON.CancelIntent', handlers.handleCancelIntent);
module.exports = app;

View File

@ -1,10 +1,13 @@
{
"name": "alexa-couchpotato",
"version": "1.0.0",
"version": "1.0.4",
"description": "A skill to ask Alexa about your Couch Potato queue.",
"main": "index.js",
"main": "src/index.js",
"scripts": {
"bundle": "mkdir -p bundle && cp -r {.env,index.js,lib,node_modules} bundle/ && cd bundle && bestzip ../lambda.zip * .env && rm -rf ../bundle"
"build": "babel src -d dist",
"zip": "cp -r {.env,deploy.env,package.json} dist/; cd dist; npm install --production; bestzip ../lambda.zip * .env;",
"bundle": "npm run build; npm run zip;",
"test-lambda": "npm run build; node-lambda run --handler dist/index.handler"
},
"author": "Joe Schmitt",
"license": "MIT",
@ -14,7 +17,11 @@
"node-couchpotato": "^0.1.4"
},
"devDependencies": {
"babel-cli": "^6.7.5",
"babel-core": "^6.7.6",
"babel-plugin-transform-runtime": "^6.7.5",
"babel-preset-es2015": "^6.6.0",
"bestzip": "^1.1.3",
"node-lambda": "^0.7.1"
"node-lambda": "^0.8.11"
}
}

View File

@ -1,4 +1,5 @@
'use strict';
var app = require('./lib');
import app from './lib';
exports.handler = app.lambda();

128
src/lib/handlers.js Normal file
View File

@ -0,0 +1,128 @@
'use strict';
import CouchPotato from 'node-couchpotato';
import {
buildPrompt,
sendSearchResponse,
formatSearchResults
} from './utils.js';
import {
WELCOME_DESCRIPTION,
HELP_RESPONSE,
CANCEL_RESPONSE
} from './responses.js';
const config = require('dotenv').config();
const cp = new CouchPotato({
url: config.CP_URL,
apikey: config.CP_API_KEY,
debug:true
});
export default function handleLaunchIntent(req, resp) {
resp
.say(WELCOME_DESCRIPTION)
.say(HELP_RESPONSE)
.send();
}
export function handleFindMovieIntent(req, resp) {
const movieName = req.slot('movieName');
cp.movie.list({search: movieName, limit_offset: 5}).then(function (searchResp) {
const movies = searchResp.movies;
if (!movies || !movies.length) {
resp.say(`Couldn't find ${movieName} queued for download. `);
cp.movie.search(movieName).then(function (searchResults) {
sendSearchResponse(searchResults, resp);
});
}
else {
const result = movies[0].info;
resp
.say(`It looks like ${result.original_title} (${result.year}) is already on your list.`)
.send();
}
});
//Async response
return false;
}
export function handleAddMovieIntent(req, resp) {
const movieName = req.slot('movieName');
cp.movie.search(movieName, 5).then(function (movies) {
movies = formatSearchResults(movies);
sendSearchResponse(movies, movieName, resp);
});
//Async response
return false;
}
export function handleYesIntent(req, resp) {
const promptData = req.session('promptData');
if (!promptData) {
console.log('Got a AMAZON.YesIntent but no promptData. Ending session.');
resp.send();
}
else if (promptData.yesAction === 'addMovie') {
const movie = promptData.searchResults[0];
cp.movie.add({
title: movie.titles[0],
identifier: movie.imdb
}).then(function () {
resp
.say(promptData.yesResponse)
.send();
});
//Async response
return false;
}
else {
console.log('Got an unexpected yesAction. PromptData:');
console.log(promptData);
resp.send();
}
}
export function handleNoIntent(req, resp) {
const promptData = req.session('promptData');
if (!promptData) {
console.log('Got a AMAZON.NoIntent but no promptData. Ending session.');
resp.send();
}
else if (promptData.noAction === 'endSession') {
resp.say(promptData.noResponse).send();
}
else if (promptData.noAction === 'suggestNextMovie') {
const movies = promptData.searchResults;
resp
.say(promptData.noResponse)
.session('promptData', buildPrompt(movies.slice(1)))
.shouldEndSession(false)
.send();
}
else {
console.log('Got an unexpected noAction. PromptData:');
console.log(promptData);
resp.send();
}
}
export function handleCancelIntent(req, resp) {
resp.say(CANCEL_RESPONSE).shouldEndSession(true).send();
}
export function handleHelpIntent(req, resp) {
resp.say(HELP_RESPONSE).send();
}

31
src/lib/index.js Normal file
View File

@ -0,0 +1,31 @@
'use strict';
import Alexa from 'alexa-app';
import handleLaunchIntent, {
handleFindMovieIntent,
handleAddMovieIntent,
handleYesIntent,
handleNoIntent,
handleCancelIntent,
handleHelpIntent
} from './handlers.js';
const app = new Alexa.app('couchPotato');
app.launch(handleLaunchIntent);
app.intent('FindMovie', handleFindMovieIntent);
app.intent('AddMovie', handleAddMovieIntent);
app.intent('AMAZON.YesIntent', handleYesIntent);
app.intent('AMAZON.NoIntent', handleNoIntent);
app.intent('AMAZON.CancelIntent', handleCancelIntent);
app.intent('AMAZON.HelpIntent', handleHelpIntent);
app.post = function(request, response, type, exception) {
if (exception) {
// Always turn an exception into a successful response
response.clear().say('An error occured: ' + exception).send();
}
};
export default app;

7
src/lib/responses.js Normal file
View File

@ -0,0 +1,7 @@
export const WELCOME_DESCRIPTION = 'This skill allows you to manage your Couch Potato movie list.';
export const HELP_RESPONSE = ['You can ask Couch Potato about the movies in your queue or add new movies',
'to it. Try asking "is The Godfather on the list?". If it\'s not and you want to add it, try',
'saying "add The Godfather".'].join(' ');
export const CANCEL_RESPONSE = 'Exiting Couch Potato';

View File

@ -1,5 +1,5 @@
function buildPrompt(movies) {
var promptData = {
export function buildPrompt(movies) {
const promptData = {
searchResults: movies,
yesAction : 'addMovie',
yesResponse: 'Added ' + movies[0].original_title + ' (' + movies[0].year + ')' + ' to your list of movies to download.'
@ -17,9 +17,8 @@ function buildPrompt(movies) {
return promptData;
}
function sendSearchResponse(movies, movieName, resp) {
if(!movies || !movies.length > 0) {
export function sendSearchResponse(movies, movieName, resp) {
if (!movies || !movies.length > 0) {
return resp.say('No movie found for ' + movieName).send();
}
@ -30,29 +29,18 @@ function sendSearchResponse(movies, movieName, resp) {
.send();
}
function formatSearchResults(movies) {
var newMovies = [];
if (movies != undefined) {
for (var i = 0; i < movies.length; i++) {
newMovies.push({
original_title: movies[i].original_title,
in_library: movies[i].in_library,
year: movies[i].year,
titles: movies[i].titles,
imdb: movies[i].imdb
});
export function formatSearchResults(movies) {
if (movies) {
return movies.map((movie) => {
return {
original_title: movie.original_title,
in_library: movie.in_library,
year: movie.year,
titles: movie.titles,
imdb: movie.imdb
}
}
});
}
return newMovies;
return [];
}
module.exports = {
buildPrompt: buildPrompt,
sendSearchResponse: sendSearchResponse,
formatSearchResults: formatSearchResults
};