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

Issue:
Error: spawn func.cmd ENOENT
If `azure-functions-core-tools` is not installed globally, spawning of process fails.

Current thought is to run `npx` as the command instead of func. Or to just run the executable within `node_modules/.bin`

Pull request description:
fix: Invoke offline without requiring global install
No longer require users to have `azure-functions-core-tools` globally installed to run `sls offline`.

Closes #319 

Patch:
diff --git a/src/services/offlineService.ts b/src/services/offlineService.ts
index e085c63..2664e82 100644
--- a/src/services/offlineService.ts
+++ b/src/services/offlineService.ts
@@ -4,6 +4,7 @@ import Serverless from "serverless";
 import configConstants from "../config";
 import { BaseService } from "./baseService";
 import { PackageService } from "./packageService";
+import path from "path";
 
 export class OfflineService extends BaseService {
 
@@ -58,6 +59,15 @@ export class OfflineService extends BaseService {
    * @param spawnArgs Array of arguments for CLI command
    */
   private spawn(command: string, spawnArgs?: string[]): Promise<void> {
+    // Run command from local node_modules
+    command = path.join(
+      this.serverless.config.servicePath,
+      "node_modules",
+      ".bin",
+      command
+    );
+    
+    // Append .cmd if running on windows
     if (process.platform === "win32") {
       command += ".cmd";
     }


Test patch:
diff --git a/src/services/offlineService.test.ts b/src/services/offlineService.test.ts
index a26fb24..bee3dc9 100644
--- a/src/services/offlineService.test.ts
+++ b/src/services/offlineService.test.ts
@@ -126,7 +126,7 @@ describe("Offline Service", () => {
     const calls = mySpawn.calls;
     expect(calls).toHaveLength(1);
     const call = calls[0];
-    expect(call.command).toEqual("func");
+    expect(call.command).toEqual(path.join("node_modules", ".bin", "func"));
     expect(call.args).toEqual(["host", "start"]);
   });
 
@@ -142,7 +142,7 @@ describe("Offline Service", () => {
     const calls = mySpawn.calls;
     expect(calls).toHaveLength(1);
     const call = calls[0];
-    expect(call.command).toEqual("func.cmd");
+    expect(call.command).toEqual(path.join("node_modules", ".bin", "func.cmd"));
     expect(call.args).toEqual(["host", "start"]);
   });
 
@@ -174,7 +174,7 @@ describe("Offline Service", () => {
     const calls = mySpawn.calls;
     expect(calls).toHaveLength(1);
     const call = calls[0];
-    expect(call.command).toEqual("func.cmd");
+    expect(call.command).toEqual(path.join("node_modules", ".bin", "func.cmd"));
     expect(call.args).toEqual(["host", "start"]);
 
     const processOnCalls = processOnSpy.mock.calls;
@@ -230,7 +230,7 @@ describe("Offline Service", () => {
     const calls = mySpawn.calls;
     expect(calls).toHaveLength(1);
     const call = calls[0];
-    expect(call.command).toEqual("func.cmd");
+    expect(call.command).toEqual(path.join("node_modules", ".bin", "func.cmd"));
     expect(call.args).toEqual(["host", "start"]);
 
     const processOnCalls = processOnSpy.mock.calls;