@learnpack/learnpack 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +695 -0
- package/bin/run +5 -0
- package/bin/run.cmd +3 -0
- package/oclif.manifest.json +1 -0
- package/package.json +111 -0
- package/plugin/command/compile.js +17 -0
- package/plugin/command/test.js +29 -0
- package/plugin/index.js +6 -0
- package/plugin/plugin.js +71 -0
- package/plugin/utils.js +78 -0
- package/src/commands/audit.js +243 -0
- package/src/commands/clean.js +27 -0
- package/src/commands/download.js +52 -0
- package/src/commands/hello.js +20 -0
- package/src/commands/init.js +133 -0
- package/src/commands/login.js +45 -0
- package/src/commands/logout.js +39 -0
- package/src/commands/publish.js +78 -0
- package/src/commands/start.js +169 -0
- package/src/commands/test.js +85 -0
- package/src/index.js +1 -0
- package/src/managers/config/allowed_files.js +12 -0
- package/src/managers/config/defaults.js +32 -0
- package/src/managers/config/exercise.js +212 -0
- package/src/managers/config/index.js +342 -0
- package/src/managers/file.js +137 -0
- package/src/managers/server/index.js +62 -0
- package/src/managers/server/routes.js +151 -0
- package/src/managers/session.js +83 -0
- package/src/managers/socket.js +185 -0
- package/src/managers/test.js +77 -0
- package/src/ui/download.js +48 -0
- package/src/utils/BaseCommand.js +34 -0
- package/src/utils/SessionCommand.js +46 -0
- package/src/utils/api.js +164 -0
- package/src/utils/audit.js +114 -0
- package/src/utils/console.js +16 -0
- package/src/utils/errors.js +90 -0
- package/src/utils/exercisesQueue.js +45 -0
- package/src/utils/fileQueue.js +194 -0
- package/src/utils/misc.js +26 -0
- package/src/utils/templates/gitignore.txt +20 -0
- package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +26 -0
- package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +25 -0
- package/src/utils/templates/incremental/README.ejs +5 -0
- package/src/utils/templates/incremental/README.es.ejs +5 -0
- package/src/utils/templates/isolated/01-hello-world/README.es.md +27 -0
- package/src/utils/templates/isolated/01-hello-world/README.md +27 -0
- package/src/utils/templates/isolated/README.ejs +5 -0
- package/src/utils/templates/isolated/README.es.ejs +5 -0
- package/src/utils/templates/no-grading/README.ejs +5 -0
- package/src/utils/templates/no-grading/README.es.ejs +5 -0
- package/src/utils/validators.js +15 -0
- package/src/utils/watcher.js +24 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
const {flags} = require('@oclif/command')
|
2
|
+
const BaseCommand = require('../utils/BaseCommand')
|
3
|
+
|
4
|
+
const fs = require('fs-extra')
|
5
|
+
const prompts = require('prompts')
|
6
|
+
const cli = require("cli-ux").default
|
7
|
+
const eta = require("eta")
|
8
|
+
|
9
|
+
const Console = require('../utils/console')
|
10
|
+
const { ValidationError } = require('../utils/errors')
|
11
|
+
let defaults = require('../managers/config/defaults.js')
|
12
|
+
|
13
|
+
const path = require('path')
|
14
|
+
const { resolve } = require('path')
|
15
|
+
|
16
|
+
class InitComand extends BaseCommand {
|
17
|
+
async run() {
|
18
|
+
const {flags} = this.parse(InitComand)
|
19
|
+
|
20
|
+
try{
|
21
|
+
// if the folder/file .learn or .breathecode aleady exists
|
22
|
+
await alreadyInitialized();
|
23
|
+
}
|
24
|
+
catch(error){
|
25
|
+
Console.error(error.message)
|
26
|
+
return false
|
27
|
+
}
|
28
|
+
|
29
|
+
let choices = await prompts([
|
30
|
+
{
|
31
|
+
type: 'select',
|
32
|
+
name: 'grading',
|
33
|
+
message: 'Is the auto-grading going to be isolated or incremental?',
|
34
|
+
choices: [
|
35
|
+
{ title: 'Incremental: Build on top of each other like a tutorial', value: 'incremental' },
|
36
|
+
{ title: 'Isolated: Small isolated exercises', value: 'isolated' },
|
37
|
+
{ title: 'No grading: No feedback or testing whatsoever', value: null },
|
38
|
+
],
|
39
|
+
},{
|
40
|
+
type: 'text',
|
41
|
+
name: 'title',
|
42
|
+
initial: 'My Interactive Tutorial',
|
43
|
+
message: 'Title for your tutorial? Press enter to leave as it is'
|
44
|
+
},{
|
45
|
+
type: 'text',
|
46
|
+
name: 'description',
|
47
|
+
initial: '',
|
48
|
+
message: 'Description for your tutorial? Press enter to leave blank'
|
49
|
+
},{
|
50
|
+
type: 'select',
|
51
|
+
name: 'difficulty',
|
52
|
+
message: 'How difficulty will be to complete the tutorial?',
|
53
|
+
choices: [
|
54
|
+
{ title: 'Begginer (no previous experience)', value: 'beginner' },
|
55
|
+
{ title: 'Easy (just a bit of experience required)', value: 'easy' },
|
56
|
+
{ title: 'Intermediate (you need experience)', value: 'intermediate' },
|
57
|
+
{ title: 'Hard (master the topic)', value: 'hard' },
|
58
|
+
],
|
59
|
+
},{
|
60
|
+
type: 'text',
|
61
|
+
name: 'duration',
|
62
|
+
initial: "1",
|
63
|
+
message: 'How many hours avg it takes to complete (number)?',
|
64
|
+
validate: value => {
|
65
|
+
var n = Math.floor(Number(value))
|
66
|
+
return n !== Infinity && String(n) === value && n >= 0
|
67
|
+
}
|
68
|
+
}
|
69
|
+
])
|
70
|
+
|
71
|
+
const packageInfo = {
|
72
|
+
...defaults.config,
|
73
|
+
grading: choices.grading,
|
74
|
+
difficulty: choices.difficulty,
|
75
|
+
duration: parseInt(choices.duration),
|
76
|
+
description: choices.description,
|
77
|
+
title: choices.title,
|
78
|
+
slug: choices.title.toLowerCase().replace(/ /g,'-').replace(/[^\w-]+/g,'')
|
79
|
+
}
|
80
|
+
|
81
|
+
cli.action.start('Initializing package')
|
82
|
+
|
83
|
+
const languages = ['en', 'es']
|
84
|
+
|
85
|
+
try{
|
86
|
+
const templatesDir = path.resolve(__dirname,"../utils/templates/"+choices.grading || "no-grading")
|
87
|
+
if(!fs.existsSync(templatesDir)) throw ValidationError(`Template ${templatesDir} does not exists`)
|
88
|
+
await fs.copySync(templatesDir, './')
|
89
|
+
|
90
|
+
// Creating README files
|
91
|
+
languages.forEach((language) => {
|
92
|
+
const readmeFilename = `README${language !== 'en' ? `.${language}` : ''}`
|
93
|
+
fs.writeFileSync(`./${readmeFilename}.md`, eta.render(fs.readFileSync(path.resolve(__dirname,`${templatesDir}/${readmeFilename}.ejs`),'utf-8'), packageInfo))
|
94
|
+
if(fs.existsSync(`./${readmeFilename}.ejs`)) fs.removeSync(`./${readmeFilename}.ejs`)
|
95
|
+
})
|
96
|
+
|
97
|
+
if(!fs.existsSync('./.gitignore')) fs.copyFile(path.resolve(__dirname,'../utils/templates/gitignore.txt'), './.gitignore')
|
98
|
+
fs.writeFileSync('./learn.json', JSON.stringify(packageInfo, null, 2))
|
99
|
+
}
|
100
|
+
catch(error){
|
101
|
+
Console.error(error.message || error)
|
102
|
+
return false
|
103
|
+
}
|
104
|
+
|
105
|
+
cli.action.stop()
|
106
|
+
Console.success(`😋 Package initialized successfully`)
|
107
|
+
Console.help(`Start the exercises by running the following command on your terminal: $ learnpack start`)
|
108
|
+
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
InitComand.description = 'Create a new learning package: Book, Tutorial or Exercise'
|
113
|
+
InitComand.flags = {
|
114
|
+
...BaseCommand.flags,
|
115
|
+
grading: flags.help({char:'h'}),
|
116
|
+
}
|
117
|
+
|
118
|
+
const alreadyInitialized = () => new Promise((resolve, reject) => {
|
119
|
+
fs.readdir('./', function(err, files) {
|
120
|
+
files = files.filter(f => ['.learn', 'learn.json', 'bc.json', '.breathecode', '.gitignore'].includes(f))
|
121
|
+
if (err) {
|
122
|
+
reject(ValidationError(err.message))
|
123
|
+
return true
|
124
|
+
} else if (files.length > 0){
|
125
|
+
reject(ValidationError("It seems the package is already initialized because we've found the following files: "+files.join(',')))
|
126
|
+
return true
|
127
|
+
}
|
128
|
+
|
129
|
+
resolve(false)
|
130
|
+
})
|
131
|
+
})
|
132
|
+
|
133
|
+
module.exports = InitComand
|
@@ -0,0 +1,45 @@
|
|
1
|
+
const {Command, flags} = require('@oclif/command')
|
2
|
+
const { prompt } = require("enquirer")
|
3
|
+
const fetch = require('node-fetch');
|
4
|
+
const SessionCommand = require('../utils/SessionCommand')
|
5
|
+
const SessionManager = require('../managers/session.js')
|
6
|
+
const Console = require('../utils/console');
|
7
|
+
const { replace } = require('node-emoji');
|
8
|
+
const { validURL } = require("../utils/validators")
|
9
|
+
// const BaseCommand = require('../utils/BaseCommand');
|
10
|
+
|
11
|
+
class PublishCommand extends SessionCommand {
|
12
|
+
async init() {
|
13
|
+
const {flags} = this.parse(PublishCommand)
|
14
|
+
await this.initSession(flags)
|
15
|
+
}
|
16
|
+
async run() {
|
17
|
+
const {flags, args} = this.parse(PublishCommand)
|
18
|
+
|
19
|
+
try{
|
20
|
+
await SessionManager.login();
|
21
|
+
}
|
22
|
+
catch(error){
|
23
|
+
Console.error("Error trying to authenticate")
|
24
|
+
Console.error(error.message || error)
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
PublishCommand.description = `Describe the command here
|
30
|
+
...
|
31
|
+
Extra documentation goes here
|
32
|
+
`
|
33
|
+
PublishCommand.flags = {
|
34
|
+
// name: flags.string({char: 'n', description: 'name to print'}),
|
35
|
+
}
|
36
|
+
PublishCommand.args =[
|
37
|
+
{
|
38
|
+
name: 'package', // name of arg to show in help and reference with args[name]
|
39
|
+
required: false, // make the arg required with `required: true`
|
40
|
+
description: 'The unique string that identifies this package on learnpack', // help description
|
41
|
+
hidden: false // hide this arg from help
|
42
|
+
}
|
43
|
+
]
|
44
|
+
|
45
|
+
module.exports = PublishCommand
|
@@ -0,0 +1,39 @@
|
|
1
|
+
const {Command, flags} = require('@oclif/command')
|
2
|
+
const { prompt } = require("enquirer")
|
3
|
+
const fetch = require('node-fetch');
|
4
|
+
const SessionCommand = require('../utils/SessionCommand')
|
5
|
+
const SessionManager = require('../managers/session.js')
|
6
|
+
const Console = require('../utils/console');
|
7
|
+
const { replace } = require('node-emoji');
|
8
|
+
const { validURL } = require("../utils/validators")
|
9
|
+
// const BaseCommand = require('../utils/BaseCommand');
|
10
|
+
|
11
|
+
class PublishCommand extends SessionCommand {
|
12
|
+
async init() {
|
13
|
+
const {flags} = this.parse(PublishCommand)
|
14
|
+
await this.initSession(flags)
|
15
|
+
}
|
16
|
+
async run() {
|
17
|
+
const {flags, args} = this.parse(PublishCommand)
|
18
|
+
|
19
|
+
SessionManager.destroy();
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
PublishCommand.description = `Describe the command here
|
24
|
+
...
|
25
|
+
Extra documentation goes here
|
26
|
+
`
|
27
|
+
PublishCommand.flags = {
|
28
|
+
// name: flags.string({char: 'n', description: 'name to print'}),
|
29
|
+
}
|
30
|
+
PublishCommand.args =[
|
31
|
+
{
|
32
|
+
name: 'package', // name of arg to show in help and reference with args[name]
|
33
|
+
required: false, // make the arg required with `required: true`
|
34
|
+
description: 'The unique string that identifies this package on learnpack', // help description
|
35
|
+
hidden: false // hide this arg from help
|
36
|
+
}
|
37
|
+
]
|
38
|
+
|
39
|
+
module.exports = PublishCommand
|
@@ -0,0 +1,78 @@
|
|
1
|
+
const {Command, flags} = require('@oclif/command')
|
2
|
+
const { prompt } = require("enquirer")
|
3
|
+
const fetch = require('node-fetch');
|
4
|
+
const SessionCommand = require('../utils/SessionCommand')
|
5
|
+
const Console = require('../utils/console');
|
6
|
+
const api = require('../utils/api');
|
7
|
+
const { replace } = require('node-emoji');
|
8
|
+
const { validURL } = require("../utils/validators")
|
9
|
+
// const BaseCommand = require('../utils/BaseCommand');
|
10
|
+
|
11
|
+
class PublishCommand extends SessionCommand {
|
12
|
+
async init() {
|
13
|
+
const {flags} = this.parse(PublishCommand)
|
14
|
+
await this.initSession(flags, true)
|
15
|
+
}
|
16
|
+
async run() {
|
17
|
+
const {flags, args} = this.parse(PublishCommand)
|
18
|
+
|
19
|
+
// avoid annonymus sessions
|
20
|
+
if(!this.session) return;
|
21
|
+
Console.info(`Session found for ${this.session.payload.email}, publishing the package...`)
|
22
|
+
|
23
|
+
const configObject = this.configManager.get()
|
24
|
+
if(configObject.slug === undefined || !configObject.slug)
|
25
|
+
throw new Error("The package is missing a slug (unique name identifier), please check your learn.json file and make sure it has a 'slug'")
|
26
|
+
if(!validURL(configObject.repository))
|
27
|
+
throw new Error("The package has a missing or invalid 'repository' on the configuration file, it needs to be a Github URL")
|
28
|
+
else{
|
29
|
+
const validateResp = await fetch(configObject.repository);
|
30
|
+
if(validateResp.status !== 200)
|
31
|
+
throw new Error(`The specified repository URL on the configuration file does not exist or its private, only public repositories are allowed at the moment: ${configObject.repository}`)
|
32
|
+
}
|
33
|
+
|
34
|
+
// start watching for file changes
|
35
|
+
try{
|
36
|
+
const data = await api.publish({
|
37
|
+
...configObject,
|
38
|
+
author: this.session.payload.user_id
|
39
|
+
});
|
40
|
+
Console.success(`Package updated and published successfully: ${configObject.slug}`)
|
41
|
+
}catch(error){
|
42
|
+
if(error.status === 404){
|
43
|
+
const answer = await prompt([{
|
44
|
+
type: 'confirm',
|
45
|
+
name: 'create',
|
46
|
+
message: `Package with slug ${configObject.slug} does not exist, do you want to create it?`,
|
47
|
+
}])
|
48
|
+
if(answer){
|
49
|
+
const data2 = await api.update({
|
50
|
+
...configObject,
|
51
|
+
author: this.session.payload.user_id
|
52
|
+
})
|
53
|
+
Console.success(`Package created and published successfully: ${configObject.slug}`)
|
54
|
+
}
|
55
|
+
else Console.error("No answer from server")
|
56
|
+
}
|
57
|
+
else Console.error(error.message)
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
PublishCommand.description = `Describe the command here
|
63
|
+
...
|
64
|
+
Extra documentation goes here
|
65
|
+
`
|
66
|
+
PublishCommand.flags = {
|
67
|
+
// name: flags.string({char: 'n', description: 'name to print'}),
|
68
|
+
}
|
69
|
+
PublishCommand.args =[
|
70
|
+
{
|
71
|
+
name: 'package', // name of arg to show in help and reference with args[name]
|
72
|
+
required: false, // make the arg required with `required: true`
|
73
|
+
description: 'The unique string that identifies this package on learnpack', // help description
|
74
|
+
hidden: false // hide this arg from help
|
75
|
+
}
|
76
|
+
]
|
77
|
+
|
78
|
+
module.exports = PublishCommand
|
@@ -0,0 +1,169 @@
|
|
1
|
+
const path = require("path")
|
2
|
+
const {flags} = require('@oclif/command')
|
3
|
+
const SessionCommand = require('../utils/SessionCommand')
|
4
|
+
const Console = require('../utils/console')
|
5
|
+
const socket = require('../managers/socket.js')
|
6
|
+
const queue = require("../utils/fileQueue")
|
7
|
+
const { download, decompress, downloadEditor } = require('../managers/file.js')
|
8
|
+
const { prioritizeHTMLFile } = require('../utils/misc')
|
9
|
+
|
10
|
+
const createServer = require('../managers/server')
|
11
|
+
|
12
|
+
class StartCommand extends SessionCommand {
|
13
|
+
constructor(...params){
|
14
|
+
super(...params)
|
15
|
+
}
|
16
|
+
|
17
|
+
// 🛑 IMPORTANT:
|
18
|
+
// Every command that will use the configManager needs this init method
|
19
|
+
async init() {
|
20
|
+
const {flags} = this.parse(StartCommand)
|
21
|
+
await this.initSession(flags)
|
22
|
+
}
|
23
|
+
|
24
|
+
async run() {
|
25
|
+
|
26
|
+
// const {flags} = this.parse(StartCommand)
|
27
|
+
|
28
|
+
// get configuration object
|
29
|
+
const configObject = this.configManager.get()
|
30
|
+
const { config } = configObject;
|
31
|
+
|
32
|
+
// build exercises
|
33
|
+
this.configManager.buildIndex()
|
34
|
+
|
35
|
+
Console.debug(`Grading: ${config.grading} ${config.disableGrading ? "(disabled)" : ""}, editor: ${config.editor.mode} ${config.editor.version}, for ${Array.isArray(config.exercises) ? config.exercises.length : 0} exercises found`)
|
36
|
+
|
37
|
+
// download app and decompress
|
38
|
+
let resp = await downloadEditor(config.editor.version, `${config.dirPath}/app.tar.gz`)
|
39
|
+
|
40
|
+
Console.info("Decompressing LearnPack UI, this may take a minute...")
|
41
|
+
await decompress(`${config.dirPath}/app.tar.gz`, `${config.dirPath}/_app/`)
|
42
|
+
|
43
|
+
const server = await createServer(configObject, this.configManager)
|
44
|
+
|
45
|
+
const dispatcher = queue.dispatcher({ create: true, path: `${config.dirPath}/vscode_queue.json` })
|
46
|
+
|
47
|
+
// listen to socket commands
|
48
|
+
socket.start(config, server)
|
49
|
+
|
50
|
+
socket.on("open", (data) => {
|
51
|
+
Console.debug("Opening these files: ", data)
|
52
|
+
|
53
|
+
let files = prioritizeHTMLFile(data.files);
|
54
|
+
|
55
|
+
dispatcher.enqueue(dispatcher.events.OPEN_FILES, files);
|
56
|
+
socket.ready('Ready to compile...')
|
57
|
+
})
|
58
|
+
|
59
|
+
socket.on("open_window", (data) => {
|
60
|
+
Console.debug("Opening window: ", data)
|
61
|
+
dispatcher.enqueue(dispatcher.events.OPEN_WINDOW, data)
|
62
|
+
socket.ready('Ready to compile...')
|
63
|
+
})
|
64
|
+
|
65
|
+
socket.on("reset", (exercise) => {
|
66
|
+
try{
|
67
|
+
this.configManager.reset(exercise.exerciseSlug)
|
68
|
+
dispatcher.enqueue(dispatcher.events.RESET_EXERCISE, exercise.exerciseSlug)
|
69
|
+
socket.ready('Ready to compile...')
|
70
|
+
}
|
71
|
+
catch(error){
|
72
|
+
socket.error('compiler-error', error.message || "There was an error reseting the exercise")
|
73
|
+
setTimeout(() => socket.ready('Ready to compile...'), 2000)
|
74
|
+
}
|
75
|
+
})
|
76
|
+
// socket.on("preview", (data) => {
|
77
|
+
// Console.debug("Preview triggered, removing the 'preview' action ")
|
78
|
+
// socket.removeAllowed("preview")
|
79
|
+
// socket.log('ready',['Ready to compile...'])
|
80
|
+
// })
|
81
|
+
|
82
|
+
socket.on("build", async (data) => {
|
83
|
+
const exercise = this.configManager.getExercise(data.exerciseSlug)
|
84
|
+
|
85
|
+
if(!exercise.language){
|
86
|
+
socket.error('compiler-error','Impossible to detect language to build for '+data.exerciseSlug+'...')
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
|
90
|
+
// validate plugins installation for compiler
|
91
|
+
//if(!this.configManager.validateEngine(exercise.language, server, socket)) return false;
|
92
|
+
|
93
|
+
socket.log('compiling','Building exercise '+data.exerciseSlug+' with '+exercise.language+'...')
|
94
|
+
const stdout = await this.config.runHook('action', {
|
95
|
+
action: 'compile',
|
96
|
+
socket, configuration: config,
|
97
|
+
exercise,
|
98
|
+
})
|
99
|
+
|
100
|
+
|
101
|
+
})
|
102
|
+
|
103
|
+
socket.on("test", async (data) => {
|
104
|
+
const exercise = this.configManager.getExercise(data.exerciseSlug)
|
105
|
+
|
106
|
+
if(!exercise.language){
|
107
|
+
socket.error('compiler-error','Impossible to detect engine language for testing for '+data.exerciseSlug+'...')
|
108
|
+
return;
|
109
|
+
}
|
110
|
+
|
111
|
+
if(config.disableGrading){
|
112
|
+
socket.ready('Grading is disabled on configuration')
|
113
|
+
return true;
|
114
|
+
}
|
115
|
+
|
116
|
+
// validate plugins installation for compiler
|
117
|
+
//if(!this.configManager.validateEngine(exercise.language, server, socket)) return false;
|
118
|
+
|
119
|
+
socket.log('testing','Testing your exercise using the '+exercise.language+' engine.')
|
120
|
+
|
121
|
+
const stdout = await this.config.runHook('action', {
|
122
|
+
action: 'test',
|
123
|
+
socket, configuration: config,
|
124
|
+
exercise,
|
125
|
+
})
|
126
|
+
this.configManager.save()
|
127
|
+
|
128
|
+
return true;
|
129
|
+
})
|
130
|
+
|
131
|
+
const terminate = () => {
|
132
|
+
Console.debug("Terminating Learnpack...")
|
133
|
+
server.terminate(() => {
|
134
|
+
this.configManager.noCurrentExercise()
|
135
|
+
dispatcher.enqueue(dispatcher.events.END)
|
136
|
+
process.exit();
|
137
|
+
})
|
138
|
+
}
|
139
|
+
|
140
|
+
server.on('close', terminate);
|
141
|
+
process.on('SIGINT', terminate);
|
142
|
+
process.on('SIGTERM', terminate);
|
143
|
+
process.on('SIGHUP', terminate);
|
144
|
+
|
145
|
+
|
146
|
+
// finish the server startup
|
147
|
+
setTimeout(() => dispatcher.enqueue(dispatcher.events.RUNNING), 1000)
|
148
|
+
|
149
|
+
// start watching for file changes
|
150
|
+
if(flags.watch) this.configManager.watchIndex((_exercises) => socket.reload(null, _exercises));
|
151
|
+
|
152
|
+
}
|
153
|
+
|
154
|
+
}
|
155
|
+
|
156
|
+
StartCommand.description = `Runs a small server with all the exercise instructions`
|
157
|
+
|
158
|
+
StartCommand.flags = {
|
159
|
+
...SessionCommand.flags,
|
160
|
+
port: flags.string({char: 'p', description: 'server port' }),
|
161
|
+
host: flags.string({char: 'h', description: 'server host' }),
|
162
|
+
disableGrading: flags.boolean({char: 'dg', description: 'disble grading functionality' }),
|
163
|
+
watch: flags.boolean({char: 'w', description: 'Watch for file changes', default: false }),
|
164
|
+
mode: flags.string({ char: 'm', description: 'Load a standalone editor or just the preview to be embeded in another editor: Choices: [standalone, preview]', options: ['standalone', 'preview'] }),
|
165
|
+
version: flags.string({ char: 'v', description: 'E.g: 1.0.1', default: null }),
|
166
|
+
grading: flags.string({ char: 'g', description: '[isolated, incremental]', options: ['isolated', 'incremental'] }),
|
167
|
+
debug: flags.boolean({char: 'd', description: 'debugger mode for more verbage', default: false })
|
168
|
+
}
|
169
|
+
module.exports = StartCommand
|
@@ -0,0 +1,85 @@
|
|
1
|
+
const Console = require("../utils/console");
|
2
|
+
const SessionCommand = require("../utils/SessionCommand");
|
3
|
+
const socket = require("../managers/socket.js");
|
4
|
+
|
5
|
+
const createServer = require("../managers/server");
|
6
|
+
const ExercisesQueue = require("../utils/exercisesQueue");
|
7
|
+
|
8
|
+
class TestCommand extends SessionCommand {
|
9
|
+
async init() {
|
10
|
+
const { flags } = this.parse(TestCommand);
|
11
|
+
await this.initSession(flags);
|
12
|
+
}
|
13
|
+
async run() {
|
14
|
+
const {
|
15
|
+
args: { exerciseSlug },
|
16
|
+
} = this.parse(TestCommand);
|
17
|
+
|
18
|
+
// Build exercises index
|
19
|
+
this.configManager.buildIndex();
|
20
|
+
|
21
|
+
let exercises = [];
|
22
|
+
|
23
|
+
// test all exercises
|
24
|
+
if (!exerciseSlug) {
|
25
|
+
exercises = this.configManager.getAllExercises();
|
26
|
+
} else {
|
27
|
+
exercises = [this.configManager.getExercise(exerciseSlug)];
|
28
|
+
}
|
29
|
+
|
30
|
+
const exercisesQueue = new ExercisesQueue(exercises);
|
31
|
+
|
32
|
+
const configObject = this.configManager.get();
|
33
|
+
|
34
|
+
let hasFailed = false;
|
35
|
+
let failedTestsCount = 0;
|
36
|
+
let successTestsCount = 0;
|
37
|
+
let testsToRunCount = exercisesQueue.size();
|
38
|
+
|
39
|
+
configObject.config.testingFinishedCallback = ({ result }) => {
|
40
|
+
if (result === "failed") {
|
41
|
+
hasFailed = true;
|
42
|
+
failedTestsCount++;
|
43
|
+
} else {
|
44
|
+
successTestsCount++;
|
45
|
+
}
|
46
|
+
|
47
|
+
if (exercisesQueue.isEmpty()) {
|
48
|
+
Console.info(
|
49
|
+
`${testsToRunCount} test${testsToRunCount > 1 ? "s" : ""} runned`
|
50
|
+
);
|
51
|
+
Console.success(
|
52
|
+
`${successTestsCount} test${successTestsCount > 1 ? "s" : ""} passed`
|
53
|
+
);
|
54
|
+
Console.error(
|
55
|
+
`${failedTestsCount} test${failedTestsCount > 1 ? "s" : ""} failed`
|
56
|
+
);
|
57
|
+
|
58
|
+
process.exit(hasFailed ? 1 : 0);
|
59
|
+
} else {
|
60
|
+
exercisesQueue.pop().test(this.config, config, socket);
|
61
|
+
}
|
62
|
+
};
|
63
|
+
|
64
|
+
const { config } = configObject;
|
65
|
+
|
66
|
+
const server = await createServer(configObject, this.configManager, true);
|
67
|
+
|
68
|
+
socket.start(config, server, true);
|
69
|
+
|
70
|
+
exercisesQueue.pop().test(this.config, config, socket);
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
TestCommand.description = `Test exercises`;
|
75
|
+
|
76
|
+
TestCommand.args = [
|
77
|
+
{
|
78
|
+
name: "exerciseSlug",
|
79
|
+
required: false,
|
80
|
+
description: "The name of the exercise to test",
|
81
|
+
hidden: false,
|
82
|
+
},
|
83
|
+
];
|
84
|
+
|
85
|
+
module.exports = TestCommand;
|
package/src/index.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
module.exports = require('@oclif/command')
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module.exports = {
|
2
|
+
extensions: [
|
3
|
+
'py', 'java','py','ruby', 'html', 'css', 'htm', 'php', //images
|
4
|
+
'js','jsx', 'ts', //images
|
5
|
+
'sh','bash', //images
|
6
|
+
'json', 'yml', 'yaml', 'csv', 'xml', // file storage extensions
|
7
|
+
'txt', 'text', 'markdown', 'readme', // compressed files
|
8
|
+
],
|
9
|
+
names: [
|
10
|
+
'package.json', 'package-lock.json'
|
11
|
+
]
|
12
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module.exports = {
|
2
|
+
config: {
|
3
|
+
port: 3000,
|
4
|
+
address: "http://localhost",
|
5
|
+
editor: {
|
6
|
+
mode: null, //[standalone, preview]
|
7
|
+
agent: null, //[vscode, theia]
|
8
|
+
version: null
|
9
|
+
},
|
10
|
+
dirPath: './.learn',
|
11
|
+
configPath: './learn.json',
|
12
|
+
outputPath: './.learn/dist',
|
13
|
+
publicPath: '/preview',
|
14
|
+
publicUrl: null,
|
15
|
+
language: "auto",
|
16
|
+
grading: 'isolated', // [isolated, incremental]
|
17
|
+
exercisesPath: './', // path to the folder that contains the exercises
|
18
|
+
webpackTemplate: null, // if you want webpack to use an HTML template
|
19
|
+
disableGrading: false,
|
20
|
+
disabledActions: [], //Possible: 'build', 'test' or 'reset'
|
21
|
+
actions: [], // ⚠️ deprecated, leave empty )
|
22
|
+
entries: {
|
23
|
+
html: "index.html",
|
24
|
+
vanillajs: "index.js",
|
25
|
+
react: "app.jsx",
|
26
|
+
node: "app.js",
|
27
|
+
python3: "app.py",
|
28
|
+
java: "app.java",
|
29
|
+
}
|
30
|
+
},
|
31
|
+
currentExercise: null
|
32
|
+
}
|