├── phantom-test.js ├── .npmignore ├── example ├── phantomjs-lambda-pack │ ├── event.json │ ├── phantom-test.js │ ├── package.json │ ├── handler.js │ ├── s-function.json │ ├── shellSync.js │ └── shell.js ├── s-project.json ├── raw.js ├── package.json └── s-resources-cf.json ├── TODO.md ├── package.json ├── .gitignore ├── README.md └── index.js /phantom-test.js: -------------------------------------------------------------------------------- 1 | console.log('Hello from phantomjs'); 2 | phantom.exit(); -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | example 4 | .gitignore 5 | dist 6 | *.log 7 | .idea/ -------------------------------------------------------------------------------- /example/phantomjs-lambda-pack/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "test this message property" 3 | } -------------------------------------------------------------------------------- /example/phantomjs-lambda-pack/phantom-test.js: -------------------------------------------------------------------------------- 1 | console.log('Hello from phantomjs'); 2 | phantom.exit(); -------------------------------------------------------------------------------- /example/s-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phantomjs-example", 3 | "custom": {}, 4 | "plugins": [] 5 | } -------------------------------------------------------------------------------- /example/phantomjs-lambda-pack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phantomjs-lambda-pack", 3 | "version": "1.0.2", 4 | "phantomjsPrebuiltVersion": "1.9.19", 5 | "description": "", 6 | "main": "handler.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "phantomjs-lambda-pack": "file:../.." 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/raw.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const phantomjsLambdaPack = require('phantomjs-lambda-pack'); 3 | const exec = phantomjsLambdaPack.exec; 4 | 5 | exports.handler = (event, context, callback) => { 6 | exec('-v', (error, stdout, stderr) => { 7 | if (error) { 8 | console.error(`exec error: ${error}`); 9 | return; 10 | } 11 | 12 | console.log(`phantom version: ${stdout}`); 13 | console.log(`Should have no error: ${stderr}`); 14 | 15 | callback(error, 'fin!!'); 16 | }); 17 | }; -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phantomjs-example", 3 | "version": "0.0.1", 4 | "description": "A Serverless Project and its Serverless Plugin dependencies.", 5 | "author": "me", 6 | "license": "MIT", 7 | "private": false, 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/" 11 | }, 12 | "dependencies": { 13 | "phantomjs-prebuilt": "^2.1.7" 14 | }, 15 | "main": "index.js", 16 | "directories": { 17 | "example": "example" 18 | }, 19 | "scripts": { 20 | "test": "echo \"Error: no test specified\" && exit 1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## To Do 2 | 1. Move lambda-pack out of the example, and make a real module out of the deal. 3 | 1. Setup an simple deploy mechanism so I can prove out the deploy 4 | 1. Support versioning 5 | 1. Upgrade to Serverless 1.0 6 | 1. Get Local Integration working, in windows and mac 7 | 1. Get CI working, add coverage metrics 8 | 1. Publish 9 | 10 | # Notes 11 | When updating the main module, make sure to update the minor version number locally and run npm install update 12 | Optionally, you could just adjust the example lambda to deploy the parent directory, to remove the NPM work internally. 13 | -------------------------------------------------------------------------------- /example/phantomjs-lambda-pack/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const pack = require('phantomjs-lambda-pack'); 4 | const exec = pack.exec; 5 | 6 | module.exports.handler = function(event, context, onComplete) { 7 | 8 | exec('-v', (error, stdout, stderr) => { 9 | if (error) { 10 | console.error(`exec error: ${error}`); 11 | return; 12 | } 13 | 14 | console.log(`phantom version: ${stdout}`); 15 | console.log(`Should have no error: ${stderr}`); 16 | 17 | console.log(`phantomjsPrebuiltVersion: ${pack.phantomjsPrebuiltVersion}`); 18 | 19 | onComplete(error, 'fin!!'); 20 | }); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phantomjs-lambda-pack", 3 | "version": "0.0.7", 4 | "phantomjsPrebuiltVersion": "2.1.8", 5 | "description": "Headless WebKit with JS API, all wrapped for AWS Lambda", 6 | "main": "index.js", 7 | "keywords": [ 8 | "phantomjs", 9 | "headless", 10 | "webkit", 11 | "AWS", 12 | "lambda" 13 | ], 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1" 16 | }, 17 | "author": "Justin England", 18 | "license": "MIT", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/justengland/phantomjs-lambda-pack.git" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/phantomjs-lambda-pack/s-function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phantomjs-lambda-pack", 3 | "runtime": "nodejs4.3", 4 | "description": "Serverless Lambda function for project: phantomjs-example", 5 | "customName": false, 6 | "customRole": false, 7 | "handler": "handler.handler", 8 | "timeout": 300, 9 | "memorySize": 1024, 10 | "authorizer": {}, 11 | "custom": { 12 | "excludePatterns": [] 13 | }, 14 | "endpoints": [], 15 | "events": [], 16 | "environment": { 17 | "SERVERLESS_PROJECT": "${project}", 18 | "SERVERLESS_STAGE": "${stage}", 19 | "SERVERLESS_REGION": "${region}" 20 | }, 21 | "vpc": { 22 | "securityGroupIds": [], 23 | "subnetIds": [] 24 | } 25 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | dist 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 29 | node_modules 30 | 31 | #IDE 32 | **/.idea 33 | 34 | #OS 35 | .DS_Store 36 | .tmp 37 | 38 | #SERVERLESS 39 | admin.env 40 | .env 41 | 42 | #Ignore _meta folder 43 | _meta/ -------------------------------------------------------------------------------- /example/phantomjs-lambda-pack/shellSync.js: -------------------------------------------------------------------------------- 1 | var execSync = require('child_process').execSync; 2 | 3 | var BUILD_DIRECTORY = '/tmp/build'; 4 | var COMMAND_DELIMITER = ';'; 5 | 6 | 7 | var isWindows = /^win/.test(process.platform); 8 | 9 | module.exports = shell; 10 | 11 | function shell(command, cwd) { 12 | console.log('shellSync:', command); 13 | 14 | 15 | // var phantomjs = require('phantomjs-prebuilt') 16 | // var binPath = phantomjs.path; 17 | // 18 | // var childArgs = [ 19 | // path.join(__dirname, 'phantomjs-script.js'), 20 | // 'some other argument (passed to phantomjs script)' 21 | // ] 22 | 23 | const options = {}; 24 | const env = process.env; 25 | env.npm_config_cache ='/tmp/.npm' 26 | options.cwd = cwd; 27 | options.env = env; 28 | 29 | return execSync(command, options).toString(); 30 | } 31 | 32 | //// Tests 33 | // console.log(shellSync('npm install phantomjs-prebuilt', '.')); 34 | //shellSync('npm install; npm run test1', '.', function() { console.log('test finished') }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhantomJS wrapper for AWS Lambda 2 | > Simplify the usage of [PhantomJS](http://phantomjs.org/) on [AWS Lambda](https://aws.amazon.com/lambda/) 3 | 4 | ```bash 5 | $ npm install phantomjs-lambda-pack --save 6 | ``` 7 | 8 | ## Usage 9 | ```js 10 | 'use strict'; 11 | const phantomjsLambdaPack = require('phantomjs-lambda-pack'); 12 | const exec = phantomjsLambdaPack.exec; 13 | 14 | exports.handler = (event, context, callback) => { 15 | exec('-v', (error, stdout, stderr) => { 16 | if (error) { 17 | console.error(`exec error: ${error}`); 18 | return; 19 | } 20 | 21 | console.log(`phantom version: ${stdout}`); 22 | console.log(`Should have no error: ${stderr}`); 23 | 24 | callback(error, 'fin!!'); 25 | }); 26 | }; 27 | ``` 28 | ### To Do 29 | 1. Support phantomjs versioning, currently it just uses the latest of [phatomjs-prebuilt](https://www.npmjs.com/package/phantomjs-prebuilt) 30 | 1. Better support in the local environments, currently phantomjs needs to be installed in the path 31 | 1. Check if the lambda memory requirements are met. 32 | 33 | ### Notes 34 | Use more memory at least 1024 and a timeout greater than 180 seconds 35 | Make sure to zip up the entire directory and ship it 36 | 37 | ### Example 38 | [Basic Version Check](https://github.com/justengland/phantomjs-lambda-pack/tree/master/example) using [Serverless](http://serverless.com/) 39 | 40 | ### Thanks 41 | [PhantomJS](http://phantomjs.org/) 42 | 43 | [AWS Lambda](https://aws.amazon.com/lambda/) 44 | 45 | [PhantomJS-Prebuilt](https://github.com/Medium/phantomjs) 46 | 47 | [Serverless](http://serverless.com/) 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/s-resources-cf.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "The AWS CloudFormation template for this Serverless application's resources outside of Lambdas and Api Gateway", 4 | "Resources": { 5 | "IamRoleLambda": { 6 | "Type": "AWS::IAM::Role", 7 | "Properties": { 8 | "AssumeRolePolicyDocument": { 9 | "Version": "2012-10-17", 10 | "Statement": [ 11 | { 12 | "Effect": "Allow", 13 | "Principal": { 14 | "Service": [ 15 | "lambda.amazonaws.com" 16 | ] 17 | }, 18 | "Action": [ 19 | "sts:AssumeRole" 20 | ] 21 | } 22 | ] 23 | }, 24 | "Path": "/" 25 | } 26 | }, 27 | "IamPolicyLambda": { 28 | "Type": "AWS::IAM::Policy", 29 | "Properties": { 30 | "PolicyName": "${stage}-${project}-lambda", 31 | "PolicyDocument": { 32 | "Version": "2012-10-17", 33 | "Statement": [ 34 | { 35 | "Effect": "Allow", 36 | "Action": [ 37 | "logs:CreateLogGroup", 38 | "logs:CreateLogStream", 39 | "logs:PutLogEvents" 40 | ], 41 | "Resource": "arn:aws:logs:${region}:*:*" 42 | } 43 | ] 44 | }, 45 | "Roles": [ 46 | { 47 | "Ref": "IamRoleLambda" 48 | } 49 | ] 50 | } 51 | } 52 | }, 53 | "Outputs": { 54 | "IamRoleArnLambda": { 55 | "Description": "ARN of the lambda IAM role", 56 | "Value": { 57 | "Fn::GetAtt": [ 58 | "IamRoleLambda", 59 | "Arn" 60 | ] 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /example/phantomjs-lambda-pack/shell.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | 3 | var BUILD_DIRECTORY = '/tmp/build'; 4 | var NPM_SH_COMMAND = 'sh ' + BUILD_DIRECTORY + '/node_modules/.bin/npm '; 5 | var NPM_WINDOES_COMMAND = BUILD_DIRECTORY + '/node_modules/.bin/npm.exe '; 6 | var COMMAND_DELIMITER = ';'; 7 | 8 | var isWindows = /^win/.test(process.platform); 9 | var npmCommand = isWindows ? NPM_WINDOES_COMMAND : NPM_SH_COMMAND; 10 | 11 | module.exports = shell; 12 | 13 | function shell(command, workingDirectory, onComplete) { 14 | console.log('--------- shellSync ------->', command); 15 | var commands = command.indexOf(COMMAND_DELIMITER) > -1 ? command.split(COMMAND_DELIMITER) 16 | : [ command ] 17 | 18 | callCommands(commands, workingDirectory, onComplete); 19 | } 20 | 21 | function callCommands(commands, workingDirectory, onComplete) { 22 | var command = commands.shift(); 23 | execute(command, workingDirectory, function() { 24 | if(commands.length > 0) { 25 | callCommands(commands, workingDirectory, onComplete) 26 | } 27 | else { 28 | onComplete && onComplete(); 29 | } 30 | }); 31 | } 32 | 33 | function execute(command, workingDirectory, onComplete) { 34 | if (!command) throw 'Command is needed'; 35 | else console.log('--------- execute ------->', command); 36 | 37 | var commandReplace = isWindows ? command : replaceNpm(command); 38 | 39 | var options = workingDirectory ? {cwd: workingDirectory} : {}; 40 | var child = exec(commandReplace, options, function (err, standardOut, standardError) { 41 | // if(standardOut) console.log(command, 'complete', '\n############ child console', commandReplace ,'############\n', standardOut); 42 | // if(standardError) console.warn(command, 'error', '\n############ child error', commandReplace ,'############\n', standardError); 43 | 44 | // Resolve with result of process 45 | onComplete && onComplete(err, standardOut, standardError); 46 | }); 47 | }; 48 | 49 | // A little regex to replace the npm command with a bash statement 50 | function replaceNpm(source) { 51 | return source.replace(/npm /g, npmCommand) 52 | } 53 | 54 | 55 | //// Tests 56 | //shellSync('npm run test1', '.', function() { console.log('test finished') }); 57 | //shellSync('npm install; npm run test1', '.', function() { console.log('test finished') }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // const shellSync = require('./shellSync'); 4 | const execSync = require('child_process').execSync; 5 | const execFile = require('child_process').execFile; 6 | const PHANTOM_PATH = '/tmp/phantom'; 7 | const phantomjsPrebuiltVersion = require('./package.json').phantomjsPrebuiltVersion; 8 | const util = require('util'); 9 | 10 | const pack = exports = module.exports = {}; 11 | 12 | pack.state = []; 13 | 14 | pack.packPack = (message) => { 15 | pack.state += ' \n' + message; 16 | console.log('pack:' + message) 17 | }; 18 | 19 | pack.isAWSHosted = () => { 20 | const functionName = process.env.AWS_LAMBDA_FUNCTION_NAME || undefined; 21 | 22 | return functionName !== undefined; 23 | }; 24 | 25 | const shellSync = (command, cwd) => { 26 | console.log('shellSync:', command); 27 | 28 | const options = {}; 29 | const env = process.env; 30 | env.npm_config_cache ='/tmp/.npm'; 31 | options.cwd = cwd; 32 | options.env = env; 33 | 34 | return execSync(command, options).toString(); 35 | }; 36 | 37 | pack.installPhantom = () => { 38 | pack.packPack("Install Phantom Called"); 39 | if(pack.isAWSHosted()) { 40 | pack.packPack("Install Phantom Called: IS HOSTED"); 41 | const mkdirOut = shellSync(`mkdir -p ${PHANTOM_PATH}`); 42 | pack.packPack(`mkdirOut: ${mkdirOut}`); 43 | 44 | const mkdir2Out = shellSync(`mkdir -p /tmp/.npm`); 45 | pack.packPack(`mkdir2Out: ${mkdir2Out}`); 46 | 47 | try { 48 | const npmInstall = shellSync(`npm install phantomjs-prebuilt@${phantomjsPrebuiltVersion}`, PHANTOM_PATH); 49 | pack.packPack(`npmInstall: [[ ${npmInstall} ]] `); 50 | } 51 | catch(e) { 52 | pack.packPack(`npmInstall error: ${e}`); 53 | } 54 | 55 | 56 | // const lsOut = shellSync('ls -a', '/tmp/phantom/node_modules/phantomjs-prebuilt/bin'); 57 | // pack.packPack(`lsOut: ${lsOut}`); 58 | } 59 | else { 60 | console.log('Its not hosted at AWS'); 61 | } 62 | }; 63 | 64 | pack.path = getPhantomPath(); 65 | 66 | pack.exec = function(args, onComplete) { 67 | 68 | console.log('exec phantom: ', args); 69 | 70 | if (!util.isArray(args)) { 71 | args = [ args ]; 72 | } 73 | 74 | const phantomPath = getPhantomPath(); 75 | return execFile(phantomPath, args, onComplete); 76 | 77 | }; 78 | 79 | // This is pretty lame because it requires phantomjs be in the path, but it keeps things light and simple for now 80 | function getPhantomPath() { 81 | 82 | if(pack.isAWSHosted()) { 83 | return '/tmp/phantom/node_modules/phantomjs-prebuilt/bin/phantomjs'; 84 | } 85 | 86 | return 'phantomjs'; 87 | 88 | } 89 | 90 | // Install By default 91 | pack.installPhantom(); --------------------------------------------------------------------------------