@learnpack/learnpack 2.1.43 → 2.1.44

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,339 +1,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 { 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 { 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
+ }