🎁 feat: run user code against tests
This commit is contained in:
commit
0486a6d141
19
.eslintrc.json
Normal file
19
.eslintrc.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/naming-convention": "warn",
|
||||
"@typescript-eslint/semi": "warn",
|
||||
"curly": "warn",
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off"
|
||||
}
|
||||
}
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
out
|
||||
node_modules
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
37
.vscode/launch.json
vendored
Normal file
37
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/demo/",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
},
|
||||
{
|
||||
"name": "Extension Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/test/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
}
|
||||
]
|
||||
}
|
||||
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"files.exclude": {
|
||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
},
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off"
|
||||
}
|
||||
20
.vscode/tasks.json
vendored
Normal file
20
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
10
.vscodeignore
Normal file
10
.vscodeignore
Normal file
@ -0,0 +1,10 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
out/test/**
|
||||
src/**
|
||||
.gitignore
|
||||
vsc-extension-quickstart.md
|
||||
**/tsconfig.json
|
||||
**/.eslintrc.json
|
||||
**/*.map
|
||||
**/*.ts
|
||||
9
CHANGELOG.md
Normal file
9
CHANGELOG.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to the "brightscreen" extension will be documented in this file.
|
||||
|
||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Initial release
|
||||
80
README.md
Normal file
80
README.md
Normal file
@ -0,0 +1,80 @@
|
||||
# <img alt="brightScreen" src="./src/media/logoBlue.svg" width="26px" /> brightscreen
|
||||
|
||||
|
||||
## VS Code Extention for Students
|
||||
|
||||
brightScreen is an extention for your favorite text editor (as long as your text editor is VS Code) that brings interactive coding tutorials to help bring a new dynamic to learning. Instead of just following along with an article or video, you can download coding challanges that help solidify those theoretical lessons into something tangible.
|
||||
|
||||
After installing a brightScreen course and activating brightScreen an new icon will appear in the Activity Pannel to the side. This will open a tree view showing all the available tests. By clicking on the `Run Tests` icon, you can test the code in your currently active editor against the instructors test senerios.
|
||||
|
||||
## New Platform for Instructors
|
||||
|
||||
brightScreen is a great place for software instructors to create interactive homework for those that follow their courses, videos, or articles. The amount of effort in seting up a brightScreen course is virtually non existant.
|
||||
|
||||
All that is required is a Git repo with a brightScreen.json configuration file that looks like something like this:
|
||||
|
||||
{
|
||||
"courseName": "Test Lesson",
|
||||
"documentationUrl": "https://brightScreen.io/courses/Test-Lesson/",
|
||||
"lessons": [
|
||||
{
|
||||
"name": "Lesson 1",
|
||||
"location": "lessonOne/indexTest.js",
|
||||
"description": "Test Description for Lesson 1",
|
||||
"executionPrefix": "node",
|
||||
"fileExtention": "js",
|
||||
"replacementSubstring": "USER_CODE",
|
||||
"documentationUrl": "https://brightScreen.io/courses/Test-Lesson/lesson-One/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
A course testing file will look something like this:
|
||||
|
||||
const codeFromUser = USER_CODE
|
||||
|
||||
const test = () => {
|
||||
const input = 'yo'
|
||||
const expectedOutput = 'yo'
|
||||
const testedValue = codeFromUser(input)
|
||||
|
||||
if (testedValue === expectedOutput) {
|
||||
console.log({
|
||||
didPass: true,
|
||||
message: 'Passed Test 1'
|
||||
})
|
||||
} else {
|
||||
console.log({
|
||||
didPass: false,
|
||||
message: 'Did not return expected value'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
test()
|
||||
|
||||
In the brightScreen.json file, for each, lesson you specify a substring you would like to replace with the User's own code and brightScreen throws it in there for you.
|
||||
|
||||
In theory, any interpreted language can be used for a course as long as it can be executed with a CLI and file location.
|
||||
|
||||
Your tests are created as a child process. To interface vack with brightScreen, your tests should return a JSON object through the Standard Out of what every platform you are testing with.
|
||||
|
||||
Example of Standard Iut from Tests
|
||||
|
||||
{
|
||||
didPass: false,
|
||||
message: 'Value was not FizzBuzz'
|
||||
}
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
`git` should be installed on your device to install courses.
|
||||
|
||||
Whatever languages or platforms the instructor has listed should also be installed on your machine. For instance, if you are working on a `JavaScript` course, you may need `Node.js` installed
|
||||
|
||||
## Release Notes
|
||||
|
||||
### 0.0.1
|
||||
|
||||
This extention is still very much in development. If you would like to see it expanded then please reach out with suggestions or sponser me on Github.
|
||||
24
demo/.brightScreen/brightScreen.json
Normal file
24
demo/.brightScreen/brightScreen.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"courseName": "Test Lesson",
|
||||
"documentationUrl": "https://brightScreen.io/courses/Test-Lesson/",
|
||||
"lessons": [
|
||||
{
|
||||
"name": "Lesson 1",
|
||||
"location": "lessonOne/indexTest.js",
|
||||
"description": "Test Description for Lesson 1",
|
||||
"executionPrefix": "node",
|
||||
"fileExtention": "js",
|
||||
"replacementSubstring": "USER_CODE",
|
||||
"documentationUrl": "https://brightScreen.io/courses/Test-Lesson/lesson-One/"
|
||||
},
|
||||
{
|
||||
"name": "Lesson 2",
|
||||
"location": "lessonTwo/indexTest.js",
|
||||
"description": "Test Description for Lesson 2",
|
||||
"executionPrefix": "node",
|
||||
"fileExtention": "js",
|
||||
"replacementSubstring": "USER_CODE",
|
||||
"documentationUrl": "https://brightScreen.io/courses/Test-Lesson/lesson-Two/"
|
||||
}
|
||||
]
|
||||
}
|
||||
21
demo/.brightScreen/lessonOne/indexTest.js
Normal file
21
demo/.brightScreen/lessonOne/indexTest.js
Normal file
@ -0,0 +1,21 @@
|
||||
const codeFromUser = USER_CODE
|
||||
|
||||
const test = () => {
|
||||
const input = 'yo'
|
||||
const expectedOutput = 'yo'
|
||||
const testedValue = codeFromUser(input)
|
||||
|
||||
if (testedValue === expectedOutput) {
|
||||
console.log({
|
||||
didPass: true,
|
||||
message: 'Passed Test 1'
|
||||
})
|
||||
} else {
|
||||
console.log({
|
||||
didPass: false,
|
||||
message: 'Did not return expected value'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
test()
|
||||
18
demo/.brightScreen/lessonTwo/indexTest.js
Normal file
18
demo/.brightScreen/lessonTwo/indexTest.js
Normal file
@ -0,0 +1,18 @@
|
||||
const codeFromUser = USER_CODE || function() { console.log('No User code Provided') }
|
||||
|
||||
|
||||
const test = () => {
|
||||
const input = 'yo'
|
||||
const expectedOutput = 'yo'
|
||||
const testedValue = codeFromUser(input)
|
||||
|
||||
if (testedValue === expectedOutput) {
|
||||
console.log('Passed Test 2')
|
||||
return true
|
||||
} else {
|
||||
console.log('Failed Test Two')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
test()
|
||||
20
demo/.brightScreen/runningTest.js
Normal file
20
demo/.brightScreen/runningTest.js
Normal file
@ -0,0 +1,20 @@
|
||||
const codeFromUser = function (props) {
|
||||
return props + 'js'
|
||||
} || function() { console.log('No User code Provided') }
|
||||
|
||||
|
||||
const test = () => {
|
||||
const input = 'yo'
|
||||
const expectedOutput = 'yo'
|
||||
const testedValue = codeFromUser(input)
|
||||
|
||||
if (testedValue === expectedOutput) {
|
||||
console.log('Passed Test 2')
|
||||
return true
|
||||
} else {
|
||||
console.log('Failed Test Two')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
test()
|
||||
3
demo/userTestOne.js
Normal file
3
demo/userTestOne.js
Normal file
@ -0,0 +1,3 @@
|
||||
function (props) {
|
||||
return props + 'js'
|
||||
}
|
||||
1949
package-lock.json
generated
Normal file
1949
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
96
package.json
Normal file
96
package.json
Normal file
@ -0,0 +1,96 @@
|
||||
{
|
||||
"name": "brightscreen",
|
||||
"displayName": "brightScreen",
|
||||
"description": "Interactive Code Tutorials",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"vscode": "^1.48.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onView:brightScreen"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"contributes": {
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "brightScreen",
|
||||
"title": "brightScreen",
|
||||
"icon": "src/media/logoBlue.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"brightScreen": [
|
||||
{
|
||||
"id": "brightScreen",
|
||||
"name": "brightScreen",
|
||||
"icon": "src/media/logoBlue.svg",
|
||||
"contextualTitle": "brightScreen"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "brightscreen.startBrightScreen",
|
||||
"title": "Start Bright Screen",
|
||||
"icon": {
|
||||
"light": "src/media/logoBlue.svg",
|
||||
"dark": "src/media/logoBlue.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "brightscreen.runTests",
|
||||
"title": "Run Tests",
|
||||
"icon": {
|
||||
"light": "src/media/runCommand.svg",
|
||||
"dark": "src/media/runCommand.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"view/title": [
|
||||
{
|
||||
"command": "brightscreen.startBrightScreen",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "brightscreen.runTests",
|
||||
"group": "inline"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viewsWelcome": [
|
||||
{
|
||||
"view": "brightScreen",
|
||||
"contents": "No Course Selected\n[Find Them Here](https://brightScreen.io/)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "tsc -p ./",
|
||||
"lint": "eslint src --ext ts",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"pretest": "npm run compile && npm run lint",
|
||||
"test": "node ./out/test/runTest.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.48.0",
|
||||
"@types/glob": "^7.1.3",
|
||||
"@types/mocha": "^8.0.0",
|
||||
"@types/node": "^14.0.27",
|
||||
"eslint": "^7.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^3.8.0",
|
||||
"@typescript-eslint/parser": "^3.8.0",
|
||||
"glob": "^7.1.6",
|
||||
"mocha": "^8.0.1",
|
||||
"typescript": "^3.8.3",
|
||||
"vscode-test": "^1.4.0"
|
||||
}
|
||||
}
|
||||
41
src/Entities/BrightScreen.ts
Normal file
41
src/Entities/BrightScreen.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import BrightScreenInterface from '../Interfaces/BrightScreenInterface'
|
||||
import LessonInterface from '../Interfaces/LessonInterface'
|
||||
|
||||
class BrightScreen {
|
||||
private static instance: BrightScreen
|
||||
public workspaceFolder: string
|
||||
public courseName: string
|
||||
public documentationUrl: string
|
||||
public lessons: LessonInterface[]
|
||||
|
||||
|
||||
constructor (props: BrightScreenInterface) {
|
||||
if (!BrightScreen.instance) BrightScreen.instance = this
|
||||
|
||||
this.workspaceFolder = props.workspaceFolder
|
||||
this.courseName = props.courseName || ''
|
||||
this.documentationUrl = props.documentationUrl || ''
|
||||
this.lessons = props.lessons || []
|
||||
|
||||
return BrightScreen.instance
|
||||
}
|
||||
|
||||
public static getInstance(): BrightScreen {
|
||||
return BrightScreen.instance
|
||||
}
|
||||
|
||||
public get props (): BrightScreenInterface {
|
||||
return {
|
||||
workspaceFolder: this.workspaceFolder,
|
||||
courseName: this.courseName,
|
||||
lessons: this.lessons
|
||||
}
|
||||
}
|
||||
|
||||
executeLessonTest (activeUserCode: string, lessonCode: string): void {
|
||||
eval(activeUserCode)
|
||||
eval(lessonCode)
|
||||
}
|
||||
}
|
||||
|
||||
export default BrightScreen
|
||||
10
src/Interfaces/BrightScreenInterface.ts
Normal file
10
src/Interfaces/BrightScreenInterface.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import LessonInterface from '../Interfaces/LessonInterface'
|
||||
|
||||
interface BrightScreenInterface {
|
||||
workspaceFolder: string,
|
||||
courseName?: string,
|
||||
documentationUrl?: string,
|
||||
lessons?: LessonInterface[]
|
||||
}
|
||||
|
||||
export default BrightScreenInterface
|
||||
11
src/Interfaces/LessonInterface.ts
Normal file
11
src/Interfaces/LessonInterface.ts
Normal file
@ -0,0 +1,11 @@
|
||||
interface LessonInterface {
|
||||
name: string,
|
||||
location: string,
|
||||
description: string,
|
||||
executionPrefix: string,
|
||||
fileExtention: string,
|
||||
replacementSubstring: string,
|
||||
documentationUrl: string
|
||||
}
|
||||
|
||||
export default LessonInterface
|
||||
56
src/UseCases/LessonsProvider.ts
Normal file
56
src/UseCases/LessonsProvider.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import * as vscode from 'vscode'
|
||||
import BrightScreen from '../Entities/BrightScreen'
|
||||
|
||||
class LessonsProvider implements vscode.TreeDataProvider<LessonTreeItem> {
|
||||
constructor() { }
|
||||
|
||||
getTreeItem (element: LessonTreeItem): vscode.TreeItem {
|
||||
return element
|
||||
}
|
||||
|
||||
getChildren (): LessonTreeItem[] {
|
||||
const brightScreen = BrightScreen.getInstance()
|
||||
|
||||
if (brightScreen.lessons) {
|
||||
const treeItems: LessonTreeItem[] = brightScreen.lessons.map(l => {
|
||||
return new LessonTreeItem(
|
||||
l.name,
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
l.executionPrefix,
|
||||
l.fileExtention,
|
||||
l.replacementSubstring,
|
||||
l.location
|
||||
)
|
||||
})
|
||||
return treeItems
|
||||
} else return []
|
||||
}
|
||||
}
|
||||
|
||||
class LessonTreeItem extends vscode.TreeItem {
|
||||
constructor (
|
||||
public readonly label: string,
|
||||
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
|
||||
public readonly executionPrefix: string,
|
||||
public readonly fileExtention: string,
|
||||
public readonly replacementSubstring: string,
|
||||
public readonly location: string
|
||||
) {
|
||||
super(label, collapsibleState)
|
||||
}
|
||||
|
||||
get tooltip (): string {
|
||||
return this.label
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return this.fileExtention
|
||||
}
|
||||
|
||||
iconPath = {
|
||||
light: `../media/runCommand.svg`,
|
||||
dark: '../media/runCommand.svg'
|
||||
}
|
||||
}
|
||||
|
||||
export default LessonsProvider
|
||||
46
src/UseCases/setupBrightScreen.ts
Normal file
46
src/UseCases/setupBrightScreen.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import * as vscode from 'vscode'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import BrightScreen from '../Entities/BrightScreen'
|
||||
import LessonInterface from '../Interfaces/LessonInterface'
|
||||
|
||||
function setupBrightScreen (): void {
|
||||
const workspaceFolder: string = vscode.workspace.rootPath || ''
|
||||
let courseName: string
|
||||
let lessons: LessonInterface[]
|
||||
|
||||
try {
|
||||
const brightScreenPath = path.join(workspaceFolder, '.brightScreen')
|
||||
const doesBrightScreenDirectoryExist: boolean = fs.existsSync(brightScreenPath)
|
||||
if (!doesBrightScreenDirectoryExist) fs.mkdirSync(brightScreenPath)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
vscode.window.showErrorMessage('Could not create .brightScreen directory')
|
||||
}
|
||||
|
||||
let brightScreenCourseConfig: any
|
||||
try {
|
||||
brightScreenCourseConfig = JSON.parse(fs.readFileSync(`${workspaceFolder}/.brightScreen/brightScreen.json`, 'utf-8'))
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
vscode.window.showErrorMessage('Could not find brightScreen config for lesson')
|
||||
return
|
||||
}
|
||||
courseName = brightScreenCourseConfig.courseName
|
||||
lessons = brightScreenCourseConfig.lessons
|
||||
|
||||
if (!courseName || !lessons || !workspaceFolder) {
|
||||
vscode.window.showErrorMessage('Required configurations not provided')
|
||||
return
|
||||
}
|
||||
|
||||
new BrightScreen({
|
||||
workspaceFolder: workspaceFolder,
|
||||
courseName: courseName,
|
||||
lessons: lessons
|
||||
})
|
||||
|
||||
vscode.window.showInformationMessage(`brightScreen has been configured for ${courseName}`)
|
||||
}
|
||||
|
||||
export default setupBrightScreen
|
||||
92
src/extension.ts
Normal file
92
src/extension.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import * as vscode from 'vscode'
|
||||
import * as fs from 'fs'
|
||||
import BrightScreen from './Entities/BrightScreen'
|
||||
import setupBrightScreen from './UseCases/setupBrightScreen'
|
||||
import LessonsProvider from './UseCases/LessonsProvider'
|
||||
import LessonInterface from './Interfaces/LessonInterface'
|
||||
import { spawn, exec } from 'child_process'
|
||||
import { stderr } from 'process'
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
console.log('brightscreen is now active')
|
||||
|
||||
let brightScreen: BrightScreen
|
||||
let workspaceFolder: string
|
||||
let lessons: LessonInterface[]
|
||||
|
||||
setupBrightScreen()
|
||||
brightScreen = BrightScreen.getInstance()
|
||||
workspaceFolder = brightScreen.workspaceFolder
|
||||
lessons = brightScreen.lessons
|
||||
|
||||
try {
|
||||
vscode.window.registerTreeDataProvider('brightScreen', new LessonsProvider())
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
const startupBrightScreenCommand = vscode.commands.registerCommand('brightscreen.startBrightScreen', (value) => {
|
||||
setupBrightScreen()
|
||||
brightScreen = BrightScreen.getInstance()
|
||||
workspaceFolder = brightScreen.workspaceFolder
|
||||
lessons = brightScreen.lessons
|
||||
})
|
||||
|
||||
const runTestCommand = vscode.commands.registerCommand('brightscreen.runTests', (treeItemContext) => {
|
||||
let lessonCodeAsString: string
|
||||
try {
|
||||
lessonCodeAsString = fs.readFileSync(`${workspaceFolder}/.brightScreen/${treeItemContext.location}`, 'utf-8')
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
vscode.window.showErrorMessage('Could not find file')
|
||||
return
|
||||
}
|
||||
|
||||
let userCodeAsString: string | undefined
|
||||
try {
|
||||
userCodeAsString = vscode.window.activeTextEditor?.document.getText()
|
||||
if (typeof userCodeAsString !== 'string') {
|
||||
vscode.window.showErrorMessage('Could not get active text editor')
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
vscode.window.showErrorMessage('Could not get active text editor')
|
||||
return
|
||||
}
|
||||
|
||||
let mergedCodeAsString: string
|
||||
try {
|
||||
mergedCodeAsString = lessonCodeAsString.replace(treeItemContext.replacementSubstring, userCodeAsString)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
vscode.window.showErrorMessage('Could not create test')
|
||||
return
|
||||
}
|
||||
|
||||
const runningTestPath = `${workspaceFolder}/.brightScreen/runningTest.${treeItemContext.fileExtention}`
|
||||
try {
|
||||
fs.writeFileSync(runningTestPath, mergedCodeAsString)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
vscode.window.showErrorMessage('Could not create ./runningTest file')
|
||||
}
|
||||
|
||||
exec(`${treeItemContext.executionPrefix} ${runningTestPath}`, (error, stdout, stderr) => {
|
||||
if (stderr) {
|
||||
console.log('stderr: ', stderr)
|
||||
return
|
||||
}
|
||||
|
||||
if (error !== null) {
|
||||
console.log('exec error: ', error)
|
||||
}
|
||||
|
||||
console.log(stdout)
|
||||
})
|
||||
})
|
||||
|
||||
context.subscriptions.push(...[runTestCommand, startupBrightScreenCommand])
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
45
src/media/logoBlue.svg
Normal file
45
src/media/logoBlue.svg
Normal file
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 331 276" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-miterlimit:10;">
|
||||
<g id="Artboard1" transform="matrix(0.838537,0,0,0.897683,-89.4004,-44.0313)">
|
||||
<rect x="106.615" y="49.05" width="393.853" height="306.879" style="fill:none;"/>
|
||||
<clipPath id="_clip1">
|
||||
<rect x="106.615" y="49.05" width="393.853" height="306.879"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#_clip1)">
|
||||
<g id="Layer_1" transform="matrix(1.19255,0,0,1.11398,-58.9948,-25.8648)">
|
||||
<path d="M223.83,336.73L388.17,336.73" style="fill:none;stroke:rgb(102,252,241);stroke-width:12px;"/>
|
||||
<path d="M286.43,293.25L328.17,293.25C329,298.81 331.3,308.51 338.6,317.6C354.89,337.85 381.41,336.9 384.69,336.73" style="fill:none;stroke:rgb(102,252,241);stroke-width:12px;"/>
|
||||
<path d="M282.44,220.94L283.89,221.15C283.97,221.16 284.04,221.18 284.12,221.19L285.87,221.6L286.57,221.76C286.99,221.86 287.42,221.85 287.84,221.74L291.43,220.78C292.09,220.6 292.66,220.17 293,219.57L293.34,218.98C293.68,218.39 293.78,217.68 293.6,217.02L293.06,215C292.88,214.34 292.46,213.78 291.87,213.44L284.85,209.31C284.77,209.26 284.68,209.21 284.61,209.15L270.06,198.19C268.14,196.74 269.05,193.68 271.45,193.54C271.85,193.52 272.26,193.5 272.68,193.49C275.7,193.41 282.17,193.54 282.12,193.54C282.12,193.54 289.11,191.95 291.21,191.4C294.34,190.59 295.42,190.31 295.5,190.25C295.81,190.01 296.07,189.74 296.29,189.47C297.04,188.58 297.06,187.29 296.39,186.34L295.72,185.39C295.27,184.76 294.57,184.36 293.8,184.3L289.16,183.95C289.04,183.94 288.92,183.92 288.8,183.9L265.18,178.77C264.92,178.71 264.66,178.7 264.39,178.72C262.31,178.91 259.92,179.13 257.22,179.38C257.02,179.4 256.83,179.44 256.65,179.5C252.83,180.76 242.03,184.33 231.61,187.77C231.3,187.87 230.98,187.91 230.66,187.9L218.72,187.25C218.45,187.24 218.17,187.26 217.91,187.33L212.98,188.65C212.53,188.77 212.13,189.01 211.8,189.33L206.36,194.86C206.22,195 206.06,195.13 205.89,195.24L183.69,209.15L149.78,228.77C149.68,228.83 149.58,228.89 149.49,228.97L146.47,231.31C146.17,231.55 145.92,231.85 145.74,232.19L140.43,242.77C140.15,243.32 140.08,243.95 140.23,244.55L178.68,277.2C178.68,277.2 180.77,266.98 180.48,265.9C180.22,264.91 182.63,260.89 183.02,260.23C183.06,260.17 183.1,260.11 183.14,260.06L183.88,259.04C184,258.88 184.13,258.73 184.28,258.6L223.41,224.9C223.61,224.73 223.84,224.59 224.08,224.48L237.39,218.76C237.76,218.6 238.15,218.53 242.45,211.16L264.97,212.37L282.19,220.9C282.28,220.92 282.36,220.93 282.44,220.94Z" style="fill:rgb(102,252,241);fill-rule:nonzero;stroke:rgb(102,252,241);stroke-width:0.75px;"/>
|
||||
<path d="M288.55,221L245.72,203.7L183.37,246.63L179.88,256.77C179.88,256.77 187.23,258.83 187.41,258.89C188.17,259.15 199.17,256.89 199.17,256.89C199.17,256.89 212.17,254.33 213.1,254.31C214.02,254.29 224.28,245.79 224.28,245.79L233.07,241.36L240.18,241.06L248.94,241.7C248.94,241.7 256.43,241.07 257.73,240.73C259.03,240.38 263.58,239.16 263.58,239.16L266.79,235.54L261.53,228.9L252.75,224.7L245.66,217.28L248.04,216.64L252.44,216.61L256.48,214.38L261.95,216.6L275.18,221.8L281.93,226.2L285.39,225.27L286.99,223.46L288.55,221" style="fill:rgb(102,252,241);fill-rule:nonzero;stroke:rgb(102,252,241);stroke-width:0.75px;"/>
|
||||
<path d="M223.76,227.76L232.91,223.7L234.71,221.83L238.46,223.82L242.08,226.99L223.76,227.76Z" style="fill:white;fill-rule:nonzero;stroke:rgb(102,252,241);stroke-width:0.75px;"/>
|
||||
<path id="Layer_3" d="M423.29,165.92L434.85,161.25C434.85,161.25 439.74,153.25 439.74,152.58C439.74,151.91 442.63,141.25 442.63,141.25L444.85,134.14L447.29,130.36L462.62,122.8L462.62,77.48L459.06,73.26L447.28,74.82L421.72,93.26L410.16,100.37L397.72,109.04L389.72,117.04L377.72,131.48L368.83,141.92L354.61,153.25L348.39,157.69L338.61,166.8L324.61,177.8L313.72,184.64L313.72,187.46L315.5,189.46L321.94,189.46L331.5,187.02L340.39,179.46L345.5,175.46L347.72,173.9L357.94,171.62L368.16,169.23L377.05,160.56L372.16,175.23L364.16,187.01L363.05,195.23L366.16,198.34C369.05,200.12 372.8,198.34 374.38,198.34L377.94,197.23" style="fill:rgb(102,252,241);fill-rule:nonzero;stroke:rgb(102,252,241);stroke-width:0.75px;"/>
|
||||
<path id="Layer_7" d="M385.09,187.36L385.09,198.41L386.51,207.21C386.51,207.21 391.12,216.3 390.94,216.81C390.76,217.32 392.71,218.92 392.71,218.92L396.96,218.92C396.96,218.92 399.45,217.88 399.45,214.67C399.45,213.94 398.63,209.96 397.81,206.11C396.97,202.18 396.12,198.4 396.12,198.4C396.12,198.4 396.34,190.36 396.12,188.59C395.9,186.82 397.67,179.37 397.67,179.37L385.09,187.36Z" style="fill:rgb(102,252,241);fill-rule:nonzero;stroke:rgb(102,252,241);stroke-width:0.75px;"/>
|
||||
<path id="Layer_8" d="M397.68,179.38L396.13,201.19L399.46,213.37L400.34,215.36C400.34,215.36 404.77,216.06 405.48,215.36L408.32,212.18L408.32,207.93C408.32,207.93 406.72,203.74 406.72,202.91C406.72,202.08 404.95,198.59 405.48,197.05C406.01,195.52 406.19,191.44 406.37,190.2C406.55,188.96 406.9,182.22 407.08,181.16C407.26,180.1 408.32,174.78 408.32,174.78L409.56,171.41L397.68,179.38Z" style="fill:rgb(102,252,241);fill-rule:nonzero;stroke:rgb(102,252,241);stroke-width:0.75px;"/>
|
||||
<path id="Layer_9" d="M407.84,171.38L405.71,178.74L409.96,175.19L411.96,175.9L411.96,188.13L416.17,195.37C416.17,195.37 419.41,196.7 420.63,196.76C422.98,196.01 423.17,193.87 422.56,190.08C422.61,189.29 421.01,187.72 420.63,185.97C420.25,184.23 420.63,175.18 420.63,175.18L419.72,170.11L422.56,168.43L426.81,162.59L433.19,159.58" style="fill:rgb(102,252,241);fill-rule:nonzero;stroke:rgb(102,252,241);stroke-width:0.75px;"/>
|
||||
<g id="Layer_10">
|
||||
<path d="M422.56,168.45L433.2,161.25L424.88,161.25L422.56,168.45Z" style="fill:rgb(102,252,241);fill-rule:nonzero;stroke:rgb(102,252,241);stroke-width:0.75px;"/>
|
||||
<path d="M409.97,175.19C409.97,175.19 410.8,188.8 411.97,188.13C413.14,187.46 415.54,174.12 414.65,174.12C413.75,174.13 409.97,175.19 409.97,175.19Z" style="fill:rgb(102,252,241);fill-rule:nonzero;stroke:rgb(102,252,241);stroke-width:0.75px;"/>
|
||||
</g>
|
||||
<path d="M463.13,262.99C463.13,269.59 457.73,274.99 451.13,274.99L156.87,274.99C150.27,274.99 144.87,269.59 144.87,262.99L144.87,85.25C144.87,78.65 150.27,73.25 156.87,73.25L451.13,73.25C457.73,73.25 463.13,78.65 463.13,85.25L463.13,262.99Z" style="fill:none;stroke:rgb(102,252,241);stroke-width:12px;"/>
|
||||
<path d="M144.94,244.57L144.94,270.24L183.39,277.22" style="fill:rgb(102,252,241);fill-rule:nonzero;stroke:rgb(102,252,241);stroke-width:0.75px;"/>
|
||||
</g>
|
||||
<g id="Layer_5" transform="matrix(1.19255,0,0,1.11398,-58.9948,-25.8648)">
|
||||
<path d="M392.376,181.649C396.196,174.059 398.54,164.19 400.87,161.58C401,161.44 401.92,160.42 403.14,158.95C403.14,158.95 403.82,158.13 404.43,157.35C409.16,151.33 411.54,145.57 411.54,145.57C412.75,142.65 414.26,138.03 414.65,132.01" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
<path d="M445.34,136.21C440.39,143.7 435.67,147.32 432.23,149.24C429.48,150.78 426.8,151.64 419.12,155.46C418.3,155.87 413.34,158.42 413.34,158.42C412.6,158.81 411.86,159.21 411.12,159.6" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
<path d="M189.87,233.08C192.21,233.53 195.89,233.88 199.83,232.48C202.38,231.58 204.57,229.76 208.94,226.13C212.74,222.97 215.01,220.58 219.52,217.54C220.52,216.87 221.31,216.38 222.08,215.93C226.07,213.63 227.8,213.54 229.89,212.46C233.02,210.83 232.27,209.4 239.08,201.25C242.37,197.32 243.25,196.81 243.8,196.53C246.68,195.09 248.06,196.23 253.47,194.86C256.45,194.1 256.67,193.59 258.84,193.42C261.17,193.24 262.24,193.72 265.21,194.01C266.77,194.17 269.06,194.29 271.89,194.06" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
<path d="M239.34,211.62C242.53,209.15 245.24,208.39 247.15,208.15C249.07,207.91 249.65,208.27 251.4,207.7C253.93,206.87 254.65,205.48 257.12,205.02C258.48,204.76 259.56,204.94 260.36,205.07C265.72,205.96 271.89,211.89 274.53,214.16C277.41,216.64 282.01,219.84 289.11,222.22" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
</g>
|
||||
<g id="Layer_6" transform="matrix(1.19255,0,0,1.11398,-58.9948,-25.8648)">
|
||||
<path d="M195.89,217.96L203.45,220.18L211,220.18L222.33,217.96L231.49,213.93L239.44,210.4C239.44,210.4 242.77,210.62 244.77,209.51C246.77,208.4 256.1,203.29 256.1,203.29L260.77,200.18L266.99,198.85C266.99,198.85 272.77,199.96 273.43,199.96" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
<path d="M254.11,209.51L257.44,209.51L260.33,208.18C260.33,208.18 264.77,209.07 266.11,209.51C267.45,209.95 272.11,209.51 272.11,209.51L273.89,208.94L278.33,213.06L286.631,221.356" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
<path d="M384.972,192.779C384.972,192.779 391.44,186.4 391.44,185.51C391.44,184.62 394.55,174.12 394.55,174.12L398.55,167.28L400.11,161.72C400.11,161.72 402.33,160.16 403.67,159.05C405,157.94 413,151.26 413.23,148.82C413.45,146.38 415.01,139.49 415.01,139.49" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
<path d="M443.89,141.51C442.56,142.62 425.67,155.73 425.67,155.73C425.67,155.73 421,159.29 418.56,159.95C416.12,160.62 408.56,162.17 408.56,162.17L405.45,163.28L402.12,163.28" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
<path d="M399.44,172.84L404.11,172.84L415,171.73L421.44,169.84" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
<path d="M397,214.62L393.44,208.18C393.44,208.18 392.77,205.38 392.55,203C392.33,200.62 390.99,199.51 390.33,197.29C389.66,195.07 387.3,192.69 387.3,192.69" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
<path d="M408.33,196.4C408.33,196.4 405.66,186.18 405.66,185.51C405.66,184.84 405.66,181.73 405.66,180.29C405.66,178.85 404.55,174.13 404.55,174.13L404.11,172.85" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
<path d="M375.952,162.292L380.33,151.07L386.33,139.51L389.89,135.07" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
1
src/media/runCommand.svg
Normal file
1
src/media/runCommand.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="16px" height="16px"><path fill="#c2e8ff" d="M24.533 26L0 26 0 34 22.933 34zM21.133 44L22.2 38 0 38 0 44zM20 52L20.533 48 0 48 0 52z"/><path fill="#fff" d="M14.605 65.5L24.413 14.5 79.39 14.5 69.19 65.5z"/><path fill="#788b9c" d="M78.78,15l-10,50h-53.57l9.615-50H78.78 M80,14H24L14,66h55.6L80,14L80,14z"/><path fill="#8bb7f0" d="M26.511 49.5L30.408 30.5 67.39 30.5 63.59 49.5z"/><path fill="#4e7ab5" d="M66.78,31l-3.6,18H27.124l3.691-18H66.78 M68,30H30l-4.102,20H64L68,30L68,30z"/><path fill="#788b9c" d="M79 14L25 14 23 24 77 24z"/><path fill="#fff" d="M31.673 42L61.4 42 62.6 36 32.873 36z"/><path fill="#37474f" d="M38.433 42L39.633 36 38.594 36 37.394 42z"/></svg>
|
||||
|
After Width: | Height: | Size: 734 B |
23
src/test/runTest.ts
Normal file
23
src/test/runTest.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import * as path from 'path';
|
||||
|
||||
import { runTests } from 'vscode-test';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// The folder containing the Extension Manifest package.json
|
||||
// Passed to `--extensionDevelopmentPath`
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
|
||||
|
||||
// The path to test runner
|
||||
// Passed to --extensionTestsPath
|
||||
const extensionTestsPath = path.resolve(__dirname, './suite/index');
|
||||
|
||||
// Download VS Code, unzip it and run the integration test
|
||||
await runTests({ extensionDevelopmentPath, extensionTestsPath });
|
||||
} catch (err) {
|
||||
console.error('Failed to run tests');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
15
src/test/suite/extension.test.ts
Normal file
15
src/test/suite/extension.test.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import * as assert from 'assert';
|
||||
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
import * as vscode from 'vscode';
|
||||
// import * as myExtension from '../../extension';
|
||||
|
||||
suite('Extension Test Suite', () => {
|
||||
vscode.window.showInformationMessage('Start all tests.');
|
||||
|
||||
test('Sample test', () => {
|
||||
assert.equal(-1, [1, 2, 3].indexOf(5));
|
||||
assert.equal(-1, [1, 2, 3].indexOf(0));
|
||||
});
|
||||
});
|
||||
38
src/test/suite/index.ts
Normal file
38
src/test/suite/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import * as path from 'path';
|
||||
import * as Mocha from 'mocha';
|
||||
import * as glob from 'glob';
|
||||
|
||||
export function run(): Promise<void> {
|
||||
// Create the mocha test
|
||||
const mocha = new Mocha({
|
||||
ui: 'tdd',
|
||||
color: true
|
||||
});
|
||||
|
||||
const testsRoot = path.resolve(__dirname, '..');
|
||||
|
||||
return new Promise((c, e) => {
|
||||
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
// Add files to the test suite
|
||||
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
|
||||
|
||||
try {
|
||||
// Run the mocha test
|
||||
mocha.run(failures => {
|
||||
if (failures > 0) {
|
||||
e(new Error(`${failures} tests failed.`));
|
||||
} else {
|
||||
c();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "out",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true /* enable all strict type-checking options */
|
||||
/* Additional Checks */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test"
|
||||
]
|
||||
}
|
||||
42
vsc-extension-quickstart.md
Normal file
42
vsc-extension-quickstart.md
Normal file
@ -0,0 +1,42 @@
|
||||
# Welcome to your VS Code Extension
|
||||
|
||||
## What's in the folder
|
||||
|
||||
* This folder contains all of the files necessary for your extension.
|
||||
* `package.json` - this is the manifest file in which you declare your extension and command.
|
||||
* The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.
|
||||
* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
|
||||
* The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
|
||||
* We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
|
||||
|
||||
## Get up and running straight away
|
||||
|
||||
* Press `F5` to open a new window with your extension loaded.
|
||||
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
|
||||
* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
|
||||
* Find output from your extension in the debug console.
|
||||
|
||||
## Make changes
|
||||
|
||||
* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
|
||||
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
|
||||
|
||||
|
||||
## Explore the API
|
||||
|
||||
* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
|
||||
|
||||
## Run tests
|
||||
|
||||
* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`.
|
||||
* Press `F5` to run the tests in a new window with your extension loaded.
|
||||
* See the output of the test result in the debug console.
|
||||
* Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder.
|
||||
* The provided test runner will only consider files matching the name pattern `**.test.ts`.
|
||||
* You can create folders inside the `test` folder to structure your tests any way you want.
|
||||
|
||||
## Go further
|
||||
|
||||
* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
|
||||
* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace.
|
||||
* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
|
||||
Loading…
x
Reference in New Issue
Block a user