@learnpack/learnpack 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. package/README.md +51 -398
  2. package/bin/run +14 -2
  3. package/oclif.manifest.json +1 -1
  4. package/package.json +135 -111
  5. package/src/commands/audit.ts +462 -0
  6. package/src/commands/clean.ts +29 -0
  7. package/src/commands/download.ts +62 -0
  8. package/src/commands/init.ts +169 -0
  9. package/src/commands/login.ts +42 -0
  10. package/src/commands/logout.ts +43 -0
  11. package/src/commands/publish.ts +107 -0
  12. package/src/commands/start.ts +229 -0
  13. package/src/commands/{test.js → test.ts} +19 -21
  14. package/src/index.ts +1 -0
  15. package/src/managers/config/allowed_files.ts +29 -0
  16. package/src/managers/config/defaults.ts +33 -0
  17. package/src/managers/config/exercise.ts +295 -0
  18. package/src/managers/config/index.ts +411 -0
  19. package/src/managers/file.ts +169 -0
  20. package/src/managers/gitpod.ts +84 -0
  21. package/src/managers/server/{index.js → index.ts} +26 -19
  22. package/src/managers/server/routes.ts +250 -0
  23. package/src/managers/session.ts +118 -0
  24. package/src/managers/socket.ts +239 -0
  25. package/src/managers/test.ts +83 -0
  26. package/src/models/action.ts +3 -0
  27. package/src/models/audit-errors.ts +4 -0
  28. package/src/models/config-manager.ts +23 -0
  29. package/src/models/config.ts +74 -0
  30. package/src/models/counter.ts +11 -0
  31. package/src/models/errors.ts +22 -0
  32. package/src/models/exercise-obj.ts +26 -0
  33. package/src/models/file.ts +5 -0
  34. package/src/models/findings.ts +18 -0
  35. package/src/models/flags.ts +10 -0
  36. package/src/models/front-matter.ts +11 -0
  37. package/src/models/gitpod-data.ts +19 -0
  38. package/src/models/language.ts +4 -0
  39. package/src/models/package.ts +7 -0
  40. package/src/models/plugin-config.ts +17 -0
  41. package/src/models/session.ts +26 -0
  42. package/src/models/socket.ts +48 -0
  43. package/src/models/status.ts +15 -0
  44. package/src/models/success-types.ts +1 -0
  45. package/src/plugin/command/compile.ts +17 -0
  46. package/src/plugin/command/test.ts +30 -0
  47. package/src/plugin/index.ts +6 -0
  48. package/src/plugin/plugin.ts +94 -0
  49. package/src/plugin/utils.ts +87 -0
  50. package/src/types/node-fetch.d.ts +1 -0
  51. package/src/ui/download.ts +71 -0
  52. package/src/utils/BaseCommand.ts +48 -0
  53. package/src/utils/SessionCommand.ts +48 -0
  54. package/src/utils/api.ts +194 -0
  55. package/src/utils/audit.ts +162 -0
  56. package/src/utils/console.ts +24 -0
  57. package/src/utils/errors.ts +117 -0
  58. package/src/utils/{exercisesQueue.js → exercisesQueue.ts} +12 -6
  59. package/src/utils/fileQueue.ts +198 -0
  60. package/src/utils/misc.ts +23 -0
  61. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +2 -4
  62. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +1 -2
  63. package/src/utils/templates/isolated/01-hello-world/README.es.md +1 -2
  64. package/src/utils/templates/isolated/01-hello-world/README.md +1 -2
  65. package/src/utils/templates/isolated/README.ejs +1 -1
  66. package/src/utils/templates/isolated/README.es.ejs +1 -1
  67. package/src/utils/validators.ts +18 -0
  68. package/src/utils/watcher.ts +27 -0
  69. package/plugin/command/compile.js +0 -17
  70. package/plugin/command/test.js +0 -29
  71. package/plugin/index.js +0 -6
  72. package/plugin/plugin.js +0 -71
  73. package/plugin/utils.js +0 -78
  74. package/src/commands/audit.js +0 -243
  75. package/src/commands/clean.js +0 -27
  76. package/src/commands/download.js +0 -52
  77. package/src/commands/hello.js +0 -20
  78. package/src/commands/init.js +0 -133
  79. package/src/commands/login.js +0 -45
  80. package/src/commands/logout.js +0 -39
  81. package/src/commands/publish.js +0 -78
  82. package/src/commands/start.js +0 -169
  83. package/src/index.js +0 -1
  84. package/src/managers/config/allowed_files.js +0 -12
  85. package/src/managers/config/defaults.js +0 -32
  86. package/src/managers/config/exercise.js +0 -212
  87. package/src/managers/config/index.js +0 -342
  88. package/src/managers/file.js +0 -137
  89. package/src/managers/server/routes.js +0 -151
  90. package/src/managers/session.js +0 -83
  91. package/src/managers/socket.js +0 -185
  92. package/src/managers/test.js +0 -77
  93. package/src/ui/download.js +0 -48
  94. package/src/utils/BaseCommand.js +0 -34
  95. package/src/utils/SessionCommand.js +0 -46
  96. package/src/utils/api.js +0 -164
  97. package/src/utils/audit.js +0 -114
  98. package/src/utils/console.js +0 -16
  99. package/src/utils/errors.js +0 -90
  100. package/src/utils/fileQueue.js +0 -194
  101. package/src/utils/misc.js +0 -26
  102. package/src/utils/validators.js +0 -15
  103. package/src/utils/watcher.js +0 -24
@@ -1,78 +0,0 @@
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
@@ -1,169 +0,0 @@
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
package/src/index.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('@oclif/command')
@@ -1,12 +0,0 @@
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
- }
@@ -1,32 +0,0 @@
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
- }
@@ -1,212 +0,0 @@
1
- const p = require("path")
2
- const frontMatter = require('front-matter')
3
- const fs = require("fs")
4
- let Console = require('../../utils/console');
5
- const allowed = require('./allowed_files')
6
- const { ValidationError } = require('../../utils/errors.js')
7
-
8
- const exercise = (path, position, configObject) => {
9
-
10
- const { config, exercises } = configObject;
11
- let slug = p.basename(path)
12
-
13
- if(!validateExerciseDirectoryName(slug)){
14
- Console.error('Exercise directory "'+slug+'" has an invalid name, it has to start with two or three digits followed by words separated by underscors or hyphen (no white spaces). e.g: 01.12-hello-world')
15
- Console.help('Verify that the folder "'+slug+'" starts with two numbers and it does not contain white spaces or weird characters.')
16
- throw ValidationError(`This exercise has a invalid name: ${slug}`)
17
- }
18
-
19
- // get all the files
20
- const files = fs.readdirSync(path)
21
-
22
- /**
23
- * build the translation array like:
24
- {
25
- "us": "path/to/Readme.md",
26
- "es": "path/to/Readme.es.md"
27
- }
28
- */
29
- var translations = {}
30
- files.filter(file => file.toLowerCase().includes('readme')).forEach(file => {
31
- const parts = file.split('.')
32
- if(parts.length === 3) translations[parts[1]] = file
33
- else translations["us"] = file
34
- })
35
-
36
- // if the slug is a dot, it means there is not "exercises" folder, and its just a single README.md
37
- if(slug == ".") slug = "default-index";
38
-
39
- const detected = detect(configObject, files);
40
- return {
41
- position, path, slug, translations,
42
- language: detected.language,
43
- entry: detected.entry ? path + "/" + detected.entry : null, //full path to the exercise entry
44
- title: slug || "Exercise",
45
- graded: files.filter(file => file.toLowerCase().startsWith('test.') || file.toLowerCase().startsWith('tests.')).length > 0,
46
- files: filterFiles(files, path),
47
- //if the exercises was on the config before I may keep the status done
48
- done: (Array.isArray(exercises) && typeof exercises[position] !== 'undefined' && path.substring(path.indexOf('exercises/')+10) == exercises[position].slug) ? exercises[position].done : false,
49
- getReadme: function(lang=null){
50
- if(lang == 'us') lang = null // <-- english is default, no need to append it to the file name
51
- if (!fs.existsSync(`${this.path}/README${lang ? "."+lang : ''}.md`)){
52
- Console.error(`Language ${lang} not found for exercise ${slug}, switching to default language`)
53
- if(lang) lang = null
54
- if (!fs.existsSync(`${this.path}/README${lang ? "."+lang : ''}.md`)) throw Error('Readme file not found for exercise: '+this.path+'/README.md')
55
- }
56
- let content = fs.readFileSync(`${this.path}/README${lang ? "."+lang : ''}.md`,"utf8")
57
- // content = content.replace(/!\[.*\](../../assets/script-test.gif)/, "<div>$1</div>")
58
- const attr = frontMatter(content)
59
- return attr
60
- },
61
- getFile: function(name){
62
- const file = this.files.find(f => f.name === name);
63
- if (!fs.existsSync(file.path)) throw Error('File not found: '+file.path)
64
- else if(fs.lstatSync(file.path).isDirectory()) return 'Error: This is not a file to be read, but a directory: '+file.path
65
-
66
- // get file content
67
- const content = fs.readFileSync(file.path)
68
-
69
- //create reset folder
70
- if (!fs.existsSync(`${config.dirPath}/resets`)) fs.mkdirSync(`${config.dirPath}/resets`)
71
- if (!fs.existsSync(`${config.dirPath}/resets/`+this.slug)){
72
- fs.mkdirSync(`${config.dirPath}/resets/`+this.slug)
73
- if (!fs.existsSync(`${config.dirPath}/resets/${this.slug}/${name}`)){
74
- fs.writeFileSync(`${config.dirPath}/resets/${this.slug}/${name}`, content)
75
- }
76
- }
77
-
78
- return content
79
- },
80
- saveFile: function(name, content){
81
- const file = this.files.find(f => f.name === name);
82
- if (!fs.existsSync(file.path)) throw Error('File not found: '+file.path)
83
- return fs.writeFileSync(file.path, content, 'utf8')
84
- },
85
- getTestReport: function(){
86
- const _path = `${config.confPath.base}/reports/${this.slug}.json`
87
- if (!fs.existsSync(_path)) return {}
88
-
89
- const content = fs.readFileSync(_path)
90
- const data = JSON.parse(content)
91
- return data
92
- },
93
- }
94
- }
95
-
96
- const validateExerciseDirectoryName = (str) => {
97
- if(str == "./") return true;
98
- const regex = /^(\d{2,3}(\.\d{1,2})?-([A-Za-z0-9]+(-|_)?)+)$/
99
- return regex.test(str)
100
- }
101
-
102
- const isCodable = (str) => {
103
- const extension = p.extname(str);
104
- return allowed.extensions.includes(extension.substring(1).toLowerCase());
105
- }
106
-
107
- const isNotConfiguration = (str) => {
108
- return !allowed.names.includes(str);
109
- }
110
-
111
- const shouldBeVisible = function(file){
112
- return (
113
- // doest not have "test." on their name
114
- (file.name.toLocaleLowerCase().indexOf('test.') == -1 && file.name.toLocaleLowerCase().indexOf('tests.') == -1 && file.name.toLocaleLowerCase().indexOf('.hide.') == -1 &&
115
- // ignore hidden files
116
- (file.name.charAt(0) != '.') &&
117
- // ignore learn.json and bc.json
118
- (file.name.toLocaleLowerCase().indexOf('learn.json') == -1) && (file.name.toLocaleLowerCase().indexOf('bc.json') == -1) &&
119
- // ignore images, videos, vectors, etc.
120
- isCodable(file.name) && isNotConfiguration(file.name) &&
121
- // readme's and directories
122
- !file.name.toLowerCase().includes("readme.") && !isDirectory(file.path) && file.name.charAt(0) != '_')
123
- );
124
- }
125
-
126
- const isDirectory = source => {
127
- //if(path.basename(source) === path.basename(config.dirPath)) return false
128
- return fs.lstatSync(source).isDirectory()
129
- }
130
-
131
- /**
132
- * Learnpack must be able to AUTOMATICALLY detect language.
133
- * Because learnpack can work with multilang exercises.
134
- */
135
- const detect = (configObject, files) => {
136
-
137
- const { config } = configObject;
138
-
139
- if(!config) throw Error(`No configuration found during the engine detection`)
140
-
141
- if(!config.entries) throw Error("No configuration found for entries, please add a 'entries' object with the default file name for your exercise entry file that is going to be used while compiling, for example: index.html for html, app.py for python3, etc.")
142
- //A language was found on the config object, but this language will only be used as last resort, learnpack will try to guess each exercise language independently based on file extension (js, jsx, html, etc.)
143
-
144
- let hasFiles = files.filter(f => f.includes('.py'))
145
- if(hasFiles.length > 0) return {
146
- language: "python3",
147
- entry: hasFiles.find(f => config.entries["python3"] === f)
148
- }
149
-
150
- hasFiles = files.filter(f => f.includes('.java'))
151
- if(hasFiles.length > 0) return {
152
- language: "java",
153
- entry: hasFiles.find(f => config.entries["java"] === f)
154
- }
155
-
156
- hasFiles = files.filter(f => f.includes('.jsx'))
157
- if(hasFiles.length > 0) return {
158
- language: "react",
159
- entry: hasFiles.find(f => config.entries["react"] === f)
160
- }
161
- const hasHTML = files.filter(f => f.includes('index.html'))
162
- const hasIndexJS = files.find(f => f.includes('index.js'))
163
- const hasJS = files.filter(f => f.includes('.js'))
164
- // angular, vue, vanillajs needs to have at least 2 files (html,css,js),
165
- // the test.js and the entry file in js
166
- // if not its just another HTML
167
-
168
- if(hasIndexJS && hasHTML.length > 0) return {
169
- language: "vanillajs",
170
- entry: hasIndexJS
171
- }
172
- else if(hasHTML.length > 0) return {
173
- language: "html",
174
- entry: hasHTML.find(f => config.entries["html"] === f)
175
- }
176
- else if(hasJS.length > 0) return {
177
- language: "node",
178
- entry: hasJS.find(f => config.entries["node"] === f)
179
- }
180
-
181
- return {
182
- language: null,
183
- entry: null
184
- };
185
- }
186
-
187
- const filterFiles = (files, basePath=".") => files.map(ex => ({
188
- path: basePath+'/'+ex,
189
- name: ex,
190
- hidden: !shouldBeVisible({ name: ex, path: basePath+'/'+ex })
191
- }))
192
- .sort((f1, f2) => {
193
- const score = { // sorting priority
194
- "index.html": 1,
195
- "styles.css": 2,
196
- "styles.scss": 2,
197
- "style.css": 2,
198
- "style.scss": 2,
199
- "index.css": 2,
200
- "index.scss": 2,
201
- "index.js": 3,
202
- "index.jsx": 3,
203
- }
204
- return score[f1.name] < score[f2.name] ? -1 : 1
205
- });
206
-
207
- module.exports = {
208
- exercise,
209
- detect,
210
- filterFiles,
211
- validateExerciseDirectoryName
212
- }