Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: coderoad/coderoad-vscode
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: a335f07
Choose a base ref
...
head repository: coderoad/coderoad-vscode
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: eaf27ff
Choose a head ref
  • 6 commits
  • 18 files changed
  • 1 contributor

Commits on Apr 17, 2020

  1. validate extension version

    Signed-off-by: shmck <shawn.j.mckay@gmail.com>
    ShMcK committed Apr 17, 2020
    Copy the full SHA
    c7aeeec View commit details
  2. validate vscode version with tutorial required version

    Signed-off-by: shmck <shawn.j.mckay@gmail.com>
    ShMcK committed Apr 17, 2020
    Copy the full SHA
    930d532 View commit details
  3. increment version for 0.3.0 release

    Signed-off-by: shmck <shawn.j.mckay@gmail.com>
    ShMcK committed Apr 17, 2020
    Copy the full SHA
    de55619 View commit details
  4. Merge pull request #278 from coderoad/validate-app-versions

    Validate app versions
    ShMcK authored Apr 17, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e225806 View commit details
  5. allow for separate coderoad directory

    Signed-off-by: shmck <shawn.j.mckay@gmail.com>
    ShMcK committed Apr 17, 2020
    Copy the full SHA
    08d7820 View commit details
  6. update changelog with testrunner path

    Signed-off-by: shmck <shawn.j.mckay@gmail.com>
    ShMcK committed Apr 17, 2020
    Copy the full SHA
    eaf27ff View commit details
33 changes: 32 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -48,4 +48,35 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how

## [0.2.4]

- Support VSCode 1.39.2
- Support VSCode 1.39.2

## [0.3.0]

- Validate the extension version against the tutorial config version. This should allow us to manage breaking changes in tutorial schema in upcoming versions. See [node-semver](https://github.com/npm/node-semver#advanced-range-syntax) for possible version ranges and options.

```json
{
"config": {
"appVersions": {
"vscode": "<0.2"
},
}
```

- Configure the CodeRoad to load and run in a different directory. The example below will:
- load a commit and run npm install to setup the test runner in its own folder.
- run "npm test" in the \$ROOT/coderoad directory on save

```json
{
"config": {
"testRunner": {
"command": "npm test",
"path": "coderoad",
"actions": {
"commits": ["a974aea"],
"commands": ["cd coderoad && npm install"]
}
},
}
```
6 changes: 3 additions & 3 deletions errors/GitProjectAlreadyExists.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
### Git Project Already Exists
### Git Remote Already Exists

CodeRoad requires an empty Git project.
Have you started this tutorial before in this workspace? The Git remote already exists.

Open a new workspace to start a tutorial.
Consider deleting your `.git` folder and restarting.
5 changes: 5 additions & 0 deletions errors/GitRemoteAlreadyExists.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### Git Project Already Exists

CodeRoad requires an empty Git project.

Open a new workspace to start a tutorial.
3 changes: 3 additions & 0 deletions errors/UnmetExtensionVersion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Unmet Tutorial Dependency

This tutorial requires a different version of CodeRoad.
2 changes: 0 additions & 2 deletions errors/UnmetTutorialDependency.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
### Unmet Tutorial Dependency

### Unmet Tutorial Dependency

Tutorial cannot reun because a dependency version doesn't match. Install the correct dependency and click "Check Again".
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.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "coderoad",
"version": "0.2.4",
"version": "0.3.0",
"description": "Play interactive coding tutorials in your editor",
"keywords": [
"tutorial",
@@ -82,4 +82,4 @@
},
"preview": true,
"publisher": "CodeRoad"
}
}
2 changes: 1 addition & 1 deletion src/actions/utils/runCommands.ts
Original file line number Diff line number Diff line change
@@ -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 exec(command)
result = await exec({ command })
} catch (error) {
console.log(`Test failed: ${error.message}`)
send({ type: 'COMMAND_FAIL', payload: { process: { ...process, status: 'FAIL' } } })
26 changes: 23 additions & 3 deletions src/channel/index.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ 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 saveCommit from '../actions/saveCommit'
import setupActions from '../actions/setupActions'
import solutionActions from '../actions/solutionActions'
@@ -110,6 +111,25 @@ class Channel implements Channel {
case 'EDITOR_TUTORIAL_CONFIG':
try {
const data: TT.Tutorial = action.payload.tutorial

// 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)

@@ -121,7 +141,7 @@ class Channel implements Channel {
const currentVersion: string | null = await version(dep.name)
if (!currentVersion) {
// use a custom error message
const error = {
const error: E.ErrorMessage = {
type: 'MissingTutorialDependency',
message:
dep.message || `Process "${dep.name}" is required but not found. It may need to be installed`,
@@ -140,7 +160,7 @@ class Channel implements Channel {
const satisfiedDependency = await compareVersions(currentVersion, dep.version)

if (!satisfiedDependency) {
const error = {
const error: E.ErrorMessage = {
type: 'UnmetTutorialDependency',
message: `Expected ${dep.name} to have version ${dep.version}, but found version ${currentVersion}`,
actions: [
@@ -155,7 +175,7 @@ class Channel implements Channel {
}

if (satisfiedDependency !== true) {
const error = satisfiedDependency || {
const error: E.ErrorMessage = satisfiedDependency || {
type: 'UnknownError',
message: `Something went wrong comparing dependency for ${name}`,
actions: [
8 changes: 7 additions & 1 deletion src/editor/commands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as TT from 'typings/tutorial'
import * as vscode from 'vscode'
import createTestRunner, { Payload } from '../services/testRunner'
import setupActions from 'actions/setupActions'
import createWebView from '../webview'

export const COMMANDS = {
@@ -47,7 +48,12 @@ export const createCommands = ({ extensionPath, workspaceState }: CreateCommandP
// setup 1x1 horizontal layout
webview.createOrShow()
},
[COMMANDS.CONFIG_TEST_RUNNER]: (config: TT.TutorialTestRunner) => {
[COMMANDS.CONFIG_TEST_RUNNER]: (config: TT.TutorialTestRunnerConfig) => {
if (config.actions) {
// setup tutorial test runner commits
// assumes git already exists
setupActions(config.actions, webview.send)
}
testRunner = createTestRunner(config, {
onSuccess: (payload: Payload) => {
// send test pass message back to client
2 changes: 1 addition & 1 deletion src/services/dependencies/index.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ const semverRegex = /(?<=^v?|\sv?)(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)

export const version = async (name: string): Promise<string | null> => {
try {
const { stdout, stderr } = await exec(`${name} --version`)
const { stdout, stderr } = await exec({ command: `${name} --version` })
if (!stderr) {
const match = stdout.match(semverRegex)
if (match) {
18 changes: 9 additions & 9 deletions src/services/git/index.ts
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ const gitOrigin = 'coderoad'

const stashAllFiles = async (): Promise<never | void> => {
// stash files including untracked (eg. newly created file)
const { stdout, stderr } = await exec(`git stash --include-untracked`)
const { stdout, stderr } = await exec({ command: `git stash --include-untracked` })
if (stderr) {
console.error(stderr)
throw new Error('Error stashing files')
@@ -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 exec(`git cherry-pick -X theirs ${commit}`)
const { stdout } = await exec({ command: `git cherry-pick -X theirs ${commit}` })
if (!stdout) {
throw new Error('No cherry-pick output')
}
@@ -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 exec(`git commit -am '${message}'`)
const { stdout, stderr } = await exec({ command: `git commit -am '${message}'` })
if (stderr) {
console.error(stderr)
throw new Error('Error saving progress to Git')
@@ -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 exec('git reset HEAD --hard && git clean -fd')
const { stderr } = await exec({ command: 'git reset HEAD --hard && git clean -fd' })
if (!stderr) {
return
}
@@ -70,7 +70,7 @@ export async function clear(): Promise<Error | void> {
}

async function init(): Promise<Error | void> {
const { stderr } = await exec('git init')
const { stderr } = await exec({ command: 'git init' })
if (stderr) {
throw new Error('Error initializing Git')
}
@@ -85,13 +85,13 @@ export async function initIfNotExists(): Promise<never | void> {

export async function checkRemoteConnects(repo: TT.TutorialRepo): Promise<never | void> {
// check for git repo
const externalRepoExists = await exec(`git ls-remote --exit-code --heads ${repo.uri}`)
const externalRepoExists = await exec({ command: `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 exec(`git ls-remote --exit-code --heads ${repo.uri} ${repo.branch}`)
const { stderr, stdout } = await exec({ command: `git ls-remote --exit-code --heads ${repo.uri} ${repo.branch}` })
if (stderr) {
throw new Error(stderr)
}
@@ -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 exec(`git remote add ${gitOrigin} ${repo} && git fetch ${gitOrigin}`)
const { stderr } = await exec({ command: `git remote add ${gitOrigin} ${repo} && git fetch ${gitOrigin}` })
if (stderr) {
const alreadyExists = stderr.match(`${gitOrigin} already exists.`)
const successfulNewBranch = stderr.match('new branch')
@@ -116,7 +116,7 @@ export async function addRemote(repo: string): Promise<never | void> {

export async function checkRemoteExists(): Promise<boolean> {
try {
const { stdout, stderr } = await exec('git remote -v')
const { stdout, stderr } = await exec({ command: 'git remote -v' })
if (stderr) {
return false
}
11 changes: 8 additions & 3 deletions src/services/node/index.ts
Original file line number Diff line number Diff line change
@@ -6,9 +6,14 @@ import { WORKSPACE_ROOT } from '../../environment'

const asyncExec = promisify(cpExec)

export const exec = (cmd: string): Promise<{ stdout: string; stderr: string }> | never => {
return asyncExec(cmd, {
cwd: WORKSPACE_ROOT,
interface ExecParams {
command: string
path?: string
}

export const exec = (params: ExecParams): Promise<{ stdout: string; stderr: string }> | never => {
return asyncExec(params.command, {
cwd: join(WORKSPACE_ROOT, params.path || ''),
})
}

9 changes: 3 additions & 6 deletions src/services/testRunner/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TutorialTestRunnerConfig } from 'typings/tutorial'
import { exec } from '../node'
import logger from '../logger'
import parser from './parser'
@@ -17,14 +18,10 @@ interface Callbacks {
onError(payload: Payload): void
}

interface TestRunnerConfig {
command: string
}

const failChannelName = 'CodeRoad (Tests)'
const logChannelName = 'CodeRoad (Logs)'

const createTestRunner = (config: TestRunnerConfig, callbacks: Callbacks) => {
const createTestRunner = (config: TutorialTestRunnerConfig, callbacks: Callbacks) => {
return async (payload: Payload, onSuccess?: () => void): Promise<void> => {
const startTime = throttle()
// throttle time early
@@ -39,7 +36,7 @@ const createTestRunner = (config: TestRunnerConfig, callbacks: Callbacks) => {

let result: { stdout: string | undefined; stderr: string | undefined }
try {
result = await exec(config.command)
result = await exec({ command: config.command, path: config.path })
} catch (err) {
result = { stdout: err.stdout, stderr: err.stack }
}
11 changes: 7 additions & 4 deletions typings/error.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
export type ErrorMessageView = 'FULL_PAGE' | 'NOTIFY' | 'NONE'

export type ErrorMessageType =
| 'UnknownError'
| 'NoWorkspaceFound'
| 'GitNotFound'
| 'WorkspaceNotEmpty'
| 'FailedToConnectToGitRepo'
| 'GitNotFound'
| 'GitProjectAlreadyExists'
| 'GitRemoteAlreadyExists'
| 'MissingTutorialDependency'
| 'NoWorkspaceFound'
| 'UnknownError'
| 'UnmetExtensionVersion'
| 'UnmetTutorialDependency'
| 'WorkspaceNotEmpty'

export type ErrorAction = {
label: string
11 changes: 9 additions & 2 deletions typings/tutorial.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export type Maybe<T> = T | null

export type TutorialConfig = {
testRunner: TutorialTestRunner
appVersions: TutorialAppVersions
testRunner: TutorialTestRunnerConfig
repo: TutorialRepo
dependencies?: TutorialDependency[]
}
@@ -50,8 +51,10 @@ export type StepActions = {
watchers: string[]
}

export interface TutorialTestRunner {
export interface TutorialTestRunnerConfig {
command: string
path?: string
actions?: StepActions
}

export interface TutorialRepo {
@@ -64,3 +67,7 @@ export interface TutorialDependency {
version: string
message?: string
}

export interface TutorialAppVersions {
vscode: string
}
2 changes: 1 addition & 1 deletion web-app/package-lock.json

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

4 changes: 2 additions & 2 deletions web-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "coderoad-app",
"version": "0.2.4",
"version": "0.3.0",
"private": true,
"scripts": {
"build": "react-app-rewired build",
@@ -73,4 +73,4 @@
"typescript": "^3.8.3",
"typescript-eslint-parser": "^22.0.0"
}
}
}