Skip to content

Feature/hooks #417

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ export { default as onValidateSetup } from './onValidateSetup'
export { default as onRunReset } from './onRunReset'
export { default as onErrorPage } from './onErrorPage'
export { onRunTest, onTestPass } from './onTest'
export { onSetupActions, onSolutionActions } from './onActions'
export { onOpenLogs } from './onOpenLogs'
55 changes: 0 additions & 55 deletions src/actions/onActions.ts

This file was deleted.

7 changes: 3 additions & 4 deletions src/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Context from './services/context/context'
import logger from './services/logger'
import { openWorkspace } from './services/workspace'
import * as actions from './actions'
import * as hooks from './services/hooks'

interface Channel {
receive(action: T.Action): Promise<void>
Expand Down Expand Up @@ -56,14 +57,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)
actions.onSetupActions({ actions: action.payload.actions, send: this.send })
hooks.onSetupEnter(action.payload.actions)
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 actions.onSolutionActions({ actions: action.payload.actions, send: this.send })
// run test following solution to update position
actions.onRunTest()
hooks.onSolutionEnter(action.payload.actions)
return
case 'EDITOR_SYNC_POSITION':
// update progress when a level is deemed complete in the client
Expand Down
40 changes: 23 additions & 17 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import * as T from 'typings'
import * as TT from 'typings/tutorial'
import * as vscode from 'vscode'
import createTestRunner from './services/testRunner'
import { onSetupActions } from './actions/onActions'
import createWebView from './services/webview'
import logger from './services/logger'
import * as hooks from './services/hooks'

export const COMMANDS = {
START: 'coderoad.start',
Expand All @@ -19,6 +19,16 @@ interface CreateCommandProps {
workspaceState: vscode.Memento
}

let sendToClient = (action: T.Action): void => {
// function is replaced when webclient loads
}

// This makes it easier to pass the send
// function throughout the codebase
export const send = (action: T.Action): void => {
sendToClient(action)
}

export const createCommands = ({ extensionPath, workspaceState }: CreateCommandProps): { [key: string]: any } => {
// React panel webview
let webview: any
Expand All @@ -36,41 +46,37 @@ export const createCommands = ({ extensionPath, workspaceState }: CreateCommandP
extensionPath,
workspaceState,
})
// make send to client function exportable
// as "send".
sendToClient = webview.send
}
},
[COMMANDS.CONFIG_TEST_RUNNER]: async (data: TT.Tutorial) => {
const testRunnerConfig = data.config.testRunner
const setup = testRunnerConfig.setup || testRunnerConfig.actions // TODO: deprecate and remove config.actions
if (setup) {
// setup tutorial test runner commits
// assumes git already exists
await onSetupActions({
actions: setup,
send: webview.send,
dir: testRunnerConfig.directory || testRunnerConfig.path,
}) // TODO: deprecate and remove config.path
const setupActions = data.config.setup
if (setupActions) {
hooks.onInit(setupActions)
}
testRunner = createTestRunner(data, {
onSuccess: (position: T.Position) => {
logger('test pass position', position)
// send test pass message back to client
webview.send({ type: 'TEST_PASS', payload: { position: { ...position, complete: true } } })
send({ type: 'TEST_PASS', payload: { position: { ...position, complete: true } } })
},
onFail: (position: T.Position, failSummary: T.TestFail): void => {
// send test fail message back to client with failure message
webview.send({ type: 'TEST_FAIL', payload: { position, fail: failSummary } })
send({ type: 'TEST_FAIL', payload: { position, fail: failSummary } })
},
onError: (position: T.Position) => {
// TODO: send test error message back to client
const message = 'Error with test runner'
webview.send({ type: 'TEST_ERROR', payload: { position, message } })
send({ type: 'TEST_ERROR', payload: { position, message } })
},
onRun: (position: T.Position) => {
// send test run message back to client
webview.send({ type: 'TEST_RUNNING', payload: { position } })
send({ type: 'TEST_RUNNING', payload: { position } })
},
onLoadSubtasks: ({ summary }) => {
webview.send({ type: 'LOAD_SUBTASK_RESULTS', payload: { summary } })
send({ type: 'LOAD_SUBTASK_RESULTS', payload: { summary } })
},
})
},
Expand All @@ -85,7 +91,7 @@ export const createCommands = ({ extensionPath, workspaceState }: CreateCommandP
testRunner({ position: currentPosition, onSuccess: callbacks?.onSuccess, subtasks })
},
[COMMANDS.ENTER]: () => {
webview.send({ type: 'KEY_PRESS_ENTER' })
send({ type: 'KEY_PRESS_ENTER' })
},
}
}
43 changes: 43 additions & 0 deletions src/services/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as TT from 'typings/tutorial'
import * as git from '../git'
import loadCommits from './utils/loadCommits'
import loadWatchers from './utils/loadWatchers'
import openFiles from './utils/openFiles'
import runCommands from './utils/runCommands'
import runVSCodeCommands from './utils/runVSCodeCommands'
import { onError as telemetryOnError } from '../telemetry'
import { onRunTest } from '../../actions/onTest'

export const onInit = async (actions: TT.StepActions): Promise<void> => {
await loadCommits(actions.commits)
await runCommands(actions.commands)
await runVSCodeCommands(actions.vscodeCommands)
}

export const onLevelEnter = async (actions: TT.StepActions): Promise<void> => {
await loadCommits(actions.commits)
await runCommands(actions.commands)
}

export const onSetupEnter = async (actions: TT.StepActions): Promise<void> => {
// TODO: set position
await loadCommits(actions.commits)
await openFiles(actions.files)
await loadWatchers(actions.watchers)
await runCommands(actions.commands)
await runVSCodeCommands(actions.vscodeCommands)
}

export const onSolutionEnter = async (actions: TT.StepActions): Promise<void> => {
// TODO: set position
await git.clear()
await loadCommits(actions.commits)
await openFiles(actions.files)
await runCommands(actions.commands)
await runVSCodeCommands(actions.vscodeCommands)
await onRunTest()
}

export const onError = async (error: Error): Promise<void> => {
telemetryOnError(error)
}
12 changes: 12 additions & 0 deletions src/services/hooks/utils/loadCommits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as git from '../../git'

const loadCommits = async (commits: string[]): Promise<void> => {
if (commits) {
// load the current list of commits for validation
for (const commit of commits) {
await git.loadCommit(commit)
}
}
}

export default loadCommits
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as chokidar from 'chokidar'
import * as vscode from 'vscode'
import { COMMANDS } from '../../commands'
import { WORKSPACE_ROOT } from '../../environment'
import { COMMANDS } from '../../../commands'
import { WORKSPACE_ROOT } from '../../../environment'

// NOTE: vscode createFileWatcher doesn't seem to detect changes outside of vscode
// such as `npm install` of a package. Went with chokidar instead
Expand All @@ -14,7 +14,7 @@ const disposeWatcher = (watcher: string) => {
delete watcherObject[watcher]
}

const loadWatchers = (watchers: string[]): void => {
const loadWatchers = (watchers: string[] = []): void => {
if (!watchers.length) {
// remove all watchers
for (const watcher of Object.keys(watcherObject)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { join } from 'path'
import * as vscode from 'vscode'
import { COMMANDS } from '../../commands'

const openFiles = async (files: string[]): Promise<void> => {
const openFiles = async (files: string[] = []): Promise<void> => {
if (!files.length) {
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import * as T from 'typings'
import { exec } from '../../services/node'
import { exec } from '../../node'
import { send } from '../../../commands'

interface RunCommands {
commands: string[]
send: (action: T.Action) => void
dir?: string
}

const runCommands = async ({ commands, send, dir }: RunCommands): Promise<void> => {
const runCommands = async (commands: string[] = []): Promise<void> => {
if (!commands.length) {
return
}
Expand All @@ -19,10 +13,10 @@ const runCommands = async ({ commands, send, dir }: RunCommands): Promise<void>
send({ type: 'COMMAND_START', payload: { process: { ...process, status: 'RUNNING' } } })
let result: { stdout: string; stderr: string }
try {
result = await exec({ command, dir })
result = await exec({ command })
console.log(result)
} catch (error) {
console.log(`Test failed: ${error.message}`)
console.error(`Command failed: ${error.message}`)
send({ type: 'COMMAND_FAIL', payload: { process: { ...process, status: 'FAIL' } } })
return
}
Expand Down
26 changes: 26 additions & 0 deletions src/services/hooks/utils/runVSCodeCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as vscode from 'vscode'
import * as TT from 'typings/tutorial'

// what are VSCode commands?
// - https://code.visualstudio.com/api/references/vscode-api#commands
// a list of commands:
// - https://code.visualstudio.com/api/references/commands (note many take params)
// - https://code.visualstudio.com/docs/getstarted/keybindings (anything keybound is a command)

const runVSCodeCommands = async (commands: TT.VSCodeCommand[] = []): Promise<void> => {
if (!commands.length) {
return
}
for (const command of commands) {
if (typeof command === 'string') {
// string named commands
await vscode.commands.executeCommand(command)
} else if (Array.isArray(command)) {
// array commands with params
const [name, params] = command
await vscode.commands.executeCommand(name, params)
}
}
}

export default runVSCodeCommands
7 changes: 3 additions & 4 deletions src/services/reset/lastHash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,9 @@ describe('lastHash', () => {
const tutorial: TT.Tutorial = {
config: {
// @ts-ignore
testRunner: {
setup: {
commits: ['abcdef2', 'abcdef3'],
},
testRunner: {},
setup: {
commits: ['abcdef2', 'abcdef3'],
},
},
levels: [
Expand Down
2 changes: 1 addition & 1 deletion src/services/reset/lastHash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const getLastCommitHash = (position: T.Position, tutorial: TT.Tutorial | null):
level = levels[levelIndex - 1]
} else {
// use init commit
const configCommits = tutorial.config.testRunner.setup?.commits
const configCommits = tutorial.config.setup?.commits
if (!configCommits) {
throw new Error('No commits found to reset back to')
}
Expand Down
2 changes: 1 addition & 1 deletion src/services/testRunner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const createTestRunner = (data: TT.Tutorial, callbacks: Callbacks): ((params: an
}
}
logger('COMMAND', command)
result = await exec({ command, dir: testRunnerConfig.directory || testRunnerConfig.path }) // TODO: remove config.path later
result = await exec({ command, dir: testRunnerConfig.directory })
} catch (err) {
result = { stdout: err.stdout, stderr: err.stack }
}
Expand Down
2 changes: 1 addition & 1 deletion src/services/webview/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const getNonce = (): string => {
return text
}

async function render(panel: vscode.WebviewPanel, rootPath: string) {
async function render(panel: vscode.WebviewPanel, rootPath: string): Promise<void> {
try {
// load copied index.html from web app build
const dom = await JSDOM.fromFile(path.join(rootPath, 'index.html'))
Expand Down
7 changes: 4 additions & 3 deletions typings/tutorial.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type TutorialConfig = {
testRunner: TestRunnerConfig
repo: TutorialRepo
dependencies?: TutorialDependency[]
setup?: StepActions
reset?: ConfigReset
}

Expand Down Expand Up @@ -60,6 +61,7 @@ export type StepActions = {
files?: string[]
watchers?: string[]
filter?: string
vscodeCommands?: VSCodeCommand[]
}

export interface TestRunnerArgs {
Expand All @@ -70,10 +72,7 @@ export interface TestRunnerArgs {
export interface TestRunnerConfig {
command: string
args: TestRunnerArgs
path?: string // deprecated
directory?: string
actions?: StepActions // deprecated
setup?: StepActions
}

export interface TutorialRepo {
Expand All @@ -90,3 +89,5 @@ export interface TutorialDependency {
export interface TutorialAppVersions {
vscode: string
}

export type VSCodeCommand = string | [string, any]