Repository: serverless/serverless-azure-functions
Pull request #276 (2019-08-28 21:49:55)

fix: Remove dead, outdated AzureProvider code
None of the functions within the `AzureProvider` are being used. Cleaning this up so as to not confuse contributors before the `v1` release


Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..9bff114
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,34 @@
+---
+name: Bug report
+about: Create a report to help us improve
+---
+
+<!--
+1. Please check if an issue already exists. This bug may have already been documented
+2. Check out and follow our Guidelines: https://github.com/serverless/serverless-azure-functions/CONTRIBUTING.md
+3. Fill out the whole template so we have a good overview on the issue
+4. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
+5. Please follow the template, otherwise we'll have to ask you to update it
+-->
+
+# This is a Bug Report
+
+## Description
+
+- What went wrong?
+- What did you expect should have happened?
+- What was the config you used?
+- What stacktrace or error message from your provider did you see?
+
+Similar or dependent issues:
+
+- #12345
+
+## Additional Data
+
+- **_Serverless Framework Version you're using_**:
+- **_Serverless CLI Version you're using_**:
+- **_Serverless Azure Plugin Version you're using_**:
+- **_Operating System_**:
+- **_Stack Trace_**:
+- **_Provider Error messages_**:
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..4e19c59
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,23 @@
+---
+name: Feature request
+about: Suggest an idea for serverless framework
+---
+
+<!--
+1. Please check if an issue already exists. This feature may have already been requested
+2. Check out and follow our Guidelines: https://github.com/serverless/serverless-azure-functions/CONTRIBUTING.md
+3. Fill out the whole template so we have a good overview on the issue
+4. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
+5. Please follow the template, otherwise we'll have to ask you to update it
+-->
+
+# This is a Feature Proposal
+
+## Description
+
+- What is the use case that should be solved. The more detail you describe this in the easier it is to understand for us.
+- If there is additional config how would it look
+
+Similar or dependent issues:
+
+- #12345
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..dc53426
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,51 @@
+<!--
+1. Please check out and follow our Contributing Guidelines: https://github.com/serverless/serverless-azure-functions/CONTRIBUTING.md
+2. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the PR
+3. Please follow the template, otherwise we'll have to ask you to update it and it will take longer until your PR is merged
+-->
+
+## What did you implement:
+
+Closes #XXXXX
+
+<!--
+Briefly describe the feature if no issue exists for this PR
+-->
+
+## How did you implement it:
+
+<!--
+If this is a nontrivial change please briefly describe your implementation so its easy for us to understand and review your code.
+-->
+
+## How can we verify it:
+
+<!--
+Add any applicable config, commands, screenshots or other resources
+to make it easy for us to verify this works. The easier you make it for us
+to review a PR, the faster we can review and merge it.
+
+Examples:
+* serverless.yml - Fully functioning to easily deploy changes
+* Screenshots - Showing the difference between your output and the master
+* Cloud Configuration - List cloud resources and show that the correct configuration is in place (e.g. AWS CLI commands)
+* Other - Anything else that comes to mind to help us evaluate
+-->
+
+## Todos:
+
+_**Note: Run `npm run test:ci` to run all validation checks on proposed changes**_
+
+- [ ] Write tests and confirm existing functionality is not broken.  
+       **Validate via `npm test`**
+- [ ] Write documentation
+- [ ] Ensure there are no lint errors.  
+       **Validate via `npm run lint`**  
+       _Note: Some reported issues can be automatically fixed by running `npm run lint:fix`_
+- [ ] Make sure code coverage hasn't dropped
+- [ ] Provide verification config / commands / resources
+- [ ] Enable "Allow edits from maintainers" for this PR
+- [ ] Update the messages below
+
+**_Is this ready for review?:_** NO  
+**_Is it a breaking change?:_** NO
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..fffd923
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,134 @@
+# Contributing Guidelines
+
+Welcome, and thanks in advance for your help! Please follow these simple guidelines :+1:
+
+# How to contribute to the Serverless Azure Plugin
+
+## When you propose a new feature or bug fix
+
+**Note:** Please make sure to write an issue first and get enough feedback before jumping into a Pull Request!
+
+- Please make sure there is an open issue discussing your contribution
+- If there isn't, please open an issue so we can talk about it before you invest time into the implementation
+- When creating an issue or pull request, follow the template that GitHub shows so that we have enough information about your request
+
+## When you want to work on an existing issue
+
+**Note:** Please write a quick comment in the corresponding issue and ask if the feature is still relevant and that you want to jump into the implementation.
+
+We will do our best to respond/review/merge your PR according to priority. We hope that you stay engaged with us during this period to insure QA. Please note that the PR will be closed if there hasn't been any activity for a long time (~ 30 days) to keep us focused and keep the repo clean.
+
+## Reviewing Pull Requests
+
+Another really useful way to contribute to Serverless is to review other peoples Pull Requests. Having feedback from multiple people is really helpful and reduces the overall time to make a final decision about the Pull Request.
+
+## Writing / improving documentation
+
+Our documentation lives on GitHub in the [docs](docs) directory. Do you see a typo or other ways to improve it? Feel free to edit it and submit a Pull Request!
+
+## Providing support
+
+The easiest thing you can do to help us move forward and make an impact on our progress is to simply provide support to other people having difficulties with their Serverless projects.
+
+You can do that by replying to [issues on Github](https://github.com/serverless/serverless-azure-functions/issues), chatting with other community members in [our Chat](http://chat.serverless.com) or helping with questions in [our Forum](http://forum.serverless.com).
+
+### Commit Message Format
+
+Each commit message consists of a **header**, a **body** and a **footer**.
+
+```text
+<type>: <short description>
+<BLANK LINE>
+<body>
+<BLANK LINE>
+<footer>
+```
+
+### Header
+
+#### Type
+
+Must be one of the following:
+
+* **build**: Changes that affect the build system or external dependencies
+* **ci**: Changes to our CI configuration files and scripts
+* **docs**: Documentation only changes
+* **feat**: A new feature
+* **fix**: A bug fix
+* **perf**: A code change that improves performance
+* **refactor**: A code change that neither fixes a bug nor adds a feature
+* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
+* **test**: Adding missing tests or correcting existing tests
+* **chore**: Updating dependencies
+
+#### Short Description
+
+Contains a succinct description of the change:
+
+* use the imperative, present tense: "change" not "changed" nor "changes"
+* don't capitalize the first letter
+* no dot (.) at the end
+
+### Body
+
+Just as in the **short description**, use the imperative, present tense: "change" not "changed" nor "changes".
+The body should include the motivation for the change and contrast this with previous behavior.
+
+### Footer
+
+The footer should contain any information about **breaking changes** and is also the place to
+reference Azure DevOps user stories/tasks or GitHub issues that this commit **closes**.
+
+### Commit Message Example
+
+```text
+fix: add debouncing to asset scroller to correct browser scroll position
+
+There is no debouncing when we store the asset container's scroll position.
+This results in erratic, jumpy scrolling and a poor user experience. Improve
+stability and usability with debouncing.
+
+closes #123
+```
+
+---
+
+# Code Style
+
+We aim for clean, consistent code style. We're using ESlint to check for codestyle issues.
+
+## Verifying linting style
+
+```
+npm run lint
+```
+
+## Fixing lint issues
+
+```
+npm run lint:fix
+```
+
+To help reduce the effort of creating contributions with this style, an [.editorconfig file](http://editorconfig.org/) is provided that your editor may use to override any conflicting global defaults and automate a subset of the style settings.
+
+# Testing
+
+We aim for a (near) 75% test coverage, so make sure your tests cover as much of your code as possible.
+
+## Test coverage
+
+During development, you can easily check coverage by running `npm test`.
+
+Please follow these Testing guidelines when writing your unit tests:
+
+- Include a top-level `describe('ClassName')` block, with the name of the class you are testing
+- Inside that top-level `describe()` block, create another `describe('#methodOne()')` block for each class method you might create or modify
+- For each method, include an `it('should do something')` test case for each logical edge case in your changes
+- As you write tests, check the code coverage and make sure all lines of code are covered. If not, just add more test cases until everything is covered
+- For reference and inspiration, please check our `tests` directory
+
+---
+
+Thanks again for being a contributor to the Serverless Community :tada:!
+
+Thanks!
\ No newline at end of file
diff --git a/README.md b/README.md
index cd669a0..1e359c2 100644
--- a/README.md
+++ b/README.md
@@ -207,7 +207,7 @@ The getting started walkthrough illustrates the interactive login experience, wh
 
 Please create issues in this repo for any problems or questions you find. Before sending a PR for any major changes, please create an issue to discuss.
 
-We're still in the process of getting everying running 100%, but please refer to the [Serverless contributing guidlines](https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md) for information on how to contribute and code of conduct.
+We're still in the process of getting everying running 100%, but please refer to the [Serverless contributing guidlines](CONTRIBUTING.md) for information on how to contribute and code of conduct.
 
 #### Local dev
 
diff --git a/package-lock.json b/package-lock.json
index 712b014..ec6deae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.0-18",
+  "version": "1.0.0-22",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index de0a35f..a8c93a8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.0-18",
+  "version": "1.0.0-22",
   "description": "Provider plugin for the Serverless Framework v1.x which adds support for Azure Functions.",
   "license": "MIT",
   "main": "./lib/index.js",
diff --git a/src/models/serverless.ts b/src/models/serverless.ts
index db09448..6aba1a0 100644
--- a/src/models/serverless.ts
+++ b/src/models/serverless.ts
@@ -50,7 +50,7 @@ export interface ServerlessAzureProvider {
   hostingEnvironment?: ResourceConfig;
   virtualNetwork?: ResourceConfig;
   armTemplate?: ArmTemplateConfig;
-  runtime: string; 
+  runtime: string;
 }
 
 export interface ServerlessAzureConfig {
@@ -58,6 +58,7 @@ export interface ServerlessAzureConfig {
   provider: ServerlessAzureProvider;
   plugins: string[];
   functions: any;
+  package: any;
 }
 
 export interface ServerlessAzureFunctionConfig {
@@ -98,6 +99,7 @@ export interface ServerlessCommandMap {
 }
 
 export interface ServerlessAzureOptions extends Serverless.Options {
+  prefix?: string;
   resourceGroup?: string;
 }
 
diff --git a/src/plugins/azureBasePlugin.ts b/src/plugins/azureBasePlugin.ts
index 75b61be..d366814 100644
--- a/src/plugins/azureBasePlugin.ts
+++ b/src/plugins/azureBasePlugin.ts
@@ -1,11 +1,12 @@
 import { Guard } from "../shared/guard";
 import Serverless from "serverless";
 import { Utils } from "../shared/utils";
-import { ServerlessCommandMap, ServerlessHookMap } from "../models/serverless";
+import { ServerlessCommandMap, ServerlessHookMap, ServerlessAzureConfig } from "../models/serverless";
 
 export abstract class AzureBasePlugin<TOptions=Serverless.Options> {
 
   public hooks: ServerlessHookMap
+  protected config: ServerlessAzureConfig;
   protected commands: ServerlessCommandMap;
 
   public constructor(
@@ -13,6 +14,7 @@ export abstract class AzureBasePlugin<TOptions=Serverless.Options> {
     protected options: TOptions,
   ) {
     Guard.null(serverless);
+    this.config = serverless.service as any;
   }
 
   protected log(message: string) {
diff --git a/src/plugins/package/azurePackagePlugin.ts b/src/plugins/package/azurePackagePlugin.ts
index 39a67c3..77b8a64 100644
--- a/src/plugins/package/azurePackagePlugin.ts
+++ b/src/plugins/package/azurePackagePlugin.ts
@@ -25,6 +25,10 @@ export class AzurePackagePlugin extends AzureBasePlugin {
       this.log("No need to create bindings. Using pre-existing package");
       return Promise.resolve();
     }
+    if (this.config.package && this.config.package.individually) {
+      throw new Error("Cannot package Azure Functions individually. " +
+        "Remove `individually` attribute from the `package` section of the serverless config");
+    }
     await this.packageService.createBindings();
     this.bindingsCreated = true;
 
diff --git a/src/provider/azureProvider.ts b/src/provider/azureProvider.ts
index 3d31d02..a046aee 100644
--- a/src/provider/azureProvider.ts
+++ b/src/provider/azureProvider.ts
@@ -1,15 +1,7 @@
-import fs from "fs";
-import { join } from "path";
-import request from "request";
 import Serverless from "serverless";
 import config from "../config";
 
-let functionAppName;
-let functionsAdminKey;
-let invocationId;
-
 export default class AzureProvider {
-  public credentials: any;
   private serverless: any;
 
   public static getProviderName() {
@@ -20,214 +12,4 @@ export default class AzureProvider {
     this.serverless = serverless;
     this.serverless.setProvider(config.providerName, this);
   }
-
-  public getAdminKey(): Promise<any> {
-    const options = {
-      url: `https://${functionAppName}${config.scmDomain}${config.masterKeyApiPath}`,
-      json: true,
-      headers: {
-        Authorization: config.bearer + this.credentials.tokenCache._entries[0].accessToken
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      request(options, (err, response, body) => {
-        if (err) return reject(err);
-        if (response.statusCode !== 200) return reject(body);
-
-        functionsAdminKey = body.masterKey;
-
-        resolve(body.masterKey);
-      });
-    });
-  }
-
-  public pingHostStatus(functionName): Promise<any> {
-    const requestUrl = `https://${functionAppName}${config.functionAppDomain}/admin/functions/${functionName}/status`;
-    const options = {
-      host: functionAppName + config.functionAppDomain,
-      method: "get",
-      url: requestUrl,
-      json: true,
-      headers: {
-        "x-functions-key": functionsAdminKey,
-        Accept: "application/json,*/*"
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      this.serverless.cli.log("Pinging host status...");
-      request(options, (err, res, body) => {
-        if (err) return reject(err);
-        if (body && body.Error) return reject(body.Error);
-
-        if (res.statusCode !== 200) {
-          return body && body.Error ? reject(body.Error) : reject(body);
-        }
-
-        resolve(res);
-      });
-    });
-  }
-
-  public getLogsStream(functionName) {
-    const logOptions = {
-      url: `https://${functionAppName}${config.scmDomain}${config.logStreamApiPath}${functionName}`,
-      headers: {
-        Authorization: config.bearer + this.credentials.tokenCache._entries[0].accessToken,
-        Accept: "*/*"
-      }
-    };
-
-    request
-      .get(logOptions)
-      .on("error", () => {
-        console.error("Disconnected from log streaming.");
-      })
-      .on("end", () => this.getLogsStream(functionName))
-      .pipe(process.stdout);
-  }
-
-  public getInvocationId(functionName): Promise<any> {
-    const options = {
-      url: `https://${functionAppName}${config.scmDomain}${config.logInvocationsApiPath + functionAppName}-${functionName}/invocations?limit=5`,
-      method: "GET",
-      json: true,
-      headers: {
-        Authorization: config.bearer + this.credentials.tokenCache._entries[0].accessToken
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      request(options, (err, response, body) => {
-        if (err) return reject(err);
-        if (response.statusCode !== 200) return reject(body);
-
-        invocationId = body.entries[0].id;
-
-        resolve(body.entries[0].id);
-      });
-    });
-  }
-
-  public getLogsForInvocationId(): Promise<any> {
-    this.serverless.cli.log(`Logs for InvocationId: ${invocationId}`);
-    const options = {
-      url: `https://${functionAppName}${config.scmDomain}${config.logOutputApiPath}${invocationId}`,
-      method: "GET",
-      json: true,
-      headers: {
-        Authorization: config.bearer + this.credentials.tokenCache._entries[0].accessToken
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      request(options, (err, response, body) => {
-        if (err) return reject(err);
-        if (response.statusCode !== 200) return reject(body);
-
-        resolve(body);
-      });
-    });
-  }
-
-  public invoke(functionName, eventType, eventData): Promise<any> {
-    if (eventType === "http") {
-      let queryString = "";
-
-      if (eventData) {
-        if (typeof eventData === "string") {
-          try {
-            eventData = JSON.parse(eventData);
-          }
-          catch (error) {
-            return Promise.reject("The specified input data isn't a valid JSON string. " +
-              "Please correct it and try invoking the function again.");
-          }
-        }
-
-        queryString = Object.keys(eventData)
-          .map((key) => `${key}=${eventData[key]}`)
-          .join("&");
-      }
-
-      return new Promise((resolve, reject) => {
-        const options = {
-          headers: {
-            "x-functions-key": functionsAdminKey
-          },
-          url: `http://${functionAppName}${config.functionAppDomain}${config.functionAppApiPath + functionName}?${queryString}`,
-          method: "GET",
-          json: true,
-        };
-
-        this.serverless.cli.log(`Invoking function "${functionName}"`);
-        request(options, (err, response, body) => {
-          if (err) return reject(err);
-          if (response.statusCode !== 200) return reject(body);
-
-          console.log(body);
-
-          resolve(body);
-        });
-      });
-    }
-
-    const requestUrl = `https://${functionAppName}${config.functionAppDomain}${config.functionsAdminApiPath}${functionName}`;
-
-    const options = {
-      host: config.functionAppDomain,
-      method: "post",
-      body: eventData,
-      url: requestUrl,
-      json: true,
-      headers: {
-        "x-functions-key": functionsAdminKey,
-        Accept: "application/json,*/*"
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      request(options, (err, res) => {
-        if (err) {
-          reject(err);
-        }
-        this.serverless.cli.log(`Invoked function at: ${requestUrl}. \nResponse statuscode: ${res.statusCode}`);
-        resolve(res);
-      });
-    });
-  }
-
-  public uploadPackageJson(): Promise<any> {
-    const packageJsonFilePath = join(this.serverless.config.servicePath, "package.json");
-    this.serverless.cli.log("Uploading package.json...");
-
-    const requestUrl = `https://${functionAppName}${config.scmDomain}${config.scmVfsPath}package.json`;
-    const options = {
-      host: functionAppName + config.scmDomain,
-      method: "put",
-      url: requestUrl,
-      json: true,
-      headers: {
-        Authorization: config.bearer + this.credentials.tokenCache._entries[0].accessToken,
-        Accept: "*/*"
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      if (fs.existsSync(packageJsonFilePath)) {
-        fs.createReadStream(packageJsonFilePath)
-          .pipe(request.put(options, (err) => {
-            if (err) {
-              reject(err);
-            } else {
-              resolve("Package.json file uploaded");
-            }
-          }));
-      }
-      else {
-        resolve("Package.json file does not exist");
-      }
-    });
-  }
 }
diff --git a/src/services/baseService.ts b/src/services/baseService.ts
index 5c3d97c..f6797e2 100644
--- a/src/services/baseService.ts
+++ b/src/services/baseService.ts
@@ -35,14 +35,14 @@ export abstract class BaseService {
   ) {
     Guard.null(serverless);
     this.setDefaultValues();
+    this.config = serverless.service as any;
+    this.setConfigFromCli();
 
     this.baseUrl = "https://management.azure.com";
     this.serviceName = this.getServiceName();
-    this.config = serverless.service as any;
     this.credentials = serverless.variables["azureCredentials"];
     this.subscriptionId = serverless.variables["subscriptionId"];
     this.resourceGroup = this.getResourceGroupName();
-    this.config.provider.resourceGroup = this.resourceGroup;
     this.deploymentConfig = this.getDeploymentConfig();
     this.deploymentName = this.getDeploymentName();
     this.artifactName = this.getArtifactName(this.deploymentName);
@@ -59,16 +59,19 @@ export abstract class BaseService {
    * Name of Azure Region for deployment
    */
   public getRegion(): string {
-    return this.options.region || this.config.provider.region;
+    return this.config.provider.region;
   }
 
   /**
    * Name of current deployment stage
    */
   public getStage(): string {
-    return this.options.stage || this.config.provider.stage;
+    return this.config.provider.stage;
   }
 
+  /**
+   * Prefix for service
+   */
   public getPrefix(): string {
     return this.config.provider.prefix;
   }
@@ -77,9 +80,7 @@ export abstract class BaseService {
    * Name of current resource group
    */
   public getResourceGroupName(): string {
-    return this.options.resourceGroup
-      || this.config.provider.resourceGroup
-      || AzureNamingService.getResourceName(this.config, null, `${this.serviceName}-rg`);
+    return this.config.provider.resourceGroup;
   }
 
   /**
@@ -243,4 +244,24 @@ export abstract class BaseService {
     }
     return timestamp;
   }
+
+  /**
+   * Overwrite values for resourceGroup, prefix, region and stage
+   * in config if passed through CLI
+   */
+  private setConfigFromCli() {
+    const { prefix, region, stage } = this.config.provider;
+    this.config.provider = {
+      ...this.config.provider,
+      prefix: this.getOption("prefix") || prefix,
+      stage: this.getOption("stage") || stage,
+      region: this.getOption("region") || region,
+    }
+    if (!this.config.provider.region && this.config.provider["location"]) {
+      this.config.provider.region = this.config.provider["location"];
+    }
+    this.config.provider.resourceGroup = (
+      this.getOption("resourceGroup", this.config.provider.resourceGroup)
+    ) || AzureNamingService.getResourceName(this.config, null, `${this.getServiceName()}-rg`);
+  }
 }
diff --git a/src/services/namingService.ts b/src/services/namingService.ts
index 86826f2..f53bf0f 100644
--- a/src/services/namingService.ts
+++ b/src/services/namingService.ts
@@ -123,13 +123,15 @@ export class AzureNamingService {
    * @param stageName The stage name
    */
   public static createShortStageName(stageName: string) {
-    Guard.empty(stageName);
+    Guard.empty(stageName, "stageName");
 
     const stageMap = {
       "dogfood": "df",
       "production": "prod",
+      "prod": "prod",
       "development": "dev",
-      "testing": "test"
+      "testing": "test",
+      "test": "test"
     };
 
     return this.createShortName(stageName, stageMap);
@@ -140,7 +142,7 @@ export class AzureNamingService {
    * @param regionName The region name
    */
   public static getNormalizedRegionName(regionName: string) {
-    Guard.empty(regionName);
+    Guard.empty(regionName, "regionName");
     return regionName.replace(/\W/g, "").toLowerCase();
   }
 
@@ -149,7 +151,7 @@ export class AzureNamingService {
    * @param regionName The azure region name
    */
   public static createShortAzureRegionName(regionName: string) {
-    Guard.empty(regionName);
+    Guard.empty(regionName, "regionName");
 
     const locationMap = {
       "north": "n",
diff --git a/src/services/offlineService.ts b/src/services/offlineService.ts
index 7288c6c..48da80e 100644
--- a/src/services/offlineService.ts
+++ b/src/services/offlineService.ts
@@ -42,13 +42,6 @@ export class OfflineService extends BaseService {
   public async cleanup() {
     this.log("Cleaning up offline files")
     await this.packageService.cleanUp();
-    const filenames = Object.keys(this.localFiles);
-    for (const filename of filenames) {
-      if (fs.existsSync(filename)) {
-        this.log(`Removing file '${filename}'`);
-        fs.unlinkSync(filename)
-      }
-    }
     this.log("Finished cleaning up offline files");
   }
 


Test patch:
diff --git a/src/plugins/package/azurePackagePlugin.test.ts b/src/plugins/package/azurePackagePlugin.test.ts
index d3d3867..e789d44 100644
--- a/src/plugins/package/azurePackagePlugin.test.ts
+++ b/src/plugins/package/azurePackagePlugin.test.ts
@@ -41,6 +41,17 @@ describe("Azure Package Plugin", () => {
     expect(PackageService.prototype.cleanUp).toBeCalled();
   });
 
+  it("Throws an error of package.individually is specified", async () => {
+    const slsService = MockFactory.createTestService();
+    slsService["package"] = { individually: true };
+    sls = MockFactory.createTestServerless({ service: slsService });
+    plugin = new AzurePackagePlugin(sls, MockFactory.createTestServerlessOptions());
+    await expect(invokeHook(plugin, "before:package:setupProviderConfiguration")).rejects.toThrow(
+      "Cannot package Azure Functions individually. " +
+      "Remove `individually` attribute from the `package` section of the serverless config"
+    );
+  });
+
   describe("Package specified in options", () => {
 
     beforeEach(() => {
diff --git a/src/provider/azureProvider.test.ts b/src/provider/azureProvider.test.ts
new file mode 100644
index 0000000..11ede69
--- /dev/null
+++ b/src/provider/azureProvider.test.ts
@@ -0,0 +1,12 @@
+import { MockFactory } from "../test/mockFactory";
+import AzureProvider from "./azureProvider"
+
+describe("Azure Provider", () => {
+
+  it("sets the provider name in initialization", () => {
+    const sls = MockFactory.createTestServerless();
+    new AzureProvider(sls);
+    expect(sls.setProvider).toBeCalledWith("azure", expect.anything());
+    expect(AzureProvider.getProviderName()).toEqual("azure");
+  });
+});
diff --git a/src/services/baseService.test.ts b/src/services/baseService.test.ts
index fb7fbc7..244eb9b 100644
--- a/src/services/baseService.test.ts
+++ b/src/services/baseService.test.ts
@@ -1,7 +1,7 @@
 import fs from "fs";
 import mockFs from "mock-fs";
 import Serverless from "serverless";
-import { ServerlessAzureOptions } from "../models/serverless";
+import { ServerlessAzureOptions, ServerlessAzureConfig } from "../models/serverless";
 import { MockFactory } from "../test/mockFactory";
 import { BaseService } from "./baseService";
 import { AzureNamingService } from "./namingService";
@@ -40,9 +40,10 @@ class MockService extends BaseService {
 describe("Base Service", () => {
   let service: MockService;
   let sls: Serverless;
+  const serviceName = "my-custom-service"
 
   const slsConfig = {
-    service: "my custom service",
+    service: serviceName,
     provider: {
       resourceGroup: "My-Resource-Group",
       deploymentName: "My-Deployment",
@@ -54,7 +55,6 @@ describe("Base Service", () => {
   });
 
   function createMockService(options?: Serverless.Options) {
-    sls = MockFactory.createTestServerless();
     sls.variables["azureCredentials"] = MockFactory.createTestAzureCredentials();
     sls.variables["subscriptionId"] = "ABC123";
     Object.assign(sls.service, slsConfig);
@@ -63,6 +63,7 @@ describe("Base Service", () => {
   }
 
   beforeEach(() => {
+    sls = MockFactory.createTestServerless();
     service = createMockService();
   });
 
@@ -198,4 +199,39 @@ describe("Base Service", () => {
 
     readStreamSpy.mockRestore();
   });
+
+  it("sets stage name from CLI", async () => {
+    const stage = "test";
+    delete (sls.service as any as ServerlessAzureConfig).provider.resourceGroup;
+    expect(sls.service.provider.stage).not.toEqual(stage);
+    service = new MockService(sls, { stage } as any);
+    expect(service.getStage()).toEqual(stage);
+    expect(service.getResourceGroupName()).toEqual(`sls-wus-${stage}-${serviceName}-rg`);
+  });
+
+  it("sets region name from CLI", async () => {
+    const region = "East US";
+    delete (sls.service as any as ServerlessAzureConfig).provider.resourceGroup;
+    expect(sls.service.provider.region).not.toEqual(region);
+    service = new MockService(sls, { region } as any);
+    expect(service.getRegion()).toEqual(region);
+    expect(service.getResourceGroupName()).toEqual(`sls-eus-dev-${serviceName}-rg`);
+  });
+
+  it("sets prefix from CLI", async () => {
+    const prefix = "prefix";
+    delete (sls.service as any as ServerlessAzureConfig).provider.resourceGroup;
+    expect(sls.service.provider["prefix"]).not.toEqual(prefix);
+    service = new MockService(sls, { prefix } as any);
+    expect(service.getPrefix()).toEqual(prefix);
+    expect(service.getResourceGroupName()).toEqual(`${prefix}-wus-dev-${serviceName}-rg`);
+  });
+
+  it("sets resource group from CLI", async () => {
+    const resourceGroup = "resourceGroup";
+    delete (sls.service as any as ServerlessAzureConfig).provider.resourceGroup;
+    expect(sls.service.provider["resourceGroup"]).not.toEqual(resourceGroup);
+    service = new MockService(sls, { resourceGroup } as any);
+    expect(service.getResourceGroupName()).toEqual(resourceGroup);
+  });
 });
diff --git a/src/services/offlineService.test.ts b/src/services/offlineService.test.ts
index e9b1427..a26fb24 100644
--- a/src/services/offlineService.test.ts
+++ b/src/services/offlineService.test.ts
@@ -47,7 +47,6 @@ describe("Offline Service", () => {
         MockFactory.createTestBindingsObject(`..${path.sep}${name}.js`)
       );
     }
-    expect(calls[calls.length - 1][0]).toEqual("local.settings.json");
     writeFileSpy.mockRestore();
   });
 
@@ -80,9 +79,9 @@ describe("Offline Service", () => {
     const rmdirSpy = jest.spyOn(fs, "rmdirSync")
     await service.cleanup();
     const unlinkCalls = unlinkSpy.mock.calls;
+    expect(unlinkCalls).toHaveLength(2);
     expect(unlinkCalls[0][0]).toBe(`hello${path.sep}function.json`);
     expect(unlinkCalls[1][0]).toBe(`goodbye${path.sep}function.json`);
-    expect(unlinkCalls[2][0]).toBe("local.settings.json");
     const rmdirCalls = rmdirSpy.mock.calls;
     expect(rmdirCalls[0][0]).toBe("hello");
     expect(rmdirCalls[1][0]).toBe("goodbye");
@@ -193,10 +192,9 @@ describe("Offline Service", () => {
 
     const unlinkCalls = unlinkSpy.mock.calls;
 
-    expect(unlinkCalls).toHaveLength(3);
+    expect(unlinkCalls).toHaveLength(2);
     expect(unlinkCalls[0][0]).toBe(`hello${path.sep}function.json`);
     expect(unlinkCalls[1][0]).toBe(`goodbye${path.sep}function.json`);
-    expect(unlinkCalls[2][0]).toBe("local.settings.json");
 
     const rmdirCalls = rmdirSpy.mock.calls;
 
diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts
index 64242c9..63a3b69 100644
--- a/src/test/mockFactory.ts
+++ b/src/test/mockFactory.ts
@@ -33,6 +33,7 @@ export class MockFactory {
     sls.variables = getAttribute(config, "variables", MockFactory.createTestVariables());
     sls.service = getAttribute(config, "service", MockFactory.createTestService());
     sls.config.servicePath = "";
+    sls.setProvider = jest.fn();
     return sls;
   }