diff --git a/CHANGELOG.md b/CHANGELOG.md index 482b93f64..2f29cd3a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.44.1 (2019-05-28) +- [Fix enterprise plugin lookup in global yarn installs](https://github.com/serverless/serverless/pull/6183) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.0...v1.44.1) + + # 1.44.0 (2019-05-28) - [Built in integration of Serverless Enterprise](https://github.com/serverless/serverless/pull/6074) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 44cceaf55..daf7260e3 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -159,6 +159,9 @@ class PluginManager { if (config.getGlobalConfig().enterpriseDisabled) { return; } + // `yarn global add` support, deps of deps are installed as deps + module.paths.unshift(path.join(__dirname, '../../../../node_modules')); + // `npm -i g` support, deps of deps are installed inside projects module.paths.unshift(path.join(__dirname, '../../node_modules')); this.loadPlugins(['@serverless/enterprise-plugin']); const sfePkgJson = require('@serverless/enterprise-plugin/package.json'); diff --git a/package-lock.json b/package-lock.json index f2d2d6c5f..74acca6cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.44.0", + "version": "1.44.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 884df1b81..ccff59f5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.44.0", + "version": "1.44.1", "engines": { "node": ">=6.0" }, @@ -58,13 +58,14 @@ "lint": "eslint . --cache", "docs": "node scripts/generate-readme.js", "integration-test-cleanup": "node scripts/integration-test-cleanup.js", + "packaging-integration-test": "jest --maxWorkers=5 packaging-suite", "simple-integration-test": "jest --maxWorkers=5 simple-suite", "complex-integration-test": "jest --maxWorkers=5 integration", "postinstall": "node ./scripts/postinstall.js" }, "mocha": { "require": "sinon-bluebird", - "-R": "tests/mocha-reporter" + "R": "tests/mocha-reporter" }, "jest": { "testRegex": "(\\.|/)(tests)\\.js$", diff --git a/tests/packaging-suite/artifact.zip b/tests/packaging-suite/artifact.zip new file mode 100644 index 000000000..325409f5c Binary files /dev/null and b/tests/packaging-suite/artifact.zip differ diff --git a/tests/packaging-suite/handler.js b/tests/packaging-suite/handler.js new file mode 100644 index 000000000..4e3448672 --- /dev/null +++ b/tests/packaging-suite/handler.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports.hello = function (event) { + return { + statusCode: 200, + body: JSON.stringify({ + message: 'Go Serverless v1.0! Your function executed successfully!', + input: event, + }, null, 2), + }; + + // Use this code if you don't use the http event with the LAMBDA-PROXY integration + // return { message: 'Go Serverless v1.0! Your function executed successfully!', event }; +}; diff --git a/tests/packaging-suite/individually.yml b/tests/packaging-suite/individually.yml new file mode 100644 index 000000000..df2d79d23 --- /dev/null +++ b/tests/packaging-suite/individually.yml @@ -0,0 +1,24 @@ +service: aws-nodejs + +provider: + name: aws + runtime: nodejs10.x + +package: + individually: true + +functions: + hello: + handler: handler.hello + package: + include: + - handler.js + exclude: + - handler2.js + hello2: + handler: handler2.hello + package: + include: + - handler2.js + exclude: + - handler.js diff --git a/tests/packaging-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js new file mode 100644 index 000000000..93da7ee95 --- /dev/null +++ b/tests/packaging-suite/packaging.tests.js @@ -0,0 +1,196 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const execSync = require('child_process').execSync; +const fse = require('fs-extra'); +const testUtils = require('../utils/index'); + +const serverlessExec = path.join(__dirname, '..', '..', 'bin', 'serverless'); + +describe('Integration test - Packaging', () => { + let cwd; + beforeEach(() => { + cwd = testUtils.getTmpDirPath(); + fse.mkdirsSync(cwd); + }); + + it('packages the default aws template correctly in the zip', () => { + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + execSync(`${serverlessExec} package`, { cwd }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + .then(zipfiles => { + expect(zipfiles).toEqual(['handler.js']); + }); + }); + + it('packages the default aws template with an npm dep correctly in the zip', () => { + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + execSync('npm init --yes', { cwd }); + execSync('npm i lodash', { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + .then(zipfiles => { + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set(['lodash'])); + expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); + }); + }); + + it('doesn\'t package a dev dependency in the zip', () => { + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + execSync('npm init --yes', { cwd }); + execSync('npm i --save-dev lodash', { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + .then(zipfiles => { + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set([])); + expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); + }); + }); + + it('ignores package json files per ignore directive in the zip', () => { + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + execSync('npm init --yes', { cwd }); + execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); + execSync('npm i lodash', { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + .then(zipfiles => { + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set(['lodash'])); + expect(nonNodeModulesFiles).toEqual(['handler.js']); + }); + }); + + it('package artifact directive works', () => { + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')); + execSync('echo \'package: {artifact: artifact.zip}\' >> serverless.yml', { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + const cfnTemplate = JSON.parse(fs.readFileSync(path.join( + cwd, '.serverless/cloudformation-template-update-stack.json'))); + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key) + .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/artifact.zip/); + delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; + expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { + Ref: 'ServerlessDeploymentBucket', + }, + }, + FunctionName: 'aws-nodejs-dev-hello', + Handler: 'handler.hello', + MemorySize: 1024, + Role: { + 'Fn::GetAtt': [ + 'IamRoleLambdaExecution', + 'Arn', + ], + }, + Runtime: 'nodejs10.x', + Timeout: 6, + }, + DependsOn: [ + 'HelloLogGroup', + 'IamRoleLambdaExecution', + ], + }); + }); + + it('creates the correct default function resource in cfn template', () => { + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + execSync(`${serverlessExec} package`, { cwd }); + const cfnTemplate = JSON.parse(fs.readFileSync(path.join( + cwd, '.serverless/cloudformation-template-update-stack.json'))); + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key) + .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/aws-nodejs.zip/); + delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; + expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { + Ref: 'ServerlessDeploymentBucket', + }, + }, + FunctionName: 'aws-nodejs-dev-hello', + Handler: 'handler.hello', + MemorySize: 1024, + Role: { + 'Fn::GetAtt': [ + 'IamRoleLambdaExecution', + 'Arn', + ], + }, + Runtime: 'nodejs10.x', + Timeout: 6, + }, + DependsOn: [ + 'HelloLogGroup', + 'IamRoleLambdaExecution', + ], + }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + .then(zipfiles => { + expect(zipfiles).toEqual(['handler.js']); + }); + }); + + it('handles package individually with include/excludes correctly', () => { + fse.copySync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler2.js')); + execSync(`${serverlessExec} package`, { cwd }); + const cfnTemplate = JSON.parse(fs.readFileSync(path.join( + cwd, '.serverless/cloudformation-template-update-stack.json'))); + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key) + .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/hello.zip/); + expect(cfnTemplate.Resources.Hello2LambdaFunction.Properties.Code.S3Key) + .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/hello2.zip/); + delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; + expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { + Ref: 'ServerlessDeploymentBucket', + }, + }, + FunctionName: 'aws-nodejs-dev-hello', + Handler: 'handler.hello', + MemorySize: 1024, + Role: { + 'Fn::GetAtt': [ + 'IamRoleLambdaExecution', + 'Arn', + ], + }, + Runtime: 'nodejs10.x', + Timeout: 6, + }, + DependsOn: [ + 'HelloLogGroup', + 'IamRoleLambdaExecution', + ], + }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/hello.zip')) + .then(zipfiles => expect(zipfiles).toEqual(['handler.js'])) + .then(() => testUtils.listZipFiles(path.join(cwd, '.serverless/hello2.zip'))) + .then(zipfiles => expect(zipfiles).toEqual(['handler2.js'])); + }); +}); diff --git a/tests/packaging-suite/serverless.yml b/tests/packaging-suite/serverless.yml new file mode 100644 index 000000000..700a3a5bd --- /dev/null +++ b/tests/packaging-suite/serverless.yml @@ -0,0 +1,10 @@ +service: aws-nodejs + +provider: + name: aws + runtime: nodejs10.x + + +functions: + hello: + handler: handler.hello diff --git a/tests/utils/index.js b/tests/utils/index.js index 687760759..081ea7a56 100644 --- a/tests/utils/index.js +++ b/tests/utils/index.js @@ -4,6 +4,7 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); const crypto = require('crypto'); +const JSZip = require('jszip'); const BbPromise = require('bluebird'); const fse = require('fs-extra'); const execSync = require('child_process').execSync; @@ -34,12 +35,16 @@ const replaceTextInFile = (filePath, subString, newSubString) => { fs.writeFileSync(filePath, fileContent.replace(subString, newSubString)); }; +const listZipFiles = filename => new JSZip().loadAsync(fs.readFileSync(filename)) + .then(zip => Object.keys(zip.files)); + module.exports = { serverlessExec, getTmpDirPath, getTmpFilePath, replaceTextInFile, ServerlessPlugin, + listZipFiles, createTestService: (templateName, testServiceDir) => { const hrtime = process.hrtime();