From 9139d8fcf8100c8b118c3c1f66b1596f1a534f76 Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 19 Jul 2020 20:37:45 -0700
Subject: [PATCH 1/8] refactor activate/deactivate

Signed-off-by: shmck <shawn.j.mckay@gmail.com>
---
 src/editor/index.ts | 48 ---------------------------------------------
 src/extension.ts    | 46 ++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 41 insertions(+), 53 deletions(-)
 delete mode 100644 src/editor/index.ts

diff --git a/src/editor/index.ts b/src/editor/index.ts
deleted file mode 100644
index bf900b5d..00000000
--- a/src/editor/index.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import * as vscode from 'vscode'
-import { createCommands } from './commands'
-import * as telemetry from '../services/telemetry'
-
-class Editor {
-  // extension context set on activation
-  // @ts-ignore
-  private vscodeExt: vscode.ExtensionContext
-
-  public activate = (vscodeExt: vscode.ExtensionContext): void => {
-    this.vscodeExt = vscodeExt
-
-    // set out 60/40 layout
-    vscode.commands.executeCommand('vscode.setEditorLayout', {
-      orientation: 0,
-      groups: [{ size: 0.6 }, { size: 0.4 }],
-    })
-
-    // commands
-    const commands = createCommands({
-      extensionPath: this.vscodeExt.extensionPath,
-      // NOTE: local storage must be bound to the vscodeExt.workspaceState
-      workspaceState: this.vscodeExt.workspaceState,
-    })
-
-    const subscribe = (sub: any) => {
-      this.vscodeExt.subscriptions.push(sub)
-    }
-
-    // register commands
-    for (const cmd in commands) {
-      const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd])
-      subscribe(command)
-    }
-
-    telemetry.activate(subscribe)
-  }
-  public deactivate = (): void => {
-    // cleanup subscriptions/tasks
-    for (const disposable of this.vscodeExt.subscriptions) {
-      disposable.dispose()
-    }
-
-    telemetry.deactivate()
-  }
-}
-
-export default Editor
diff --git a/src/extension.ts b/src/extension.ts
index a12e7e2d..1f64f83b 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -1,10 +1,46 @@
-import Editor from './editor'
+import * as vscode from 'vscode'
+import { createCommands } from './editor/commands'
+import * as telemetry from './services/telemetry'
 
-// vscode editor
-export const editor = new Editor()
+let onDeactivate = () => {}
 
 // activate run on vscode extension initialization
-export const activate = editor.activate
+export const activate = (vscodeExt: vscode.ExtensionContext): void => {
+  // set out default 60/40 layout
+  vscode.commands.executeCommand('vscode.setEditorLayout', {
+    orientation: 0,
+    groups: [{ size: 0.6 }, { size: 0.4 }],
+  })
+
+  // commands
+  const commands = createCommands({
+    extensionPath: vscodeExt.extensionPath,
+    // NOTE: local storage must be bound to the vscodeExt.workspaceState
+    workspaceState: vscodeExt.workspaceState,
+  })
+
+  const subscribe = (sub: any) => {
+    vscodeExt.subscriptions.push(sub)
+  }
+
+  // register commands
+  for (const cmd in commands) {
+    const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd])
+    subscribe(command)
+  }
+
+  telemetry.activate(subscribe)
+
+  onDeactivate = () => {
+    // cleanup subscriptions/tasks
+    // handled within activate because it requires access to subscriptions
+    for (const disposable of vscodeExt.subscriptions) {
+      disposable.dispose()
+    }
+
+    telemetry.deactivate()
+  }
+}
 
 // deactivate run on vscode extension shut down
-export const deactivate = editor.deactivate
+export const deactivate = (): void => onDeactivate()

From 91feec8e8a9301039f2bef2ebf87fce6ffa6b33e Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 19 Jul 2020 20:58:22 -0700
Subject: [PATCH 2/8] refactor send actions

Signed-off-by: shmck <shawn.j.mckay@gmail.com>
---
 src/actions/index.ts                          |  2 +
 src/actions/onErrorPage.ts                    | 26 ++++++++
 src/actions/onTestPass.ts                     | 16 +++++
 src/actions/saveCommit.ts                     |  7 ---
 src/actions/tutorialConfig.ts                 |  2 +-
 src/actions/utils/loadWatchers.ts             |  2 +-
 src/actions/utils/openFiles.ts                |  2 +-
 src/{channel/index.ts => channel.ts}          | 62 +++++--------------
 src/{editor => }/commands.ts                  |  8 +--
 src/extension.ts                              |  2 +-
 src/{channel => services/context}/context.ts  |  0
 .../context}/state/Position.ts                |  0
 .../context}/state/Progress.ts                |  2 +-
 .../context}/state/Tutorial.ts                |  2 +-
 src/services/node/index.ts                    |  5 ++
 15 files changed, 76 insertions(+), 62 deletions(-)
 create mode 100644 src/actions/index.ts
 create mode 100644 src/actions/onErrorPage.ts
 create mode 100644 src/actions/onTestPass.ts
 delete mode 100644 src/actions/saveCommit.ts
 rename src/{channel/index.ts => channel.ts} (86%)
 rename src/{editor => }/commands.ts (95%)
 rename src/{channel => services/context}/context.ts (100%)
 rename src/{channel => services/context}/state/Position.ts (100%)
 rename src/{channel => services/context}/state/Progress.ts (97%)
 rename src/{channel => services/context}/state/Tutorial.ts (94%)

diff --git a/src/actions/index.ts b/src/actions/index.ts
new file mode 100644
index 00000000..37089b12
--- /dev/null
+++ b/src/actions/index.ts
@@ -0,0 +1,2 @@
+export { default as onErrorPage } from './onErrorPage'
+export { default as onTestPass } from './onTestPass'
diff --git a/src/actions/onErrorPage.ts b/src/actions/onErrorPage.ts
new file mode 100644
index 00000000..dd4dc742
--- /dev/null
+++ b/src/actions/onErrorPage.ts
@@ -0,0 +1,26 @@
+import * as T from 'typings'
+import { readFile } from '../services/node'
+import logger from '../services/logger'
+
+const onErrorPage = async (action: T.Action) => {
+  // Error middleware
+  if (action?.payload?.error?.type) {
+    // load error markdown message
+    const error = action.payload.error
+    const errorMarkdown = await readFile(__dirname, '..', '..', 'errors', `${action.payload.error.type}.md`).catch(
+      () => {
+        // onError(new Error(`Error Markdown file not found for ${action.type}`))
+      },
+    )
+
+    // log error to console for safe keeping
+    logger(`ERROR:\n ${errorMarkdown}`)
+
+    if (errorMarkdown) {
+      // add a clearer error message for the user
+      error.message = `${errorMarkdown}\n\n${error.message}`
+    }
+  }
+}
+
+export default onErrorPage
diff --git a/src/actions/onTestPass.ts b/src/actions/onTestPass.ts
new file mode 100644
index 00000000..0169a942
--- /dev/null
+++ b/src/actions/onTestPass.ts
@@ -0,0 +1,16 @@
+import * as git from '../services/git'
+import * as T from 'typings'
+import Context from '../services/context/context'
+
+const onTestPass = (action: T.Action, context: Context) => {
+  const tutorial = context.tutorial.get()
+  if (!tutorial) {
+    throw new Error('Error with current tutorial. Tutorial may be missing an id.')
+  }
+  // update local storage stepProgress
+  const progress = context.progress.setStepComplete(tutorial, action.payload.position.stepId)
+  context.position.setPositionFromProgress(tutorial, progress)
+  git.saveCommit('Save progress')
+}
+
+export default onTestPass
diff --git a/src/actions/saveCommit.ts b/src/actions/saveCommit.ts
deleted file mode 100644
index 74002c5e..00000000
--- a/src/actions/saveCommit.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import * as git from '../services/git'
-
-async function saveCommit(): Promise<void> {
-  git.saveCommit('Save progress')
-}
-
-export default saveCommit
diff --git a/src/actions/tutorialConfig.ts b/src/actions/tutorialConfig.ts
index d01510f3..5084d735 100644
--- a/src/actions/tutorialConfig.ts
+++ b/src/actions/tutorialConfig.ts
@@ -1,7 +1,7 @@
 import * as E from 'typings/error'
 import * as TT from 'typings/tutorial'
 import * as vscode from 'vscode'
-import { COMMANDS } from '../editor/commands'
+import { COMMANDS } from '../commands'
 import * as git from '../services/git'
 import { DISABLE_RUN_ON_SAVE } from '../environment'
 
diff --git a/src/actions/utils/loadWatchers.ts b/src/actions/utils/loadWatchers.ts
index 9dab23ec..978a3ce2 100644
--- a/src/actions/utils/loadWatchers.ts
+++ b/src/actions/utils/loadWatchers.ts
@@ -1,6 +1,6 @@
 import * as chokidar from 'chokidar'
 import * as vscode from 'vscode'
-import { COMMANDS } from '../../editor/commands'
+import { COMMANDS } from '../../commands'
 import { WORKSPACE_ROOT } from '../../environment'
 
 // NOTE: vscode createFileWatcher doesn't seem to detect changes outside of vscode
diff --git a/src/actions/utils/openFiles.ts b/src/actions/utils/openFiles.ts
index 34580f04..35e97710 100644
--- a/src/actions/utils/openFiles.ts
+++ b/src/actions/utils/openFiles.ts
@@ -1,6 +1,6 @@
 import { join } from 'path'
 import * as vscode from 'vscode'
-import { COMMANDS } from '../../editor/commands'
+import { COMMANDS } from '../../commands'
 
 const openFiles = async (files: string[]) => {
   if (!files.length) {
diff --git a/src/channel/index.ts b/src/channel.ts
similarity index 86%
rename from src/channel/index.ts
rename to src/channel.ts
index 19cff9e4..4aceefb8 100644
--- a/src/channel/index.ts
+++ b/src/channel.ts
@@ -4,25 +4,20 @@ import * as E from 'typings/error'
 import * as vscode from 'vscode'
 import fetch from 'node-fetch'
 import { satisfies } from 'semver'
-import saveCommit from '../actions/saveCommit'
-import { setupActions, solutionActions } from '../actions/setupActions'
-import tutorialConfig from '../actions/tutorialConfig'
-import { COMMANDS } from '../editor/commands'
-import Context from './context'
-import { readFile } from 'fs'
-import { join } from 'path'
-import { promisify } from 'util'
-import logger from '../services/logger'
-import { version, compareVersions } from '../services/dependencies'
-import { openWorkspace, checkWorkspaceEmpty } from '../services/workspace'
-import { showOutput } from '../services/testRunner/output'
-import { exec } from '../services/node'
-import { WORKSPACE_ROOT, TUTORIAL_URL } from '../environment'
-import reset from '../services/reset'
-import getLastCommitHash from '../services/reset/lastHash'
-import { onEvent } from '../services/telemetry'
-
-const readFileAsync = promisify(readFile)
+import { setupActions, solutionActions } from './actions/setupActions'
+import tutorialConfig from './actions/tutorialConfig'
+import { COMMANDS } from './commands'
+import Context from './services/context/context'
+import logger from './services/logger'
+import { version, compareVersions } from './services/dependencies'
+import { openWorkspace, checkWorkspaceEmpty } from './services/workspace'
+import { showOutput } from './services/testRunner/output'
+import { exec } from './services/node'
+import { WORKSPACE_ROOT, TUTORIAL_URL } from './environment'
+import reset from './services/reset'
+import getLastCommitHash from './services/reset/lastHash'
+import { onEvent } from './services/telemetry'
+import * as actions from './actions'
 
 interface Channel {
   receive(action: T.Action): Promise<void>
@@ -359,24 +354,8 @@ class Channel implements Channel {
   }
   // send to webview
   public send = async (action: T.Action): Promise<void> => {
-    // Error middleware
-    if (action?.payload?.error?.type) {
-      // load error markdown message
-      const error = action.payload.error
-      const errorMarkdownFile = join(__dirname, '..', '..', 'errors', `${action.payload.error.type}.md`)
-      const errorMarkdown = await readFileAsync(errorMarkdownFile).catch(() => {
-        // onError(new Error(`Error Markdown file not found for ${action.type}`))
-      })
-
-      // log error to console for safe keeping
-      logger(`ERROR:\n ${errorMarkdown}`)
-
-      if (errorMarkdown) {
-        // add a clearer error message for the user
-        error.message = `${errorMarkdown}\n\n${error.message}`
-      }
-    }
-
+    // load error page if error action is triggered
+    actions.onErrorPage(action)
     // action may be an object.type or plain string
     const actionType: string = typeof action === 'string' ? action : action.type
 
@@ -384,14 +363,7 @@ class Channel implements Channel {
 
     switch (actionType) {
       case 'TEST_PASS':
-        const tutorial = this.context.tutorial.get()
-        if (!tutorial) {
-          throw new Error('Error with current tutorial. Tutorial may be missing an id.')
-        }
-        // update local storage stepProgress
-        const progress = this.context.progress.setStepComplete(tutorial, action.payload.position.stepId)
-        this.context.position.setPositionFromProgress(tutorial, progress)
-        saveCommit()
+        actions.onTestPass(action, this.context)
     }
 
     // send message
diff --git a/src/editor/commands.ts b/src/commands.ts
similarity index 95%
rename from src/editor/commands.ts
rename to src/commands.ts
index a7ab6915..3f9989d4 100644
--- a/src/editor/commands.ts
+++ b/src/commands.ts
@@ -1,10 +1,10 @@
 import * as T from 'typings'
 import * as TT from 'typings/tutorial'
 import * as vscode from 'vscode'
-import createTestRunner from '../services/testRunner'
-import { setupActions } from '../actions/setupActions'
-import createWebView from '../services/webview'
-import logger from '../services/logger'
+import createTestRunner from './services/testRunner'
+import { setupActions } from './actions/setupActions'
+import createWebView from './services/webview'
+import logger from './services/logger'
 
 export const COMMANDS = {
   START: 'coderoad.start',
diff --git a/src/extension.ts b/src/extension.ts
index 1f64f83b..49a65006 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -1,5 +1,5 @@
 import * as vscode from 'vscode'
-import { createCommands } from './editor/commands'
+import { createCommands } from './commands'
 import * as telemetry from './services/telemetry'
 
 let onDeactivate = () => {}
diff --git a/src/channel/context.ts b/src/services/context/context.ts
similarity index 100%
rename from src/channel/context.ts
rename to src/services/context/context.ts
diff --git a/src/channel/state/Position.ts b/src/services/context/state/Position.ts
similarity index 100%
rename from src/channel/state/Position.ts
rename to src/services/context/state/Position.ts
diff --git a/src/channel/state/Progress.ts b/src/services/context/state/Progress.ts
similarity index 97%
rename from src/channel/state/Progress.ts
rename to src/services/context/state/Progress.ts
index 691d52bf..8bd2daa8 100644
--- a/src/channel/state/Progress.ts
+++ b/src/services/context/state/Progress.ts
@@ -1,7 +1,7 @@
 import * as T from 'typings'
 import * as TT from 'typings/tutorial'
 import * as vscode from 'vscode'
-import Storage from '../../services/storage'
+import Storage from '../../storage'
 
 const defaultValue: T.Progress = {
   levels: {},
diff --git a/src/channel/state/Tutorial.ts b/src/services/context/state/Tutorial.ts
similarity index 94%
rename from src/channel/state/Tutorial.ts
rename to src/services/context/state/Tutorial.ts
index 3a91d19c..4f0ccb55 100644
--- a/src/channel/state/Tutorial.ts
+++ b/src/services/context/state/Tutorial.ts
@@ -1,6 +1,6 @@
 import * as TT from 'typings/tutorial'
 import * as vscode from 'vscode'
-import Storage from '../../services/storage'
+import Storage from '../../storage'
 
 // Tutorial
 class Tutorial {
diff --git a/src/services/node/index.ts b/src/services/node/index.ts
index 7026c7a6..a90dd208 100644
--- a/src/services/node/index.ts
+++ b/src/services/node/index.ts
@@ -6,6 +6,7 @@ import { WORKSPACE_ROOT } from '../../environment'
 
 const asyncExec = promisify(cpExec)
 const asyncRemoveFile = promisify(fs.unlink)
+const asyncReadFile = promisify(fs.readFile)
 
 interface ExecParams {
   command: string
@@ -24,3 +25,7 @@ export const exists = (...paths: string[]): boolean | never => {
 export const removeFile = (...paths: string[]) => {
   return asyncRemoveFile(join(WORKSPACE_ROOT, ...paths))
 }
+
+export const readFile = (...paths: string[]) => {
+  return asyncReadFile(join(...paths))
+}

From 7f12a0f362307eda078f803c5a55dab1b1d3de7a Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 19 Jul 2020 21:05:23 -0700
Subject: [PATCH 3/8] refactor onEditorStartup

Signed-off-by: shmck <shawn.j.mckay@gmail.com>
---
 src/actions/index.ts     |  1 +
 src/actions/onStartup.ts | 79 ++++++++++++++++++++++++++++++++++++++++
 src/channel.ts           | 65 +--------------------------------
 3 files changed, 81 insertions(+), 64 deletions(-)
 create mode 100644 src/actions/onStartup.ts

diff --git a/src/actions/index.ts b/src/actions/index.ts
index 37089b12..ae5091aa 100644
--- a/src/actions/index.ts
+++ b/src/actions/index.ts
@@ -1,2 +1,3 @@
+export { default as onStartup } from './onStartup'
 export { default as onErrorPage } from './onErrorPage'
 export { default as onTestPass } from './onTestPass'
diff --git a/src/actions/onStartup.ts b/src/actions/onStartup.ts
new file mode 100644
index 00000000..2c3de424
--- /dev/null
+++ b/src/actions/onStartup.ts
@@ -0,0 +1,79 @@
+import * as vscode from 'vscode'
+import * as T from 'typings'
+import * as TT from 'typings/tutorial'
+import * as E from 'typings/error'
+import Context from '../services/context/context'
+import { WORKSPACE_ROOT, TUTORIAL_URL } from '../environment'
+import fetch from 'node-fetch'
+import logger from '../services/logger'
+
+const onStartup = async (
+  context: Context,
+  workspaceState: vscode.Memento,
+  send: (action: T.Action) => Promise<void>,
+) => {
+  try {
+    // check if a workspace is open, otherwise nothing works
+    const noActiveWorkspace = !WORKSPACE_ROOT.length
+    if (noActiveWorkspace) {
+      const error: E.ErrorMessage = {
+        type: 'NoWorkspaceFound',
+        message: '',
+        actions: [
+          {
+            label: 'Open Workspace',
+            transition: 'REQUEST_WORKSPACE',
+          },
+        ],
+      }
+      send({ type: 'NO_WORKSPACE', payload: { error } })
+      return
+    }
+
+    const env = {
+      machineId: vscode.env.machineId,
+      sessionId: vscode.env.sessionId,
+    }
+
+    // load tutorial from url
+    if (TUTORIAL_URL) {
+      try {
+        const tutorialRes = await fetch(TUTORIAL_URL)
+        const tutorial = await tutorialRes.json()
+        send({ type: 'START_TUTORIAL_FROM_URL', payload: { tutorial } })
+        return
+      } catch (e) {
+        console.log(`Failed to load tutorial from url ${TUTORIAL_URL} with error "${e.message}"`)
+      }
+    }
+
+    // continue from tutorial from local storage
+    const tutorial: TT.Tutorial | null = context.tutorial.get()
+
+    // no stored tutorial, must start new tutorial
+    if (!tutorial || !tutorial.id) {
+      send({ type: 'START_NEW_TUTORIAL', payload: { env } })
+      return
+    }
+
+    // load continued tutorial position & progress
+    const { position, progress } = await context.setTutorial(workspaceState, tutorial)
+    logger('CONTINUE STATE', position, progress)
+
+    if (progress.complete) {
+      // tutorial is already complete
+      send({ type: 'TUTORIAL_ALREADY_COMPLETE', payload: { env } })
+      return
+    }
+    // communicate to client the tutorial & stepProgress state
+    send({ type: 'LOAD_STORED_TUTORIAL', payload: { env, tutorial, progress, position } })
+  } catch (e) {
+    const error = {
+      type: 'UnknownError',
+      message: `Location: Editor startup\n\n${e.message}`,
+    }
+    send({ type: 'EDITOR_STARTUP_FAILED', payload: { error } })
+  }
+}
+
+export default onStartup
diff --git a/src/channel.ts b/src/channel.ts
index 4aceefb8..446ffd7d 100644
--- a/src/channel.ts
+++ b/src/channel.ts
@@ -2,7 +2,6 @@ import * as T from 'typings'
 import * as TT from 'typings/tutorial'
 import * as E from 'typings/error'
 import * as vscode from 'vscode'
-import fetch from 'node-fetch'
 import { satisfies } from 'semver'
 import { setupActions, solutionActions } from './actions/setupActions'
 import tutorialConfig from './actions/tutorialConfig'
@@ -13,7 +12,6 @@ import { version, compareVersions } from './services/dependencies'
 import { openWorkspace, checkWorkspaceEmpty } from './services/workspace'
 import { showOutput } from './services/testRunner/output'
 import { exec } from './services/node'
-import { WORKSPACE_ROOT, TUTORIAL_URL } from './environment'
 import reset from './services/reset'
 import getLastCommitHash from './services/reset/lastHash'
 import { onEvent } from './services/telemetry'
@@ -50,68 +48,7 @@ class Channel implements Channel {
 
     switch (actionType) {
       case 'EDITOR_STARTUP':
-        try {
-          // check if a workspace is open, otherwise nothing works
-          const noActiveWorkspace = !WORKSPACE_ROOT.length
-          if (noActiveWorkspace) {
-            const error: E.ErrorMessage = {
-              type: 'NoWorkspaceFound',
-              message: '',
-              actions: [
-                {
-                  label: 'Open Workspace',
-                  transition: 'REQUEST_WORKSPACE',
-                },
-              ],
-            }
-            this.send({ type: 'NO_WORKSPACE', payload: { error } })
-            return
-          }
-
-          const env = {
-            machineId: vscode.env.machineId,
-            sessionId: vscode.env.sessionId,
-          }
-
-          // load tutorial from url
-          if (TUTORIAL_URL) {
-            try {
-              const tutorialRes = await fetch(TUTORIAL_URL)
-              const tutorial = await tutorialRes.json()
-              this.send({ type: 'START_TUTORIAL_FROM_URL', payload: { tutorial } })
-              return
-            } catch (e) {
-              console.log(`Failed to load tutorial from url ${TUTORIAL_URL} with error "${e.message}"`)
-            }
-          }
-
-          // continue from tutorial from local storage
-          const tutorial: TT.Tutorial | null = this.context.tutorial.get()
-
-          // no stored tutorial, must start new tutorial
-          if (!tutorial || !tutorial.id) {
-            this.send({ type: 'START_NEW_TUTORIAL', payload: { env } })
-            return
-          }
-
-          // load continued tutorial position & progress
-          const { position, progress } = await this.context.setTutorial(this.workspaceState, tutorial)
-          logger('CONTINUE STATE', position, progress)
-
-          if (progress.complete) {
-            // tutorial is already complete
-            this.send({ type: 'TUTORIAL_ALREADY_COMPLETE', payload: { env } })
-            return
-          }
-          // communicate to client the tutorial & stepProgress state
-          this.send({ type: 'LOAD_STORED_TUTORIAL', payload: { env, tutorial, progress, position } })
-        } catch (e) {
-          const error = {
-            type: 'UnknownError',
-            message: `Location: Editor startup\n\n${e.message}`,
-          }
-          this.send({ type: 'EDITOR_STARTUP_FAILED', payload: { error } })
-        }
+        actions.onStartup(this.context, this.workspaceState, this.send)
         return
 
       // clear tutorial local storage

From ae3ee13736743e18c0651cc3e75ba2192ed2096e Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 19 Jul 2020 21:13:02 -0700
Subject: [PATCH 4/8] refactor onTutorialConfig

Signed-off-by: shmck <shawn.j.mckay@gmail.com>
---
 src/actions/index.ts                      |   1 +
 src/actions/onTutorialConfig.ts           | 121 ++++++++++++++++++++++
 src/actions/{ => utils}/tutorialConfig.ts |   6 +-
 src/channel.ts                            | 116 +--------------------
 4 files changed, 129 insertions(+), 115 deletions(-)
 create mode 100644 src/actions/onTutorialConfig.ts
 rename src/actions/{ => utils}/tutorialConfig.ts (93%)

diff --git a/src/actions/index.ts b/src/actions/index.ts
index ae5091aa..3049b498 100644
--- a/src/actions/index.ts
+++ b/src/actions/index.ts
@@ -1,3 +1,4 @@
 export { default as onStartup } from './onStartup'
+export { default as onTutorialConfig } from './onTutorialConfig'
 export { default as onErrorPage } from './onErrorPage'
 export { default as onTestPass } from './onTestPass'
diff --git a/src/actions/onTutorialConfig.ts b/src/actions/onTutorialConfig.ts
new file mode 100644
index 00000000..7a1f7221
--- /dev/null
+++ b/src/actions/onTutorialConfig.ts
@@ -0,0 +1,121 @@
+import * as vscode from 'vscode'
+import * as T from 'typings'
+import * as TT from 'typings/tutorial'
+import * as E from 'typings/error'
+import { satisfies } from 'semver'
+import { onEvent } from '../services/telemetry'
+import { version, compareVersions } from '../services/dependencies'
+import Context from '../services/context/context'
+import tutorialConfig from './utils/tutorialConfig'
+
+const onTutorialConfig = async (action: T.Action, context: Context, workspaceState: vscode.Memento, send: any) => {
+  try {
+    const data: TT.Tutorial = action.payload.tutorial
+
+    onEvent('tutorial_start', {
+      tutorial_id: data.id,
+      tutorial_version: data.version,
+      tutorial_title: data.summary.title,
+    })
+
+    // validate extension version
+    const expectedAppVersion = data.config?.appVersions?.vscode
+    if (expectedAppVersion) {
+      const extension = vscode.extensions.getExtension('coderoad.coderoad')
+      if (extension) {
+        const currentAppVersion = extension.packageJSON.version
+        const satisfied = satisfies(currentAppVersion, expectedAppVersion)
+        if (!satisfied) {
+          const error: E.ErrorMessage = {
+            type: 'UnmetExtensionVersion',
+            message: `Expected CodeRoad v${expectedAppVersion}, but found v${currentAppVersion}`,
+          }
+          send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
+          return
+        }
+      }
+    }
+
+    // setup tutorial config (save watcher, test runner, etc)
+    await context.setTutorial(workspaceState, data)
+
+    // validate dependencies
+    const dependencies = data.config.dependencies
+    if (dependencies && dependencies.length) {
+      for (const dep of dependencies) {
+        // check dependency is installed
+        const currentVersion: string | null = await version(dep.name)
+        if (!currentVersion) {
+          // use a custom error message
+          const error: E.ErrorMessage = {
+            type: 'MissingTutorialDependency',
+            message: dep.message || `Process "${dep.name}" is required but not found. It may need to be installed`,
+            actions: [
+              {
+                label: 'Check Again',
+                transition: 'TRY_AGAIN',
+              },
+            ],
+          }
+          send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
+          return
+        }
+
+        // check dependency version
+        const satisfiedDependency = await compareVersions(currentVersion, dep.version)
+
+        if (!satisfiedDependency) {
+          const error: E.ErrorMessage = {
+            type: 'UnmetTutorialDependency',
+            message: `Expected ${dep.name} to have version ${dep.version}, but found version ${currentVersion}`,
+            actions: [
+              {
+                label: 'Check Again',
+                transition: 'TRY_AGAIN',
+              },
+            ],
+          }
+          send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
+          return
+        }
+
+        if (satisfiedDependency !== true) {
+          const error: E.ErrorMessage = satisfiedDependency || {
+            type: 'UnknownError',
+            message: `Something went wrong comparing dependency for ${name}`,
+            actions: [
+              {
+                label: 'Try Again',
+                transition: 'TRY_AGAIN',
+              },
+            ],
+          }
+          send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
+          return
+        }
+      }
+    }
+
+    const error: E.ErrorMessage | void = await tutorialConfig({ data }).catch((error: Error) => ({
+      type: 'UnknownError',
+      message: `Location: tutorial config.\n\n${error.message}`,
+    }))
+
+    // has error
+    if (error && error.type) {
+      send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
+      return
+    }
+
+    // report back to the webview that setup is complete
+    send({ type: 'TUTORIAL_CONFIGURED' })
+  } catch (e) {
+    const error = {
+      type: 'UnknownError',
+      message: `Location: EditorTutorialConfig.\n\n ${e.message}`,
+    }
+    send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
+  }
+}
+
+export default onTutorialConfig
diff --git a/src/actions/tutorialConfig.ts b/src/actions/utils/tutorialConfig.ts
similarity index 93%
rename from src/actions/tutorialConfig.ts
rename to src/actions/utils/tutorialConfig.ts
index 5084d735..a4ef01d9 100644
--- a/src/actions/tutorialConfig.ts
+++ b/src/actions/utils/tutorialConfig.ts
@@ -1,9 +1,9 @@
 import * as E from 'typings/error'
 import * as TT from 'typings/tutorial'
 import * as vscode from 'vscode'
-import { COMMANDS } from '../commands'
-import * as git from '../services/git'
-import { DISABLE_RUN_ON_SAVE } from '../environment'
+import { COMMANDS } from '../../commands'
+import * as git from '../../services/git'
+import { DISABLE_RUN_ON_SAVE } from '../../environment'
 
 interface TutorialConfigParams {
   data: TT.Tutorial
diff --git a/src/channel.ts b/src/channel.ts
index 446ffd7d..16c11978 100644
--- a/src/channel.ts
+++ b/src/channel.ts
@@ -2,19 +2,18 @@ import * as T from 'typings'
 import * as TT from 'typings/tutorial'
 import * as E from 'typings/error'
 import * as vscode from 'vscode'
-import { satisfies } from 'semver'
 import { setupActions, solutionActions } from './actions/setupActions'
-import tutorialConfig from './actions/tutorialConfig'
+import tutorialConfig from './actions/utils/tutorialConfig'
 import { COMMANDS } from './commands'
 import Context from './services/context/context'
 import logger from './services/logger'
-import { version, compareVersions } from './services/dependencies'
+import { version } from './services/dependencies'
 import { openWorkspace, checkWorkspaceEmpty } from './services/workspace'
 import { showOutput } from './services/testRunner/output'
 import { exec } from './services/node'
 import reset from './services/reset'
 import getLastCommitHash from './services/reset/lastHash'
-import { onEvent } from './services/telemetry'
+
 import * as actions from './actions'
 
 interface Channel {
@@ -58,114 +57,7 @@ class Channel implements Channel {
         return
       // configure test runner, language, git
       case 'EDITOR_TUTORIAL_CONFIG':
-        try {
-          const data: TT.Tutorial = action.payload.tutorial
-
-          onEvent('tutorial_start', {
-            tutorial_id: data.id,
-            tutorial_version: data.version,
-            tutorial_title: data.summary.title,
-          })
-
-          // validate extension version
-          const expectedAppVersion = data.config?.appVersions?.vscode
-          if (expectedAppVersion) {
-            const extension = vscode.extensions.getExtension('coderoad.coderoad')
-            if (extension) {
-              const currentAppVersion = extension.packageJSON.version
-              const satisfied = satisfies(currentAppVersion, expectedAppVersion)
-              if (!satisfied) {
-                const error: E.ErrorMessage = {
-                  type: 'UnmetExtensionVersion',
-                  message: `Expected CodeRoad v${expectedAppVersion}, but found v${currentAppVersion}`,
-                }
-                this.send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
-                return
-              }
-            }
-          }
-
-          // setup tutorial config (save watcher, test runner, etc)
-          await this.context.setTutorial(this.workspaceState, data)
-
-          // validate dependencies
-          const dependencies = data.config.dependencies
-          if (dependencies && dependencies.length) {
-            for (const dep of dependencies) {
-              // check dependency is installed
-              const currentVersion: string | null = await version(dep.name)
-              if (!currentVersion) {
-                // use a custom error message
-                const error: E.ErrorMessage = {
-                  type: 'MissingTutorialDependency',
-                  message:
-                    dep.message || `Process "${dep.name}" is required but not found. It may need to be installed`,
-                  actions: [
-                    {
-                      label: 'Check Again',
-                      transition: 'TRY_AGAIN',
-                    },
-                  ],
-                }
-                this.send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
-                return
-              }
-
-              // check dependency version
-              const satisfiedDependency = await compareVersions(currentVersion, dep.version)
-
-              if (!satisfiedDependency) {
-                const error: E.ErrorMessage = {
-                  type: 'UnmetTutorialDependency',
-                  message: `Expected ${dep.name} to have version ${dep.version}, but found version ${currentVersion}`,
-                  actions: [
-                    {
-                      label: 'Check Again',
-                      transition: 'TRY_AGAIN',
-                    },
-                  ],
-                }
-                this.send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
-                return
-              }
-
-              if (satisfiedDependency !== true) {
-                const error: E.ErrorMessage = satisfiedDependency || {
-                  type: 'UnknownError',
-                  message: `Something went wrong comparing dependency for ${name}`,
-                  actions: [
-                    {
-                      label: 'Try Again',
-                      transition: 'TRY_AGAIN',
-                    },
-                  ],
-                }
-                this.send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
-                return
-              }
-            }
-          }
-
-          const error: E.ErrorMessage | void = await tutorialConfig({ data }).catch((error: Error) => ({
-            type: 'UnknownError',
-            message: `Location: tutorial config.\n\n${error.message}`,
-          }))
-
-          // has error
-          if (error && error.type) {
-            this.send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
-            return
-          }
-
-          // report back to the webview that setup is complete
-          this.send({ type: 'TUTORIAL_CONFIGURED' })
-        } catch (e) {
-          const error = {
-            type: 'UnknownError',
-            message: `Location: EditorTutorialConfig.\n\n ${e.message}`,
-          }
-          this.send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
-        }
+        actions.onTutorialConfig(action, this.context, this.workspaceState, this.send)
         return
       case 'EDITOR_TUTORIAL_CONTINUE_CONFIG':
         try {

From 15166d1ac544cf117796fc02cd9a96a0fb29aa2c Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 19 Jul 2020 21:16:49 -0700
Subject: [PATCH 5/8] refactor onTutorialContinueConfig

Signed-off-by: shmck <shawn.j.mckay@gmail.com>
---
 src/actions/index.ts                    |  1 +
 src/actions/onTutorialContinueConfig.ts | 29 +++++++++++++++++++++++++
 src/channel.ts                          | 20 +----------------
 3 files changed, 31 insertions(+), 19 deletions(-)
 create mode 100644 src/actions/onTutorialContinueConfig.ts

diff --git a/src/actions/index.ts b/src/actions/index.ts
index 3049b498..975765e3 100644
--- a/src/actions/index.ts
+++ b/src/actions/index.ts
@@ -1,4 +1,5 @@
 export { default as onStartup } from './onStartup'
 export { default as onTutorialConfig } from './onTutorialConfig'
+export { default as onTutorialContinueConfig } from './onTutorialContinueConfig'
 export { default as onErrorPage } from './onErrorPage'
 export { default as onTestPass } from './onTestPass'
diff --git a/src/actions/onTutorialContinueConfig.ts b/src/actions/onTutorialContinueConfig.ts
new file mode 100644
index 00000000..2610b14b
--- /dev/null
+++ b/src/actions/onTutorialContinueConfig.ts
@@ -0,0 +1,29 @@
+import * as vscode from 'vscode'
+import * as T from 'typings'
+import * as TT from 'typings/tutorial'
+import Context from '../services/context/context'
+import tutorialConfig from './utils/tutorialConfig'
+import { COMMANDS } from '../commands'
+
+const onTutorialContinueConfig = async (action: T.Action, context: Context, send: any) => {
+  try {
+    const tutorialContinue: TT.Tutorial | null = context.tutorial.get()
+    if (!tutorialContinue) {
+      throw new Error('Invalid tutorial to continue')
+    }
+    await tutorialConfig({
+      data: tutorialContinue,
+      alreadyConfigured: true,
+    })
+    // update the current stepId on startup
+    vscode.commands.executeCommand(COMMANDS.SET_CURRENT_POSITION, action.payload.position)
+  } catch (e) {
+    const error = {
+      type: 'UnknownError',
+      message: `Location: Editor tutorial continue config.\n\n ${e.message}`,
+    }
+    send({ type: 'CONTINUE_FAILED', payload: { error } })
+  }
+}
+
+export default onTutorialContinueConfig
diff --git a/src/channel.ts b/src/channel.ts
index 16c11978..d339df83 100644
--- a/src/channel.ts
+++ b/src/channel.ts
@@ -3,7 +3,6 @@ import * as TT from 'typings/tutorial'
 import * as E from 'typings/error'
 import * as vscode from 'vscode'
 import { setupActions, solutionActions } from './actions/setupActions'
-import tutorialConfig from './actions/utils/tutorialConfig'
 import { COMMANDS } from './commands'
 import Context from './services/context/context'
 import logger from './services/logger'
@@ -60,24 +59,7 @@ class Channel implements Channel {
         actions.onTutorialConfig(action, this.context, this.workspaceState, this.send)
         return
       case 'EDITOR_TUTORIAL_CONTINUE_CONFIG':
-        try {
-          const tutorialContinue: TT.Tutorial | null = this.context.tutorial.get()
-          if (!tutorialContinue) {
-            throw new Error('Invalid tutorial to continue')
-          }
-          await tutorialConfig({
-            data: tutorialContinue,
-            alreadyConfigured: true,
-          })
-          // update the current stepId on startup
-          vscode.commands.executeCommand(COMMANDS.SET_CURRENT_POSITION, action.payload.position)
-        } catch (e) {
-          const error = {
-            type: 'UnknownError',
-            message: `Location: Editor tutorial continue config.\n\n ${e.message}`,
-          }
-          this.send({ type: 'CONTINUE_FAILED', payload: { error } })
-        }
+        actions.onTutorialContinueConfig(action, this.context, this.send)
         return
       case 'EDITOR_VALIDATE_SETUP':
         try {

From 72ef62ca979a8f387a8358ff81eacb68666ce49e Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 19 Jul 2020 21:19:05 -0700
Subject: [PATCH 6/8] refactor onValidateSetup

Signed-off-by: shmck <shawn.j.mckay@gmail.com>
---
 src/actions/index.ts           |  1 +
 src/actions/onValidateSetup.ts | 54 ++++++++++++++++++++++++++++++++++
 src/channel.ts                 | 52 ++------------------------------
 3 files changed, 57 insertions(+), 50 deletions(-)
 create mode 100644 src/actions/onValidateSetup.ts

diff --git a/src/actions/index.ts b/src/actions/index.ts
index 975765e3..a2315d22 100644
--- a/src/actions/index.ts
+++ b/src/actions/index.ts
@@ -1,5 +1,6 @@
 export { default as onStartup } from './onStartup'
 export { default as onTutorialConfig } from './onTutorialConfig'
 export { default as onTutorialContinueConfig } from './onTutorialContinueConfig'
+export { default as onValidateSetup } from './onValidateSetup'
 export { default as onErrorPage } from './onErrorPage'
 export { default as onTestPass } from './onTestPass'
diff --git a/src/actions/onValidateSetup.ts b/src/actions/onValidateSetup.ts
new file mode 100644
index 00000000..90657e90
--- /dev/null
+++ b/src/actions/onValidateSetup.ts
@@ -0,0 +1,54 @@
+import * as E from 'typings/error'
+import { version } from '../services/dependencies'
+import { checkWorkspaceEmpty } from '../services/workspace'
+
+const onValidateSetup = async (send: any) => {
+  try {
+    // check workspace is selected
+    const isEmptyWorkspace = await checkWorkspaceEmpty()
+    if (!isEmptyWorkspace) {
+      const error: E.ErrorMessage = {
+        type: 'WorkspaceNotEmpty',
+        message: '',
+        actions: [
+          {
+            label: 'Open Workspace',
+            transition: 'REQUEST_WORKSPACE',
+          },
+          {
+            label: 'Check Again',
+            transition: 'RETRY',
+          },
+        ],
+      }
+      send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
+      return
+    }
+    // check Git is installed.
+    // Should wait for workspace before running otherwise requires access to root folder
+    const isGitInstalled = await version('git')
+    if (!isGitInstalled) {
+      const error: E.ErrorMessage = {
+        type: 'GitNotFound',
+        message: '',
+        actions: [
+          {
+            label: 'Check Again',
+            transition: 'RETRY',
+          },
+        ],
+      }
+      send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
+      return
+    }
+    send({ type: 'SETUP_VALIDATED' })
+  } catch (e) {
+    const error = {
+      type: 'UknownError',
+      message: e.message,
+    }
+    send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
+  }
+}
+
+export default onValidateSetup
diff --git a/src/channel.ts b/src/channel.ts
index d339df83..9ffe6926 100644
--- a/src/channel.ts
+++ b/src/channel.ts
@@ -1,13 +1,11 @@
 import * as T from 'typings'
 import * as TT from 'typings/tutorial'
-import * as E from 'typings/error'
 import * as vscode from 'vscode'
 import { setupActions, solutionActions } from './actions/setupActions'
 import { COMMANDS } from './commands'
 import Context from './services/context/context'
 import logger from './services/logger'
-import { version } from './services/dependencies'
-import { openWorkspace, checkWorkspaceEmpty } from './services/workspace'
+import { openWorkspace } from './services/workspace'
 import { showOutput } from './services/testRunner/output'
 import { exec } from './services/node'
 import reset from './services/reset'
@@ -48,7 +46,6 @@ class Channel implements Channel {
       case 'EDITOR_STARTUP':
         actions.onStartup(this.context, this.workspaceState, this.send)
         return
-
       // clear tutorial local storage
       case 'TUTORIAL_CLEAR':
         // clear current progress/position/tutorial
@@ -62,52 +59,7 @@ class Channel implements Channel {
         actions.onTutorialContinueConfig(action, this.context, this.send)
         return
       case 'EDITOR_VALIDATE_SETUP':
-        try {
-          // check workspace is selected
-          const isEmptyWorkspace = await checkWorkspaceEmpty()
-          if (!isEmptyWorkspace) {
-            const error: E.ErrorMessage = {
-              type: 'WorkspaceNotEmpty',
-              message: '',
-              actions: [
-                {
-                  label: 'Open Workspace',
-                  transition: 'REQUEST_WORKSPACE',
-                },
-                {
-                  label: 'Check Again',
-                  transition: 'RETRY',
-                },
-              ],
-            }
-            this.send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
-            return
-          }
-          // check Git is installed.
-          // Should wait for workspace before running otherwise requires access to root folder
-          const isGitInstalled = await version('git')
-          if (!isGitInstalled) {
-            const error: E.ErrorMessage = {
-              type: 'GitNotFound',
-              message: '',
-              actions: [
-                {
-                  label: 'Check Again',
-                  transition: 'RETRY',
-                },
-              ],
-            }
-            this.send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
-            return
-          }
-          this.send({ type: 'SETUP_VALIDATED' })
-        } catch (e) {
-          const error = {
-            type: 'UknownError',
-            message: e.message,
-          }
-          this.send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
-        }
+        actions.onValidateSetup(this.send)
         return
       case 'EDITOR_REQUEST_WORKSPACE':
         openWorkspace()

From a9d6561bff8bbdfb206c752e83f983e591db8b4d Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 19 Jul 2020 21:22:19 -0700
Subject: [PATCH 7/8] refactor onRunReset

Signed-off-by: shmck <shawn.j.mckay@gmail.com>
---
 src/actions/index.ts      |  1 +
 src/actions/onRunReset.ts | 32 ++++++++++++++++++++++++++++++++
 src/channel.ts            | 27 +--------------------------
 3 files changed, 34 insertions(+), 26 deletions(-)
 create mode 100644 src/actions/onRunReset.ts

diff --git a/src/actions/index.ts b/src/actions/index.ts
index a2315d22..a2774cb9 100644
--- a/src/actions/index.ts
+++ b/src/actions/index.ts
@@ -2,5 +2,6 @@ export { default as onStartup } from './onStartup'
 export { default as onTutorialConfig } from './onTutorialConfig'
 export { default as onTutorialContinueConfig } from './onTutorialContinueConfig'
 export { default as onValidateSetup } from './onValidateSetup'
+export { default as onRunReset } from './onRunReset'
 export { default as onErrorPage } from './onErrorPage'
 export { default as onTestPass } from './onTestPass'
diff --git a/src/actions/onRunReset.ts b/src/actions/onRunReset.ts
new file mode 100644
index 00000000..745c9123
--- /dev/null
+++ b/src/actions/onRunReset.ts
@@ -0,0 +1,32 @@
+import * as T from 'typings'
+import * as TT from 'typings/tutorial'
+import Context from '../services/context/context'
+import { exec } from '../services/node'
+import reset from '../services/reset'
+import getLastCommitHash from '../services/reset/lastHash'
+
+const onRunReset = async (context: Context) => {
+  // reset to timeline
+  const tutorial: TT.Tutorial | null = context.tutorial.get()
+  const position: T.Position = context.position.get()
+
+  // get last pass commit
+  const hash = getLastCommitHash(position, tutorial?.levels || [])
+
+  const branch = tutorial?.config.repo.branch
+
+  if (!branch) {
+    console.error('No repo branch found for tutorial')
+    return
+  }
+
+  // load timeline until last pass commit
+  reset({ branch, hash })
+
+  // if tutorial.config.reset.command, run it
+  if (tutorial?.config?.reset?.command) {
+    await exec({ command: tutorial.config.reset.command })
+  }
+}
+
+export default onRunReset
diff --git a/src/channel.ts b/src/channel.ts
index 9ffe6926..ce39af67 100644
--- a/src/channel.ts
+++ b/src/channel.ts
@@ -1,5 +1,4 @@
 import * as T from 'typings'
-import * as TT from 'typings/tutorial'
 import * as vscode from 'vscode'
 import { setupActions, solutionActions } from './actions/setupActions'
 import { COMMANDS } from './commands'
@@ -7,10 +6,6 @@ import Context from './services/context/context'
 import logger from './services/logger'
 import { openWorkspace } from './services/workspace'
 import { showOutput } from './services/testRunner/output'
-import { exec } from './services/node'
-import reset from './services/reset'
-import getLastCommitHash from './services/reset/lastHash'
-
 import * as actions from './actions'
 
 interface Channel {
@@ -88,27 +83,7 @@ class Channel implements Channel {
         vscode.commands.executeCommand(COMMANDS.RUN_TEST, action?.payload)
         return
       case 'EDITOR_RUN_RESET':
-        // reset to timeline
-        const tutorial: TT.Tutorial | null = this.context.tutorial.get()
-        const position: T.Position = this.context.position.get()
-
-        // get last pass commit
-        const hash = getLastCommitHash(position, tutorial?.levels || [])
-
-        const branch = tutorial?.config.repo.branch
-
-        if (!branch) {
-          console.error('No repo branch found for tutorial')
-          return
-        }
-
-        // load timeline until last pass commit
-        reset({ branch, hash })
-
-        // if tutorial.config.reset.command, run it
-        if (tutorial?.config?.reset?.command) {
-          await exec({ command: tutorial.config.reset.command })
-        }
+        actions.onRunReset(this.context)
         return
       default:
         logger(`No match for action type: ${actionType}`)

From 0ec09029d1bf233469802a327abfe4ac46e774e1 Mon Sep 17 00:00:00 2001
From: shmck <shawn.j.mckay@gmail.com>
Date: Sun, 19 Jul 2020 21:27:22 -0700
Subject: [PATCH 8/8] refactor setup/solution actions

Signed-off-by: shmck <shawn.j.mckay@gmail.com>
---
 src/actions/index.ts                          | 1 +
 src/actions/{setupActions.ts => onActions.ts} | 6 +++---
 src/channel.ts                                | 6 +++---
 src/commands.ts                               | 4 ++--
 4 files changed, 9 insertions(+), 8 deletions(-)
 rename src/actions/{setupActions.ts => onActions.ts} (85%)

diff --git a/src/actions/index.ts b/src/actions/index.ts
index a2774cb9..a4c88726 100644
--- a/src/actions/index.ts
+++ b/src/actions/index.ts
@@ -5,3 +5,4 @@ export { default as onValidateSetup } from './onValidateSetup'
 export { default as onRunReset } from './onRunReset'
 export { default as onErrorPage } from './onErrorPage'
 export { default as onTestPass } from './onTestPass'
+export { onSetupActions, onSolutionActions } from './onActions'
diff --git a/src/actions/setupActions.ts b/src/actions/onActions.ts
similarity index 85%
rename from src/actions/setupActions.ts
rename to src/actions/onActions.ts
index 21e74fb3..1e3d4a05 100644
--- a/src/actions/setupActions.ts
+++ b/src/actions/onActions.ts
@@ -13,7 +13,7 @@ interface SetupActions {
   dir?: string
 }
 
-export const setupActions = async ({ actions, send, dir }: SetupActions): Promise<void> => {
+export const onSetupActions = async ({ actions, send, dir }: SetupActions): Promise<void> => {
   if (!actions) {
     return
   }
@@ -49,7 +49,7 @@ export const setupActions = async ({ actions, send, dir }: SetupActions): Promis
   }
 }
 
-export const solutionActions = async (params: SetupActions): Promise<void> => {
+export const onSolutionActions = async (params: SetupActions): Promise<void> => {
   await git.clear()
-  return setupActions(params).catch(onError)
+  return onSetupActions(params).catch(onError)
 }
diff --git a/src/channel.ts b/src/channel.ts
index ce39af67..1a7b116d 100644
--- a/src/channel.ts
+++ b/src/channel.ts
@@ -1,6 +1,6 @@
 import * as T from 'typings'
 import * as vscode from 'vscode'
-import { setupActions, solutionActions } from './actions/setupActions'
+import { setupActions, solutionActions } from './actions/onActions'
 import { COMMANDS } from './commands'
 import Context from './services/context/context'
 import logger from './services/logger'
@@ -62,12 +62,12 @@ class Channel implements Channel {
       // load step actions (git commits, commands, open files)
       case 'SETUP_ACTIONS':
         await vscode.commands.executeCommand(COMMANDS.SET_CURRENT_POSITION, action.payload.position)
-        setupActions({ actions: action.payload.actions, send: this.send })
+        actions.onSetupActions({ actions: action.payload.actions, send: this.send })
         return
       // load solution step actions (git commits, commands, open files)
       case 'SOLUTION_ACTIONS':
         await vscode.commands.executeCommand(COMMANDS.SET_CURRENT_POSITION, action.payload.position)
-        await solutionActions({ actions: action.payload.actions, send: this.send })
+        await actions.onSolutionActions({ actions: action.payload.actions, send: this.send })
         // run test following solution to update position
         vscode.commands.executeCommand(COMMANDS.RUN_TEST)
         return
diff --git a/src/commands.ts b/src/commands.ts
index 3f9989d4..6b36ec62 100644
--- a/src/commands.ts
+++ b/src/commands.ts
@@ -2,7 +2,7 @@ import * as T from 'typings'
 import * as TT from 'typings/tutorial'
 import * as vscode from 'vscode'
 import createTestRunner from './services/testRunner'
-import { setupActions } from './actions/setupActions'
+import { onSetupActions } from './actions/onActions'
 import createWebView from './services/webview'
 import logger from './services/logger'
 
@@ -57,7 +57,7 @@ export const createCommands = ({ extensionPath, workspaceState }: CreateCommandP
       if (setup) {
         // setup tutorial test runner commits
         // assumes git already exists
-        await setupActions({
+        await onSetupActions({
           actions: setup,
           send: webview.send,
           dir: testRunnerConfig.directory || testRunnerConfig.path,