@learnpack/learnpack 2.1.25 → 2.1.27

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. package/README.md +16 -16
  2. package/bin/run +17 -17
  3. package/bin/run.cmd +3 -3
  4. package/oclif.manifest.json +1 -1
  5. package/package.json +139 -138
  6. package/src/commands/audit.ts +134 -109
  7. package/src/commands/clean.ts +29 -29
  8. package/src/commands/download.ts +62 -62
  9. package/src/commands/init.ts +39 -39
  10. package/src/commands/login.ts +42 -42
  11. package/src/commands/logout.ts +43 -43
  12. package/src/commands/publish.ts +107 -107
  13. package/src/commands/start.ts +264 -234
  14. package/src/commands/test.ts +85 -85
  15. package/src/index.ts +1 -1
  16. package/src/managers/config/allowed_files.ts +29 -29
  17. package/src/managers/config/defaults.ts +4 -2
  18. package/src/managers/config/exercise.ts +309 -302
  19. package/src/managers/config/index.ts +159 -138
  20. package/src/managers/file.ts +178 -169
  21. package/src/managers/gitpod.ts +84 -84
  22. package/src/managers/server/index.ts +78 -69
  23. package/src/managers/server/routes.ts +139 -90
  24. package/src/managers/session.ts +147 -118
  25. package/src/managers/socket.ts +252 -239
  26. package/src/managers/test.ts +83 -83
  27. package/src/models/action.ts +10 -3
  28. package/src/models/config-manager.ts +23 -23
  29. package/src/models/config.ts +9 -2
  30. package/src/models/counter.ts +11 -11
  31. package/src/models/errors.ts +22 -22
  32. package/src/models/exercise-obj.ts +6 -3
  33. package/src/models/file.ts +5 -5
  34. package/src/models/findings.ts +18 -18
  35. package/src/models/flags.ts +10 -10
  36. package/src/models/front-matter.ts +11 -11
  37. package/src/models/gitpod-data.ts +19 -19
  38. package/src/models/language.ts +4 -4
  39. package/src/models/package.ts +7 -7
  40. package/src/models/plugin-config.ts +17 -17
  41. package/src/models/session.ts +29 -26
  42. package/src/models/socket.ts +54 -48
  43. package/src/models/status.ts +16 -15
  44. package/src/models/success-types.ts +1 -1
  45. package/src/plugin/command/compile.ts +17 -17
  46. package/src/plugin/command/test.ts +30 -30
  47. package/src/plugin/index.ts +6 -6
  48. package/src/plugin/plugin.ts +94 -94
  49. package/src/plugin/utils.ts +87 -87
  50. package/src/types/node-fetch.d.ts +1 -1
  51. package/src/ui/download.ts +71 -71
  52. package/src/utils/BaseCommand.ts +48 -48
  53. package/src/utils/SessionCommand.ts +48 -48
  54. package/src/utils/api.ts +246 -194
  55. package/src/utils/audit.ts +392 -395
  56. package/src/utils/console.ts +24 -24
  57. package/src/utils/errors.ts +117 -117
  58. package/src/utils/exercisesQueue.ts +51 -51
  59. package/src/utils/fileQueue.ts +198 -198
  60. package/src/utils/misc.ts +23 -23
  61. package/src/utils/templates/gitignore.txt +19 -19
  62. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +24 -24
  63. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +24 -24
  64. package/src/utils/templates/incremental/README.ejs +4 -4
  65. package/src/utils/templates/incremental/README.es.ejs +4 -4
  66. package/src/utils/templates/isolated/01-hello-world/README.es.md +26 -26
  67. package/src/utils/templates/isolated/01-hello-world/README.md +26 -26
  68. package/src/utils/templates/isolated/README.ejs +4 -4
  69. package/src/utils/templates/isolated/README.es.ejs +4 -4
  70. package/src/utils/templates/no-grading/README.ejs +4 -4
  71. package/src/utils/templates/no-grading/README.es.ejs +4 -4
  72. package/src/utils/validators.ts +18 -18
  73. package/src/utils/watcher.ts +14 -14
  74. package/lib/commands/audit.d.ts +0 -6
  75. package/lib/commands/audit.js +0 -317
  76. package/lib/commands/clean.d.ts +0 -8
  77. package/lib/commands/clean.js +0 -25
  78. package/lib/commands/download.d.ts +0 -13
  79. package/lib/commands/download.js +0 -55
  80. package/lib/commands/init.d.ts +0 -9
  81. package/lib/commands/init.js +0 -123
  82. package/lib/commands/login.d.ts +0 -14
  83. package/lib/commands/login.js +0 -37
  84. package/lib/commands/logout.d.ts +0 -14
  85. package/lib/commands/logout.js +0 -37
  86. package/lib/commands/publish.d.ts +0 -14
  87. package/lib/commands/publish.js +0 -82
  88. package/lib/commands/start.d.ts +0 -7
  89. package/lib/commands/start.js +0 -165
  90. package/lib/commands/test.d.ts +0 -6
  91. package/lib/commands/test.js +0 -62
  92. package/lib/index.d.ts +0 -1
  93. package/lib/index.js +0 -4
  94. package/lib/managers/config/allowed_files.d.ts +0 -5
  95. package/lib/managers/config/allowed_files.js +0 -30
  96. package/lib/managers/config/defaults.d.ts +0 -37
  97. package/lib/managers/config/defaults.js +0 -38
  98. package/lib/managers/config/exercise.d.ts +0 -36
  99. package/lib/managers/config/exercise.js +0 -230
  100. package/lib/managers/config/index.d.ts +0 -3
  101. package/lib/managers/config/index.js +0 -302
  102. package/lib/managers/file.d.ts +0 -13
  103. package/lib/managers/file.js +0 -134
  104. package/lib/managers/gitpod.d.ts +0 -3
  105. package/lib/managers/gitpod.js +0 -67
  106. package/lib/managers/server/index.d.ts +0 -6
  107. package/lib/managers/server/index.js +0 -51
  108. package/lib/managers/server/routes.d.ts +0 -4
  109. package/lib/managers/server/routes.js +0 -167
  110. package/lib/managers/session.d.ts +0 -3
  111. package/lib/managers/session.js +0 -104
  112. package/lib/managers/socket.d.ts +0 -3
  113. package/lib/managers/socket.js +0 -164
  114. package/lib/managers/test.d.ts +0 -0
  115. package/lib/managers/test.js +0 -84
  116. package/lib/models/action.d.ts +0 -2
  117. package/lib/models/action.js +0 -2
  118. package/lib/models/audit.d.ts +0 -15
  119. package/lib/models/audit.js +0 -2
  120. package/lib/models/config-manager.d.ts +0 -21
  121. package/lib/models/config-manager.js +0 -2
  122. package/lib/models/config.d.ts +0 -60
  123. package/lib/models/config.js +0 -2
  124. package/lib/models/counter.d.ts +0 -11
  125. package/lib/models/counter.js +0 -2
  126. package/lib/models/errors.d.ts +0 -15
  127. package/lib/models/errors.js +0 -2
  128. package/lib/models/exercise-obj.d.ts +0 -27
  129. package/lib/models/exercise-obj.js +0 -2
  130. package/lib/models/file.d.ts +0 -5
  131. package/lib/models/file.js +0 -2
  132. package/lib/models/findings.d.ts +0 -17
  133. package/lib/models/findings.js +0 -2
  134. package/lib/models/flags.d.ts +0 -10
  135. package/lib/models/flags.js +0 -2
  136. package/lib/models/front-matter.d.ts +0 -11
  137. package/lib/models/front-matter.js +0 -2
  138. package/lib/models/gitpod-data.d.ts +0 -16
  139. package/lib/models/gitpod-data.js +0 -2
  140. package/lib/models/language.d.ts +0 -4
  141. package/lib/models/language.js +0 -2
  142. package/lib/models/package.d.ts +0 -7
  143. package/lib/models/package.js +0 -2
  144. package/lib/models/plugin-config.d.ts +0 -16
  145. package/lib/models/plugin-config.js +0 -2
  146. package/lib/models/session.d.ts +0 -23
  147. package/lib/models/session.js +0 -2
  148. package/lib/models/socket.d.ts +0 -31
  149. package/lib/models/socket.js +0 -2
  150. package/lib/models/status.d.ts +0 -1
  151. package/lib/models/status.js +0 -2
  152. package/lib/models/success-types.d.ts +0 -1
  153. package/lib/models/success-types.js +0 -2
  154. package/lib/plugin/command/compile.d.ts +0 -6
  155. package/lib/plugin/command/compile.js +0 -18
  156. package/lib/plugin/command/test.d.ts +0 -6
  157. package/lib/plugin/command/test.js +0 -25
  158. package/lib/plugin/index.d.ts +0 -27
  159. package/lib/plugin/index.js +0 -7
  160. package/lib/plugin/plugin.d.ts +0 -8
  161. package/lib/plugin/plugin.js +0 -68
  162. package/lib/plugin/utils.d.ts +0 -16
  163. package/lib/plugin/utils.js +0 -58
  164. package/lib/ui/download.d.ts +0 -5
  165. package/lib/ui/download.js +0 -61
  166. package/lib/utils/BaseCommand.d.ts +0 -8
  167. package/lib/utils/BaseCommand.js +0 -41
  168. package/lib/utils/SessionCommand.d.ts +0 -10
  169. package/lib/utils/SessionCommand.js +0 -47
  170. package/lib/utils/api.d.ts +0 -12
  171. package/lib/utils/api.js +0 -173
  172. package/lib/utils/audit.d.ts +0 -16
  173. package/lib/utils/audit.js +0 -302
  174. package/lib/utils/console.d.ts +0 -12
  175. package/lib/utils/console.js +0 -19
  176. package/lib/utils/errors.d.ts +0 -17
  177. package/lib/utils/errors.js +0 -100
  178. package/lib/utils/exercisesQueue.d.ts +0 -9
  179. package/lib/utils/exercisesQueue.js +0 -38
  180. package/lib/utils/fileQueue.d.ts +0 -40
  181. package/lib/utils/fileQueue.js +0 -168
  182. package/lib/utils/misc.d.ts +0 -1
  183. package/lib/utils/misc.js +0 -23
  184. package/lib/utils/validators.d.ts +0 -5
  185. package/lib/utils/validators.js +0 -17
  186. package/lib/utils/watcher.d.ts +0 -2
  187. package/lib/utils/watcher.js +0 -23
@@ -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
+ }