@learnpack/learnpack 2.1.44 → 2.1.46

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