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