├── .gitignore ├── README.md ├── index.js ├── package.json ├── scripts ├── createUpIgnoreFile.js ├── getProductionDeps.js └── webpack.config.babel.js └── src ├── cmd.js ├── index.js └── webpack.config.template.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Lock files 4 | package-lock.json 5 | yarn.lock 6 | 7 | # IntelliJ project files 8 | .idea 9 | *.iml 10 | *.ipr 11 | *.iws 12 | 13 | # Mac files 14 | *.DS_Store 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lambda-pack 2 | 3 | Package your AWS Lambda efficiently, ready to be deployed with [apex/up](https://github.com/apex/up) 4 | 5 | ### Installation 6 | 7 | Install and save to devDependencies: 8 | ```bash 9 | npm install --save-dev lambdapack 10 | ``` 11 | 12 | ### Project configuration 13 | 14 | Run for the first time in your node.js project root folder: 15 | 16 | ```bash 17 | npx lambdapack 18 | ``` 19 | 20 | This will setup everything that is needed: 21 | - Add scripts to `package.json` 22 | - Create `webpack.config.babel.js` file 23 | - Create/Update `.babelrc` config file 24 | - Create/Update `up.json` file 25 | 26 | ### Deploy 27 | 28 | Now you can deploy using [apex/up](https://github.com/apex/up): 29 | 30 | ```bash 31 | up 32 | ``` 33 | 34 | and 35 | 36 | ```bash 37 | up deploy production 38 | ``` 39 | 40 | ### Future improvements 41 | 42 | - Bundle Node.js 8 (or latest) inside package 43 | - Allow to customize the whitelist and blacklist 44 | - Support other deployment methods other than `apex/up` 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const main = require('./src') 2 | module.exports = main 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambdapack", 3 | "version": "0.1.4", 4 | "description": "Package your AWS Lambda efficiently", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "bin": { 10 | "lambdapack": "src/cmd.js" 11 | }, 12 | "dependencies": { 13 | "babel-core": "6.x.x", 14 | "babel-jest": "^18.0.0", 15 | "babel-loader": "^7.1.2", 16 | "babel-polyfill": "^6.26.0", 17 | "babel-preset-env": "^1.6.0", 18 | "nodemon": "1.x.x", 19 | "nodemon-webpack-plugin": "^0.1.3", 20 | "webpack": "^3.6.0", 21 | "webpack-node-externals": "^1.6.0" 22 | }, 23 | "keywords": [ 24 | "apex", 25 | "up", 26 | "aws", 27 | "lambda", 28 | "webpack" 29 | ], 30 | "author": "Torii", 31 | "license": "MIT", 32 | "repository": "https://github.com/toriihq/lambdapack.git" 33 | } 34 | -------------------------------------------------------------------------------- /scripts/createUpIgnoreFile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const getProductionDeps = require('./getProductionDeps') 4 | 5 | const config = { 6 | outputFile: path.resolve('.', '.upignore'), 7 | whitelist: [ 8 | '.babelrc', 9 | 'build', 10 | 'babel-polyfill' 11 | ], 12 | blacklist: [ 13 | 'node_modules/*', 14 | 'aws-sdk', 15 | 'package-lock.json', 16 | 'npm-debug.log', 17 | 'yarn.lock', 18 | 'yarn-error.log', 19 | '__mocks__', 20 | '__tests__', 21 | 'readme*', 22 | 'readme.md', 23 | 'README.md', 24 | 'CHANGELOG.md', 25 | 'changelog*', 26 | 'history.md', 27 | 'CONTRIBUTING.md', 28 | 'LICENSE', 29 | '*.js.map', 30 | 'examples', 31 | 'tests' 32 | ] 33 | } 34 | 35 | const doNotIgnore = dep => `!${dep}` 36 | 37 | const removeBlacklistedDeps = dep => !config.blacklist.includes(dep) 38 | 39 | const includeNamespaces = dep => { 40 | if (dep.indexOf('/') >= 0) { 41 | return dep.split('/')[0] 42 | } 43 | return dep 44 | } 45 | 46 | console.log('Reading production dependencies') 47 | console.time('Done') 48 | const productionDeps = getProductionDeps() 49 | .filter(removeBlacklistedDeps) 50 | .map(includeNamespaces) 51 | console.timeEnd('Done') 52 | 53 | const upignore = [].concat( 54 | config.blacklist, 55 | config.whitelist.map(doNotIgnore), 56 | productionDeps.map(doNotIgnore) 57 | ).join('\n') 58 | 59 | console.log(`Writing upignore file: ${config.outputFile}`) 60 | console.time('Done') 61 | fs.writeFileSync(config.outputFile, upignore, { flag: 'w' }) 62 | console.timeEnd('Done') 63 | -------------------------------------------------------------------------------- /scripts/getProductionDeps.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const { execSync } = require('child_process') 4 | 5 | const flattenNpmDeps = (obj, deps) => { 6 | const { dependencies } = (obj || {}) 7 | if (typeof dependencies !== 'object') { 8 | return 9 | } 10 | Object.keys(dependencies).forEach(dep => { 11 | deps[dep] = true 12 | flattenNpmDeps(dependencies[dep], deps) 13 | }) 14 | } 15 | 16 | const readProductionDeps = (cmd) => { 17 | let output 18 | try { 19 | output = execSync(cmd) 20 | } catch (e) { 21 | output = e.stdout 22 | } 23 | return JSON.parse(output.toString()) 24 | } 25 | 26 | const flattenYarnDeps = (obj, deps) => { 27 | const { children } = (obj || {}) 28 | if (!children || children.length === 0) { 29 | return 30 | } 31 | children.forEach(depObject => { 32 | const lastIndex = depObject.name.lastIndexOf('@') 33 | const dep = lastIndex >= 0 ? depObject.name.substr(0, lastIndex) : depObject.name 34 | deps[dep] = true 35 | flattenYarnDeps(depObject, deps) 36 | }) 37 | } 38 | 39 | const useYarn = () => { 40 | const deps = {} 41 | const json = readProductionDeps('yarn list --prod --json 2>/dev/null') 42 | flattenYarnDeps({ children: json.data.trees }, deps) 43 | return Object.keys(deps) 44 | } 45 | 46 | const useNpm = () => { 47 | const deps = {} 48 | const json = readProductionDeps('npm ls --prod --json 2>/dev/null') 49 | flattenNpmDeps(json, deps) 50 | return Object.keys(deps) 51 | } 52 | 53 | module.exports = () => { 54 | const hasYarnLock = fs.existsSync(path.resolve('.', 'yarn.lock')) 55 | return hasYarnLock ? useYarn() : useNpm() 56 | } 57 | -------------------------------------------------------------------------------- /scripts/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const Webpack = require('webpack') 3 | const nodeExternals = require('webpack-node-externals') 4 | const NodemonPlugin = require('nodemon-webpack-plugin') 5 | 6 | module.exports = ({ entry }) => ({ 7 | entry: ['babel-polyfill', entry], 8 | target: 'node', 9 | output: { 10 | path: path.resolve('./build'), 11 | filename: 'index.js', 12 | libraryTarget: 'commonjs2' 13 | }, 14 | externals: [ 15 | nodeExternals({ 16 | whitelist: [ 17 | 'babel-polyfill' 18 | ] 19 | }) 20 | ], 21 | resolve: { 22 | extensions: ['.web.js', '.js', '.json', '.web.jsx', '.jsx', '.json'] 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.(js|jsx)$/, 28 | loader: 'babel-loader', 29 | options: { 30 | cacheDirectory: true 31 | }, 32 | exclude: [/node_modules/] 33 | } 34 | ] 35 | }, 36 | plugins: [ 37 | new NodemonPlugin({ 38 | nodeArgs: process.env.NODE_DEBUG_OPTION ? [ process.env.NODE_DEBUG_OPTION ] : [] 39 | }), 40 | new Webpack.NoEmitOnErrorsPlugin(), 41 | new Webpack.DefinePlugin({ 42 | 'process.env.WEBPACK': true 43 | }), 44 | new Webpack.LoaderOptionsPlugin({ 45 | minimize: false, 46 | debug: false 47 | }) 48 | ], 49 | devtool: 'source-map' 50 | }) 51 | -------------------------------------------------------------------------------- /src/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const lambdapack = require('./') 4 | 5 | try { 6 | lambdapack.init() 7 | console.log('') 8 | console.log('Changed files: (check them into source control)') 9 | console.log(' package.json') 10 | console.log(' up.json') 11 | console.log(' webpack.config.babel.js') 12 | console.log(' .babelrc') 13 | console.log('') 14 | console.log('Usage:') 15 | console.log(' npm run start Start the server and watch for file changes (dev mode)') 16 | console.log(' npm run start-server Start the server') 17 | console.log(' npm run build Bundle the project into build/index.js') 18 | console.log(' up start Run local dev server (after build)') 19 | console.log(' up Deploy to development') 20 | console.log(' up deploy production Deploy to production') 21 | console.log(' up build Create out.zip') 22 | console.log('') 23 | } catch (e) { 24 | console.error('') 25 | console.error('Error:', e.message) 26 | console.error('\nNo changes made, fix the errors and try again') 27 | console.error('') 28 | process.exit(1) 29 | } 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const webpackTemplate = require('./webpack.config.template') 4 | 5 | const requireJsonIfExists = (file) => { 6 | const filePath = path.resolve('.', file) 7 | const exists = fs.existsSync(filePath) 8 | return exists && JSON.parse(fs.readFileSync(filePath).toString()) 9 | } 10 | 11 | const requireJsonOrFail = (file) => { 12 | const filePath = path.resolve('.', file) 13 | const exists = fs.existsSync(filePath) 14 | if (!exists) { 15 | throw new Error(`"${file}" could not be found. Please run this in the root folder of your project`) 16 | } 17 | 18 | return JSON.parse(fs.readFileSync(filePath).toString()) 19 | } 20 | 21 | const handlePackageJson = () => { 22 | const scripts = { 23 | 'build': 'webpack --progress', 24 | 'start': 'webpack --watch', 25 | 'start-server': 'NODE_ENV=$UP_STAGE node build/index.js' 26 | } 27 | 28 | const json = requireJsonOrFail('package.json') 29 | 30 | if (json.name === 'lambdapack') { 31 | throw new Error('Do not run "lambdapack" on the "lambdapack" project') 32 | } 33 | 34 | Object.keys(scripts).forEach(script => { 35 | if (json.scripts && json.scripts[script]) { 36 | throw new Error(`package.json already includes a "${script}" script "${json.scripts[script]}". Remove it and run "lambdapack" again`) 37 | } 38 | }) 39 | 40 | json.scripts = Object.assign({}, json.scripts, scripts) 41 | 42 | return json 43 | } 44 | 45 | const handleBabelRc = () => { 46 | const preset = [ 47 | 'env', 48 | { 49 | targets: { 50 | node: '6.10.3' 51 | } 52 | } 53 | ] 54 | 55 | const json = requireJsonIfExists('.babelrc') || { 56 | presets: [] 57 | } 58 | 59 | json.presets = json.presets || [] 60 | json.presets.push(preset) 61 | 62 | return json 63 | } 64 | 65 | const handleWebpack = (entry) => { 66 | const webpackFiles = [ 67 | 'webpack.config.js', 68 | 'webpack.config.babel.js' 69 | ] 70 | webpackFiles.forEach(file => { 71 | const exists = fs.existsSync(path.resolve('.', file)) 72 | if (exists) { 73 | throw new Error(`"${file}" already exists. "lambdapack" is designed to take over webpack transpiling. Remove it and run "lambdapack" again`) 74 | } 75 | }) 76 | 77 | if (entry.substr(0, 2) !== './') { 78 | entry = './' + entry 79 | } 80 | 81 | return webpackTemplate(entry) 82 | } 83 | 84 | const handleUpJson = () => { 85 | const hooks = { 86 | 'build': [ 87 | 'npm run build', 88 | 'node ./node_modules/lambdapack/scripts/createUpIgnoreFile.js' 89 | ], 90 | 'clean': [ 91 | 'rm -f .upignore' 92 | ] 93 | } 94 | 95 | const json = requireJsonIfExists('up.json') || { 96 | hooks: {} 97 | } 98 | 99 | Object.keys(hooks).forEach(hook => { 100 | if (json.hooks && json.hooks[hook]) { 101 | throw new Error(`up.json already includes a "${hook}" script "${json.hooks[hook]}". Remove it and run "lambdapack" again`) 102 | } 103 | }) 104 | 105 | json.hooks = Object.assign({}, json.hooks, hooks) 106 | json.proxy = json.proxy || {} 107 | json.proxy.command = 'npm run start-server' 108 | 109 | return json 110 | } 111 | 112 | const writeJson = (file, json) => { 113 | const data = (typeof json === 'string') ? json : JSON.stringify(json, null, 2) 114 | fs.writeFileSync(path.resolve('.', file), data) 115 | } 116 | 117 | const init = () => { 118 | const packageJson = handlePackageJson() 119 | const babelRcJson = handleBabelRc() 120 | const webpackConfig = handleWebpack(packageJson.main || 'server.js') 121 | const upJson = handleUpJson() 122 | 123 | writeJson('package.json', packageJson) 124 | writeJson('.babelrc', babelRcJson) 125 | writeJson('webpack.config.babel.js', webpackConfig) 126 | writeJson('up.json', upJson) 127 | } 128 | 129 | module.exports = { 130 | init 131 | } -------------------------------------------------------------------------------- /src/webpack.config.template.js: -------------------------------------------------------------------------------- 1 | module.exports = (entry) => ` 2 | import webpackConfig from './node_modules/lambdapack/scripts/webpack.config.babel.js' 3 | 4 | export default webpackConfig({ 5 | entry: '${entry}' 6 | }) 7 | ` 8 | --------------------------------------------------------------------------------