Skip to content

Commit 3fe89f0

Browse files
authored
Merge pull request #236 from coderoad/fix/check-git-remote
Fix/check git remote
2 parents 4291060 + 3bbf2be commit 3fe89f0

21 files changed

+255
-190
lines changed

Diff for: errors/FailedToConnectToGitRepo.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
### Failed to Connect to Git Repo
2+
3+
There are several possible causes:
4+
5+
- you may not be connected to the internet or have an unstable connection.
6+
- you may not have access permission to the remote tutorial repo.
7+
- the remote tutorial repo may not exist at the provided location

Diff for: errors/GitNotFound.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Git Not Found
2+
3+
Make sure you install Git. See the [Git docs](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) for help.

Diff for: errors/GitProjectAlreadyExists.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### Git Project Already Exists
2+
3+
CodeRoad requires an empty Git project.
4+
5+
Open a new workspace to start a tutorial.

Diff for: errors/UnknownError.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### Unknown Error
2+
3+
Sorry! An unknown error occurred.
4+
5+
Please help out by posting an issue at github.com/coderoad/coderoad-vscode/issues/new/choose!

Diff for: errors/WorkspaceNotEmpty.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### Select An Empty VSCode Workspace
2+
3+
Start a project in an empty folder.
4+
5+
Once selected, the extension will close and need to be re-started.

Diff for: src/actions/tutorialConfig.ts

+36-19
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,54 @@
1-
import * as T from 'typings'
1+
import * as E from 'typings/error'
22
import * as TT from 'typings/tutorial'
33
import * as vscode from 'vscode'
44
import { COMMANDS } from '../editor/commands'
55
import * as git from '../services/git'
6-
import onError from '../services/sentry/onError'
76

87
interface TutorialConfigParams {
98
config: TT.TutorialConfig
109
alreadyConfigured?: boolean
1110
onComplete?(): void
1211
}
1312

14-
const tutorialConfig = async (
15-
{ config, alreadyConfigured }: TutorialConfigParams,
16-
handleError: (msg: T.ErrorMessage) => void,
17-
) => {
13+
const tutorialConfig = async ({ config, alreadyConfigured }: TutorialConfigParams): Promise<E.ErrorMessage | void> => {
1814
if (!alreadyConfigured) {
1915
// setup git, add remote
20-
await git.initIfNotExists().catch((error) => {
21-
onError(new Error('Git not found'))
22-
// failed to setup git
23-
handleError({
24-
title: error.message,
25-
description:
26-
'Make sure you install Git. See the docs for help https://git-scm.com/book/en/v2/Getting-Started-Installing-Git',
27-
})
28-
})
16+
const initError: E.ErrorMessage | void = await git.initIfNotExists().catch(
17+
(error: Error): E.ErrorMessage => ({
18+
type: 'GitNotFound',
19+
message: error.message,
20+
actions: [{ label: 'Retry', transition: '' }],
21+
}),
22+
)
23+
24+
if (initError) {
25+
return initError
26+
}
27+
28+
// verify that internet is connected, remote exists and branch exists
29+
const remoteConnectError: E.ErrorMessage | void = await git.checkRemoteConnects(config.repo).catch(
30+
(error: Error): E.ErrorMessage => ({
31+
type: 'FailedToConnectToGitRepo',
32+
message: error.message,
33+
actions: [{ label: 'Retry', transition: '' }],
34+
}),
35+
)
36+
37+
if (remoteConnectError) {
38+
return remoteConnectError
39+
}
2940

3041
// TODO if remote not already set
31-
await git.setupRemote(config.repo.uri).catch((error) => {
32-
onError(error)
33-
handleError({ title: error.message, description: 'Remove your current Git project and reload the editor' })
34-
})
42+
const coderoadRemoteError: E.ErrorMessage | void = await git.setupCodeRoadRemote(config.repo.uri).catch(
43+
(error: Error): E.ErrorMessage => ({
44+
type: 'GitRemoteAlreadyExists',
45+
message: error.message,
46+
}),
47+
)
48+
49+
if (coderoadRemoteError) {
50+
return coderoadRemoteError
51+
}
3552
}
3653

3754
await vscode.commands.executeCommand(COMMANDS.CONFIG_TEST_RUNNER, config.testRunner)

Diff for: src/channel/index.ts

+70-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as T from 'typings'
22
import * as TT from 'typings/tutorial'
3+
import * as E from 'typings/error'
34
import * as vscode from 'vscode'
45
import saveCommit from '../actions/saveCommit'
56
import setupActions from '../actions/setupActions'
@@ -10,6 +11,11 @@ import logger from '../services/logger'
1011
import Context from './context'
1112
import { version as gitVersion } from '../services/git'
1213
import { openWorkspace, checkWorkspaceEmpty } from '../services/workspace'
14+
import { readFile } from 'fs'
15+
import { join } from 'path'
16+
import { promisify } from 'util'
17+
18+
const readFileAsync = promisify(readFile)
1319

1420
interface Channel {
1521
receive(action: T.Action): Promise<void>
@@ -39,7 +45,9 @@ class Channel implements Channel {
3945
public receive = async (action: T.Action) => {
4046
// action may be an object.type or plain string
4147
const actionType: string = typeof action === 'string' ? action : action.type
42-
const onError = (error: T.ErrorMessage) => this.send({ type: 'ERROR', payload: { error } })
48+
// const onError = (error: T.ErrorMessage) => this.send({ type: 'ERROR', payload: { error } })
49+
50+
// console.log(`ACTION: ${actionType}`)
4351

4452
switch (actionType) {
4553
case 'EDITOR_ENV_GET':
@@ -86,7 +94,16 @@ class Channel implements Channel {
8694
// setup tutorial config (save watcher, test runner, etc)
8795
await this.context.setTutorial(this.workspaceState, data)
8896

89-
await tutorialConfig({ config: data.config }, onError)
97+
const error: E.ErrorMessage | void = await tutorialConfig({ config: data.config }).catch((error: Error) => ({
98+
type: 'UnknownError',
99+
message: `Location: tutorial config.\n\n${error.message}`,
100+
}))
101+
102+
// has error
103+
if (error && error.type) {
104+
this.send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
105+
return
106+
}
90107

91108
// report back to the webview that setup is complete
92109
this.send({ type: 'TUTORIAL_CONFIGURED' })
@@ -97,34 +114,54 @@ class Channel implements Channel {
97114
throw new Error('Invalid tutorial to continue')
98115
}
99116
const continueConfig: TT.TutorialConfig = tutorialContinue.config
100-
await tutorialConfig(
101-
{
102-
config: continueConfig,
103-
alreadyConfigured: true,
104-
},
105-
onError,
106-
)
117+
await tutorialConfig({
118+
config: continueConfig,
119+
alreadyConfigured: true,
120+
})
107121
// update the current stepId on startup
108122
vscode.commands.executeCommand(COMMANDS.SET_CURRENT_STEP, action.payload)
109123
return
110124
case 'EDITOR_VALIDATE_SETUP':
111125
// 1. check workspace is selected
112126
const isEmptyWorkspace = await checkWorkspaceEmpty(this.workspaceRoot.uri.path)
113127
if (!isEmptyWorkspace) {
114-
this.send({ type: 'NOT_EMPTY_WORKSPACE' })
128+
const error: E.ErrorMessage = {
129+
type: 'WorkspaceNotEmpty',
130+
message: '',
131+
actions: [
132+
{
133+
label: 'Open Workspace',
134+
transition: 'REQUEST_WORKSPACE',
135+
},
136+
{
137+
label: 'Check Again',
138+
transition: 'RETRY',
139+
},
140+
],
141+
}
142+
this.send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
115143
return
116144
}
117145
// 2. check Git is installed.
118146
// Should wait for workspace before running otherwise requires access to root folder
119147
const isGitInstalled = await gitVersion()
120148
if (!isGitInstalled) {
121-
this.send({ type: 'GIT_NOT_INSTALLED' })
149+
const error: E.ErrorMessage = {
150+
type: 'GitNotFound',
151+
message: '',
152+
actions: [
153+
{
154+
label: 'Check Again',
155+
transition: 'RETRY',
156+
},
157+
],
158+
}
159+
this.send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
122160
return
123161
}
124162
this.send({ type: 'SETUP_VALIDATED' })
125163
return
126164
case 'EDITOR_REQUEST_WORKSPACE':
127-
console.log('request workspace')
128165
openWorkspace()
129166
return
130167
// load step actions (git commits, commands, open files)
@@ -146,6 +183,24 @@ class Channel implements Channel {
146183
}
147184
// send to webview
148185
public send = async (action: T.Action) => {
186+
// Error middleware
187+
if (action?.payload?.error?.type) {
188+
// load error markdown message
189+
const error = action.payload.error
190+
const errorMarkdownFile = join(__dirname, '..', '..', 'errors', `${action.payload.error.type}.md`)
191+
const errorMarkdown = await readFileAsync(errorMarkdownFile).catch(() => {
192+
// onError(new Error(`Error Markdown file not found for ${action.type}`))
193+
})
194+
195+
// log error to console for safe keeping
196+
console.log(`ERROR:\n ${errorMarkdown}`)
197+
198+
if (errorMarkdown) {
199+
// add a clearer error message for the user
200+
error.message = `${errorMarkdown}\n${error.message}`
201+
}
202+
}
203+
149204
// action may be an object.type or plain string
150205
const actionType: string = typeof action === 'string' ? action : action.type
151206
switch (actionType) {
@@ -160,8 +215,9 @@ class Channel implements Channel {
160215
saveCommit()
161216
}
162217

163-
const success = await this.postMessage(action)
164-
if (!success) {
218+
// send message
219+
const sentToClient = await this.postMessage(action)
220+
if (!sentToClient) {
165221
throw new Error(`Message post failure: ${JSON.stringify(action)}`)
166222
}
167223
}

Diff for: src/services/git/index.ts

+31-17
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import * as TT from 'typings/tutorial'
12
import node from '../node'
23
import logger from '../logger'
3-
import onError from '../sentry/onError'
44

55
const gitOrigin = 'coderoad'
66

7-
const stashAllFiles = async () => {
7+
const stashAllFiles = async (): Promise<never | void> => {
88
// stash files including untracked (eg. newly created file)
99
const { stdout, stderr } = await node.exec(`git stash --include-untracked`)
1010
if (stderr) {
@@ -13,7 +13,7 @@ const stashAllFiles = async () => {
1313
}
1414
}
1515

16-
const cherryPickCommit = async (commit: string, count = 0): Promise<void> => {
16+
const cherryPickCommit = async (commit: string, count = 0): Promise<never | void> => {
1717
if (count > 1) {
1818
console.warn('cherry-pick failed')
1919
return
@@ -37,7 +37,7 @@ const cherryPickCommit = async (commit: string, count = 0): Promise<void> => {
3737
SINGLE git cherry-pick %COMMIT%
3838
if fails, will stash all and retry
3939
*/
40-
export function loadCommit(commit: string): Promise<void> {
40+
export function loadCommit(commit: string): Promise<never | void> {
4141
return cherryPickCommit(commit)
4242
}
4343

@@ -46,7 +46,7 @@ export function loadCommit(commit: string): Promise<void> {
4646
git commit -am '${level}/${step} complete'
4747
*/
4848

49-
export async function saveCommit(message: string): Promise<void> {
49+
export async function saveCommit(message: string): Promise<never | void> {
5050
const { stdout, stderr } = await node.exec(`git commit -am '${message}'`)
5151
if (stderr) {
5252
console.error(stderr)
@@ -55,7 +55,7 @@ export async function saveCommit(message: string): Promise<void> {
5555
logger(['save with commit & continue stdout', stdout])
5656
}
5757

58-
export async function clear(): Promise<void> {
58+
export async function clear(): Promise<Error | void> {
5959
try {
6060
// commit progress to git
6161
const { stderr } = await node.exec('git reset HEAD --hard && git clean -fd')
@@ -82,23 +82,38 @@ export async function version(): Promise<string | null> {
8282
return null
8383
}
8484

85-
async function init(): Promise<void> {
85+
async function init(): Promise<Error | void> {
8686
const { stderr } = await node.exec('git init')
8787
if (stderr) {
88-
const error = new Error('Error initializing Git')
89-
onError(error)
90-
throw error
88+
throw new Error('Error initializing Git')
9189
}
9290
}
9391

94-
export async function initIfNotExists(): Promise<void> {
92+
export async function initIfNotExists(): Promise<never | void> {
9593
const hasGitInit = node.exists('.git')
9694
if (!hasGitInit) {
9795
await init()
9896
}
9997
}
10098

101-
export async function addRemote(repo: string): Promise<void> {
99+
export async function checkRemoteConnects(repo: TT.TutorialRepo): Promise<never | void> {
100+
// check for git repo
101+
const externalRepoExists = await node.exec(`git ls-remote --exit-code --heads ${repo.uri}`)
102+
if (externalRepoExists.stderr) {
103+
// no repo found or no internet connection
104+
throw new Error(externalRepoExists.stderr)
105+
}
106+
// check for git repo branch
107+
const { stderr, stdout } = await node.exec(`git ls-remote --exit-code --heads ${repo.uri} ${repo.branch}`)
108+
if (stderr) {
109+
throw new Error(stderr)
110+
}
111+
if (!stdout || !stdout.length) {
112+
throw new Error('Tutorial branch does not exist')
113+
}
114+
}
115+
116+
export async function addRemote(repo: string): Promise<never | void> {
102117
const { stderr } = await node.exec(`git remote add ${gitOrigin} ${repo} && git fetch ${gitOrigin}`)
103118
if (stderr) {
104119
const alreadyExists = stderr.match(`${gitOrigin} already exists.`)
@@ -126,14 +141,13 @@ export async function checkRemoteExists(): Promise<boolean> {
126141
}
127142
}
128143

129-
export async function setupRemote(repo: string): Promise<void> {
144+
export async function setupCodeRoadRemote(repo: string): Promise<never | void> {
130145
// check coderoad remote not taken
131146
const hasRemote = await checkRemoteExists()
132147
// git remote add coderoad tutorial
133148
// git fetch coderoad
134-
if (!hasRemote) {
135-
await addRemote(repo)
136-
} else {
137-
throw new Error('A Remote is already configured')
149+
if (hasRemote) {
150+
throw new Error('A CodeRoad remote is already configured')
138151
}
152+
await addRemote(repo)
139153
}

Diff for: src/services/workspace/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import * as vscode from 'vscode'
22
import * as fs from 'fs'
3+
import { promisify } from 'util'
4+
5+
const readDir = promisify(fs.readdir)
36

47
export const openWorkspace = () => {
58
const openInNewWindow = false
@@ -9,7 +12,7 @@ export const openWorkspace = () => {
912
export const checkWorkspaceEmpty = async (dirname: string) => {
1013
let files
1114
try {
12-
files = await fs.promises.readdir(dirname)
15+
files = await readDir(dirname)
1316
} catch (error) {
1417
throw new Error('Failed to check workspace')
1518
}

Diff for: tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"emitDecoratorMetadata": true,
2121
"paths": {
2222
"typings": ["../typings/index.d.ts"],
23-
"typings/tutorial": ["../typings/tutorial.d.ts"]
23+
"typings/tutorial": ["../typings/tutorial.d.ts"],
24+
"typings/error": ["../typings/error.d.ts"]
2425
},
2526
"allowJs": true,
2627
"removeComments": true

0 commit comments

Comments
 (0)