Skip to content

Fix/cr fail on startup #249

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 3 commits into from
Apr 12, 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
3 changes: 3 additions & 0 deletions errors/NoWorkspaceFound.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Open a Workspace Folder

CodeRoad requires a workspace folder to run. Open a new workspace and re-launch CodeRoad.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "coderoad",
"version": "0.2.1",
"version": "0.2.2",
"description": "Play interactive coding tutorials in your editor",
"keywords": [
"tutorial",
Expand All @@ -26,7 +26,6 @@
"scripts": {
"build": "./scripts/build.sh",
"postinstall": "node ./node_modules/vscode/bin/install",
"publish": "vsce publish -p $PERSONAL_ACCESS_TOKEN --packagePath ./releases/coderoad-$npm_package_version.vsix --baseContentUrl https://github.com/coderoad/coderoad-vscode/blob/master --baseImagesUrl https://github.com/coderoad/coderoad-vscode/blob/master",
"lint": "eslint src/**/*ts",
"package": "./scripts/package.sh",
"storybook": "cd web-app && npm run storybook",
Expand Down
4 changes: 1 addition & 3 deletions src/actions/setupActions.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import * as T from 'typings'
import * as TT from 'typings/tutorial'
import * as vscode from 'vscode'
import * as git from '../services/git'
import loadWatchers from './utils/loadWatchers'
import openFiles from './utils/openFiles'
import runCommands from './utils/runCommands'
import onError from '../services/sentry/onError'

const setupActions = async (
workspaceRoot: vscode.WorkspaceFolder,
actions: TT.StepActions,
send: (action: T.Action) => void, // send messages to client
): Promise<void> => {
Expand All @@ -26,7 +24,7 @@ const setupActions = async (
openFiles(files || [])

// 3. start file watchers
loadWatchers(watchers || [], workspaceRoot.uri)
loadWatchers(watchers || [])

// 4. run command
await runCommands(commands || [], send).catch(onError)
Expand Down
9 changes: 2 additions & 7 deletions src/actions/solutionActions.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import * as T from 'typings'
import * as TT from 'typings/tutorial'
import * as vscode from 'vscode'
import * as git from '../services/git'
import setupActions from './setupActions'
import onError from '../services/sentry/onError'

const solutionActions = async (
workspaceRoot: vscode.WorkspaceFolder,
stepActions: TT.StepActions,
send: (action: T.Action) => void,
): Promise<void> => {
const solutionActions = async (stepActions: TT.StepActions, send: (action: T.Action) => void): Promise<void> => {
await git.clear()
return setupActions(workspaceRoot, stepActions, send).catch(onError)
return setupActions(stepActions, send).catch(onError)
}

export default solutionActions
10 changes: 3 additions & 7 deletions src/actions/utils/loadWatchers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as chokidar from 'chokidar'
import * as vscode from 'vscode'
import { COMMANDS } from '../../editor/commands'
import environment 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 @@ -13,7 +14,7 @@ const disposeWatcher = (watcher: string) => {
delete watcherObject[watcher]
}

const loadWatchers = (watchers: string[], workspaceUri: vscode.Uri) => {
const loadWatchers = (watchers: string[]) => {
if (!watchers.length) {
// remove all watchers
for (const watcher of Object.keys(watcherObject)) {
Expand All @@ -24,13 +25,8 @@ const loadWatchers = (watchers: string[], workspaceUri: vscode.Uri) => {
if (!watcherObject[watcher]) {
// see how glob patterns are used in VSCode (not like a regex)
// https://code.visualstudio.com/api/references/vscode-api#GlobPattern
const rootUri = vscode.workspace.getWorkspaceFolder(workspaceUri)
if (!rootUri) {
return
}

const fsWatcher: chokidar.FSWatcher = chokidar.watch(watcher, {
cwd: rootUri.uri.path,
cwd: environment.WORKSPACE_ROOT,
interval: 1000,
})

Expand Down
4 changes: 2 additions & 2 deletions src/actions/utils/runCommands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as T from 'typings'
import node from '../../services/node'
import { exec } from '../../services/node'

const runCommands = async (commands: string[], send: (action: T.Action) => void) => {
if (!commands.length) {
Expand All @@ -13,7 +13,7 @@ const runCommands = async (commands: string[], send: (action: T.Action) => void)
send({ type: 'COMMAND_START', payload: { process: { ...process, status: 'RUNNING' } } })
let result: { stdout: string; stderr: string }
try {
result = await node.exec(command)
result = await exec(command)
} catch (error) {
console.log(error)
send({ type: 'COMMAND_FAIL', payload: { process: { ...process, status: 'FAIL' } } })
Expand Down
33 changes: 23 additions & 10 deletions src/channel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { openWorkspace, checkWorkspaceEmpty } from '../services/workspace'
import { readFile } from 'fs'
import { join } from 'path'
import { promisify } from 'util'
import { compare } from 'semver'
import environment from '../environment'

const readFileAsync = promisify(readFile)

Expand All @@ -26,18 +26,15 @@ interface Channel {
interface ChannelProps {
postMessage: (action: T.Action) => Thenable<boolean>
workspaceState: vscode.Memento
workspaceRoot: vscode.WorkspaceFolder
}

class Channel implements Channel {
private postMessage: (action: T.Action) => Thenable<boolean>
private workspaceState: vscode.Memento
private workspaceRoot: vscode.WorkspaceFolder
private context: Context
constructor({ postMessage, workspaceState, workspaceRoot }: ChannelProps) {
constructor({ postMessage, workspaceState }: ChannelProps) {
// workspaceState used for local storage
this.workspaceState = workspaceState
this.workspaceRoot = workspaceRoot
this.postMessage = postMessage
this.context = new Context(workspaceState)
}
Expand All @@ -52,6 +49,22 @@ class Channel implements Channel {

switch (actionType) {
case 'EDITOR_ENV_GET':
// check if a workspace is open, otherwise nothing works
const noActiveWorksapce = !environment.WORKSPACE_ROOT.length
if (noActiveWorksapce) {
const error: E.ErrorMessage = {
type: 'NoWorkspaceFound',
message: '',
actions: [
{
label: 'Open Workspace',
transition: 'REQUEST_WORKSPACE',
},
],
}
this.send({ type: 'NO_WORKSPACE', payload: { error } })
return
}
this.send({
type: 'ENV_LOAD',
payload: {
Expand Down Expand Up @@ -180,8 +193,8 @@ class Channel implements Channel {
vscode.commands.executeCommand(COMMANDS.SET_CURRENT_STEP, action.payload)
return
case 'EDITOR_VALIDATE_SETUP':
// 1. check workspace is selected
const isEmptyWorkspace = await checkWorkspaceEmpty(this.workspaceRoot.uri.path)
// check workspace is selected
const isEmptyWorkspace = await checkWorkspaceEmpty()
if (!isEmptyWorkspace) {
const error: E.ErrorMessage = {
type: 'WorkspaceNotEmpty',
Expand All @@ -200,7 +213,7 @@ class Channel implements Channel {
this.send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
return
}
// 2. check Git is installed.
// check Git is installed.
// Should wait for workspace before running otherwise requires access to root folder
const isGitInstalled = await version('git')
if (!isGitInstalled) {
Expand All @@ -225,11 +238,11 @@ class Channel implements Channel {
// load step actions (git commits, commands, open files)
case 'SETUP_ACTIONS':
await vscode.commands.executeCommand(COMMANDS.SET_CURRENT_STEP, action.payload)
setupActions(this.workspaceRoot, action.payload, this.send)
setupActions(action.payload, this.send)
return
// load solution step actions (git commits, commands, open files)
case 'SOLUTION_ACTIONS':
await solutionActions(this.workspaceRoot, action.payload, this.send)
await solutionActions(action.payload, this.send)
// run test following solution to update position
vscode.commands.executeCommand(COMMANDS.RUN_TEST, action.payload)
return
Expand Down
4 changes: 1 addition & 3 deletions src/editor/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ export const COMMANDS = {
interface CreateCommandProps {
extensionPath: string
workspaceState: vscode.Memento
workspaceRoot: vscode.WorkspaceFolder
}

export const createCommands = ({ extensionPath, workspaceState, workspaceRoot }: CreateCommandProps) => {
export const createCommands = ({ extensionPath, workspaceState }: CreateCommandProps) => {
// React panel webview
let webview: any
let currentStepId = ''
Expand All @@ -41,7 +40,6 @@ export const createCommands = ({ extensionPath, workspaceState, workspaceRoot }:
webview = createWebView({
extensionPath,
workspaceState,
workspaceRoot,
})
},
// open React webview
Expand Down
8 changes: 0 additions & 8 deletions src/editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,10 @@ class Editor {
}

private activateCommands = (): void => {
// set workspace root for node executions
const workspaceRoots: vscode.WorkspaceFolder[] | undefined = vscode.workspace.workspaceFolders
if (!workspaceRoots || !workspaceRoots.length) {
throw new Error('No workspace root path')
}
const workspaceRoot: vscode.WorkspaceFolder = workspaceRoots[0]

const commands = createCommands({
extensionPath: this.vscodeExt.extensionPath,
// NOTE: local storage must be bound to the vscodeExt.workspaceState
workspaceState: this.vscodeExt.workspaceState,
workspaceRoot,
})

// register commands
Expand Down
5 changes: 5 additions & 0 deletions src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ require('dotenv').config({
path: './web-app/.env',
})

import * as vscode from 'vscode'
import { getWorkspaceRoot } from './services/workspace'

interface Environment {
VERSION: string
NODE_ENV: string
LOG: boolean
API_URL: string
SENTRY_DSN: string | null
WORKSPACE_ROOT: string
}

const environment: Environment = {
Expand All @@ -16,6 +20,7 @@ const environment: Environment = {
LOG: (process.env.LOG || '').toLowerCase() === 'true',
API_URL: process.env.REACT_APP_GQL_URI || '',
SENTRY_DSN: process.env.SENTRY_DSN || null,
WORKSPACE_ROOT: getWorkspaceRoot(),
}

export default environment
4 changes: 2 additions & 2 deletions src/services/dependencies/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { satisfies } from 'semver'
import node from '../node'
import { exec } from '../node'

const semverRegex = /(?<=^v?|\sv?)(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|[\da-z-]*[a-z-][\da-z-]*)(?:\.(?:0|[1-9]\d*|[\da-z-]*[a-z-][\da-z-]*))*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?(?=$|\s)/gi

export const version = async (name: string): Promise<string | null> => {
try {
const { stdout, stderr } = await node.exec(`${name} --version`)
const { stdout, stderr } = await exec(`${name} --version`)
if (!stderr) {
const match = stdout.match(semverRegex)
if (match) {
Expand Down
22 changes: 11 additions & 11 deletions src/services/git/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as TT from 'typings/tutorial'
import node from '../node'
import { exec, exists } from '../node'
import logger from '../logger'

const gitOrigin = 'coderoad'

const stashAllFiles = async (): Promise<never | void> => {
// stash files including untracked (eg. newly created file)
const { stdout, stderr } = await node.exec(`git stash --include-untracked`)
const { stdout, stderr } = await exec(`git stash --include-untracked`)
if (stderr) {
console.error(stderr)
throw new Error('Error stashing files')
Expand All @@ -21,7 +21,7 @@ const cherryPickCommit = async (commit: string, count = 0): Promise<never | void
try {
// cherry-pick pulls commits from another branch
// -X theirs merges and accepts incoming changes over existing changes
const { stdout } = await node.exec(`git cherry-pick -X theirs ${commit}`)
const { stdout } = await exec(`git cherry-pick -X theirs ${commit}`)
if (!stdout) {
throw new Error('No cherry-pick output')
}
Expand All @@ -47,7 +47,7 @@ export function loadCommit(commit: string): Promise<never | void> {
*/

export async function saveCommit(message: string): Promise<never | void> {
const { stdout, stderr } = await node.exec(`git commit -am '${message}'`)
const { stdout, stderr } = await exec(`git commit -am '${message}'`)
if (stderr) {
console.error(stderr)
throw new Error('Error saving progress to Git')
Expand All @@ -58,7 +58,7 @@ export async function saveCommit(message: string): Promise<never | void> {
export async function clear(): Promise<Error | void> {
try {
// commit progress to git
const { stderr } = await node.exec('git reset HEAD --hard && git clean -fd')
const { stderr } = await exec('git reset HEAD --hard && git clean -fd')
if (!stderr) {
return
}
Expand All @@ -70,28 +70,28 @@ export async function clear(): Promise<Error | void> {
}

async function init(): Promise<Error | void> {
const { stderr } = await node.exec('git init')
const { stderr } = await exec('git init')
if (stderr) {
throw new Error('Error initializing Git')
}
}

export async function initIfNotExists(): Promise<never | void> {
const hasGitInit = node.exists('.git')
const hasGitInit = exists('.git')
if (!hasGitInit) {
await init()
}
}

export async function checkRemoteConnects(repo: TT.TutorialRepo): Promise<never | void> {
// check for git repo
const externalRepoExists = await node.exec(`git ls-remote --exit-code --heads ${repo.uri}`)
const externalRepoExists = await exec(`git ls-remote --exit-code --heads ${repo.uri}`)
if (externalRepoExists.stderr) {
// no repo found or no internet connection
throw new Error(externalRepoExists.stderr)
}
// check for git repo branch
const { stderr, stdout } = await node.exec(`git ls-remote --exit-code --heads ${repo.uri} ${repo.branch}`)
const { stderr, stdout } = await exec(`git ls-remote --exit-code --heads ${repo.uri} ${repo.branch}`)
if (stderr) {
throw new Error(stderr)
}
Expand All @@ -101,7 +101,7 @@ export async function checkRemoteConnects(repo: TT.TutorialRepo): Promise<never
}

export async function addRemote(repo: string): Promise<never | void> {
const { stderr } = await node.exec(`git remote add ${gitOrigin} ${repo} && git fetch ${gitOrigin}`)
const { stderr } = await exec(`git remote add ${gitOrigin} ${repo} && git fetch ${gitOrigin}`)
if (stderr) {
const alreadyExists = stderr.match(`${gitOrigin} already exists.`)
const successfulNewBranch = stderr.match('new branch')
Expand All @@ -116,7 +116,7 @@ export async function addRemote(repo: string): Promise<never | void> {

export async function checkRemoteExists(): Promise<boolean> {
try {
const { stdout, stderr } = await node.exec('git remote -v')
const { stdout, stderr } = await exec('git remote -v')
if (stderr) {
return false
}
Expand Down
Loading