@learnpack/learnpack 2.1.18 → 2.1.23

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