@learnpack/learnpack 2.1.20 → 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, ISchemaItem } from "../models/audit";
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() {
@@ -72,193 +70,18 @@ class AuditCommand extends SessionCommand {
72
70
  }
73
71
  });
74
72
 
75
- // This function checks that each of the url's are working.
76
- const checkUrl = async (file: IFile, exercise: IExercise) => {
77
- if (!fs.existsSync(file.path))
78
- return false;
79
- const content: string = fs.readFileSync(file.path).toString();
80
- const isEmpty = Audit.checkForEmptySpaces(content);
81
- if (isEmpty || !content)
82
- errors.push({
83
- exercise: exercise.title!,
84
- msg: `This file (${file.name}) doesn't have any content inside.`,
85
- });
86
-
87
- const frontmatter: IFrontmatter = fm(content);
88
- for (const attribute in frontmatter.attributes) {
89
- if (
90
- Object.prototype.hasOwnProperty.call(
91
- frontmatter.attributes,
92
- attribute
93
- ) &&
94
- (attribute === "intro" || attribute === "tutorial")
95
- ) {
96
- counter.links.total++;
97
- try {
98
- // eslint-disable-next-line
99
- let res = await fetch(frontmatter.attributes[attribute], {
100
- method: "HEAD",
101
- });
102
- if (!res.ok) {
103
- counter.links.error++;
104
- errors.push({
105
- exercise: exercise.title!,
106
- msg: `This link is broken (${res.ok}): ${frontmatter.attributes[attribute]}`,
107
- });
108
- }
109
- } catch {
110
- counter.links.error++;
111
- errors.push({
112
- exercise: exercise.title,
113
- msg: `This link is broken: ${frontmatter.attributes[attribute]}`,
114
- });
115
- }
116
- }
117
- }
118
-
119
- // Check url's of each README file.
120
- const findings: IFindings = Audit.findInFile(
121
- ["relativeImages", "externalImages", "markdownLinks"],
122
- content
123
- );
124
- type findingsType =
125
- | "relativeImages"
126
- | "externalImages"
127
- | "markdownLinks"
128
- | "url"
129
- | "uploadcare";
130
- for (const finding in findings) {
131
- if (Object.prototype.hasOwnProperty.call(findings, finding)) {
132
- const obj = findings[finding as findingsType];
133
- // Valdites all the relative path images.
134
- if (
135
- finding === "relativeImages" &&
136
- Object.keys(obj!).length > 0
137
- ) {
138
- for (const img in obj) {
139
- if (Object.prototype.hasOwnProperty.call(obj, img)) {
140
- // Validates if the image is in the assets folder.
141
- counter.images.total++;
142
- const relativePath = path
143
- .relative(
144
- exercise.path.replace(/\\/gm, "/"),
145
- `${config!.config?.dirPath}/assets/${obj[img].relUrl}`
146
- )
147
- .replace(/\\/gm, "/");
148
- if (relativePath !== obj[img].absUrl.split("?").shift()) {
149
- counter.images.error++;
150
- errors.push({
151
- exercise: exercise.title,
152
- msg: `This relative path (${obj[img].relUrl}) is not pointing to the assets folder.`,
153
- });
154
- }
155
-
156
- if (
157
- !fs.existsSync(
158
- `${config!.config?.dirPath}/assets/${obj[img].relUrl}`
159
- )
160
- ) {
161
- counter.images.error++;
162
- errors.push({
163
- exercise: exercise.title,
164
- msg: `The file ${obj[img].relUrl} doesn't exist in the assets folder.`,
165
- });
166
- }
167
- }
168
- }
169
- } else if (
170
- finding === "externalImages" &&
171
- Object.keys(obj!).length > 0
172
- ) {
173
- // Valdites all the aboslute path images.
174
- for (const img in obj) {
175
- if (Object.prototype.hasOwnProperty.call(obj, img)) {
176
- counter.images.total++;
177
- if (
178
- fs.existsSync(
179
- `${config!.config?.dirPath}/assets${obj[img].mdUrl
180
- .split("?")
181
- .shift()}`
182
- )
183
- ) {
184
- const relativePath = path
185
- .relative(
186
- exercise.path.replace(/\\/gm, "/"),
187
- `${config!.config?.dirPath}/assets/${obj[img].mdUrl}`
188
- )
189
- .replace(/\\/gm, "/");
190
- warnings.push({
191
- exercise: exercise.title,
192
- 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}".`,
193
- });
194
- }
195
-
196
- try {
197
- // eslint-disable-next-line
198
- let res = await fetch(obj[img].absUrl, {
199
- method: "HEAD",
200
- });
201
- if (!res.ok) {
202
- counter.images.error++;
203
- errors.push({
204
- exercise: exercise.title,
205
- msg: `This link is broken: ${obj[img].absUrl}`,
206
- });
207
- }
208
- } catch {
209
- counter.images.error++;
210
- errors.push({
211
- exercise: exercise.title,
212
- msg: `This link is broken: ${obj[img].absUrl}`,
213
- });
214
- }
215
- }
216
- }
217
- } else if (
218
- finding === "markdownLinks" &&
219
- Object.keys(obj!).length > 0
220
- ) {
221
- for (const link in obj) {
222
- if (Object.prototype.hasOwnProperty.call(obj, link)) {
223
- counter.links.total++;
224
- try {
225
- // eslint-disable-next-line
226
- let res = await fetch(obj[link].mdUrl, {
227
- method: "HEAD",
228
- });
229
- if (res.status > 399 && res.status < 500) {
230
- Console.log(
231
- "Response links:",
232
- res.status,
233
- obj[link].mdUrl,
234
- res
235
- );
236
- counter.links.error++;
237
- errors.push({
238
- exercise: exercise.title,
239
- msg: `This link is broken: ${obj[link].mdUrl}`,
240
- });
241
- }
242
- } catch {
243
- counter.links.error++;
244
- errors.push({
245
- exercise: exercise.title,
246
- msg: `This link is broken: ${obj[link].mdUrl}`,
247
- });
248
- }
249
- }
250
- }
251
- }
252
- }
253
- }
254
-
255
- return true;
256
- };
257
-
258
73
  // This function is being created because the find method doesn't work with promises.
259
74
  const find = async (file: IFile, lang: string, exercise: IExercise) => {
260
75
  if (file.name === lang) {
261
- await checkUrl(file, exercise);
76
+ await Audit.checkUrl(
77
+ config!,
78
+ file.path,
79
+ file.name,
80
+ exercise,
81
+ errors,
82
+ warnings,
83
+ counter
84
+ );
262
85
  return true;
263
86
  }
264
87
 
@@ -449,11 +272,8 @@ files.push(` ${item.exercise}`);
449
272
  for (const readme of readmeFiles) {
450
273
  counter.readmeFiles += readme.count;
451
274
  }
452
-
453
- await Audit.showWarnings(warnings);
454
- await Audit.showErrors(errors, counter);
455
275
  } else {
456
- // This is the audit for Projects
276
+ // This is the audit code for Projects
457
277
 
458
278
  // Getting the learn.json schema
459
279
  const schemaResponse = await fetch(
@@ -473,19 +293,69 @@ files.push(` ${item.exercise}`);
473
293
  process.exit(1);
474
294
  }
475
295
 
476
- // Checkimg the README.md file
477
- const readme = fs.readFileSync("./README.md").toString();
478
- if (!readme)
479
- errors.push({
480
- exercise: undefined,
481
- msg: 'There is no "README.md" located in the root of the project.',
482
- });
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/;
483
300
 
484
- if (readme.length < 800)
485
- errors.push({
486
- exercise: undefined,
487
- msg: `The "README.md" file should have at least 800 characters (It currently have: ${readme.length}).`,
488
- });
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
+ }
316
+
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
+ }
326
+
327
+ const readme = fs.readFileSync(path.resolve(readmeFile)).toString();
328
+
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;
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
+ );
355
+ }
356
+
357
+ // Adding the translations to the learn.json
358
+ learnjson.translations = translations;
489
359
 
490
360
  // Checking if the preview image (from the learn.json) is OK.
491
361
  try {
@@ -503,87 +373,23 @@ files.push(` ${item.exercise}`);
503
373
  });
504
374
  }
505
375
 
506
- // Checking each of the schema rules that are mandatory.
507
- for (const schemaItem of schema) {
508
- const learnItem = learnjson[schemaItem.key];
376
+ const date = new Date();
377
+ learnjson.validationAt = date.getTime();
509
378
 
510
- if (schemaItem.mandatory) {
511
- Console.info(`Checking for the "${schemaItem.key}" property...`);
512
-
513
- if (!learnItem) {
514
- errors.push({
515
- exercise: undefined,
516
- msg: `learn.json missing "${schemaItem.key}" mandatory property.`,
517
- });
518
- return;
519
- }
520
-
521
- if (schemaItem.max_size && learnItem.length > schemaItem.max_size)
522
- errors.push({
523
- exercise: undefined,
524
- msg: `The "${schemaItem.key}" property should have a maximum size of ${schemaItem.max_size}`,
525
- });
526
-
527
- if (schemaItem.enum) {
528
- if (typeof learnItem === "object") {
529
- let valid = true;
530
- for (const ele of learnItem) {
531
- if (!schemaItem.enum!.includes(ele))
532
- valid = false;
533
- }
534
-
535
- if (!valid)
536
- errors.push({
537
- exercise: undefined,
538
- msg: `The "${
539
- schemaItem.key
540
- }" property (current: ${learnItem}) should be one of the following values: ${schemaItem.enum.join(
541
- ", "
542
- )}.`,
543
- });
544
- } else if (!schemaItem.enum.includes(learnItem.toLowerCase()))
545
- errors.push({
546
- exercise: undefined,
547
- msg: `The "${
548
- schemaItem.key
549
- }" property (current: ${learnItem}) should be one of the following values: ${schemaItem.enum.join(
550
- ", "
551
- )}.`,
552
- });
553
- }
554
-
555
- if (schemaItem.type === "url" && schemaItem.allowed_extensions) {
556
- let valid = false;
557
- for (const ele of schemaItem.allowed_extensions) {
558
- if (learnItem.split(".").includes(ele))
559
- valid = true;
560
- }
561
-
562
- if (!valid)
563
- errors.push({
564
- exercise: undefined,
565
- msg: `The "${
566
- schemaItem.key
567
- }" property should have one of the allowed extensions: ${schemaItem.allowed_extensions.join(
568
- ", "
569
- )}.`,
570
- });
571
- }
572
-
573
- if (
574
- schemaItem.max_item_size &&
575
- learnItem.length > schemaItem.max_item_size
576
- )
577
- errors.push({
578
- exercise: undefined,
579
- msg: `The "${schemaItem.key}" property has more items than allowed (${schemaItem.max_item_size}).`,
580
- });
581
- }
582
- }
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";
583
385
 
584
- /* eslint-disable-next-line */
585
- await Audit.showErrors(errors, undefined);
386
+ // Writes the "learn.json" file with all the new properties
387
+ await fs.promises.writeFile("./learn.json", JSON.stringify(learnjson));
586
388
  }
389
+
390
+ await Audit.showWarnings(warnings);
391
+ // eslint-disable-next-line
392
+ await Audit.showErrors(errors, undefined);
587
393
  }
588
394
  }
589
395
  }