@learnpack/learnpack 2.1.24 → 2.1.26

Sign up to get free protection for your applications and to get access to all the features.
Files changed (182) hide show
  1. package/README.md +16 -16
  2. package/bin/run +17 -17
  3. package/bin/run.cmd +3 -3
  4. package/lib/commands/audit.d.ts +6 -6
  5. package/lib/commands/audit.js +342 -317
  6. package/lib/commands/clean.d.ts +8 -8
  7. package/lib/commands/clean.js +25 -25
  8. package/lib/commands/download.d.ts +13 -13
  9. package/lib/commands/download.js +55 -55
  10. package/lib/commands/init.d.ts +9 -9
  11. package/lib/commands/init.js +123 -123
  12. package/lib/commands/login.d.ts +14 -14
  13. package/lib/commands/login.js +37 -37
  14. package/lib/commands/logout.d.ts +14 -14
  15. package/lib/commands/logout.js +37 -37
  16. package/lib/commands/publish.d.ts +14 -14
  17. package/lib/commands/publish.js +82 -82
  18. package/lib/commands/start.d.ts +7 -7
  19. package/lib/commands/start.js +165 -165
  20. package/lib/commands/test.d.ts +6 -6
  21. package/lib/commands/test.js +62 -62
  22. package/lib/index.d.ts +1 -1
  23. package/lib/index.js +4 -4
  24. package/lib/managers/config/allowed_files.d.ts +5 -5
  25. package/lib/managers/config/allowed_files.js +30 -30
  26. package/lib/managers/config/defaults.d.ts +39 -37
  27. package/lib/managers/config/defaults.js +40 -38
  28. package/lib/managers/config/exercise.d.ts +36 -36
  29. package/lib/managers/config/exercise.js +233 -230
  30. package/lib/managers/config/index.d.ts +3 -3
  31. package/lib/managers/config/index.js +320 -302
  32. package/lib/managers/file.d.ts +13 -13
  33. package/lib/managers/file.js +134 -134
  34. package/lib/managers/gitpod.d.ts +3 -3
  35. package/lib/managers/gitpod.js +67 -67
  36. package/lib/managers/server/index.d.ts +6 -6
  37. package/lib/managers/server/index.js +58 -51
  38. package/lib/managers/server/routes.d.ts +4 -4
  39. package/lib/managers/server/routes.js +167 -167
  40. package/lib/managers/session.d.ts +3 -3
  41. package/lib/managers/session.js +104 -104
  42. package/lib/managers/socket.d.ts +3 -3
  43. package/lib/managers/socket.js +164 -164
  44. package/lib/managers/test.js +84 -84
  45. package/lib/models/action.d.ts +2 -2
  46. package/lib/models/action.js +2 -2
  47. package/lib/models/audit.d.ts +15 -15
  48. package/lib/models/audit.js +2 -2
  49. package/lib/models/config-manager.d.ts +21 -21
  50. package/lib/models/config-manager.js +2 -2
  51. package/lib/models/config.d.ts +62 -60
  52. package/lib/models/config.js +2 -2
  53. package/lib/models/counter.d.ts +11 -11
  54. package/lib/models/counter.js +2 -2
  55. package/lib/models/errors.d.ts +15 -15
  56. package/lib/models/errors.js +2 -2
  57. package/lib/models/exercise-obj.d.ts +27 -27
  58. package/lib/models/exercise-obj.js +2 -2
  59. package/lib/models/file.d.ts +5 -5
  60. package/lib/models/file.js +2 -2
  61. package/lib/models/findings.d.ts +17 -17
  62. package/lib/models/findings.js +2 -2
  63. package/lib/models/flags.d.ts +10 -10
  64. package/lib/models/flags.js +2 -2
  65. package/lib/models/front-matter.d.ts +11 -11
  66. package/lib/models/front-matter.js +2 -2
  67. package/lib/models/gitpod-data.d.ts +16 -16
  68. package/lib/models/gitpod-data.js +2 -2
  69. package/lib/models/language.d.ts +4 -4
  70. package/lib/models/language.js +2 -2
  71. package/lib/models/package.d.ts +7 -7
  72. package/lib/models/package.js +2 -2
  73. package/lib/models/plugin-config.d.ts +16 -16
  74. package/lib/models/plugin-config.js +2 -2
  75. package/lib/models/session.d.ts +23 -23
  76. package/lib/models/session.js +2 -2
  77. package/lib/models/socket.d.ts +31 -31
  78. package/lib/models/socket.js +2 -2
  79. package/lib/models/status.d.ts +1 -1
  80. package/lib/models/status.js +2 -2
  81. package/lib/models/success-types.d.ts +1 -1
  82. package/lib/models/success-types.js +2 -2
  83. package/lib/plugin/command/compile.d.ts +6 -6
  84. package/lib/plugin/command/compile.js +18 -18
  85. package/lib/plugin/command/test.d.ts +6 -6
  86. package/lib/plugin/command/test.js +25 -25
  87. package/lib/plugin/index.d.ts +27 -27
  88. package/lib/plugin/index.js +7 -7
  89. package/lib/plugin/plugin.d.ts +8 -8
  90. package/lib/plugin/plugin.js +68 -68
  91. package/lib/plugin/utils.d.ts +16 -16
  92. package/lib/plugin/utils.js +58 -58
  93. package/lib/ui/download.d.ts +5 -5
  94. package/lib/ui/download.js +61 -61
  95. package/lib/utils/BaseCommand.d.ts +8 -8
  96. package/lib/utils/BaseCommand.js +41 -41
  97. package/lib/utils/SessionCommand.d.ts +10 -10
  98. package/lib/utils/SessionCommand.js +47 -47
  99. package/lib/utils/api.d.ts +12 -12
  100. package/lib/utils/api.js +173 -173
  101. package/lib/utils/audit.d.ts +16 -16
  102. package/lib/utils/audit.js +302 -302
  103. package/lib/utils/console.d.ts +12 -12
  104. package/lib/utils/console.js +19 -19
  105. package/lib/utils/errors.d.ts +17 -17
  106. package/lib/utils/errors.js +100 -100
  107. package/lib/utils/exercisesQueue.d.ts +9 -9
  108. package/lib/utils/exercisesQueue.js +38 -38
  109. package/lib/utils/fileQueue.d.ts +40 -40
  110. package/lib/utils/fileQueue.js +168 -168
  111. package/lib/utils/misc.d.ts +1 -1
  112. package/lib/utils/misc.js +23 -23
  113. package/lib/utils/validators.d.ts +5 -5
  114. package/lib/utils/validators.js +17 -17
  115. package/lib/utils/watcher.d.ts +2 -2
  116. package/lib/utils/watcher.js +23 -23
  117. package/oclif.manifest.json +1 -1
  118. package/package.json +138 -138
  119. package/src/commands/audit.ts +443 -418
  120. package/src/commands/clean.ts +29 -29
  121. package/src/commands/download.ts +62 -62
  122. package/src/commands/login.ts +42 -42
  123. package/src/commands/logout.ts +43 -43
  124. package/src/commands/publish.ts +107 -107
  125. package/src/commands/start.ts +238 -234
  126. package/src/commands/test.ts +85 -85
  127. package/src/index.ts +1 -1
  128. package/src/managers/config/allowed_files.ts +29 -29
  129. package/src/managers/config/defaults.ts +2 -0
  130. package/src/managers/config/exercise.ts +309 -302
  131. package/src/managers/config/index.ts +22 -1
  132. package/src/managers/file.ts +169 -169
  133. package/src/managers/gitpod.ts +84 -84
  134. package/src/managers/server/index.ts +77 -69
  135. package/src/managers/session.ts +118 -118
  136. package/src/managers/socket.ts +239 -239
  137. package/src/managers/test.ts +83 -83
  138. package/src/models/action.ts +3 -3
  139. package/src/models/config-manager.ts +23 -23
  140. package/src/models/config.ts +2 -0
  141. package/src/models/counter.ts +11 -11
  142. package/src/models/errors.ts +22 -22
  143. package/src/models/file.ts +5 -5
  144. package/src/models/findings.ts +18 -18
  145. package/src/models/flags.ts +10 -10
  146. package/src/models/front-matter.ts +11 -11
  147. package/src/models/gitpod-data.ts +19 -19
  148. package/src/models/language.ts +4 -4
  149. package/src/models/package.ts +7 -7
  150. package/src/models/plugin-config.ts +17 -17
  151. package/src/models/session.ts +26 -26
  152. package/src/models/socket.ts +48 -48
  153. package/src/models/status.ts +15 -15
  154. package/src/models/success-types.ts +1 -1
  155. package/src/plugin/command/compile.ts +17 -17
  156. package/src/plugin/command/test.ts +30 -30
  157. package/src/plugin/index.ts +6 -6
  158. package/src/plugin/plugin.ts +94 -94
  159. package/src/plugin/utils.ts +87 -87
  160. package/src/types/node-fetch.d.ts +1 -1
  161. package/src/ui/download.ts +71 -71
  162. package/src/utils/BaseCommand.ts +48 -48
  163. package/src/utils/SessionCommand.ts +48 -48
  164. package/src/utils/api.ts +194 -194
  165. package/src/utils/audit.ts +395 -395
  166. package/src/utils/console.ts +24 -24
  167. package/src/utils/errors.ts +117 -117
  168. package/src/utils/exercisesQueue.ts +51 -51
  169. package/src/utils/fileQueue.ts +198 -198
  170. package/src/utils/misc.ts +23 -23
  171. package/src/utils/templates/gitignore.txt +19 -19
  172. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +24 -24
  173. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +24 -24
  174. package/src/utils/templates/incremental/README.ejs +4 -4
  175. package/src/utils/templates/incremental/README.es.ejs +4 -4
  176. package/src/utils/templates/isolated/01-hello-world/README.es.md +26 -26
  177. package/src/utils/templates/isolated/01-hello-world/README.md +26 -26
  178. package/src/utils/templates/isolated/README.ejs +4 -4
  179. package/src/utils/templates/isolated/README.es.ejs +4 -4
  180. package/src/utils/templates/no-grading/README.ejs +4 -4
  181. package/src/utils/templates/no-grading/README.es.ejs +4 -4
  182. package/src/utils/validators.ts +18 -18
@@ -1,302 +1,309 @@
1
- import * as p from "path";
2
- // import frontMatter from 'front-matter'
3
- import * as fs from "fs";
4
- import Console from "../../utils/console";
5
- import allowed from "./allowed_files";
6
-
7
- import { IConfigObj } from "../../models/config";
8
- import { IFile } from "../../models/file";
9
- import { IExercise } from "../../models/exercise-obj";
10
-
11
- // eslint-disable-next-line
12
- const frontMatter = require("front-matter");
13
-
14
- export const exercise = (
15
- path: string,
16
- position: number,
17
- configObject: IConfigObj
18
- ): IExercise => {
19
- const { config, exercises } = configObject;
20
- let slug = p.basename(path);
21
-
22
- if (!validateExerciseDirectoryName(slug)) {
23
- Console.error(
24
- `Exercise directory ${slug} has an invalid name, it has to start with two or three digits followed by words separated by underscors or hyphen (no white spaces). e.g: 01.12-hello-world`
25
- );
26
- }
27
-
28
- // get all the files
29
- const files = fs.readdirSync(path);
30
-
31
- /**
32
- * build the translation array like:
33
- {
34
- "us": "path/to/Readme.md",
35
- "es": "path/to/Readme.es.md"
36
- }
37
- */
38
- const translations: { [key: string]: string } = {};
39
- for (const file of files.filter(file =>
40
- file.toLowerCase().includes("readme")
41
- )) {
42
- const parts = file.split(".");
43
-
44
- if (parts.length === 3)
45
- translations[parts[1]] = file;
46
- else
47
- translations.us = file;
48
- }
49
-
50
- // if the slug is a dot, it means there is not "exercises" folder, and its just a single README.md
51
- if (slug === ".")
52
- slug = "default-index";
53
-
54
- const detected = detect(configObject, files);
55
-
56
- const exerciseObj: IExercise = {
57
- position,
58
- path,
59
- slug,
60
- translations,
61
- language: detected?.language,
62
- entry: detected?.entry ? path + "/" + detected.entry : null, // full path to the exercise entry
63
- title: slug || "Exercise",
64
- graded: files.some(
65
- file =>
66
- file.toLowerCase().startsWith("test.") ||
67
- file.toLowerCase().startsWith("tests.")
68
- ),
69
- files: filterFiles(files, path),
70
- // if the exercises was on the config before I may keep the status done
71
- done:
72
- Array.isArray(exercises) &&
73
- typeof exercises[position] !== "undefined" &&
74
- path.slice(Math.max(0, path.indexOf("exercises/") + 10)) ===
75
- exercises[position].slug ?
76
- exercises[position].done :
77
- false,
78
- getReadme: function (lang = null) {
79
- if (lang === "us")
80
- lang = null; // <-- english is default, no need to append it to the file name
81
-
82
- if (!fs.existsSync(`${this.path}/README${lang ? "." + lang : ""}.md`)) {
83
- Console.error(
84
- `Language ${lang} not found for exercise ${slug}, switching to default language`
85
- );
86
-
87
- if (lang)
88
- lang = null;
89
-
90
- if (!fs.existsSync(`${this.path}/README${lang ? "." + lang : ""}.md`))
91
- throw new Error(
92
- "Readme file not found for exercise: " + this.path + "/README.md"
93
- );
94
- }
95
-
96
- const content = fs.readFileSync(
97
- `${this.path}/README${lang ? "." + lang : ""}.md`,
98
- "utf8"
99
- );
100
- const attr = frontMatter(content);
101
- return attr;
102
- },
103
- getFile: function (name: string) {
104
- const file: IFile | undefined = this.files.find(
105
- (f: IFile) => f.name === name
106
- );
107
-
108
- if (!file || !fs.existsSync(file.path)) {
109
- throw new Error(`File not found: + ${file?.path}`);
110
- } else if (fs.lstatSync(file.path).isDirectory()) {
111
- return (
112
- "Error: This is not a file to be read, but a directory: " + file.path
113
- );
114
- }
115
-
116
- // get file content
117
- const content = fs.readFileSync(file.path);
118
-
119
- // create reset folder
120
- if (!fs.existsSync(`${config?.dirPath}/resets`))
121
- fs.mkdirSync(`${config?.dirPath}/resets`);
122
- if (!fs.existsSync(`${config?.dirPath}/resets/` + this.slug)) {
123
- fs.mkdirSync(`${config?.dirPath}/resets/` + this.slug);
124
- if (!fs.existsSync(`${config?.dirPath}/resets/${this.slug}/${name}`)) {
125
- fs.writeFileSync(
126
- `${config?.dirPath}/resets/${this.slug}/${name}`,
127
- content
128
- );
129
- }
130
- }
131
-
132
- return content;
133
- },
134
- saveFile: function (name: string, content: string) {
135
- const file: IFile | undefined = this.files.find(
136
- (f: IFile) => f.name === name
137
- );
138
-
139
- if (file) {
140
- if (!fs.existsSync(file.path)) {
141
- throw new Error("File not found: " + file.path);
142
- }
143
-
144
- return fs.writeFileSync(file.path, content, "utf8");
145
- }
146
- },
147
- getTestReport: function () {
148
- const _path = `${configObject?.confPath?.base}/reports/${this.slug}.json`;
149
-
150
- if (!fs.existsSync(_path))
151
- return {};
152
-
153
- const content = fs.readFileSync(_path);
154
- const data = JSON.parse(`${content}`);
155
- return data;
156
- },
157
- };
158
-
159
- return exerciseObj;
160
- };
161
-
162
- export const validateExerciseDirectoryName = (str: string) => {
163
- if (str === "./")
164
- return true;
165
- // TODO: Add nameValidationREgex from the config
166
- const regex = /^(\d{2,3}(\.\d{1,2})?-([\dA-Za-z]+(-|_)?)+)$/;
167
- return regex.test(str);
168
- };
169
-
170
- export const isCodable = (str: string) => {
171
- const extension = p.extname(str);
172
- return allowed.extensions.includes(extension.slice(1).toLowerCase());
173
- };
174
-
175
- const isNotConfiguration = (str: string) => {
176
- return !allowed.names.includes(str);
177
- };
178
-
179
- export const shouldBeVisible = function (file: IFile) {
180
- return (
181
- // doest not have "test." on their name
182
- !file.name.toLocaleLowerCase().includes("test.") &&
183
- !file.name.toLocaleLowerCase().includes("tests.") &&
184
- !file.name.toLocaleLowerCase().includes(".hide.") &&
185
- // ignore hidden files
186
- file.name.charAt(0) !== "." &&
187
- // ignore learn.json and bc.json
188
- !file.name.toLocaleLowerCase().includes("learn.json") &&
189
- !file.name.toLocaleLowerCase().includes("bc.json") &&
190
- // ignore images, videos, vectors, etc.
191
- isCodable(file.name) &&
192
- isNotConfiguration(file.name) &&
193
- // readme's and directories
194
- !file.name.toLowerCase().includes("readme.") &&
195
- !isDirectory(file.path) &&
196
- file.name.charAt(0) !== "_"
197
- );
198
- };
199
-
200
- export const isDirectory = (source: string) => {
201
- // if(path.basename(source) === path.basename(config.dirPath)) return false
202
- return fs.lstatSync(source).isDirectory();
203
- };
204
-
205
- export const detect = (
206
- configObject: IConfigObj | undefined,
207
- files: Array<string>
208
- ) => {
209
- if (!configObject) {
210
- return;
211
- }
212
-
213
- const { config } = configObject;
214
-
215
- if (!config)
216
- throw new Error("No configuration found during the engine detection");
217
-
218
- if (!config.entries)
219
- throw new Error(
220
- "No configuration found for entries, please add a 'entries' object with the default file name for your exercise entry file that is going to be used while compiling, for example: index.html for html, app.py for python3, etc."
221
- );
222
- // A language was found on the config object, but this language will only be used as last resort, learnpack will try to guess each exercise language independently based on file extension (js, jsx, html, etc.)
223
-
224
- let hasFiles = files.filter(f => f.includes(".py"));
225
- if (hasFiles.length > 0)
226
- return {
227
- language: "python3",
228
- entry: hasFiles.find(f => config.entries.python3 === f),
229
- };
230
-
231
- hasFiles = files.filter(f => f.includes(".java"));
232
- if (hasFiles.length > 0)
233
- return {
234
- language: "java",
235
- entry: hasFiles.find(f => config.entries.java === f),
236
- };
237
-
238
- hasFiles = files.filter(f => f.includes(".jsx"));
239
- if (hasFiles.length > 0)
240
- return {
241
- language: "react",
242
- entry: hasFiles.find(f => config.entries.react === f),
243
- };
244
- const hasHTML = files.filter(f => f.includes("index.html"));
245
- const hasIndexJS = files.find(f => f.includes("index.js"));
246
- const hasJS = files.filter(f => f.includes(".js"));
247
- // angular, vue, vanillajs needs to have at least 2 files (html,css,js),
248
- // the test.js and the entry file in js
249
- // if not its just another HTML
250
-
251
- if (hasIndexJS && hasHTML.length > 0)
252
- return {
253
- language: "vanillajs",
254
- entry: hasIndexJS,
255
- };
256
- if (hasHTML.length > 0)
257
- return {
258
- language: "html",
259
- entry: hasHTML.find(f => config.entries.html === f),
260
- };
261
- if (hasJS.length > 0)
262
- return {
263
- language: "node",
264
- entry: hasJS.find(f => config.entries.node === f),
265
- };
266
-
267
- return {
268
- language: null,
269
- entry: null,
270
- };
271
- };
272
-
273
- export const filterFiles = (files: Array<string>, basePath = ".") =>
274
- files
275
- .map((ex: string) => ({
276
- path: basePath + "/" + ex,
277
- name: ex,
278
- hidden: !shouldBeVisible({
279
- name: ex,
280
- path: basePath + "/" + ex,
281
- } as IFile),
282
- }))
283
- .sort((f1, f2) => {
284
- const score: { [key: string]: number } = {
285
- // sorting priority
286
- "index.html": 1,
287
- "styles.css": 2,
288
- "styles.scss": 2,
289
- "style.css": 2,
290
- "style.scss": 2,
291
- "index.css": 2,
292
- "index.scss": 2,
293
- "index.js": 3,
294
- };
295
- return score[f1.name] < score[f2.name] ? -1 : 1;
296
- });
297
-
298
- export default {
299
- exercise,
300
- detect,
301
- filterFiles,
302
- };
1
+ import * as p from "path";
2
+ // import frontMatter from 'front-matter'
3
+ import * as fs from "fs";
4
+ import Console from "../../utils/console";
5
+ import allowed from "./allowed_files";
6
+
7
+ import { IConfigObj } from "../../models/config";
8
+ import { IFile } from "../../models/file";
9
+ import { IExercise } from "../../models/exercise-obj";
10
+
11
+ // eslint-disable-next-line
12
+ const frontMatter = require("front-matter");
13
+
14
+ export const exercise = (
15
+ path: string,
16
+ position: number,
17
+ configObject: IConfigObj
18
+ ): IExercise => {
19
+ const { config, exercises } = configObject;
20
+ let slug = p.basename(path);
21
+
22
+ if (!validateExerciseDirectoryName(slug)) {
23
+ Console.error(
24
+ `Exercise directory ${slug} has an invalid name, it has to start with two or three digits followed by words separated by underscors or hyphen (no white spaces). e.g: 01.12-hello-world`
25
+ );
26
+ }
27
+
28
+ // get all the files
29
+ const files = fs.readdirSync(path);
30
+
31
+ /**
32
+ * build the translation array like:
33
+ {
34
+ "us": "path/to/Readme.md",
35
+ "es": "path/to/Readme.es.md"
36
+ }
37
+ */
38
+ const translations: { [key: string]: string } = {};
39
+ for (const file of files.filter(file =>
40
+ file.toLowerCase().includes("readme")
41
+ )) {
42
+ const parts = file.split(".");
43
+
44
+ if (parts.length === 3)
45
+ translations[parts[1]] = file;
46
+ else
47
+ translations.us = file;
48
+ }
49
+
50
+ // if the slug is a dot, it means there is not "exercises" folder, and its just a single README.md
51
+ if (slug === ".")
52
+ slug = "default-index";
53
+
54
+ const detected = detect(configObject, files);
55
+
56
+ const exerciseObj: IExercise = {
57
+ position,
58
+ path,
59
+ slug,
60
+ translations,
61
+ language: detected?.language,
62
+ entry: detected?.entry ? path + "/" + detected.entry : null, // full path to the exercise entry
63
+ title: slug || "Exercise",
64
+ graded: files.some(
65
+ (file: any) =>
66
+ file.toLowerCase().startsWith("test.") ||
67
+ file.toLowerCase().startsWith("tests.")
68
+ ),
69
+ files: filterFiles(files, path),
70
+ // if the exercises was on the config before I may keep the status done
71
+ done:
72
+ Array.isArray(exercises) &&
73
+ typeof exercises[position] !== "undefined" &&
74
+ path.slice(Math.max(0, path.indexOf("exercises/") + 10)) ===
75
+ exercises[position].slug ?
76
+ exercises[position].done :
77
+ false,
78
+ getReadme: function (lang = null) {
79
+ if (lang === "us")
80
+ lang = null; // <-- english is default, no need to append it to the file name
81
+
82
+ if (!fs.existsSync(`${this.path}/README${lang ? "." + lang : ""}.md`)) {
83
+ Console.error(
84
+ `Language ${lang} not found for exercise ${slug}, switching to default language`
85
+ );
86
+
87
+ if (lang)
88
+ lang = null;
89
+
90
+ if (!fs.existsSync(`${this.path}/README${lang ? "." + lang : ""}.md`))
91
+ throw new Error(
92
+ "Readme file not found for exercise: " + this.path + "/README.md"
93
+ );
94
+ }
95
+
96
+ const content = fs.readFileSync(
97
+ `${this.path}/README${lang ? "." + lang : ""}.md`,
98
+ "utf8"
99
+ );
100
+ const attr = frontMatter(content);
101
+ return attr;
102
+ },
103
+ getFile: function (name: string) {
104
+ const file: IFile | undefined = this.files.find(
105
+ (f: IFile) => f.name === name
106
+ );
107
+
108
+ if (!file || !fs.existsSync(file.path)) {
109
+ throw new Error(`File not found: + ${file?.path}`);
110
+ } else if (fs.lstatSync(file.path).isDirectory()) {
111
+ return (
112
+ "Error: This is not a file to be read, but a directory: " + file.path
113
+ );
114
+ }
115
+
116
+ // get file content
117
+ const content = fs.readFileSync(file.path);
118
+
119
+ // create reset folder
120
+ if (!fs.existsSync(`${config?.dirPath}/resets`))
121
+ fs.mkdirSync(`${config?.dirPath}/resets`);
122
+ if (!fs.existsSync(`${config?.dirPath}/resets/` + this.slug)) {
123
+ fs.mkdirSync(`${config?.dirPath}/resets/` + this.slug);
124
+ for (const _file of this.files) {
125
+ const fileContent = fs.readFileSync(_file.path);
126
+ if (
127
+ !fs.existsSync(
128
+ `${config?.dirPath}/resets/${this.slug}/${_file.name}`
129
+ )
130
+ ) {
131
+ fs.writeFileSync(
132
+ `${config?.dirPath}/resets/${this.slug}/${_file.name}`,
133
+ fileContent
134
+ );
135
+ }
136
+ }
137
+ }
138
+
139
+ return content;
140
+ },
141
+ saveFile: function (name: string, content: string) {
142
+ const file: IFile | undefined = this.files.find(
143
+ (f: IFile) => f.name === name
144
+ );
145
+
146
+ if (file) {
147
+ if (!fs.existsSync(file.path)) {
148
+ throw new Error("File not found: " + file.path);
149
+ }
150
+
151
+ return fs.writeFileSync(file.path, content, "utf8");
152
+ }
153
+ },
154
+ getTestReport: function () {
155
+ const _path = `${configObject?.confPath?.base}/reports/${this.slug}.json`;
156
+
157
+ if (!fs.existsSync(_path))
158
+ return {};
159
+
160
+ const content = fs.readFileSync(_path);
161
+ const data = JSON.parse(`${content}`);
162
+ return data;
163
+ },
164
+ };
165
+
166
+ return exerciseObj;
167
+ };
168
+
169
+ export const validateExerciseDirectoryName = (str: string) => {
170
+ if (str === "./")
171
+ return true;
172
+ // TODO: Add nameValidationREgex from the config
173
+ const regex = /^(\d{2,3}(\.\d{1,2})?-([\dA-Za-z]+(-|_)?)+)$/;
174
+ return regex.test(str);
175
+ };
176
+
177
+ export const isCodable = (str: string) => {
178
+ const extension = p.extname(str);
179
+ return allowed.extensions.includes(extension.slice(1).toLowerCase());
180
+ };
181
+
182
+ const isNotConfiguration = (str: string) => {
183
+ return !allowed.names.includes(str);
184
+ };
185
+
186
+ export const shouldBeVisible = function (file: IFile) {
187
+ return (
188
+ // doest not have "test." on their name
189
+ !file.name.toLocaleLowerCase().includes("test.") &&
190
+ !file.name.toLocaleLowerCase().includes("tests.") &&
191
+ !file.name.toLocaleLowerCase().includes(".hide.") &&
192
+ // ignore hidden files
193
+ file.name.charAt(0) !== "." &&
194
+ // ignore learn.json and bc.json
195
+ !file.name.toLocaleLowerCase().includes("learn.json") &&
196
+ !file.name.toLocaleLowerCase().includes("bc.json") &&
197
+ // ignore images, videos, vectors, etc.
198
+ isCodable(file.name) &&
199
+ isNotConfiguration(file.name) &&
200
+ // readme's and directories
201
+ !file.name.toLowerCase().includes("readme.") &&
202
+ !isDirectory(file.path) &&
203
+ file.name.charAt(0) !== "_"
204
+ );
205
+ };
206
+
207
+ export const isDirectory = (source: string) => {
208
+ // if(path.basename(source) === path.basename(config.dirPath)) return false
209
+ return fs.lstatSync(source).isDirectory();
210
+ };
211
+
212
+ export const detect = (
213
+ configObject: IConfigObj | undefined,
214
+ files: Array<string>
215
+ ) => {
216
+ if (!configObject) {
217
+ return;
218
+ }
219
+
220
+ const { config } = configObject;
221
+
222
+ if (!config)
223
+ throw new Error("No configuration found during the engine detection");
224
+
225
+ if (!config.entries)
226
+ throw new Error(
227
+ "No configuration found for entries, please add a 'entries' object with the default file name for your exercise entry file that is going to be used while compiling, for example: index.html for html, app.py for python3, etc."
228
+ );
229
+ // A language was found on the config object, but this language will only be used as last resort, learnpack will try to guess each exercise language independently based on file extension (js, jsx, html, etc.)
230
+
231
+ let hasFiles = files.filter(f => f.includes(".py"));
232
+ if (hasFiles.length > 0)
233
+ return {
234
+ language: "python3",
235
+ entry: hasFiles.find(f => config.entries.python3 === f),
236
+ };
237
+
238
+ hasFiles = files.filter(f => f.includes(".java"));
239
+ if (hasFiles.length > 0)
240
+ return {
241
+ language: "java",
242
+ entry: hasFiles.find(f => config.entries.java === f),
243
+ };
244
+
245
+ hasFiles = files.filter(f => f.includes(".jsx"));
246
+ if (hasFiles.length > 0)
247
+ return {
248
+ language: "react",
249
+ entry: hasFiles.find(f => config.entries.react === f),
250
+ };
251
+ const hasHTML = files.filter(f => f.includes("index.html"));
252
+ const hasIndexJS = files.find(f => f.includes("index.js"));
253
+ const hasJS = files.filter(f => f.includes(".js"));
254
+ // angular, vue, vanillajs needs to have at least 2 files (html,css,js),
255
+ // the test.js and the entry file in js
256
+ // if not its just another HTML
257
+
258
+ if (hasIndexJS && hasHTML.length > 0)
259
+ return {
260
+ language: "vanillajs",
261
+ entry: hasIndexJS,
262
+ };
263
+ if (hasHTML.length > 0)
264
+ return {
265
+ language: "html",
266
+ entry: hasHTML.find(f => config.entries.html === f),
267
+ };
268
+ if (hasJS.length > 0)
269
+ return {
270
+ language: "node",
271
+ entry: hasJS.find(f => config.entries.node === f),
272
+ };
273
+
274
+ return {
275
+ language: null,
276
+ entry: null,
277
+ };
278
+ };
279
+
280
+ export const filterFiles = (files: Array<string>, basePath = ".") =>
281
+ files
282
+ .map((ex: string) => ({
283
+ path: basePath + "/" + ex,
284
+ name: ex,
285
+ hidden: !shouldBeVisible({
286
+ name: ex,
287
+ path: basePath + "/" + ex,
288
+ } as IFile),
289
+ }))
290
+ .sort((f1, f2) => {
291
+ const score: { [key: string]: number } = {
292
+ // sorting priority
293
+ "index.html": 1,
294
+ "styles.css": 2,
295
+ "styles.scss": 2,
296
+ "style.css": 2,
297
+ "style.scss": 2,
298
+ "index.css": 2,
299
+ "index.scss": 2,
300
+ "index.js": 3,
301
+ };
302
+ return score[f1.name] < score[f2.name] ? -1 : 1;
303
+ });
304
+
305
+ export default {
306
+ exercise,
307
+ detect,
308
+ filterFiles,
309
+ };
@@ -57,6 +57,23 @@ const getGitpodAddress = () => {
57
57
  return "http://localhost";
58
58
  };
59
59
 
60
+ const getCodespacesNamespace = () => {
61
+ // https://orange-rotary-phone-wxpg49q5gcg4rp-3000.app.github.dev
62
+ const codespace_name = shell
63
+ .exec("echo $CODESPACE_NAME", { silent: true })
64
+ .stdout.replace(/(\r\n|\n|\r)/gm, "");
65
+
66
+ if (
67
+ !codespace_name ||
68
+ codespace_name === "" ||
69
+ codespace_name === undefined
70
+ ) {
71
+ return null;
72
+ }
73
+
74
+ return codespace_name;
75
+ };
76
+
60
77
  export default async ({
61
78
  grading,
62
79
  mode,
@@ -124,12 +141,17 @@ export default async ({
124
141
  });
125
142
 
126
143
  // auto detect agent (if possible)
144
+ const codespaces_workspace = getCodespacesNamespace();
127
145
  if (shell.which("gp") && configObj && configObj.config) {
128
146
  configObj.config.editor.agent = "vscode";
129
147
  configObj.address = getGitpodAddress();
130
148
  configObj.config.publicUrl = `https://${
131
149
  configObj.config.port
132
150
  }-${configObj.address?.slice(8)}`;
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`;
133
155
  } else if (configObj.config && !configObj.config.editor.agent) {
134
156
  configObj.config.editor.agent = "localhost";
135
157
  }
@@ -370,7 +392,6 @@ onChange();
370
392
  configObj.config.translations = [
371
393
  ...new Set(configObj.config.translations),
372
394
  ];
373
-
374
395
  fs.writeFileSync(
375
396
  configObj.config.dirPath + "/config.json",
376
397
  JSON.stringify(configObj, null, 4)