@learnpack/learnpack 2.1.26 → 2.1.28
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 +10 -10
- package/lib/commands/start.js +15 -4
- package/lib/managers/file.d.ts +1 -0
- package/lib/managers/file.js +8 -1
- package/lib/managers/server/routes.js +48 -14
- package/lib/managers/session.d.ts +1 -1
- package/lib/managers/session.js +39 -12
- package/lib/managers/socket.d.ts +1 -1
- package/lib/managers/socket.js +57 -43
- package/lib/models/action.d.ts +1 -1
- package/lib/models/config.d.ts +1 -1
- package/lib/models/exercise-obj.d.ts +3 -0
- package/lib/models/session.d.ts +4 -1
- package/lib/models/socket.d.ts +7 -6
- package/lib/models/status.d.ts +1 -1
- package/lib/utils/api.d.ts +2 -0
- package/lib/utils/api.js +51 -6
- package/oclif.manifest.json +1 -1
- package/package.json +3 -1
- package/src/commands/audit.ts +113 -113
- package/src/commands/clean.ts +10 -10
- package/src/commands/download.ts +18 -18
- package/src/commands/init.ts +39 -39
- package/src/commands/login.ts +13 -13
- package/src/commands/logout.ts +9 -9
- package/src/commands/publish.ts +25 -25
- package/src/commands/start.ts +101 -75
- package/src/commands/test.ts +34 -34
- package/src/managers/config/allowed_files.ts +2 -2
- package/src/managers/config/defaults.ts +2 -2
- package/src/managers/config/exercise.ts +79 -79
- package/src/managers/config/index.ts +145 -145
- package/src/managers/file.ts +74 -65
- package/src/managers/server/index.ts +32 -31
- package/src/managers/server/routes.ts +139 -90
- package/src/managers/session.ts +53 -24
- package/src/managers/socket.ts +92 -79
- package/src/models/action.ts +8 -1
- package/src/models/config-manager.ts +2 -2
- package/src/models/config.ts +7 -2
- package/src/models/exercise-obj.ts +6 -3
- package/src/models/plugin-config.ts +2 -2
- package/src/models/session.ts +5 -2
- package/src/models/socket.ts +12 -6
- package/src/models/status.ts +15 -14
- package/src/plugin/command/compile.ts +10 -10
- package/src/plugin/command/test.ts +14 -14
- package/src/plugin/index.ts +5 -5
- package/src/plugin/plugin.ts +26 -26
- package/src/plugin/utils.ts +23 -23
- package/src/utils/BaseCommand.ts +16 -16
- package/src/utils/api.ts +143 -91
- package/src/utils/audit.ts +93 -96
- package/src/utils/exercisesQueue.ts +15 -15
- package/src/utils/fileQueue.ts +85 -85
- package/src/utils/watcher.ts +14 -14
@@ -1,24 +1,24 @@
|
|
1
|
-
import * as path from "path"
|
2
|
-
import * as fs from "fs"
|
3
|
-
import * as shell from "shelljs"
|
4
|
-
import Console from "../../utils/console"
|
5
|
-
import watch from "../../utils/watcher"
|
1
|
+
import * as path from "path"
|
2
|
+
import * as fs from "fs"
|
3
|
+
import * as shell from "shelljs"
|
4
|
+
import Console from "../../utils/console"
|
5
|
+
import watch from "../../utils/watcher"
|
6
6
|
import {
|
7
7
|
ValidationError,
|
8
8
|
NotFoundError,
|
9
9
|
InternalError,
|
10
|
-
} from "../../utils/errors"
|
10
|
+
} from "../../utils/errors"
|
11
11
|
|
12
|
-
import defaults from "./defaults"
|
13
|
-
import { exercise } from "./exercise"
|
12
|
+
import defaults from "./defaults"
|
13
|
+
import { exercise } from "./exercise"
|
14
14
|
|
15
|
-
import { rmSync } from "../file"
|
16
|
-
import { IConfigObj, TConfigObjAttributes, TMode } from "../../models/config"
|
15
|
+
import { rmSync } from "../file"
|
16
|
+
import { IConfigObj, TConfigObjAttributes, TMode } from "../../models/config"
|
17
17
|
import {
|
18
18
|
IConfigManagerAttributes,
|
19
19
|
IConfigManager,
|
20
|
-
} from "../../models/config-manager"
|
21
|
-
import { IFile } from "../../models/file"
|
20
|
+
} from "../../models/config-manager"
|
21
|
+
import { IFile } from "../../models/file"
|
22
22
|
|
23
23
|
/* exercise folder name standard */
|
24
24
|
|
@@ -30,49 +30,49 @@ const chalk = require("chalk");
|
|
30
30
|
/* exercise folder name standard */
|
31
31
|
|
32
32
|
const getConfigPath = () => {
|
33
|
-
const possibleFileNames = ["learn.json", ".learn/learn.json"]
|
34
|
-
const config = possibleFileNames.find(file => fs.existsSync(file)) || null
|
33
|
+
const possibleFileNames = ["learn.json", ".learn/learn.json"]
|
34
|
+
const config = possibleFileNames.find(file => fs.existsSync(file)) || null
|
35
35
|
if (config && fs.existsSync(".breathecode"))
|
36
|
-
return { config, base: ".breathecode" }
|
36
|
+
return { config, base: ".breathecode" }
|
37
37
|
if (config === null)
|
38
38
|
throw NotFoundError(
|
39
39
|
"learn.json file not found on current folder, is this a learnpack package?"
|
40
|
-
)
|
41
|
-
return { config, base: ".learn" }
|
42
|
-
}
|
40
|
+
)
|
41
|
+
return { config, base: ".learn" }
|
42
|
+
}
|
43
43
|
|
44
44
|
const getExercisesPath = (base: string) => {
|
45
|
-
const possibleFileNames = ["./exercises", base + "/exercises", "./"]
|
46
|
-
return possibleFileNames.find(file => fs.existsSync(file)) || null
|
47
|
-
}
|
45
|
+
const possibleFileNames = ["./exercises", base + "/exercises", "./"]
|
46
|
+
return possibleFileNames.find(file => fs.existsSync(file)) || null
|
47
|
+
}
|
48
48
|
|
49
49
|
const getGitpodAddress = () => {
|
50
50
|
if (shell.exec("gp -h", { silent: true }).code === 0) {
|
51
51
|
return shell
|
52
52
|
.exec("gp url", { silent: true })
|
53
|
-
.stdout.replace(/(\r\n|\n|\r)/gm, "")
|
53
|
+
.stdout.replace(/(\r\n|\n|\r)/gm, "")
|
54
54
|
}
|
55
55
|
|
56
|
-
Console.debug("Gitpod command line tool not found")
|
57
|
-
return "http://localhost"
|
58
|
-
}
|
56
|
+
Console.debug("Gitpod command line tool not found")
|
57
|
+
return "http://localhost"
|
58
|
+
}
|
59
59
|
|
60
60
|
const getCodespacesNamespace = () => {
|
61
61
|
// https://orange-rotary-phone-wxpg49q5gcg4rp-3000.app.github.dev
|
62
62
|
const codespace_name = shell
|
63
63
|
.exec("echo $CODESPACE_NAME", { silent: true })
|
64
|
-
.stdout.replace(/(\r\n|\n|\r)/gm, "")
|
64
|
+
.stdout.replace(/(\r\n|\n|\r)/gm, "")
|
65
65
|
|
66
66
|
if (
|
67
67
|
!codespace_name ||
|
68
68
|
codespace_name === "" ||
|
69
69
|
codespace_name === undefined
|
70
70
|
) {
|
71
|
-
return null
|
71
|
+
return null
|
72
72
|
}
|
73
73
|
|
74
|
-
return codespace_name
|
75
|
-
}
|
74
|
+
return codespace_name
|
75
|
+
}
|
76
76
|
|
77
77
|
export default async ({
|
78
78
|
grading,
|
@@ -80,46 +80,46 @@ export default async ({
|
|
80
80
|
disableGrading,
|
81
81
|
version,
|
82
82
|
}: IConfigManagerAttributes): Promise<IConfigManager> => {
|
83
|
-
const confPath = getConfigPath()
|
84
|
-
Console.debug("This is the config path: ", confPath)
|
83
|
+
const confPath = getConfigPath()
|
84
|
+
Console.debug("This is the config path: ", confPath)
|
85
85
|
|
86
|
-
let configObj: IConfigObj = {}
|
86
|
+
let configObj: IConfigObj = {}
|
87
87
|
|
88
88
|
if (confPath) {
|
89
|
-
const bcContent = fs.readFileSync(confPath.config)
|
90
|
-
let hiddenBcContent = {}
|
89
|
+
const bcContent = fs.readFileSync(confPath.config)
|
90
|
+
let hiddenBcContent = {}
|
91
91
|
if (fs.existsSync(confPath.base + "/config.json")) {
|
92
|
-
hiddenBcContent = fs.readFileSync(confPath.base + "/config.json")
|
93
|
-
hiddenBcContent = JSON.parse(hiddenBcContent as string)
|
92
|
+
hiddenBcContent = fs.readFileSync(confPath.base + "/config.json")
|
93
|
+
hiddenBcContent = JSON.parse(hiddenBcContent as string)
|
94
94
|
if (!hiddenBcContent)
|
95
95
|
throw new Error(
|
96
96
|
`Invalid ${confPath.base}/config.json syntax: Unable to parse.`
|
97
|
-
)
|
97
|
+
)
|
98
98
|
}
|
99
99
|
|
100
|
-
const jsonConfig = JSON.parse(`${bcContent}`)
|
100
|
+
const jsonConfig = JSON.parse(`${bcContent}`)
|
101
101
|
if (!jsonConfig)
|
102
|
-
throw new Error(`Invalid ${confPath.config} syntax: Unable to parse.`)
|
102
|
+
throw new Error(`Invalid ${confPath.config} syntax: Unable to parse.`)
|
103
103
|
|
104
|
-
let session: number
|
104
|
+
let session: number
|
105
105
|
|
106
106
|
// add using id to the installation
|
107
107
|
if (!jsonConfig.session) {
|
108
|
-
session = Math.floor(Math.random() * 10_000_000_000_000_000_000)
|
108
|
+
session = Math.floor(Math.random() * 10_000_000_000_000_000_000)
|
109
109
|
} else {
|
110
|
-
session = jsonConfig.session
|
111
|
-
delete jsonConfig.session
|
110
|
+
session = jsonConfig.session
|
111
|
+
delete jsonConfig.session
|
112
112
|
}
|
113
113
|
|
114
114
|
configObj = deepMerge(hiddenBcContent, {
|
115
115
|
config: jsonConfig,
|
116
116
|
session: session,
|
117
|
-
})
|
118
|
-
Console.debug("Content from the configuration .json ", configObj)
|
117
|
+
})
|
118
|
+
Console.debug("Content from the configuration .json ", configObj)
|
119
119
|
} else {
|
120
120
|
throw ValidationError(
|
121
121
|
"No learn.json file has been found, make sure you are in the folder"
|
122
|
-
)
|
122
|
+
)
|
123
123
|
}
|
124
124
|
|
125
125
|
configObj = deepMerge(defaults || {}, configObj, {
|
@@ -127,10 +127,10 @@ export default async ({
|
|
127
127
|
grading: grading || configObj.config?.grading,
|
128
128
|
configPath: confPath.config,
|
129
129
|
},
|
130
|
-
})
|
130
|
+
})
|
131
131
|
|
132
132
|
if (configObj.config) {
|
133
|
-
configObj.config.outputPath = confPath.base + "/dist"
|
133
|
+
configObj.config.outputPath = confPath.base + "/dist"
|
134
134
|
}
|
135
135
|
|
136
136
|
Console.debug("This is your configuration object: ", {
|
@@ -138,50 +138,50 @@ export default async ({
|
|
138
138
|
exercises: configObj.exercises ?
|
139
139
|
configObj.exercises.map(e => e.slug) :
|
140
140
|
[],
|
141
|
-
})
|
141
|
+
})
|
142
142
|
|
143
143
|
// auto detect agent (if possible)
|
144
|
-
const codespaces_workspace = getCodespacesNamespace()
|
144
|
+
const codespaces_workspace = getCodespacesNamespace()
|
145
145
|
if (shell.which("gp") && configObj && configObj.config) {
|
146
|
-
configObj.config.editor.agent = "vscode"
|
147
|
-
configObj.address = getGitpodAddress()
|
146
|
+
configObj.config.editor.agent = "vscode"
|
147
|
+
configObj.address = getGitpodAddress()
|
148
148
|
configObj.config.publicUrl = `https://${
|
149
149
|
configObj.config.port
|
150
|
-
}-${configObj.address?.slice(8)}
|
150
|
+
}-${configObj.address?.slice(8)}`
|
151
151
|
} else if (configObj.config && codespaces_workspace) {
|
152
|
-
configObj.config.editor.agent = "vscode"
|
153
|
-
configObj.address = `https://${codespaces_workspace}.github.dev
|
154
|
-
configObj.config.publicUrl = `https://${codespaces_workspace}-${configObj.config.port}.app.github.dev
|
152
|
+
configObj.config.editor.agent = "vscode"
|
153
|
+
configObj.address = `https://${codespaces_workspace}.github.dev`
|
154
|
+
configObj.config.publicUrl = `https://${codespaces_workspace}-${configObj.config.port}.app.github.dev`
|
155
155
|
} else if (configObj.config && !configObj.config.editor.agent) {
|
156
|
-
configObj.config.editor.agent = "localhost"
|
156
|
+
configObj.config.editor.agent = "localhost"
|
157
157
|
}
|
158
158
|
|
159
159
|
if (configObj.config && !configObj.config.publicUrl)
|
160
|
-
configObj.config.publicUrl = `${configObj.address}:${configObj.config.port}
|
160
|
+
configObj.config.publicUrl = `${configObj.address}:${configObj.config.port}`
|
161
161
|
|
162
162
|
// Assign default editor mode if not set already
|
163
163
|
if (configObj.config && !mode) {
|
164
|
-
configObj.config.editor.mode = mode as TMode
|
164
|
+
configObj.config.editor.mode = mode as TMode
|
165
165
|
}
|
166
166
|
|
167
167
|
if (configObj.config && !configObj.config.editor.mode)
|
168
168
|
configObj.config.editor.mode =
|
169
|
-
configObj.config.editor.agent === "localhost" ? "standalone" : "preview"
|
169
|
+
configObj.config.editor.agent === "localhost" ? "standalone" : "preview"
|
170
170
|
|
171
171
|
if (version && configObj.config)
|
172
|
-
configObj.config.editor.version = version
|
172
|
+
configObj.config.editor.version = version
|
173
173
|
else if (configObj.config && configObj.config.editor.version === null) {
|
174
|
-
Console.debug("Config version not found, downloading default.")
|
174
|
+
Console.debug("Config version not found, downloading default.")
|
175
175
|
const resp = await fetch(
|
176
176
|
"https://raw.githubusercontent.com/learnpack/coding-ide/learnpack/package.json"
|
177
|
-
)
|
178
|
-
const packageJSON = await resp.json()
|
179
|
-
configObj.config.editor.version = packageJSON.version || "1.0.73"
|
177
|
+
)
|
178
|
+
const packageJSON = await resp.json()
|
179
|
+
configObj.config.editor.version = packageJSON.version || "1.0.73"
|
180
180
|
}
|
181
181
|
|
182
182
|
if (configObj.config) {
|
183
|
-
configObj.config.dirPath = "./" + confPath.base
|
184
|
-
configObj.config.exercisesPath = getExercisesPath(confPath.base) || "./"
|
183
|
+
configObj.config.dirPath = "./" + confPath.base
|
184
|
+
configObj.config.exercisesPath = getExercisesPath(confPath.base) || "./"
|
185
185
|
}
|
186
186
|
|
187
187
|
return {
|
@@ -192,125 +192,125 @@ configObj.config.editor.version = version;
|
|
192
192
|
const alias = (_l: string) => {
|
193
193
|
const map: any = {
|
194
194
|
python3: "python",
|
195
|
-
}
|
195
|
+
}
|
196
196
|
if (map[_l])
|
197
|
-
return map[_l]
|
198
|
-
return _l
|
199
|
-
}
|
197
|
+
return map[_l]
|
198
|
+
return _l
|
199
|
+
}
|
200
200
|
|
201
201
|
// decode aliases
|
202
|
-
language = alias(language)
|
202
|
+
language = alias(language)
|
203
203
|
|
204
204
|
if (this.validLanguages[language])
|
205
|
-
return true
|
205
|
+
return true
|
206
206
|
|
207
|
-
Console.debug(`Validating engine for ${language} compilation`)
|
208
|
-
let result = shell.exec("learnpack plugins", { silent: true })
|
207
|
+
Console.debug(`Validating engine for ${language} compilation`)
|
208
|
+
let result = shell.exec("learnpack plugins", { silent: true })
|
209
209
|
|
210
210
|
if (
|
211
211
|
result.code === 0 &&
|
212
212
|
result.stdout.includes(`learnpack-${language}`)
|
213
213
|
) {
|
214
|
-
this.validLanguages[language] = true
|
215
|
-
return true
|
214
|
+
this.validLanguages[language] = true
|
215
|
+
return true
|
216
216
|
}
|
217
217
|
|
218
|
-
Console.info(`Language engine for ${language} not found, installing...`)
|
218
|
+
Console.info(`Language engine for ${language} not found, installing...`)
|
219
219
|
result = shell.exec(`learnpack plugins:install learnpack-${language}`, {
|
220
220
|
silent: true,
|
221
|
-
})
|
221
|
+
})
|
222
222
|
if (result.code === 0) {
|
223
223
|
socket.log(
|
224
224
|
"compiling",
|
225
225
|
"Installing the python compiler, you will have to reset the exercises after installation by writing on your terminal: $ learnpack run"
|
226
|
-
)
|
226
|
+
)
|
227
227
|
Console.info(
|
228
228
|
`Successfully installed the ${language} exercise engine, \n please start learnpack again by running the following command: \n ${chalk.white(
|
229
229
|
"$ learnpack start"
|
230
230
|
)}\n\n `
|
231
|
-
)
|
232
|
-
server.terminate()
|
233
|
-
return false
|
231
|
+
)
|
232
|
+
server.terminate()
|
233
|
+
return false
|
234
234
|
}
|
235
235
|
|
236
|
-
this.validLanguages[language] = false
|
237
|
-
socket.error(`Error installing ${language} exercise engine`)
|
238
|
-
Console.error(`Error installing ${language} exercise engine`)
|
239
|
-
Console.log(result.stdout)
|
240
|
-
throw InternalError(`Error installing ${language} exercise engine`)
|
236
|
+
this.validLanguages[language] = false
|
237
|
+
socket.error(`Error installing ${language} exercise engine`)
|
238
|
+
Console.error(`Error installing ${language} exercise engine`)
|
239
|
+
Console.log(result.stdout)
|
240
|
+
throw InternalError(`Error installing ${language} exercise engine`)
|
241
241
|
},
|
242
242
|
clean: () => {
|
243
243
|
if (configObj.config) {
|
244
244
|
if (configObj.config.outputPath) {
|
245
|
-
rmSync(configObj.config.outputPath)
|
245
|
+
rmSync(configObj.config.outputPath)
|
246
246
|
}
|
247
247
|
|
248
|
-
rmSync(configObj.config.dirPath + "/_app")
|
249
|
-
rmSync(configObj.config.dirPath + "/reports")
|
250
|
-
rmSync(configObj.config.dirPath + "/.session")
|
251
|
-
rmSync(configObj.config.dirPath + "/resets")
|
248
|
+
rmSync(configObj.config.dirPath + "/_app")
|
249
|
+
rmSync(configObj.config.dirPath + "/reports")
|
250
|
+
rmSync(configObj.config.dirPath + "/.session")
|
251
|
+
rmSync(configObj.config.dirPath + "/resets")
|
252
252
|
|
253
253
|
// clean tag gz
|
254
254
|
if (fs.existsSync(configObj.config.dirPath + "/app.tar.gz"))
|
255
|
-
fs.unlinkSync(configObj.config.dirPath + "/app.tar.gz")
|
255
|
+
fs.unlinkSync(configObj.config.dirPath + "/app.tar.gz")
|
256
256
|
|
257
257
|
if (fs.existsSync(configObj.config.dirPath + "/config.json"))
|
258
|
-
fs.unlinkSync(configObj.config.dirPath + "/config.json")
|
258
|
+
fs.unlinkSync(configObj.config.dirPath + "/config.json")
|
259
259
|
|
260
260
|
if (fs.existsSync(configObj.config.dirPath + "/vscode_queue.json"))
|
261
|
-
fs.unlinkSync(configObj.config.dirPath + "/vscode_queue.json")
|
261
|
+
fs.unlinkSync(configObj.config.dirPath + "/vscode_queue.json")
|
262
262
|
}
|
263
263
|
},
|
264
264
|
getExercise: slug => {
|
265
|
-
Console.debug("ExercisePath Slug", slug)
|
265
|
+
Console.debug("ExercisePath Slug", slug)
|
266
266
|
const exercise = (configObj.exercises || []).find(
|
267
267
|
ex => ex.slug === slug
|
268
|
-
)
|
268
|
+
)
|
269
269
|
if (!exercise)
|
270
|
-
throw ValidationError(`Exercise ${slug} not found`)
|
270
|
+
throw ValidationError(`Exercise ${slug} not found`)
|
271
271
|
|
272
|
-
return exercise
|
272
|
+
return exercise
|
273
273
|
},
|
274
274
|
getAllExercises: () => {
|
275
|
-
return configObj.exercises
|
275
|
+
return configObj.exercises
|
276
276
|
},
|
277
277
|
startExercise: function (slug: string) {
|
278
|
-
const exercise = this.getExercise(slug)
|
278
|
+
const exercise = this.getExercise(slug)
|
279
279
|
|
280
280
|
// set config.json with current exercise
|
281
|
-
configObj.currentExercise = exercise.slug
|
281
|
+
configObj.currentExercise = exercise.slug
|
282
282
|
|
283
|
-
this.save()
|
283
|
+
this.save()
|
284
284
|
|
285
285
|
// eslint-disable-next-line
|
286
286
|
exercise.files.forEach((f: IFile) => {
|
287
287
|
if (configObj.config) {
|
288
|
-
const _path = configObj.config.outputPath + "/" + f.name
|
288
|
+
const _path = configObj.config.outputPath + "/" + f.name
|
289
289
|
if (f.hidden === false && fs.existsSync(_path))
|
290
|
-
fs.unlinkSync(_path)
|
290
|
+
fs.unlinkSync(_path)
|
291
291
|
}
|
292
|
-
})
|
292
|
+
})
|
293
293
|
|
294
|
-
return exercise
|
294
|
+
return exercise
|
295
295
|
},
|
296
296
|
noCurrentExercise: function () {
|
297
|
-
configObj.currentExercise = null
|
298
|
-
this.save()
|
297
|
+
configObj.currentExercise = null
|
298
|
+
this.save()
|
299
299
|
},
|
300
300
|
reset: slug => {
|
301
301
|
if (
|
302
302
|
configObj.config &&
|
303
303
|
!fs.existsSync(`${configObj.config.dirPath}/resets/` + slug)
|
304
304
|
)
|
305
|
-
throw ValidationError("Could not find the original files for " + slug)
|
305
|
+
throw ValidationError("Could not find the original files for " + slug)
|
306
306
|
|
307
307
|
const exercise = (configObj.exercises || []).find(
|
308
308
|
ex => ex.slug === slug
|
309
|
-
)
|
309
|
+
)
|
310
310
|
if (!exercise)
|
311
311
|
throw ValidationError(
|
312
312
|
`Exercise ${slug} not found on the configuration`
|
313
|
-
)
|
313
|
+
)
|
314
314
|
|
315
315
|
if (configObj.config) {
|
316
316
|
for (const fileName of fs.readdirSync(
|
@@ -318,110 +318,110 @@ fs.unlinkSync(_path);
|
|
318
318
|
)) {
|
319
319
|
const content = fs.readFileSync(
|
320
320
|
`${configObj.config?.dirPath}/resets/${slug}/${fileName}`
|
321
|
-
)
|
322
|
-
fs.writeFileSync(`${exercise.path}/${fileName}`, content)
|
321
|
+
)
|
322
|
+
fs.writeFileSync(`${exercise.path}/${fileName}`, content)
|
323
323
|
}
|
324
324
|
}
|
325
325
|
},
|
326
326
|
buildIndex: function () {
|
327
|
-
Console.info("Building the exercise index...")
|
327
|
+
Console.info("Building the exercise index...")
|
328
328
|
|
329
329
|
const isDirectory = (source: string) => {
|
330
|
-
const name = path.basename(source)
|
330
|
+
const name = path.basename(source)
|
331
331
|
if (name === path.basename(configObj?.config?.dirPath || ""))
|
332
|
-
return false
|
332
|
+
return false
|
333
333
|
// ignore folders that start with a dot
|
334
334
|
if (name.charAt(0) === "." || name.charAt(0) === "_")
|
335
|
-
return false
|
335
|
+
return false
|
336
336
|
|
337
|
-
return fs.lstatSync(source).isDirectory()
|
338
|
-
}
|
337
|
+
return fs.lstatSync(source).isDirectory()
|
338
|
+
}
|
339
339
|
|
340
340
|
const getDirectories = (source: string) =>
|
341
341
|
fs
|
342
342
|
.readdirSync(source)
|
343
343
|
.map(name => path.join(source, name))
|
344
|
-
.filter(isDirectory)
|
344
|
+
.filter(isDirectory)
|
345
345
|
// add the .learn folder
|
346
346
|
if (!fs.existsSync(confPath.base))
|
347
|
-
fs.mkdirSync(confPath.base)
|
347
|
+
fs.mkdirSync(confPath.base)
|
348
348
|
// add the outout folder where webpack will publish the the html/css/js files
|
349
349
|
if (
|
350
350
|
configObj.config &&
|
351
351
|
configObj.config.outputPath &&
|
352
352
|
!fs.existsSync(configObj.config.outputPath)
|
353
353
|
)
|
354
|
-
fs.mkdirSync(configObj.config.outputPath)
|
354
|
+
fs.mkdirSync(configObj.config.outputPath)
|
355
355
|
|
356
356
|
// TODO: we could use npm library front-mater to read the title of the exercises from the README.md
|
357
357
|
const grupedByDirectory = getDirectories(
|
358
358
|
configObj?.config?.exercisesPath || ""
|
359
|
-
)
|
359
|
+
)
|
360
360
|
configObj.exercises =
|
361
361
|
grupedByDirectory.length > 0 ?
|
362
362
|
grupedByDirectory.map((path, position) =>
|
363
363
|
exercise(path, position, configObj)
|
364
364
|
) :
|
365
|
-
[exercise(configObj?.config?.exercisesPath || "", 0, configObj)]
|
366
|
-
this.save()
|
365
|
+
[exercise(configObj?.config?.exercisesPath || "", 0, configObj)]
|
366
|
+
this.save()
|
367
367
|
},
|
368
368
|
watchIndex: function (onChange: () => void) {
|
369
369
|
if (configObj.config && !configObj.config.exercisesPath)
|
370
370
|
throw ValidationError(
|
371
371
|
"No exercises directory to watch: " + configObj.config.exercisesPath
|
372
|
-
)
|
372
|
+
)
|
373
373
|
|
374
|
-
this.buildIndex()
|
374
|
+
this.buildIndex()
|
375
375
|
watch(configObj?.config?.exercisesPath || "", onChange)
|
376
376
|
.then((/* eventname, filename */) => {
|
377
|
-
Console.debug("Changes detected on your exercises")
|
378
|
-
this.buildIndex()
|
377
|
+
Console.debug("Changes detected on your exercises")
|
378
|
+
this.buildIndex()
|
379
379
|
if (onChange)
|
380
|
-
onChange()
|
380
|
+
onChange()
|
381
381
|
})
|
382
382
|
.catch(error => {
|
383
|
-
throw error
|
384
|
-
})
|
383
|
+
throw error
|
384
|
+
})
|
385
385
|
},
|
386
386
|
save: () => {
|
387
|
-
Console.debug("Saving configuration with: ", configObj)
|
387
|
+
Console.debug("Saving configuration with: ", configObj)
|
388
388
|
|
389
389
|
// remove the duplicates form the actions array
|
390
390
|
// configObj.config.actions = [...new Set(configObj.config.actions)];
|
391
391
|
if (configObj.config) {
|
392
392
|
configObj.config.translations = [
|
393
393
|
...new Set(configObj.config.translations),
|
394
|
-
]
|
394
|
+
]
|
395
395
|
fs.writeFileSync(
|
396
396
|
configObj.config.dirPath + "/config.json",
|
397
397
|
JSON.stringify(configObj, null, 4)
|
398
|
-
)
|
398
|
+
)
|
399
399
|
}
|
400
400
|
},
|
401
|
-
} as IConfigManager
|
402
|
-
}
|
401
|
+
} as IConfigManager
|
402
|
+
}
|
403
403
|
|
404
404
|
function deepMerge(...sources: any): any {
|
405
|
-
let acc: any = {}
|
405
|
+
let acc: any = {}
|
406
406
|
for (const source of sources) {
|
407
407
|
if (Array.isArray(source)) {
|
408
408
|
if (!Array.isArray(acc)) {
|
409
|
-
acc = []
|
409
|
+
acc = []
|
410
410
|
}
|
411
411
|
|
412
|
-
acc = [...source]
|
412
|
+
acc = [...source]
|
413
413
|
} else if (source instanceof Object) {
|
414
414
|
// eslint-disable-next-line
|
415
415
|
for (let [key, value] of Object.entries(source)) {
|
416
416
|
if (value instanceof Object && key in acc) {
|
417
|
-
value = deepMerge(acc[key], value)
|
417
|
+
value = deepMerge(acc[key], value)
|
418
418
|
}
|
419
419
|
|
420
420
|
if (value !== undefined)
|
421
|
-
acc = { ...acc, [key]: value }
|
421
|
+
acc = { ...acc, [key]: value }
|
422
422
|
}
|
423
423
|
}
|
424
424
|
}
|
425
425
|
|
426
|
-
return acc
|
426
|
+
return acc
|
427
427
|
}
|