@learnpack/learnpack 2.1.18 → 2.1.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -16,138 +16,150 @@ class AuditCommand extends SessionCommand_1.default {
16
16
  await this.initSession(flags);
17
17
  }
18
18
  async run() {
19
- var _a, _b, _c, _d, _e, _f, _g;
19
+ var _a, _b, _c, _d, _e, _f, _g, _h;
20
20
  console_1.default.log("Running command audit...");
21
21
  // Get configuration object.
22
22
  let config = (_a = this.configManager) === null || _a === void 0 ? void 0 : _a.get();
23
23
  if (config) {
24
24
  const errors = [];
25
25
  const warnings = [];
26
- const counter = {
27
- images: {
28
- error: 0,
29
- total: 0,
30
- },
31
- links: {
32
- error: 0,
33
- total: 0,
34
- },
35
- exercises: 0,
36
- readmeFiles: 0,
37
- };
38
- // Checks if learnpack clean has been run
39
- audit_1.default.checkLearnpackClean(config, errors);
40
- // Build exercises if they are not built yet.
41
- (_b = this.configManager) === null || _b === void 0 ? void 0 : _b.buildIndex();
42
- config = (_c = this.configManager) === null || _c === void 0 ? void 0 : _c.get();
43
- // Check if the exercises folder has some files within any ./exercise
44
- const exercisesPath = config.config.exercisesPath;
45
- fs.readdir(exercisesPath, (err, files) => {
46
- if (err) {
47
- return console.log("Unable to scan directory: " + err);
48
- }
49
- // listing all files using forEach
50
- for (const file of files) {
51
- // Do whatever you want to do with the file
52
- const filePath = path.join(exercisesPath, file);
53
- if (fs.statSync(filePath).isFile())
54
- warnings.push({
55
- exercise: file,
56
- msg: "This file is not inside any exercise folder.",
57
- });
58
- }
59
- });
60
- // This function checks that each of the url's are working.
61
- const checkUrl = async (file, exercise) => {
62
- var _a, _b, _c, _d;
63
- if (!fs.existsSync(file.path))
64
- return false;
65
- const content = fs.readFileSync(file.path).toString();
66
- const isEmpty = audit_1.default.checkForEmptySpaces(content);
67
- if (isEmpty || !content)
68
- errors.push({
69
- exercise: exercise.title,
70
- msg: `This file (${file.name}) doesn't have any content inside.`,
71
- });
72
- const frontmatter = fm(content);
73
- for (const attribute in frontmatter.attributes) {
74
- if (Object.prototype.hasOwnProperty.call(frontmatter.attributes, attribute) &&
75
- (attribute === "intro" || attribute === "tutorial")) {
76
- counter.links.total++;
77
- try {
78
- // eslint-disable-next-line
79
- let res = await fetch(frontmatter.attributes[attribute], {
80
- method: "HEAD",
26
+ if (((_b = config === null || config === void 0 ? void 0 : config.config) === null || _b === void 0 ? void 0 : _b.projectType) === "tutorial") {
27
+ const counter = {
28
+ images: {
29
+ error: 0,
30
+ total: 0,
31
+ },
32
+ links: {
33
+ error: 0,
34
+ total: 0,
35
+ },
36
+ exercises: 0,
37
+ readmeFiles: 0,
38
+ };
39
+ // Checks if learnpack clean has been run
40
+ audit_1.default.checkLearnpackClean(config, errors);
41
+ // Build exercises if they are not built yet.
42
+ (_c = this.configManager) === null || _c === void 0 ? void 0 : _c.buildIndex();
43
+ config = (_d = this.configManager) === null || _d === void 0 ? void 0 : _d.get();
44
+ // Check if the exercises folder has some files within any ./exercise
45
+ const exercisesPath = config.config.exercisesPath;
46
+ fs.readdir(exercisesPath, (err, files) => {
47
+ if (err) {
48
+ return console.log("Unable to scan directory: " + err);
49
+ }
50
+ // listing all files using forEach
51
+ for (const file of files) {
52
+ // Do whatever you want to do with the file
53
+ const filePath = path.join(exercisesPath, file);
54
+ if (fs.statSync(filePath).isFile())
55
+ warnings.push({
56
+ exercise: file,
57
+ msg: "This file is not inside any exercise folder.",
81
58
  });
82
- if (!res.ok) {
59
+ }
60
+ });
61
+ // This function checks that each of the url's are working.
62
+ const checkUrl = async (file, exercise) => {
63
+ var _a, _b, _c, _d;
64
+ if (!fs.existsSync(file.path))
65
+ return false;
66
+ const content = fs.readFileSync(file.path).toString();
67
+ const isEmpty = audit_1.default.checkForEmptySpaces(content);
68
+ if (isEmpty || !content)
69
+ errors.push({
70
+ exercise: exercise.title,
71
+ msg: `This file (${file.name}) doesn't have any content inside.`,
72
+ });
73
+ const frontmatter = fm(content);
74
+ for (const attribute in frontmatter.attributes) {
75
+ if (Object.prototype.hasOwnProperty.call(frontmatter.attributes, attribute) &&
76
+ (attribute === "intro" || attribute === "tutorial")) {
77
+ counter.links.total++;
78
+ try {
79
+ // eslint-disable-next-line
80
+ let res = await fetch(frontmatter.attributes[attribute], {
81
+ method: "HEAD",
82
+ });
83
+ if (!res.ok) {
84
+ counter.links.error++;
85
+ errors.push({
86
+ exercise: exercise.title,
87
+ msg: `This link is broken (${res.ok}): ${frontmatter.attributes[attribute]}`,
88
+ });
89
+ }
90
+ }
91
+ catch (_e) {
83
92
  counter.links.error++;
84
93
  errors.push({
85
94
  exercise: exercise.title,
86
- msg: `This link is broken (${res.ok}): ${frontmatter.attributes[attribute]}`,
95
+ msg: `This link is broken: ${frontmatter.attributes[attribute]}`,
87
96
  });
88
97
  }
89
98
  }
90
- catch (_e) {
91
- counter.links.error++;
92
- errors.push({
93
- exercise: exercise.title,
94
- msg: `This link is broken: ${frontmatter.attributes[attribute]}`,
95
- });
96
- }
97
99
  }
98
- }
99
- // Check url's of each README file.
100
- const findings = audit_1.default.findInFile(["relativeImages", "externalImages", "markdownLinks"], content);
101
- for (const finding in findings) {
102
- if (Object.prototype.hasOwnProperty.call(findings, finding)) {
103
- const obj = findings[finding];
104
- // Valdites all the relative path images.
105
- if (finding === "relativeImages" && Object.keys(obj).length > 0) {
106
- for (const img in obj) {
107
- if (Object.prototype.hasOwnProperty.call(obj, img)) {
108
- // Validates if the image is in the assets folder.
109
- counter.images.total++;
110
- const relativePath = path
111
- .relative(exercise.path.replace(/\\/gm, "/"), `${(_a = config.config) === null || _a === void 0 ? void 0 : _a.dirPath}/assets/${obj[img].relUrl}`)
112
- .replace(/\\/gm, "/");
113
- if (relativePath !== obj[img].absUrl.split("?").shift()) {
114
- counter.images.error++;
115
- errors.push({
116
- exercise: exercise.title,
117
- msg: `This relative path (${obj[img].relUrl}) is not pointing to the assets folder.`,
118
- });
119
- }
120
- if (!fs.existsSync(`${(_b = config.config) === null || _b === void 0 ? void 0 : _b.dirPath}/assets/${obj[img].relUrl}`)) {
121
- counter.images.error++;
122
- errors.push({
123
- exercise: exercise.title,
124
- msg: `The file ${obj[img].relUrl} doesn't exist in the assets folder.`,
125
- });
126
- }
127
- }
128
- }
129
- }
130
- else if (finding === "externalImages" &&
131
- Object.keys(obj).length > 0) {
132
- // Valdites all the aboslute path images.
133
- for (const img in obj) {
134
- if (Object.prototype.hasOwnProperty.call(obj, img)) {
135
- counter.images.total++;
136
- if (fs.existsSync(`${(_c = config.config) === null || _c === void 0 ? void 0 : _c.dirPath}/assets${obj[img].mdUrl
137
- .split("?")
138
- .shift()}`)) {
100
+ // Check url's of each README file.
101
+ const findings = audit_1.default.findInFile(["relativeImages", "externalImages", "markdownLinks"], content);
102
+ for (const finding in findings) {
103
+ if (Object.prototype.hasOwnProperty.call(findings, finding)) {
104
+ const obj = findings[finding];
105
+ // Valdites all the relative path images.
106
+ if (finding === "relativeImages" &&
107
+ Object.keys(obj).length > 0) {
108
+ for (const img in obj) {
109
+ if (Object.prototype.hasOwnProperty.call(obj, img)) {
110
+ // Validates if the image is in the assets folder.
111
+ counter.images.total++;
139
112
  const relativePath = path
140
- .relative(exercise.path.replace(/\\/gm, "/"), `${(_d = config.config) === null || _d === void 0 ? void 0 : _d.dirPath}/assets/${obj[img].mdUrl}`)
113
+ .relative(exercise.path.replace(/\\/gm, "/"), `${(_a = config.config) === null || _a === void 0 ? void 0 : _a.dirPath}/assets/${obj[img].relUrl}`)
141
114
  .replace(/\\/gm, "/");
142
- warnings.push({
143
- exercise: exercise.title,
144
- msg: `On this exercise you have an image with an absolute path "${obj[img].absUrl}". We recommend you to replace it by the relative path: "${relativePath}".`,
145
- });
115
+ if (relativePath !== obj[img].absUrl.split("?").shift()) {
116
+ counter.images.error++;
117
+ errors.push({
118
+ exercise: exercise.title,
119
+ msg: `This relative path (${obj[img].relUrl}) is not pointing to the assets folder.`,
120
+ });
121
+ }
122
+ if (!fs.existsSync(`${(_b = config.config) === null || _b === void 0 ? void 0 : _b.dirPath}/assets/${obj[img].relUrl}`)) {
123
+ counter.images.error++;
124
+ errors.push({
125
+ exercise: exercise.title,
126
+ msg: `The file ${obj[img].relUrl} doesn't exist in the assets folder.`,
127
+ });
128
+ }
146
129
  }
147
- try {
148
- // eslint-disable-next-line
149
- let res = await fetch(obj[img].absUrl, { method: "HEAD" });
150
- if (!res.ok) {
130
+ }
131
+ }
132
+ else if (finding === "externalImages" &&
133
+ Object.keys(obj).length > 0) {
134
+ // Valdites all the aboslute path images.
135
+ for (const img in obj) {
136
+ if (Object.prototype.hasOwnProperty.call(obj, img)) {
137
+ counter.images.total++;
138
+ if (fs.existsSync(`${(_c = config.config) === null || _c === void 0 ? void 0 : _c.dirPath}/assets${obj[img].mdUrl
139
+ .split("?")
140
+ .shift()}`)) {
141
+ const relativePath = path
142
+ .relative(exercise.path.replace(/\\/gm, "/"), `${(_d = config.config) === null || _d === void 0 ? void 0 : _d.dirPath}/assets/${obj[img].mdUrl}`)
143
+ .replace(/\\/gm, "/");
144
+ warnings.push({
145
+ exercise: exercise.title,
146
+ msg: `On this exercise you have an image with an absolute path "${obj[img].absUrl}". We recommend you to replace it by the relative path: "${relativePath}".`,
147
+ });
148
+ }
149
+ try {
150
+ // eslint-disable-next-line
151
+ let res = await fetch(obj[img].absUrl, {
152
+ method: "HEAD",
153
+ });
154
+ if (!res.ok) {
155
+ counter.images.error++;
156
+ errors.push({
157
+ exercise: exercise.title,
158
+ msg: `This link is broken: ${obj[img].absUrl}`,
159
+ });
160
+ }
161
+ }
162
+ catch (_f) {
151
163
  counter.images.error++;
152
164
  errors.push({
153
165
  exercise: exercise.title,
@@ -155,26 +167,28 @@ class AuditCommand extends SessionCommand_1.default {
155
167
  });
156
168
  }
157
169
  }
158
- catch (_f) {
159
- counter.images.error++;
160
- errors.push({
161
- exercise: exercise.title,
162
- msg: `This link is broken: ${obj[img].absUrl}`,
163
- });
164
- }
165
170
  }
166
171
  }
167
- }
168
- else if (finding === "markdownLinks" &&
169
- Object.keys(obj).length > 0) {
170
- for (const link in obj) {
171
- if (Object.prototype.hasOwnProperty.call(obj, link)) {
172
- counter.links.total++;
173
- try {
174
- // eslint-disable-next-line
175
- let res = await fetch(obj[link].mdUrl, { method: "HEAD" });
176
- if (res.status > 399 && res.status < 500) {
177
- console_1.default.log("Response links:", res.status, obj[link].mdUrl, res);
172
+ else if (finding === "markdownLinks" &&
173
+ Object.keys(obj).length > 0) {
174
+ for (const link in obj) {
175
+ if (Object.prototype.hasOwnProperty.call(obj, link)) {
176
+ counter.links.total++;
177
+ try {
178
+ // eslint-disable-next-line
179
+ let res = await fetch(obj[link].mdUrl, {
180
+ method: "HEAD",
181
+ });
182
+ if (res.status > 399 && res.status < 500) {
183
+ console_1.default.log("Response links:", res.status, obj[link].mdUrl, res);
184
+ counter.links.error++;
185
+ errors.push({
186
+ exercise: exercise.title,
187
+ msg: `This link is broken: ${obj[link].mdUrl}`,
188
+ });
189
+ }
190
+ }
191
+ catch (_g) {
178
192
  counter.links.error++;
179
193
  errors.push({
180
194
  exercise: exercise.title,
@@ -182,167 +196,260 @@ class AuditCommand extends SessionCommand_1.default {
182
196
  });
183
197
  }
184
198
  }
185
- catch (_g) {
186
- counter.links.error++;
187
- errors.push({
188
- exercise: exercise.title,
189
- msg: `This link is broken: ${obj[link].mdUrl}`,
190
- });
191
- }
192
199
  }
193
200
  }
194
201
  }
195
202
  }
196
- }
197
- return true;
198
- };
199
- // This function is being created because the find method doesn't work with promises.
200
- const find = async (file, lang, exercise) => {
201
- if (file.name === lang) {
202
- await checkUrl(file, exercise);
203
203
  return true;
204
- }
205
- return false;
206
- };
207
- console_1.default.debug("config", config);
208
- console_1.default.info(" Checking if the config file is fine...");
209
- // These two lines check if the 'slug' property is inside the configuration object.
210
- console_1.default.debug("Checking if the slug property is inside the configuration object...");
211
- if (!((_d = config.config) === null || _d === void 0 ? void 0 : _d.slug))
212
- errors.push({
213
- exercise: undefined,
214
- msg: "The slug property is not in the configuration object",
215
- });
216
- // These two lines check if the 'repository' property is inside the configuration object.
217
- console_1.default.debug("Checking if the repository property is inside the configuration object...");
218
- if (!((_e = config.config) === null || _e === void 0 ? void 0 : _e.repository))
219
- errors.push({
220
- exercise: undefined,
221
- msg: "The repository property is not in the configuration object",
222
- });
223
- else
224
- audit_1.default.isUrl((_f = config.config) === null || _f === void 0 ? void 0 : _f.repository, errors, counter);
225
- // These two lines check if the 'description' property is inside the configuration object.
226
- console_1.default.debug("Checking if the description property is inside the configuration object...");
227
- if (!((_g = config.config) === null || _g === void 0 ? void 0 : _g.description))
228
- errors.push({
229
- exercise: undefined,
230
- msg: "The description property is not in the configuration object",
231
- });
232
- if (errors.length === 0)
233
- console_1.default.log("The config file is ok");
234
- // Validates if images and links are working at every README file.
235
- const exercises = config.exercises;
236
- const readmeFiles = [];
237
- if (exercises && exercises.length > 0) {
238
- console_1.default.info(" Checking if the images are working...");
239
- for (const index in exercises) {
240
- if (Object.prototype.hasOwnProperty.call(exercises, index)) {
241
- const exercise = exercises[index];
242
- if (!exercise_1.validateExerciseDirectoryName(exercise.title))
243
- errors.push({
244
- exercise: exercise.title,
245
- msg: `The exercise ${exercise.title} has an invalid name.`,
246
- });
247
- let readmeFilesCount = { exercise: exercise.title, count: 0 };
248
- if (Object.keys(exercise.translations).length === 0)
249
- errors.push({
250
- exercise: exercise.title,
251
- msg: `The exercise ${exercise.title} doesn't have a README.md file.`,
252
- });
253
- if (exercise.language === "python3" ||
254
- exercise.language === "python") {
255
- for (const f of exercise.files.map(f => f)) {
256
- if (f.path.includes("test.py") || f.path.includes("tests.py")) {
257
- const content = fs.readFileSync(f.path).toString();
258
- const isEmpty = audit_1.default.checkForEmptySpaces(content);
259
- if (isEmpty || !content)
260
- errors.push({
261
- exercise: exercise.title,
262
- msg: `This file (${f.name}) doesn't have any content inside.`,
263
- });
204
+ };
205
+ // This function is being created because the find method doesn't work with promises.
206
+ const find = async (file, lang, exercise) => {
207
+ if (file.name === lang) {
208
+ await checkUrl(file, exercise);
209
+ return true;
210
+ }
211
+ return false;
212
+ };
213
+ console_1.default.debug("config", config);
214
+ console_1.default.info(" Checking if the config file is fine...");
215
+ // These two lines check if the 'slug' property is inside the configuration object.
216
+ console_1.default.debug("Checking if the slug property is inside the configuration object...");
217
+ if (!((_e = config.config) === null || _e === void 0 ? void 0 : _e.slug))
218
+ errors.push({
219
+ exercise: undefined,
220
+ msg: "The slug property is not in the configuration object",
221
+ });
222
+ // These two lines check if the 'repository' property is inside the configuration object.
223
+ console_1.default.debug("Checking if the repository property is inside the configuration object...");
224
+ if (!((_f = config.config) === null || _f === void 0 ? void 0 : _f.repository))
225
+ errors.push({
226
+ exercise: undefined,
227
+ msg: "The repository property is not in the configuration object",
228
+ });
229
+ else
230
+ audit_1.default.isUrl((_g = config.config) === null || _g === void 0 ? void 0 : _g.repository, errors, counter);
231
+ // These two lines check if the 'description' property is inside the configuration object.
232
+ console_1.default.debug("Checking if the description property is inside the configuration object...");
233
+ if (!((_h = config.config) === null || _h === void 0 ? void 0 : _h.description))
234
+ errors.push({
235
+ exercise: undefined,
236
+ msg: "The description property is not in the configuration object",
237
+ });
238
+ if (errors.length === 0)
239
+ console_1.default.log("The config file is ok");
240
+ // Validates if images and links are working at every README file.
241
+ const exercises = config.exercises;
242
+ const readmeFiles = [];
243
+ if (exercises && exercises.length > 0) {
244
+ console_1.default.info(" Checking if the images are working...");
245
+ for (const index in exercises) {
246
+ if (Object.prototype.hasOwnProperty.call(exercises, index)) {
247
+ const exercise = exercises[index];
248
+ if (!exercise_1.validateExerciseDirectoryName(exercise.title))
249
+ errors.push({
250
+ exercise: exercise.title,
251
+ msg: `The exercise ${exercise.title} has an invalid name.`,
252
+ });
253
+ let readmeFilesCount = { exercise: exercise.title, count: 0 };
254
+ if (Object.keys(exercise.translations).length === 0)
255
+ errors.push({
256
+ exercise: exercise.title,
257
+ msg: `The exercise ${exercise.title} doesn't have a README.md file.`,
258
+ });
259
+ if (exercise.language === "python3" ||
260
+ exercise.language === "python") {
261
+ for (const f of exercise.files.map(f => f)) {
262
+ if (f.path.includes("test.py") ||
263
+ f.path.includes("tests.py")) {
264
+ const content = fs.readFileSync(f.path).toString();
265
+ const isEmpty = audit_1.default.checkForEmptySpaces(content);
266
+ if (isEmpty || !content)
267
+ errors.push({
268
+ exercise: exercise.title,
269
+ msg: `This file (${f.name}) doesn't have any content inside.`,
270
+ });
271
+ }
264
272
  }
265
273
  }
266
- }
267
- else {
268
- for (const f of exercise.files.map(f => f)) {
269
- if (f.path.includes("test.js") || f.path.includes("tests.js")) {
270
- const content = fs.readFileSync(f.path).toString();
271
- const isEmpty = audit_1.default.checkForEmptySpaces(content);
272
- if (isEmpty || !content)
274
+ else {
275
+ for (const f of exercise.files.map(f => f)) {
276
+ if (f.path.includes("test.js") ||
277
+ f.path.includes("tests.js")) {
278
+ const content = fs.readFileSync(f.path).toString();
279
+ const isEmpty = audit_1.default.checkForEmptySpaces(content);
280
+ if (isEmpty || !content)
281
+ errors.push({
282
+ exercise: exercise.title,
283
+ msg: `This file (${f.name}) doesn't have any content inside.`,
284
+ });
285
+ }
286
+ }
287
+ }
288
+ for (const lang in exercise.translations) {
289
+ if (Object.prototype.hasOwnProperty.call(exercise.translations, lang)) {
290
+ const files = [];
291
+ const findResultPromises = [];
292
+ for (const file of exercise.files) {
293
+ const found = find(file, exercise.translations[lang], exercise);
294
+ findResultPromises.push(found);
295
+ }
296
+ // eslint-disable-next-line
297
+ let findResults = await Promise.all(findResultPromises);
298
+ for (const found of findResults) {
299
+ if (found) {
300
+ readmeFilesCount = Object.assign(Object.assign({}, readmeFilesCount), { count: readmeFilesCount.count + 1 });
301
+ files.push(found);
302
+ }
303
+ }
304
+ if (!files.includes(true))
273
305
  errors.push({
274
306
  exercise: exercise.title,
275
- msg: `This file (${f.name}) doesn't have any content inside.`,
307
+ msg: "This exercise doesn't have a README.md file.",
276
308
  });
277
309
  }
278
310
  }
311
+ readmeFiles.push(readmeFilesCount);
279
312
  }
280
- for (const lang in exercise.translations) {
281
- if (Object.prototype.hasOwnProperty.call(exercise.translations, lang)) {
282
- const files = [];
283
- const findResultPromises = [];
284
- for (const file of exercise.files) {
285
- const found = find(file, exercise.translations[lang], exercise);
286
- findResultPromises.push(found);
287
- }
288
- // eslint-disable-next-line
289
- let findResults = await Promise.all(findResultPromises);
290
- for (const found of findResults) {
291
- if (found) {
292
- readmeFilesCount = Object.assign(Object.assign({}, readmeFilesCount), { count: readmeFilesCount.count + 1 });
293
- files.push(found);
294
- }
313
+ }
314
+ }
315
+ else
316
+ errors.push({
317
+ exercise: undefined,
318
+ msg: "The exercises array is empty.",
319
+ });
320
+ console_1.default.log(`${counter.images.total - counter.images.error} images ok from ${counter.images.total}`);
321
+ console_1.default.info(" Checking if important files are missing... (README's, translations, gitignore...)");
322
+ // Check if all the exercises has the same ammount of README's, this way we can check if they have the same ammount of translations.
323
+ const files = [];
324
+ let count = 0;
325
+ for (const item of readmeFiles) {
326
+ if (count < item.count)
327
+ count = item.count;
328
+ }
329
+ for (const item of readmeFiles) {
330
+ if (item.count !== count)
331
+ files.push(` ${item.exercise}`);
332
+ }
333
+ if (files.length > 0) {
334
+ const filesString = files.join(",");
335
+ warnings.push({
336
+ exercise: undefined,
337
+ msg: files.length === 1 ?
338
+ `This exercise is missing translations:${filesString}` :
339
+ `These exercises are missing translations:${filesString}`,
340
+ });
341
+ }
342
+ // Checks if the .gitignore file exists.
343
+ if (!fs.existsSync(".gitignore"))
344
+ warnings.push({
345
+ exercise: undefined,
346
+ msg: ".gitignore file doesn't exist",
347
+ });
348
+ counter.exercises = exercises.length;
349
+ for (const readme of readmeFiles) {
350
+ counter.readmeFiles += readme.count;
351
+ }
352
+ await audit_1.default.showWarnings(warnings);
353
+ await audit_1.default.showErrors(errors, counter);
354
+ }
355
+ else {
356
+ // This is the audit for Projects
357
+ // Getting the learn.json schema
358
+ const schemaResponse = await fetch("https://raw.githubusercontent.com/tommygonzaleza/project-template/main/.github/learn-schema.json");
359
+ const schema = await schemaResponse.json();
360
+ // Checking the "learn.json" file:
361
+ const learnjson = JSON.parse(fs.readFileSync("./learn.json").toString());
362
+ if (!learnjson) {
363
+ console_1.default.error("There is no learn.json file located in the root of the project.");
364
+ process.exit(1);
365
+ }
366
+ // Checkimg the README.md file
367
+ const readme = fs.readFileSync("./README.md").toString();
368
+ if (!readme)
369
+ errors.push({
370
+ exercise: undefined,
371
+ msg: 'There is no "README.md" located in the root of the project.',
372
+ });
373
+ if (readme.length < 800)
374
+ errors.push({
375
+ exercise: undefined,
376
+ msg: `The "README.md" file should have at least 800 characters (It currently have: ${readme.length}).`,
377
+ });
378
+ // Checking if the preview image (from the learn.json) is OK.
379
+ try {
380
+ const res = await fetch(learnjson.preview, { method: "HEAD" });
381
+ if (!res.ok) {
382
+ errors.push({
383
+ exercise: undefined,
384
+ msg: `The link of the "preview" is broken: ${learnjson.preview}`,
385
+ });
386
+ }
387
+ }
388
+ catch (_j) {
389
+ errors.push({
390
+ exercise: undefined,
391
+ msg: `The link of the "preview" is broken: ${learnjson.preview}`,
392
+ });
393
+ }
394
+ // Checking each of the schema rules that are mandatory.
395
+ for (const schemaItem of schema) {
396
+ const learnItem = learnjson[schemaItem.key];
397
+ if (schemaItem.mandatory) {
398
+ console_1.default.info(`Checking for the "${schemaItem.key}" property...`);
399
+ if (!learnItem) {
400
+ errors.push({
401
+ exercise: undefined,
402
+ msg: `learn.json missing "${schemaItem.key}" mandatory property.`,
403
+ });
404
+ return;
405
+ }
406
+ if (schemaItem.max_size && learnItem.length > schemaItem.max_size)
407
+ errors.push({
408
+ exercise: undefined,
409
+ msg: `The "${schemaItem.key}" property should have a maximum size of ${schemaItem.max_size}`,
410
+ });
411
+ if (schemaItem.enum) {
412
+ if (typeof learnItem === "object") {
413
+ let valid = true;
414
+ for (const ele of learnItem) {
415
+ if (!schemaItem.enum.includes(ele))
416
+ valid = false;
295
417
  }
296
- if (!files.includes(true))
418
+ if (!valid)
297
419
  errors.push({
298
- exercise: exercise.title,
299
- msg: "This exercise doesn't have a README.md file.",
420
+ exercise: undefined,
421
+ msg: `The "${schemaItem.key}" property (current: ${learnItem}) should be one of the following values: ${schemaItem.enum.join(", ")}.`,
300
422
  });
301
423
  }
424
+ else if (!schemaItem.enum.includes(learnItem.toLowerCase()))
425
+ errors.push({
426
+ exercise: undefined,
427
+ msg: `The "${schemaItem.key}" property (current: ${learnItem}) should be one of the following values: ${schemaItem.enum.join(", ")}.`,
428
+ });
429
+ }
430
+ if (schemaItem.type === "url" && schemaItem.allowed_extensions) {
431
+ let valid = false;
432
+ for (const ele of schemaItem.allowed_extensions) {
433
+ if (learnItem.split(".").includes(ele))
434
+ valid = true;
435
+ }
436
+ if (!valid)
437
+ errors.push({
438
+ exercise: undefined,
439
+ msg: `The "${schemaItem.key}" property should have one of the allowed extensions: ${schemaItem.allowed_extensions.join(", ")}.`,
440
+ });
302
441
  }
303
- readmeFiles.push(readmeFilesCount);
442
+ if (schemaItem.max_item_size &&
443
+ learnItem.length > schemaItem.max_item_size)
444
+ errors.push({
445
+ exercise: undefined,
446
+ msg: `The "${schemaItem.key}" property has more items than allowed (${schemaItem.max_item_size}).`,
447
+ });
304
448
  }
305
449
  }
450
+ /* eslint-disable-next-line */
451
+ await audit_1.default.showErrors(errors, undefined);
306
452
  }
307
- else
308
- errors.push({
309
- exercise: undefined,
310
- msg: "The exercises array is empty.",
311
- });
312
- console_1.default.log(`${counter.images.total - counter.images.error} images ok from ${counter.images.total}`);
313
- console_1.default.info(" Checking if important files are missing... (README's, translations, gitignore...)");
314
- // Check if all the exercises has the same ammount of README's, this way we can check if they have the same ammount of translations.
315
- const files = [];
316
- let count = 0;
317
- for (const item of readmeFiles) {
318
- if (count < item.count)
319
- count = item.count;
320
- }
321
- for (const item of readmeFiles) {
322
- if (item.count !== count)
323
- files.push(` ${item.exercise}`);
324
- }
325
- if (files.length > 0) {
326
- const filesString = files.join(",");
327
- warnings.push({
328
- exercise: undefined,
329
- msg: files.length === 1 ?
330
- `This exercise is missing translations:${filesString}` :
331
- `These exercises are missing translations:${filesString}`,
332
- });
333
- }
334
- // Checks if the .gitignore file exists.
335
- if (!fs.existsSync(".gitignore"))
336
- warnings.push({
337
- exercise: undefined,
338
- msg: ".gitignore file doesn't exist",
339
- });
340
- counter.exercises = exercises.length;
341
- for (const readme of readmeFiles) {
342
- counter.readmeFiles += readme.count;
343
- }
344
- await audit_1.default.showWarnings(warnings);
345
- await audit_1.default.showErrors(errors, counter);
346
453
  }
347
454
  }
348
455
  }