Repository: serverless/serverless-azure-functions (ts)

Issue:
Error when performing interactive login without a .azure directory
If a user performs an interactive login without having a .azure directory in their home directory, the deployment fails and nothing can be deployed. Would prefer that the plugin addressed this without user intervention. 

Pull request description:
fix: Create default .azure directory if not present
fix: Create default .azure directory if not present

The pluging currently saves credentials from interactive login in the .azure directory by default. If the user doesn't have a .azure directory, current;y the plugin hits an error. This fix checks to make sure the .azure directory exists and creates it if not. 

Closes #317 

Patch:
diff --git a/src/plugins/login/utils/simpleFileTokenCache.ts b/src/plugins/login/utils/simpleFileTokenCache.ts
index 968cb1c..601e167 100644
--- a/src/plugins/login/utils/simpleFileTokenCache.ts
+++ b/src/plugins/login/utils/simpleFileTokenCache.ts
@@ -3,7 +3,7 @@ import path from "path";
 import os from "os";
 import * as adal from "adal-node";
 
-const CONFIG_DIRECTORY = path.join(os.homedir(), ".azure");
+let CONFIG_DIRECTORY = path.join(os.homedir(), ".azure");
 const DEFAULT_SLS_TOKEN_FILE = path.join(CONFIG_DIRECTORY, "slsTokenCache.json");
 
 export class SimpleFileTokenCache implements adal.TokenCache {
@@ -11,6 +11,11 @@ export class SimpleFileTokenCache implements adal.TokenCache {
   private subscriptions: any[] = [];
 
   public constructor(private tokenPath: string = DEFAULT_SLS_TOKEN_FILE) {
+    if(tokenPath === DEFAULT_SLS_TOKEN_FILE && !fs.existsSync(CONFIG_DIRECTORY)) {
+      CONFIG_DIRECTORY = path.join(os.homedir(), ".azure");
+      this.tokenPath = CONFIG_DIRECTORY;
+      fs.mkdirSync(CONFIG_DIRECTORY);
+    }
     this.load();
   }
 


Test patch:
diff --git a/src/plugins/login/utils/simpleFileTokenCache.test.ts b/src/plugins/login/utils/simpleFileTokenCache.test.ts
index d0ca509..e9383e4 100644
--- a/src/plugins/login/utils/simpleFileTokenCache.test.ts
+++ b/src/plugins/login/utils/simpleFileTokenCache.test.ts
@@ -1,4 +1,6 @@
 import fs from "fs";
+import path from "path";
+import os from "os";
 import mockFs from "mock-fs";
 import { MockFactory } from "../../../test/mockFactory";
 import { SimpleFileTokenCache } from "./simpleFileTokenCache";
@@ -8,7 +10,7 @@ describe("Simple File Token Cache", () => {
 
   let fileContent = {
     entries: [],
-    subscriptions: [],
+    subscriptions: []
   };
 
   afterEach(() => {
@@ -23,10 +25,39 @@ describe("Simple File Token Cache", () => {
 
     const expected = {
       entries: [],
-      subscriptions: [],
+      subscriptions: []
     };
 
-    expect(writeFileSpy).toBeCalledWith(tokenFilePath, JSON.stringify(expected));
+    expect(writeFileSpy).toBeCalledWith(
+      tokenFilePath,
+      JSON.stringify(expected)
+    );
+    writeFileSpy.mockRestore();
+  });
+
+  it("Create .azure default directory if it doesn't exist", () => {
+    const expected = path.join(os.homedir(), ".azure");
+
+    mockFs();
+
+    const writeFileSpy = jest.spyOn(fs, "writeFileSync");
+    writeFileSpy.mockImplementation(() => undefined);
+
+    const mkdirSpy = jest.spyOn(fs, "mkdirSync");
+    mkdirSpy.mockImplementation(() => undefined);
+
+    const existsSpy = jest.spyOn(fs, "existsSync");
+    existsSpy.mockImplementation(() => false);
+
+    const readFileSpy = jest.spyOn(fs, "readFileSync");
+    readFileSpy.mockImplementation(() => "{\"entries\": []}");
+
+    new SimpleFileTokenCache();
+    expect(mkdirSpy).toBeCalledWith(expected);
+
+    mkdirSpy.mockRestore();
+    existsSpy.mockRestore();
+    readFileSpy.mockRestore();
     writeFileSpy.mockRestore();
   });
 
@@ -34,7 +65,7 @@ describe("Simple File Token Cache", () => {
     fileContent.entries = MockFactory.createTestTokenCacheEntries();
 
     mockFs({
-      "slsTokenCache.json": JSON.stringify(fileContent),
+      "slsTokenCache.json": JSON.stringify(fileContent)
     });
 
     const readFileSpy = jest.spyOn(fs, "readFileSync");
@@ -43,7 +74,7 @@ describe("Simple File Token Cache", () => {
     expect(readFileSpy).toBeCalled();
     expect(tokenCache.first()).not.toBeNull();
     readFileSpy.mockRestore();
-  })
+  });
 
   it("Saves to file after token is added", () => {
     mockFs();
@@ -56,11 +87,14 @@ describe("Simple File Token Cache", () => {
 
     const expected = {
       entries: testEntries,
-      subscriptions: [],
+      subscriptions: []
     };
 
     expect(tokenCache.isEmpty()).toBe(false);
-    expect(writeFileSpy).toBeCalledWith(tokenFilePath, JSON.stringify(expected));
+    expect(writeFileSpy).toBeCalledWith(
+      tokenFilePath,
+      JSON.stringify(expected)
+    );
     writeFileSpy.mockRestore();
   });
 
@@ -75,16 +109,19 @@ describe("Simple File Token Cache", () => {
 
     const expected = {
       entries: [],
-      subscriptions: testSubs,
+      subscriptions: testSubs
     };
 
-    expect(writeFileSpy).toBeCalledWith(tokenFilePath, JSON.stringify(expected));
+    expect(writeFileSpy).toBeCalledWith(
+      tokenFilePath,
+      JSON.stringify(expected)
+    );
     writeFileSpy.mockRestore();
   });
 
   it("Doesn't fail adding subs if unable to parse JSON from file", () => {
     mockFs({
-      "slsTokenCache.json": JSON.stringify(""),
+      "slsTokenCache.json": JSON.stringify("")
     });
 
     const writeFileSpy = jest.spyOn(fs, "writeFileSync");
@@ -95,16 +132,19 @@ describe("Simple File Token Cache", () => {
 
     const expected = {
       entries: [],
-      subscriptions: testSubs,
+      subscriptions: testSubs
     };
 
-    expect(writeFileSpy).toBeCalledWith(tokenFilePath, JSON.stringify(expected));
+    expect(writeFileSpy).toBeCalledWith(
+      tokenFilePath,
+      JSON.stringify(expected)
+    );
     writeFileSpy.mockRestore();
   });
 
   it("Doesn't fail removing entries if unable to parse JSON from file", () => {
     mockFs({
-      "slsTokenCache.json": JSON.stringify(""),
+      "slsTokenCache.json": JSON.stringify("")
     });
 
     const writeFileSpy = jest.spyOn(fs, "writeFileSync");
@@ -113,20 +153,23 @@ describe("Simple File Token Cache", () => {
     const testEntries = MockFactory.createTestTokenCacheEntries();
 
     testFileCache.addSubs(testSubs);
-    testFileCache.remove(testEntries)
+    testFileCache.remove(testEntries);
 
     const expected = {
       entries: [],
-      subscriptions: testSubs,
+      subscriptions: testSubs
     };
 
-    expect(writeFileSpy).toBeCalledWith(tokenFilePath, JSON.stringify(expected));
+    expect(writeFileSpy).toBeCalledWith(
+      tokenFilePath,
+      JSON.stringify(expected)
+    );
     writeFileSpy.mockRestore();
   });
 
   it("Doesn't fail find if unable to parse JSON from file", () => {
     mockFs({
-      "slsTokenCache.json": JSON.stringify(""),
+      "slsTokenCache.json": JSON.stringify("")
     });
 
     const testFileCache = new SimpleFileTokenCache(tokenFilePath);
@@ -134,7 +177,7 @@ describe("Simple File Token Cache", () => {
 
     testFileCache.addSubs(testSubs);
     const cb = jest.fn();
-    const result = testFileCache.find({key: "value"}, cb);
+    const result = testFileCache.find({ key: "value" }, cb);
 
     expect(cb).toBeCalledWith(null, result);
     expect(result).toEqual([]);