@learnpack/learnpack 5.0.31 → 5.0.32
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 +12 -12
- package/lib/managers/telemetry.d.ts +2 -0
- package/lib/managers/telemetry.js +7 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/start.ts +361 -361
- package/src/managers/socket.ts +274 -274
- package/src/managers/telemetry.ts +10 -0
- package/src/models/action.ts +12 -12
package/src/commands/start.ts
CHANGED
@@ -1,361 +1,361 @@
|
|
1
|
-
// import path from "path";
|
2
|
-
import { flags } from "@oclif/command"
|
3
|
-
import * as fs from "fs"
|
4
|
-
import * as path from "path"
|
5
|
-
|
6
|
-
import SessionCommand from "../utils/SessionCommand"
|
7
|
-
import Console from "../utils/console"
|
8
|
-
import socket from "../managers/socket"
|
9
|
-
import TelemetryManager, { TStep } from "../managers/telemetry"
|
10
|
-
import createServer from "../managers/server"
|
11
|
-
import queue from "../utils/fileQueue"
|
12
|
-
import {
|
13
|
-
decompress,
|
14
|
-
downloadEditor,
|
15
|
-
checkIfDirectoryExists,
|
16
|
-
} from "../managers/file"
|
17
|
-
import { prioritizeHTMLFile } from "../utils/misc"
|
18
|
-
|
19
|
-
import { IGitpodData } from "../models/gitpod-data"
|
20
|
-
import { IExercise, IExerciseData } from "../models/exercise-obj"
|
21
|
-
import { eventManager } from "../utils/osOperations"
|
22
|
-
import { cli } from "cli-ux"
|
23
|
-
import { TOpenWindowData } from "../models/socket"
|
24
|
-
import {
|
25
|
-
checkNotInstalledPlugins,
|
26
|
-
checkNotInstalledDependencies,
|
27
|
-
} from "../utils/checkNotInstalled"
|
28
|
-
|
29
|
-
export default class StartCommand extends SessionCommand {
|
30
|
-
static description = "Runs a small server with all the exercise instructions"
|
31
|
-
|
32
|
-
static flags = {
|
33
|
-
...SessionCommand.flags,
|
34
|
-
port: flags.string({ char: "p", description: "server port" }),
|
35
|
-
host: flags.string({ char: "h", description: "server host" }),
|
36
|
-
disableGrading: flags.boolean({
|
37
|
-
char: "D",
|
38
|
-
description: "disble grading functionality",
|
39
|
-
default: false,
|
40
|
-
}),
|
41
|
-
// disableGrading: flags.boolean({char: 'dg', description: 'disble grading functionality', default: false }),
|
42
|
-
watch: flags.boolean({
|
43
|
-
char: "w",
|
44
|
-
description: "Watch for file changes",
|
45
|
-
default: false,
|
46
|
-
}),
|
47
|
-
editor: flags.string({
|
48
|
-
char: "e",
|
49
|
-
description: "[preview, extension]",
|
50
|
-
options: ["extension", "preview"],
|
51
|
-
}),
|
52
|
-
version: flags.string({
|
53
|
-
char: "v",
|
54
|
-
description: "E.g: 1.0.1",
|
55
|
-
default: undefined,
|
56
|
-
}),
|
57
|
-
grading: flags.string({
|
58
|
-
char: "g",
|
59
|
-
description: "[isolated, incremental]",
|
60
|
-
options: ["isolated", "incremental"],
|
61
|
-
}),
|
62
|
-
debug: flags.boolean({
|
63
|
-
char: "d",
|
64
|
-
description: "debugger mode for more verbage",
|
65
|
-
default: false,
|
66
|
-
}),
|
67
|
-
}
|
68
|
-
|
69
|
-
// 🛑 IMPORTANT
|
70
|
-
// Every command that will use the configManager needs this init method
|
71
|
-
async init() {
|
72
|
-
const { flags } = this.parse(StartCommand)
|
73
|
-
await this.initSession(flags)
|
74
|
-
}
|
75
|
-
|
76
|
-
async run() {
|
77
|
-
// get configuration object
|
78
|
-
const configObject = this.configManager?.get()
|
79
|
-
|
80
|
-
const hasXDG = await eventManager.checkXDGInstalled()
|
81
|
-
|
82
|
-
const installedPlugins = this.config.plugins.map(plugin => {
|
83
|
-
return `${plugin.pjson.name}`
|
84
|
-
})
|
85
|
-
|
86
|
-
if (configObject) {
|
87
|
-
const { config } = configObject
|
88
|
-
|
89
|
-
// build exerises
|
90
|
-
this.configManager?.buildIndex()
|
91
|
-
|
92
|
-
Console.debug(
|
93
|
-
`Grading: ${config?.grading} ${
|
94
|
-
config?.disabledActions?.includes("test") ? "(disabled)" : ""
|
95
|
-
}, editor: ${config?.editor.mode} ${config?.editor.version}, for ${
|
96
|
-
Array.isArray(configObject?.exercises) ?
|
97
|
-
configObject?.exercises.length :
|
98
|
-
0
|
99
|
-
} exercises found`
|
100
|
-
)
|
101
|
-
|
102
|
-
const neededPlugins = await checkNotInstalledPlugins(
|
103
|
-
configObject?.exercises || [],
|
104
|
-
installedPlugins,
|
105
|
-
this
|
106
|
-
)
|
107
|
-
|
108
|
-
const allDepsInstalled = await checkNotInstalledDependencies(
|
109
|
-
neededPlugins.needed
|
110
|
-
)
|
111
|
-
if (!allDepsInstalled) {
|
112
|
-
this.exit(1)
|
113
|
-
}
|
114
|
-
|
115
|
-
const appAlreadyExists = checkIfDirectoryExists(`${config?.dirPath}/_app`)
|
116
|
-
|
117
|
-
if (!appAlreadyExists) {
|
118
|
-
// download app and decompress
|
119
|
-
await downloadEditor(
|
120
|
-
config?.editor.version,
|
121
|
-
`${config?.dirPath}/app.tar.gz`
|
122
|
-
)
|
123
|
-
|
124
|
-
Console.info("Decompressing LearnPack UI, this may take a minute...")
|
125
|
-
await decompress(
|
126
|
-
`${config?.dirPath}/app.tar.gz`,
|
127
|
-
`${config?.dirPath}/_app/`
|
128
|
-
)
|
129
|
-
}
|
130
|
-
|
131
|
-
// listen to socket commands
|
132
|
-
if (config && this.configManager) {
|
133
|
-
const server = await createServer(
|
134
|
-
configObject,
|
135
|
-
this.configManager,
|
136
|
-
process.env.NODE_ENV === "test"
|
137
|
-
)
|
138
|
-
server.setMaxListeners(30)
|
139
|
-
|
140
|
-
// I should call a method to get the EventListener
|
141
|
-
const dispatcher = queue.dispatcher({
|
142
|
-
create: true,
|
143
|
-
path: `${config.dirPath}/vscode_queue.json`,
|
144
|
-
})
|
145
|
-
|
146
|
-
if (configObject.exercises) {
|
147
|
-
const agent = configObject.config?.editor.agent || ""
|
148
|
-
const path = configObject.config?.dirPath || ""
|
149
|
-
const tutorialSlug = configObject.config?.slug || ""
|
150
|
-
|
151
|
-
const steps: TStep[] = configObject.exercises.map(
|
152
|
-
(e: IExercise, index): TStep => ({
|
153
|
-
slug: e.slug,
|
154
|
-
position: e.position || index,
|
155
|
-
files: e.files,
|
156
|
-
ai_interactions: [],
|
157
|
-
compilations: [],
|
158
|
-
tests: [],
|
159
|
-
quiz_submissions: [],
|
160
|
-
is_testeable: e.graded || false,
|
161
|
-
})
|
162
|
-
)
|
163
|
-
if (path && steps.length > 0) {
|
164
|
-
TelemetryManager.start(agent, steps, path, tutorialSlug)
|
165
|
-
}
|
166
|
-
|
167
|
-
if (config.telemetry) {
|
168
|
-
TelemetryManager.urls = config.telemetry
|
169
|
-
}
|
170
|
-
}
|
171
|
-
|
172
|
-
socket.start(config, server, false)
|
173
|
-
|
174
|
-
socket.on("open", (data: IGitpodData) => {
|
175
|
-
Console.debug("Opening these files: ", data)
|
176
|
-
|
177
|
-
const files = prioritizeHTMLFile(data.files)
|
178
|
-
|
179
|
-
if (config.editor.agent !== "os") {
|
180
|
-
// Console.info("Opening files for vscode agent")
|
181
|
-
eventManager.enqueue(dispatcher.events.OPEN_FILES, files)
|
182
|
-
} else {
|
183
|
-
// dispatcher.enqueue(dispatcher.events.OPEN_FILES, files)
|
184
|
-
Console.debug("Ignoring files for os agent")
|
185
|
-
}
|
186
|
-
|
187
|
-
socket.ready("Ready to compile...")
|
188
|
-
})
|
189
|
-
|
190
|
-
socket.on("open_window", (data: TOpenWindowData) => {
|
191
|
-
Console.debug("Opening window: ", data)
|
192
|
-
|
193
|
-
// cli.open(data.url); This uses XDG under the ground
|
194
|
-
if (config.os !== "linux" || (config.os === "linux" && hasXDG)) {
|
195
|
-
eventManager.enqueue(dispatcher.events.OPEN_WINDOW, data)
|
196
|
-
} else {
|
197
|
-
dispatcher.enqueue(dispatcher.events.OPEN_WINDOW, data)
|
198
|
-
}
|
199
|
-
|
200
|
-
socket.log("open_window", "", undefined, data.url)
|
201
|
-
})
|
202
|
-
|
203
|
-
socket.on("open_terminal", (exercise: IExerciseData) => {
|
204
|
-
Console.debug("Opening terminal: ", exercise)
|
205
|
-
// eventManager.enqueue(dispatcher.events.OPEN_TERMINAL, exercise);
|
206
|
-
if (config.editor.agent === "vscode") {
|
207
|
-
dispatcher.enqueue(dispatcher.events.OPEN_TERMINAL, exercise)
|
208
|
-
}
|
209
|
-
})
|
210
|
-
|
211
|
-
socket.on("reset", (exercise: IExerciseData) => {
|
212
|
-
try {
|
213
|
-
this.configManager?.reset(exercise.exerciseSlug)
|
214
|
-
socket.ready("Ready to compile...")
|
215
|
-
} catch (error) {
|
216
|
-
socket.error(
|
217
|
-
"compiler-error",
|
218
|
-
(error as TypeError).message ||
|
219
|
-
"There was an error reseting the exercise"
|
220
|
-
)
|
221
|
-
setTimeout(() => socket.ready("Ready to compile..."), 2000)
|
222
|
-
}
|
223
|
-
})
|
224
|
-
|
225
|
-
socket.on("build", async (data: IExerciseData) => {
|
226
|
-
const exercise = this.configManager?.getExercise(data.exerciseSlug)
|
227
|
-
|
228
|
-
if (!exercise?.language) {
|
229
|
-
socket.error(
|
230
|
-
"compiler-error",
|
231
|
-
"Impossible to detect language to build for " +
|
232
|
-
data.exerciseSlug +
|
233
|
-
"..."
|
234
|
-
)
|
235
|
-
return
|
236
|
-
}
|
237
|
-
|
238
|
-
socket.log(
|
239
|
-
"compiling",
|
240
|
-
"Building exercise " +
|
241
|
-
data.exerciseSlug +
|
242
|
-
" with " +
|
243
|
-
exercise.language +
|
244
|
-
"..."
|
245
|
-
)
|
246
|
-
await this.config.runHook("action", {
|
247
|
-
action: "compile",
|
248
|
-
socket,
|
249
|
-
configuration: config,
|
250
|
-
exercise,
|
251
|
-
telemetry: TelemetryManager,
|
252
|
-
})
|
253
|
-
})
|
254
|
-
|
255
|
-
socket.on("telemetry_event", (data: any) => {
|
256
|
-
const { stepPosition, event, eventData } = data
|
257
|
-
|
258
|
-
TelemetryManager.registerStepEvent(stepPosition, event, eventData)
|
259
|
-
})
|
260
|
-
|
261
|
-
socket.on("test", async (data: IExerciseData) => {
|
262
|
-
const exercise = this.configManager?.getExercise(data.exerciseSlug)
|
263
|
-
|
264
|
-
if (!exercise?.language) {
|
265
|
-
socket.error(
|
266
|
-
"compiler-error",
|
267
|
-
"Impossible to detect engine language for testing for " +
|
268
|
-
data.exerciseSlug +
|
269
|
-
"..."
|
270
|
-
)
|
271
|
-
return
|
272
|
-
}
|
273
|
-
|
274
|
-
if (
|
275
|
-
config?.disabledActions!.includes("test") ||
|
276
|
-
config?.disableGrading
|
277
|
-
) {
|
278
|
-
socket.ready("Grading is disabled on configuration")
|
279
|
-
return true
|
280
|
-
}
|
281
|
-
|
282
|
-
socket.log(
|
283
|
-
"testing",
|
284
|
-
"Testing your exercise using the " + exercise.language + " engine."
|
285
|
-
)
|
286
|
-
|
287
|
-
await this.config.runHook("action", {
|
288
|
-
action: "test",
|
289
|
-
socket,
|
290
|
-
configuration: config,
|
291
|
-
exercise,
|
292
|
-
telemetry: TelemetryManager,
|
293
|
-
})
|
294
|
-
|
295
|
-
try {
|
296
|
-
if (!configObject.config) {
|
297
|
-
return
|
298
|
-
}
|
299
|
-
|
300
|
-
const getReportPath = (ext: string) => {
|
301
|
-
if (!configObject.config?.dirPath) {
|
302
|
-
throw new Error("No directory path found in config")
|
303
|
-
}
|
304
|
-
|
305
|
-
return path.join(
|
306
|
-
configObject.config.dirPath,
|
307
|
-
"reports",
|
308
|
-
`${exercise.slug}`,
|
309
|
-
`report.${ext}`
|
310
|
-
)
|
311
|
-
}
|
312
|
-
|
313
|
-
const markdownReportPath = getReportPath("md")
|
314
|
-
const textReportPath = getReportPath("txt")
|
315
|
-
|
316
|
-
if (fs.existsSync(markdownReportPath)) {
|
317
|
-
let reportContent = ""
|
318
|
-
reportContent = fs.readFileSync(markdownReportPath, "utf8")
|
319
|
-
socket.dialog(reportContent)
|
320
|
-
}
|
321
|
-
|
322
|
-
if (fs.existsSync(textReportPath)) {
|
323
|
-
let reportContent = ""
|
324
|
-
reportContent = fs.readFileSync(textReportPath, "utf8")
|
325
|
-
socket.dialog(reportContent)
|
326
|
-
}
|
327
|
-
} catch (error) {
|
328
|
-
Console.debug("Error finding report for exercise.slug", error)
|
329
|
-
}
|
330
|
-
|
331
|
-
this.configManager?.save()
|
332
|
-
|
333
|
-
return true
|
334
|
-
})
|
335
|
-
|
336
|
-
const terminate = async () => {
|
337
|
-
Console.error("Terminating Learnpack...")
|
338
|
-
await TelemetryManager.submit()
|
339
|
-
this.configManager?.noCurrentExercise()
|
340
|
-
dispatcher.enqueue(dispatcher.events.END)
|
341
|
-
process.exit()
|
342
|
-
}
|
343
|
-
|
344
|
-
server.on("close", terminate)
|
345
|
-
process.on("SIGINT", terminate)
|
346
|
-
process.on("SIGTERM", terminate)
|
347
|
-
process.on("SIGHUP", terminate)
|
348
|
-
|
349
|
-
// finish the server startup
|
350
|
-
setTimeout(() => dispatcher.enqueue(dispatcher.events.RUNNING), 1000)
|
351
|
-
|
352
|
-
// start watching for file changes
|
353
|
-
if (StartCommand.flags.watch)
|
354
|
-
this.configManager.watchIndex((_filename, _fileContent) => {
|
355
|
-
// Instead of reloading with socket.reload(), I just notify the frontend for the file change
|
356
|
-
socket.emit("file_change", "ready", [_filename, _fileContent])
|
357
|
-
})
|
358
|
-
}
|
359
|
-
}
|
360
|
-
}
|
361
|
-
}
|
1
|
+
// import path from "path";
|
2
|
+
import { flags } from "@oclif/command"
|
3
|
+
import * as fs from "fs"
|
4
|
+
import * as path from "path"
|
5
|
+
|
6
|
+
import SessionCommand from "../utils/SessionCommand"
|
7
|
+
import Console from "../utils/console"
|
8
|
+
import socket from "../managers/socket"
|
9
|
+
import TelemetryManager, { TStep } from "../managers/telemetry"
|
10
|
+
import createServer from "../managers/server"
|
11
|
+
import queue from "../utils/fileQueue"
|
12
|
+
import {
|
13
|
+
decompress,
|
14
|
+
downloadEditor,
|
15
|
+
checkIfDirectoryExists,
|
16
|
+
} from "../managers/file"
|
17
|
+
import { prioritizeHTMLFile } from "../utils/misc"
|
18
|
+
|
19
|
+
import { IGitpodData } from "../models/gitpod-data"
|
20
|
+
import { IExercise, IExerciseData } from "../models/exercise-obj"
|
21
|
+
import { eventManager } from "../utils/osOperations"
|
22
|
+
import { cli } from "cli-ux"
|
23
|
+
import { TOpenWindowData } from "../models/socket"
|
24
|
+
import {
|
25
|
+
checkNotInstalledPlugins,
|
26
|
+
checkNotInstalledDependencies,
|
27
|
+
} from "../utils/checkNotInstalled"
|
28
|
+
|
29
|
+
export default class StartCommand extends SessionCommand {
|
30
|
+
static description = "Runs a small server with all the exercise instructions"
|
31
|
+
|
32
|
+
static flags = {
|
33
|
+
...SessionCommand.flags,
|
34
|
+
port: flags.string({ char: "p", description: "server port" }),
|
35
|
+
host: flags.string({ char: "h", description: "server host" }),
|
36
|
+
disableGrading: flags.boolean({
|
37
|
+
char: "D",
|
38
|
+
description: "disble grading functionality",
|
39
|
+
default: false,
|
40
|
+
}),
|
41
|
+
// disableGrading: flags.boolean({char: 'dg', description: 'disble grading functionality', default: false }),
|
42
|
+
watch: flags.boolean({
|
43
|
+
char: "w",
|
44
|
+
description: "Watch for file changes",
|
45
|
+
default: false,
|
46
|
+
}),
|
47
|
+
editor: flags.string({
|
48
|
+
char: "e",
|
49
|
+
description: "[preview, extension]",
|
50
|
+
options: ["extension", "preview"],
|
51
|
+
}),
|
52
|
+
version: flags.string({
|
53
|
+
char: "v",
|
54
|
+
description: "E.g: 1.0.1",
|
55
|
+
default: undefined,
|
56
|
+
}),
|
57
|
+
grading: flags.string({
|
58
|
+
char: "g",
|
59
|
+
description: "[isolated, incremental]",
|
60
|
+
options: ["isolated", "incremental"],
|
61
|
+
}),
|
62
|
+
debug: flags.boolean({
|
63
|
+
char: "d",
|
64
|
+
description: "debugger mode for more verbage",
|
65
|
+
default: false,
|
66
|
+
}),
|
67
|
+
}
|
68
|
+
|
69
|
+
// 🛑 IMPORTANT
|
70
|
+
// Every command that will use the configManager needs this init method
|
71
|
+
async init() {
|
72
|
+
const { flags } = this.parse(StartCommand)
|
73
|
+
await this.initSession(flags)
|
74
|
+
}
|
75
|
+
|
76
|
+
async run() {
|
77
|
+
// get configuration object
|
78
|
+
const configObject = this.configManager?.get()
|
79
|
+
|
80
|
+
const hasXDG = await eventManager.checkXDGInstalled()
|
81
|
+
|
82
|
+
const installedPlugins = this.config.plugins.map(plugin => {
|
83
|
+
return `${plugin.pjson.name}`
|
84
|
+
})
|
85
|
+
|
86
|
+
if (configObject) {
|
87
|
+
const { config } = configObject
|
88
|
+
|
89
|
+
// build exerises
|
90
|
+
this.configManager?.buildIndex()
|
91
|
+
|
92
|
+
Console.debug(
|
93
|
+
`Grading: ${config?.grading} ${
|
94
|
+
config?.disabledActions?.includes("test") ? "(disabled)" : ""
|
95
|
+
}, editor: ${config?.editor.mode} ${config?.editor.version}, for ${
|
96
|
+
Array.isArray(configObject?.exercises) ?
|
97
|
+
configObject?.exercises.length :
|
98
|
+
0
|
99
|
+
} exercises found`
|
100
|
+
)
|
101
|
+
|
102
|
+
const neededPlugins = await checkNotInstalledPlugins(
|
103
|
+
configObject?.exercises || [],
|
104
|
+
installedPlugins,
|
105
|
+
this
|
106
|
+
)
|
107
|
+
|
108
|
+
const allDepsInstalled = await checkNotInstalledDependencies(
|
109
|
+
neededPlugins.needed
|
110
|
+
)
|
111
|
+
if (!allDepsInstalled) {
|
112
|
+
this.exit(1)
|
113
|
+
}
|
114
|
+
|
115
|
+
const appAlreadyExists = checkIfDirectoryExists(`${config?.dirPath}/_app`)
|
116
|
+
|
117
|
+
if (!appAlreadyExists) {
|
118
|
+
// download app and decompress
|
119
|
+
await downloadEditor(
|
120
|
+
config?.editor.version,
|
121
|
+
`${config?.dirPath}/app.tar.gz`
|
122
|
+
)
|
123
|
+
|
124
|
+
Console.info("Decompressing LearnPack UI, this may take a minute...")
|
125
|
+
await decompress(
|
126
|
+
`${config?.dirPath}/app.tar.gz`,
|
127
|
+
`${config?.dirPath}/_app/`
|
128
|
+
)
|
129
|
+
}
|
130
|
+
|
131
|
+
// listen to socket commands
|
132
|
+
if (config && this.configManager) {
|
133
|
+
const server = await createServer(
|
134
|
+
configObject,
|
135
|
+
this.configManager,
|
136
|
+
process.env.NODE_ENV === "test"
|
137
|
+
)
|
138
|
+
server.setMaxListeners(30)
|
139
|
+
|
140
|
+
// I should call a method to get the EventListener
|
141
|
+
const dispatcher = queue.dispatcher({
|
142
|
+
create: true,
|
143
|
+
path: `${config.dirPath}/vscode_queue.json`,
|
144
|
+
})
|
145
|
+
|
146
|
+
if (configObject.exercises) {
|
147
|
+
const agent = configObject.config?.editor.agent || ""
|
148
|
+
const path = configObject.config?.dirPath || ""
|
149
|
+
const tutorialSlug = configObject.config?.slug || ""
|
150
|
+
|
151
|
+
const steps: TStep[] = configObject.exercises.map(
|
152
|
+
(e: IExercise, index): TStep => ({
|
153
|
+
slug: e.slug,
|
154
|
+
position: e.position || index,
|
155
|
+
files: e.files,
|
156
|
+
ai_interactions: [],
|
157
|
+
compilations: [],
|
158
|
+
tests: [],
|
159
|
+
quiz_submissions: [],
|
160
|
+
is_testeable: e.graded || false,
|
161
|
+
})
|
162
|
+
)
|
163
|
+
if (path && steps.length > 0) {
|
164
|
+
TelemetryManager.start(agent, steps, path, tutorialSlug)
|
165
|
+
}
|
166
|
+
|
167
|
+
if (config.telemetry) {
|
168
|
+
TelemetryManager.urls = config.telemetry
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
socket.start(config, server, false)
|
173
|
+
|
174
|
+
socket.on("open", (data: IGitpodData) => {
|
175
|
+
Console.debug("Opening these files: ", data)
|
176
|
+
|
177
|
+
const files = prioritizeHTMLFile(data.files)
|
178
|
+
|
179
|
+
if (config.editor.agent !== "os") {
|
180
|
+
// Console.info("Opening files for vscode agent")
|
181
|
+
eventManager.enqueue(dispatcher.events.OPEN_FILES, files)
|
182
|
+
} else {
|
183
|
+
// dispatcher.enqueue(dispatcher.events.OPEN_FILES, files)
|
184
|
+
Console.debug("Ignoring files for os agent")
|
185
|
+
}
|
186
|
+
|
187
|
+
socket.ready("Ready to compile...")
|
188
|
+
})
|
189
|
+
|
190
|
+
socket.on("open_window", (data: TOpenWindowData) => {
|
191
|
+
Console.debug("Opening window: ", data)
|
192
|
+
|
193
|
+
// cli.open(data.url); This uses XDG under the ground
|
194
|
+
if (config.os !== "linux" || (config.os === "linux" && hasXDG)) {
|
195
|
+
eventManager.enqueue(dispatcher.events.OPEN_WINDOW, data)
|
196
|
+
} else {
|
197
|
+
dispatcher.enqueue(dispatcher.events.OPEN_WINDOW, data)
|
198
|
+
}
|
199
|
+
|
200
|
+
socket.log("open_window", "", undefined, data.url)
|
201
|
+
})
|
202
|
+
|
203
|
+
socket.on("open_terminal", (exercise: IExerciseData) => {
|
204
|
+
Console.debug("Opening terminal: ", exercise)
|
205
|
+
// eventManager.enqueue(dispatcher.events.OPEN_TERMINAL, exercise);
|
206
|
+
if (config.editor.agent === "vscode") {
|
207
|
+
dispatcher.enqueue(dispatcher.events.OPEN_TERMINAL, exercise)
|
208
|
+
}
|
209
|
+
})
|
210
|
+
|
211
|
+
socket.on("reset", (exercise: IExerciseData) => {
|
212
|
+
try {
|
213
|
+
this.configManager?.reset(exercise.exerciseSlug)
|
214
|
+
socket.ready("Ready to compile...")
|
215
|
+
} catch (error) {
|
216
|
+
socket.error(
|
217
|
+
"compiler-error",
|
218
|
+
(error as TypeError).message ||
|
219
|
+
"There was an error reseting the exercise"
|
220
|
+
)
|
221
|
+
setTimeout(() => socket.ready("Ready to compile..."), 2000)
|
222
|
+
}
|
223
|
+
})
|
224
|
+
|
225
|
+
socket.on("build", async (data: IExerciseData) => {
|
226
|
+
const exercise = this.configManager?.getExercise(data.exerciseSlug)
|
227
|
+
|
228
|
+
if (!exercise?.language) {
|
229
|
+
socket.error(
|
230
|
+
"compiler-error",
|
231
|
+
"Impossible to detect language to build for " +
|
232
|
+
data.exerciseSlug +
|
233
|
+
"..."
|
234
|
+
)
|
235
|
+
return
|
236
|
+
}
|
237
|
+
|
238
|
+
socket.log(
|
239
|
+
"compiling",
|
240
|
+
"Building exercise " +
|
241
|
+
data.exerciseSlug +
|
242
|
+
" with " +
|
243
|
+
exercise.language +
|
244
|
+
"..."
|
245
|
+
)
|
246
|
+
await this.config.runHook("action", {
|
247
|
+
action: "compile",
|
248
|
+
socket,
|
249
|
+
configuration: config,
|
250
|
+
exercise,
|
251
|
+
telemetry: TelemetryManager,
|
252
|
+
})
|
253
|
+
})
|
254
|
+
|
255
|
+
socket.on("telemetry_event", (data: any) => {
|
256
|
+
const { stepPosition, event, eventData } = data
|
257
|
+
|
258
|
+
TelemetryManager.registerStepEvent(stepPosition, event, eventData)
|
259
|
+
})
|
260
|
+
|
261
|
+
socket.on("test", async (data: IExerciseData) => {
|
262
|
+
const exercise = this.configManager?.getExercise(data.exerciseSlug)
|
263
|
+
|
264
|
+
if (!exercise?.language) {
|
265
|
+
socket.error(
|
266
|
+
"compiler-error",
|
267
|
+
"Impossible to detect engine language for testing for " +
|
268
|
+
data.exerciseSlug +
|
269
|
+
"..."
|
270
|
+
)
|
271
|
+
return
|
272
|
+
}
|
273
|
+
|
274
|
+
if (
|
275
|
+
config?.disabledActions!.includes("test") ||
|
276
|
+
config?.disableGrading
|
277
|
+
) {
|
278
|
+
socket.ready("Grading is disabled on configuration")
|
279
|
+
return true
|
280
|
+
}
|
281
|
+
|
282
|
+
socket.log(
|
283
|
+
"testing",
|
284
|
+
"Testing your exercise using the " + exercise.language + " engine."
|
285
|
+
)
|
286
|
+
|
287
|
+
await this.config.runHook("action", {
|
288
|
+
action: "test",
|
289
|
+
socket,
|
290
|
+
configuration: config,
|
291
|
+
exercise,
|
292
|
+
telemetry: TelemetryManager,
|
293
|
+
})
|
294
|
+
|
295
|
+
try {
|
296
|
+
if (!configObject.config) {
|
297
|
+
return
|
298
|
+
}
|
299
|
+
|
300
|
+
const getReportPath = (ext: string) => {
|
301
|
+
if (!configObject.config?.dirPath) {
|
302
|
+
throw new Error("No directory path found in config")
|
303
|
+
}
|
304
|
+
|
305
|
+
return path.join(
|
306
|
+
configObject.config.dirPath,
|
307
|
+
"reports",
|
308
|
+
`${exercise.slug}`,
|
309
|
+
`report.${ext}`
|
310
|
+
)
|
311
|
+
}
|
312
|
+
|
313
|
+
const markdownReportPath = getReportPath("md")
|
314
|
+
const textReportPath = getReportPath("txt")
|
315
|
+
|
316
|
+
if (fs.existsSync(markdownReportPath)) {
|
317
|
+
let reportContent = ""
|
318
|
+
reportContent = fs.readFileSync(markdownReportPath, "utf8")
|
319
|
+
socket.dialog(reportContent)
|
320
|
+
}
|
321
|
+
|
322
|
+
if (fs.existsSync(textReportPath)) {
|
323
|
+
let reportContent = ""
|
324
|
+
reportContent = fs.readFileSync(textReportPath, "utf8")
|
325
|
+
socket.dialog(reportContent)
|
326
|
+
}
|
327
|
+
} catch (error) {
|
328
|
+
Console.debug("Error finding report for exercise.slug", error)
|
329
|
+
}
|
330
|
+
|
331
|
+
this.configManager?.save()
|
332
|
+
|
333
|
+
return true
|
334
|
+
})
|
335
|
+
|
336
|
+
const terminate = async () => {
|
337
|
+
Console.error("Terminating Learnpack...")
|
338
|
+
await TelemetryManager.submit()
|
339
|
+
this.configManager?.noCurrentExercise()
|
340
|
+
dispatcher.enqueue(dispatcher.events.END)
|
341
|
+
process.exit()
|
342
|
+
}
|
343
|
+
|
344
|
+
server.on("close", terminate)
|
345
|
+
process.on("SIGINT", terminate)
|
346
|
+
process.on("SIGTERM", terminate)
|
347
|
+
process.on("SIGHUP", terminate)
|
348
|
+
|
349
|
+
// finish the server startup
|
350
|
+
setTimeout(() => dispatcher.enqueue(dispatcher.events.RUNNING), 1000)
|
351
|
+
|
352
|
+
// start watching for file changes
|
353
|
+
if (StartCommand.flags.watch)
|
354
|
+
this.configManager.watchIndex((_filename, _fileContent) => {
|
355
|
+
// Instead of reloading with socket.reload(), I just notify the frontend for the file change
|
356
|
+
socket.emit("file_change", "ready", [_filename, _fileContent])
|
357
|
+
})
|
358
|
+
}
|
359
|
+
}
|
360
|
+
}
|
361
|
+
}
|