Repository: HyperBrain/serverless-aws-alias (js)

Issue:
Stage is added twice and erroneous
Hi there

if I define the stage with a variable the stage is added twice and the second add is erroneous because the variable is not mapped
**myLambda-myStage-${self:custom.stage}**

***reproduce***
```
serverless create --template aws-java-maven --path myLambda
```
in the created directory:
change serverless.yml to
```
service: myLambda
custom:
  stage: myStage
provider:
  name: aws
  runtime: java8
  stage: ${self:custom.stage}
package:
  artifact: target/hello-dev.jar
functions:
  hello:
    handler: com.serverless.Handler
plugins:
  - serverless-aws-alias
```
then run commands
```
mvn clean package
npm install --save serverless-aws-alias
serverless deploy -v
```
The output of the deploy step is
```
Serverless: Packaging service...
Serverless: Preparing alias ...
Serverless: Processing custom resources
Serverless: Removing resources: 
Serverless: Processing functions
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
CloudFormation - CREATE_IN_PROGRESS - AWS::CloudFormation::Stack - myLambda-myStage
CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_COMPLETE - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_COMPLETE - AWS::CloudFormation::Stack - myLambda-myStage
Serverless: Stack create finished...
 
  Serverless Error ---------------------------------------
 
     The stack alias name "myLambda-myStage-${self:custom.stage}"
     is not valid. A service name should only contain alphanumeric
     (case sensitive) and hyphens. It should start with an
     alphabetic character and shouldn't exceed 128 characters.
 
  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Forums:        forum.serverless.com
     Chat:          gitter.im/serverless/serverless
 
  Your Environment Information -----------------------------
     OS:                 linux
     Node Version:       6.9.1
     Serverless Version: 1.13.2
```

Best regards

Pull request description:
Fix stage name retrieval
Fixes #45 

The initialization of `stage` and `stackName` are moved to `validate()`. This makes sure, that the service configuration has been prepared properly (variable substitution) and is ready to be used.
With the introduction of promised variable retrieval, timings have changed in SLS.

Patch:
diff --git a/index.js b/index.js
index f198733..bd40a65 100644
--- a/index.js
+++ b/index.js
@@ -23,14 +23,8 @@ class AwsAlias {
 
 	constructor(serverless, options) {
 		this._serverless = serverless;
-		this._options = options;
+		this._options = options || {};
 		this._provider = this._serverless.getProvider('aws');
-		this._stage = this._options.stage || this._serverless.service.provider.stage;
-		this._alias = this._options.alias || this._stage;
-		this._stackName = this._provider.naming.getStackName();
-
-		// Make alias available as ${self:provider.alias}
-		this._serverless.service.provider.alias = this._alias;
 
 		/**
 		 * Load stack helpers from Serverless installation.
@@ -118,6 +112,7 @@ class AwsAlias {
 				.then(this.updateAliasStack),
 
 			'after:info:info': () => BbPromise.bind(this)
+				.then(this.validate)
 				.then(this.listAliases),
 
 			// Override the logs command - must be, because the $LATEST filter
diff --git a/lib/validate.js b/lib/validate.js
index 40e0187..f98d57b 100644
--- a/lib/validate.js
+++ b/lib/validate.js
@@ -15,6 +15,14 @@ module.exports = {
 			return BbPromise.reject(new this.serverless.classes.Error('Serverless verion must be >= 1.12.0'));
 		}
 
+		// Set configuration
+		this._stage = this._provider.getStage();
+		this._alias = this._options.alias || this._stage;
+		this._stackName = this._provider.naming.getStackName();
+
+		// Make alias available as ${self:provider.alias}
+		this._serverless.service.provider.alias = this._alias;
+
 		// Parse and check plugin options
 		if (this._options['alias-resources']) {
 			this._aliasResources = true;


Test patch:
diff --git a/test/configureAliasStack.test.js b/test/configureAliasStack.test.js
index e00cc0c..f168799 100644
--- a/test/configureAliasStack.test.js
+++ b/test/configureAliasStack.test.js
@@ -63,7 +63,8 @@ describe('configureAliasStack', () => {
 			serverless.service.provider.compiledCloudFormationTemplate = require('./data/sls-stack-1.json');
 			const cfTemplate = serverless.service.provider.compiledCloudFormationTemplate;
 
-			return expect(awsAlias.configureAliasStack()).to.be.fulfilled
+			return expect(awsAlias.validate()).to.be.fulfilled
+			.then(() => expect(awsAlias.configureAliasStack()).to.be.fulfilled)
 			.then(() => BbPromise.all([
 				expect(cfTemplate).to.have.deep.property('Outputs.ServerlessAliasReference.Value', 'REFERENCE'),
 				expect(cfTemplate).to.have.deep.property('Outputs.ServerlessAliasReference.Export.Name', 'testService-myStage-ServerlessAliasReference'),
diff --git a/test/createAliasStack.test.js b/test/createAliasStack.test.js
index 90b68a9..c98499c 100644
--- a/test/createAliasStack.test.js
+++ b/test/createAliasStack.test.js
@@ -39,7 +39,7 @@ describe('createAliasStack', () => {
 			stage: 'myStage',
 			region: 'us-east-1',
 		};
-		serverless.setProvider('aws', new AwsProvider(serverless));
+		serverless.setProvider('aws', new AwsProvider(serverless, options));
 		serverless.cli = new serverless.classes.CLI(serverless);
 		serverless.service.service = 'testService';
 		serverless.service.provider.compiledCloudFormationAliasTemplate = {};
@@ -58,7 +58,7 @@ describe('createAliasStack', () => {
 	describe('#createAlias()', () => {
 		it('Should call CF with correct default parameters', () => {
 			const expectedCFData = {
-				StackName: 'testService-dev-myAlias',
+				StackName: 'testService-myStage-myAlias',
 				Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
 				OnFailure: 'DELETE',
 				Parameters: [],
@@ -76,7 +76,8 @@ describe('createAliasStack', () => {
 
 			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
 
-			return expect(awsAlias.createAlias()).to.be.fulfilled
+			return expect(awsAlias.validate()).to.be.fulfilled
+			.then(() => expect(awsAlias.createAlias()).to.be.fulfilled)
 			.then(() => BbPromise.all([
 				expect(providerRequestStub).to.have.been.calledOnce,
 				expect(monitorStackStub).to.have.been.calledOnce,
@@ -89,7 +90,7 @@ describe('createAliasStack', () => {
 
 		it('should set stack tags', () => {
 			const expectedCFData = {
-				StackName: 'testService-dev-myAlias',
+				StackName: 'testService-myStage-myAlias',
 				Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
 				OnFailure: 'DELETE',
 				Parameters: [],
@@ -111,7 +112,8 @@ describe('createAliasStack', () => {
 
 			serverless.service.provider.compiledCloudFormationAliasCreateTemplate = {};
 
-			return expect(awsAlias.createAlias()).to.be.fulfilled
+			return expect(awsAlias.validate()).to.be.fulfilled
+			.then(() => expect(awsAlias.createAlias()).to.be.fulfilled)
 			.then(() => BbPromise.all([
 				expect(providerRequestStub).to.have.been.calledOnce,
 				expect(providerRequestStub).to.have.been
diff --git a/test/index.test.js b/test/index.test.js
index abd1f88..4523e1f 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -28,17 +28,22 @@ describe('AwsAlias', () => {
 			region: 'us-east-1',
 		};
 		serverless.service.service = 'myService';
-		serverless.setProvider('aws', new AwsProvider(serverless));
+		serverless.setProvider('aws', new AwsProvider(serverless, options));
 	});
 
 	describe('constructor', () => {
 		it('should initialize the plugin without options', () => {
+			const awsAlias = new AwsAlias(serverless);
+
+			expect(awsAlias).to.have.property('_serverless', serverless);
+			expect(awsAlias).to.have.property('_options').to.deep.equal({});
+		});
+
+		it('should initialize the plugin with empty options', () => {
 			const awsAlias = new AwsAlias(serverless, {});
 
 			expect(awsAlias).to.have.property('_serverless', serverless);
 			expect(awsAlias).to.have.property('_options').to.deep.equal({});
-			expect(awsAlias).to.have.property('_stage', 'dev');
-			expect(awsAlias).to.have.property('_alias', 'dev');
 		});
 
 		it('should initialize the plugin with options', () => {
@@ -46,21 +51,20 @@ describe('AwsAlias', () => {
 
 			expect(awsAlias).to.have.property('_serverless', serverless);
 			expect(awsAlias).to.have.property('_options').to.deep.equal(options);
-			expect(awsAlias).to.have.property('_stage', 'myStage');
-			expect(awsAlias).to.have.property('_alias', 'myStage');
 		});
 	});
 
 	it('should expose standard properties', () => {
 		const awsAlias = new AwsAlias(serverless, options);
 
+		awsAlias._stackName = 'myStack';
+
 		expect(awsAlias).to.have.property('serverless', serverless);
 		expect(awsAlias).to.have.property('options').to.deep.equal(options);
-		expect(awsAlias).to.have.property('stackName', 'myService-dev');
-		expect(awsAlias).to.have.deep.property('serverless.service.provider.alias', 'myStage');
 		expect(awsAlias).to.have.property('commands', awsAlias._commands);
 		expect(awsAlias).to.have.property('hooks', awsAlias._hooks);
 		expect(awsAlias).to.have.property('provider', awsAlias._provider);
+		expect(awsAlias).to.have.property('stackName', 'myStack');
 	});
 
 	describe('hook', () => {
diff --git a/test/validate.test.js b/test/validate.test.js
index 7c00071..71379d0 100644
--- a/test/validate.test.js
+++ b/test/validate.test.js
@@ -4,6 +4,7 @@
  */
 
 const getInstalledPath = require('get-installed-path');
+const BbPromise = require('bluebird');
 const chai = require('chai');
 const AWSAlias = require('../index');
 
@@ -25,7 +26,8 @@ describe('#validate()', () => {
 			stage: 'myStage',
 			region: 'us-east-1',
 		};
-		serverless.setProvider('aws', new AwsProvider(serverless));
+		serverless.service.service = 'myService';
+		serverless.setProvider('aws', new AwsProvider(serverless, options));
 		serverless.cli = new serverless.classes.CLI(serverless);
 		awsAlias = new AWSAlias(serverless, options);
 	});
@@ -45,6 +47,15 @@ describe('#validate()', () => {
 		return expect(awsAlias.validate()).to.eventually.be.fulfilled;
 	});
 
+	it('should initialize the plugin with options', () => {
+		return expect(awsAlias.validate()).to.eventually.be.fulfilled
+		.then(() => BbPromise.all([
+			expect(awsAlias).to.have.property('_stage', 'myStage'),
+			expect(awsAlias).to.have.property('_alias', 'myStage'),
+			expect(awsAlias).to.have.property('_stackName', 'myService-myStage'),
+		]));
+	});
+
 	it('should succeed', () => {
 		return expect(awsAlias.validate()).to.eventually.be.fulfilled;
 	});