@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.
Files changed (54) hide show
  1. package/README.md +695 -0
  2. package/bin/run +5 -0
  3. package/bin/run.cmd +3 -0
  4. package/oclif.manifest.json +1 -0
  5. package/package.json +111 -0
  6. package/plugin/command/compile.js +17 -0
  7. package/plugin/command/test.js +29 -0
  8. package/plugin/index.js +6 -0
  9. package/plugin/plugin.js +71 -0
  10. package/plugin/utils.js +78 -0
  11. package/src/commands/audit.js +243 -0
  12. package/src/commands/clean.js +27 -0
  13. package/src/commands/download.js +52 -0
  14. package/src/commands/hello.js +20 -0
  15. package/src/commands/init.js +133 -0
  16. package/src/commands/login.js +45 -0
  17. package/src/commands/logout.js +39 -0
  18. package/src/commands/publish.js +78 -0
  19. package/src/commands/start.js +169 -0
  20. package/src/commands/test.js +85 -0
  21. package/src/index.js +1 -0
  22. package/src/managers/config/allowed_files.js +12 -0
  23. package/src/managers/config/defaults.js +32 -0
  24. package/src/managers/config/exercise.js +212 -0
  25. package/src/managers/config/index.js +342 -0
  26. package/src/managers/file.js +137 -0
  27. package/src/managers/server/index.js +62 -0
  28. package/src/managers/server/routes.js +151 -0
  29. package/src/managers/session.js +83 -0
  30. package/src/managers/socket.js +185 -0
  31. package/src/managers/test.js +77 -0
  32. package/src/ui/download.js +48 -0
  33. package/src/utils/BaseCommand.js +34 -0
  34. package/src/utils/SessionCommand.js +46 -0
  35. package/src/utils/api.js +164 -0
  36. package/src/utils/audit.js +114 -0
  37. package/src/utils/console.js +16 -0
  38. package/src/utils/errors.js +90 -0
  39. package/src/utils/exercisesQueue.js +45 -0
  40. package/src/utils/fileQueue.js +194 -0
  41. package/src/utils/misc.js +26 -0
  42. package/src/utils/templates/gitignore.txt +20 -0
  43. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +26 -0
  44. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +25 -0
  45. package/src/utils/templates/incremental/README.ejs +5 -0
  46. package/src/utils/templates/incremental/README.es.ejs +5 -0
  47. package/src/utils/templates/isolated/01-hello-world/README.es.md +27 -0
  48. package/src/utils/templates/isolated/01-hello-world/README.md +27 -0
  49. package/src/utils/templates/isolated/README.ejs +5 -0
  50. package/src/utils/templates/isolated/README.es.ejs +5 -0
  51. package/src/utils/templates/no-grading/README.ejs +5 -0
  52. package/src/utils/templates/no-grading/README.es.ejs +5 -0
  53. package/src/utils/validators.js +15 -0
  54. 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
+ }