@learnpack/learnpack 2.1.19 → 2.1.24

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @learnpack/learnpack
21
21
  $ learnpack COMMAND
22
22
  running command...
23
23
  $ learnpack (-v|--version|version)
24
- @learnpack/learnpack/2.1.19 win32-x64 node-v16.14.0
24
+ @learnpack/learnpack/2.1.24 win32-x64 node-v16.14.0
25
25
  $ learnpack --help [COMMAND]
26
26
  USAGE
27
27
  $ learnpack COMMAND
@@ -74,7 +74,7 @@ DESCRIPTION
74
74
  12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
75
75
  ```
76
76
 
77
- _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.19/src\commands\audit.ts)_
77
+ _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.24/src\commands\audit.ts)_
78
78
 
79
79
  ## `learnpack clean`
80
80
 
@@ -89,7 +89,7 @@ DESCRIPTION
89
89
  Extra documentation goes here
90
90
  ```
91
91
 
92
- _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.19/src\commands\clean.ts)_
92
+ _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.24/src\commands\clean.ts)_
93
93
 
94
94
  ## `learnpack download [PACKAGE]`
95
95
 
@@ -107,7 +107,7 @@ DESCRIPTION
107
107
  Extra documentation goes here
108
108
  ```
109
109
 
110
- _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.19/src\commands\download.ts)_
110
+ _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.24/src\commands\download.ts)_
111
111
 
112
112
  ## `learnpack help [COMMAND]`
113
113
 
@@ -138,7 +138,7 @@ OPTIONS
138
138
  -h, --grading show CLI help
139
139
  ```
140
140
 
141
- _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.19/src\commands\init.ts)_
141
+ _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.24/src\commands\init.ts)_
142
142
 
143
143
  ## `learnpack login [PACKAGE]`
144
144
 
@@ -156,7 +156,7 @@ DESCRIPTION
156
156
  Extra documentation goes here
157
157
  ```
158
158
 
159
- _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.19/src\commands\login.ts)_
159
+ _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.24/src\commands\login.ts)_
160
160
 
161
161
  ## `learnpack logout [PACKAGE]`
162
162
 
@@ -174,7 +174,7 @@ DESCRIPTION
174
174
  Extra documentation goes here
175
175
  ```
176
176
 
177
- _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.19/src\commands\logout.ts)_
177
+ _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.24/src\commands\logout.ts)_
178
178
 
179
179
  ## `learnpack plugins`
180
180
 
@@ -309,7 +309,7 @@ DESCRIPTION
309
309
  Extra documentation goes here
310
310
  ```
311
311
 
312
- _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.19/src\commands\publish.ts)_
312
+ _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.24/src\commands\publish.ts)_
313
313
 
314
314
  ## `learnpack start`
315
315
 
@@ -330,7 +330,7 @@ OPTIONS
330
330
  -w, --watch Watch for file changes
331
331
  ```
332
332
 
333
- _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.19/src\commands\start.ts)_
333
+ _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.24/src\commands\start.ts)_
334
334
 
335
335
  ## `learnpack test [EXERCISESLUG]`
336
336
 
@@ -344,5 +344,5 @@ ARGUMENTS
344
344
  EXERCISESLUG The name of the exercise to test
345
345
  ```
346
346
 
347
- _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.19/src\commands\test.ts)_
347
+ _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v2.1.24/src\commands\test.ts)_
348
348
  <!-- commandsstop -->
@@ -8,8 +8,6 @@ const SessionCommand_1 = require("../utils/SessionCommand");
8
8
  const path = require("path");
9
9
  // eslint-disable-next-line
10
10
  const fetch = require("node-fetch");
11
- // eslint-disable-next-line
12
- const fm = require("front-matter");
13
11
  class AuditCommand extends SessionCommand_1.default {
14
12
  async init() {
15
13
  const { flags } = this.parse(AuditCommand);
@@ -58,154 +56,10 @@ class AuditCommand extends SessionCommand_1.default {
58
56
  });
59
57
  }
60
58
  });
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) {
92
- counter.links.error++;
93
- errors.push({
94
- exercise: exercise.title,
95
- msg: `This link is broken: ${frontmatter.attributes[attribute]}`,
96
- });
97
- }
98
- }
99
- }
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++;
112
- const relativePath = path
113
- .relative(exercise.path.replace(/\\/gm, "/"), `${(_a = config.config) === null || _a === void 0 ? void 0 : _a.dirPath}/assets/${obj[img].relUrl}`)
114
- .replace(/\\/gm, "/");
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
- }
129
- }
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) {
163
- counter.images.error++;
164
- errors.push({
165
- exercise: exercise.title,
166
- msg: `This link is broken: ${obj[img].absUrl}`,
167
- });
168
- }
169
- }
170
- }
171
- }
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) {
192
- counter.links.error++;
193
- errors.push({
194
- exercise: exercise.title,
195
- msg: `This link is broken: ${obj[link].mdUrl}`,
196
- });
197
- }
198
- }
199
- }
200
- }
201
- }
202
- }
203
- return true;
204
- };
205
59
  // This function is being created because the find method doesn't work with promises.
206
60
  const find = async (file, lang, exercise) => {
207
61
  if (file.name === lang) {
208
- await checkUrl(file, exercise);
62
+ await audit_1.default.checkUrl(config, file.path, file.name, exercise, errors, warnings, counter);
209
63
  return true;
210
64
  }
211
65
  return false;
@@ -349,11 +203,9 @@ class AuditCommand extends SessionCommand_1.default {
349
203
  for (const readme of readmeFiles) {
350
204
  counter.readmeFiles += readme.count;
351
205
  }
352
- await audit_1.default.showWarnings(warnings);
353
- await audit_1.default.showErrors(errors, counter);
354
206
  }
355
207
  else {
356
- // This is the audit for Projects
208
+ // This is the audit code for Projects
357
209
  // Getting the learn.json schema
358
210
  const schemaResponse = await fetch("https://raw.githubusercontent.com/tommygonzaleza/project-template/main/.github/learn-schema.json");
359
211
  const schema = await schemaResponse.json();
@@ -363,18 +215,53 @@ class AuditCommand extends SessionCommand_1.default {
363
215
  console_1.default.error("There is no learn.json file located in the root of the project.");
364
216
  process.exit(1);
365
217
  }
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
- });
218
+ // Checking the README.md files and possible translations.
219
+ let readmeFiles = [];
220
+ const translations = [];
221
+ const translationRegex = /README\.([a-z]{2,3})\.md/;
222
+ try {
223
+ const data = await fs.promises.readdir("./");
224
+ readmeFiles = data.filter(file => file.includes("README"));
225
+ if (readmeFiles.length === 0)
226
+ errors.push({
227
+ exercise: undefined,
228
+ msg: `There is no README file in the repository.`,
229
+ });
230
+ }
231
+ catch (error) {
232
+ if (error)
233
+ console_1.default.error("There was an error getting the directory files", error);
234
+ }
235
+ for (const readmeFile of readmeFiles) {
236
+ // Checking the language of each README file.
237
+ if (readmeFile === "README.md")
238
+ translations.push("us");
239
+ else {
240
+ const regexGroups = translationRegex.exec(readmeFile);
241
+ if (regexGroups)
242
+ translations.push(regexGroups[1]);
243
+ }
244
+ const readme = fs.readFileSync(path.resolve(readmeFile)).toString();
245
+ const isEmpty = audit_1.default.checkForEmptySpaces(readme);
246
+ if (isEmpty || !readme) {
247
+ errors.push({
248
+ exercise: undefined,
249
+ msg: `This file "${readmeFile}" doesn't have any content inside.`,
250
+ });
251
+ continue;
252
+ }
253
+ if (readme.length < 800)
254
+ errors.push({
255
+ exercise: undefined,
256
+ msg: `The "${readmeFile}" file should have at least 800 characters (It currently have: ${readme.length}).`,
257
+ });
258
+ // eslint-disable-next-line
259
+ await audit_1.default.checkUrl(config, path.resolve(readmeFile), readmeFile, undefined, errors, warnings,
260
+ // eslint-disable-next-line
261
+ undefined);
262
+ }
263
+ // Adding the translations to the learn.json
264
+ learnjson.translations = translations;
378
265
  // Checking if the preview image (from the learn.json) is OK.
379
266
  try {
380
267
  const res = await fetch(learnjson.preview, { method: "HEAD" });
@@ -391,83 +278,38 @@ class AuditCommand extends SessionCommand_1.default {
391
278
  msg: `The link of the "preview" is broken: ${learnjson.preview}`,
392
279
  });
393
280
  }
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;
417
- }
418
- if (!valid)
419
- errors.push({
420
- exercise: undefined,
421
- msg: `The "${schemaItem.key}" property (current: ${learnItem}) should be one of the following values: ${schemaItem.enum.join(", ")}.`,
422
- });
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
- });
441
- }
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
- });
448
- }
449
- }
450
- /* eslint-disable-next-line */
451
- await audit_1.default.showErrors(errors, undefined);
281
+ const date = new Date();
282
+ learnjson.validationAt = date.getTime();
283
+ if (errors.length > 0)
284
+ learnjson.validationStatus = "error";
285
+ else if (warnings.length > 0)
286
+ learnjson.validationStatus = "warning";
287
+ else
288
+ learnjson.validationStatus = "success";
289
+ // Writes the "learn.json" file with all the new properties
290
+ await fs.promises.writeFile("./learn.json", JSON.stringify(learnjson));
452
291
  }
292
+ await audit_1.default.showWarnings(warnings);
293
+ // eslint-disable-next-line
294
+ await audit_1.default.showErrors(errors, undefined);
453
295
  }
454
296
  }
455
297
  }
456
- AuditCommand.description = `learnpack audit is the command in charge of creating an auditory of the repository
457
- ...
458
- learnpack audit checks for the following information in a repository:
459
- 1. The configuration object has slug, repository and description. (Error)
460
- 2. The command learnpack clean has been run. (Error)
461
- 3. If a markdown or test file doesn't have any content. (Error)
462
- 4. The links are accessing to valid servers. (Error)
463
- 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)
464
- 6. The external images are working (If they are pointing to a valid server). (Error)
465
- 7. The exercises directory names are valid. (Error)
466
- 8. If an exercise doesn't have a README file. (Error)
467
- 9. The exercises array (Of the config file) has content. (Error)
468
- 10. The exercses have the same translations. (Warning)
469
- 11. The .gitignore file exists. (Warning)
470
- 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
298
+ AuditCommand.description = `learnpack audit is the command in charge of creating an auditory of the repository
299
+ ...
300
+ learnpack audit checks for the following information in a repository:
301
+ 1. The configuration object has slug, repository and description. (Error)
302
+ 2. The command learnpack clean has been run. (Error)
303
+ 3. If a markdown or test file doesn't have any content. (Error)
304
+ 4. The links are accessing to valid servers. (Error)
305
+ 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)
306
+ 6. The external images are working (If they are pointing to a valid server). (Error)
307
+ 7. The exercises directory names are valid. (Error)
308
+ 8. If an exercise doesn't have a README file. (Error)
309
+ 9. The exercises array (Of the config file) has content. (Error)
310
+ 10. The exercses have the same translations. (Warning)
311
+ 11. The .gitignore file exists. (Warning)
312
+ 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
471
313
  `;
472
314
  AuditCommand.flags = {
473
315
  // name: flags.string({char: 'n', description: 'name to print'}),
@@ -9,7 +9,6 @@ const cli_ux_1 = require("cli-ux");
9
9
  const eta = require("eta");
10
10
  const console_1 = require("../utils/console");
11
11
  const errors_1 = require("../utils/errors");
12
- const defaults_1 = require("../managers/config/defaults");
13
12
  const path = require("path");
14
13
  class InitComand extends BaseCommand_1.default {
15
14
  async run() {
@@ -70,10 +69,17 @@ class InitComand extends BaseCommand_1.default {
70
69
  },
71
70
  },
72
71
  ]);
73
- const packageInfo = Object.assign(Object.assign({}, defaults_1.default.config), { grading: choices.grading, difficulty: choices.difficulty, duration: parseInt(choices.duration), description: choices.description, title: choices.title, slug: choices.title
72
+ const packageInfo = {
73
+ grading: choices.grading,
74
+ difficulty: choices.difficulty,
75
+ duration: parseInt(choices.duration),
76
+ description: choices.description,
77
+ title: choices.title,
78
+ slug: choices.title
74
79
  .toLowerCase()
75
80
  .replace(/ /g, "-")
76
- .replace(/[^\w-]+/g, "") });
81
+ .replace(/[^\w-]+/g, ""),
82
+ };
77
83
  cli_ux_1.default.action.start("Initializing package");
78
84
  const languages = ["en", "es"];
79
85
  const templatesDir = path.resolve(__dirname, "../../src/utils/templates/" + choices.grading || "no-grading");
@@ -2,11 +2,14 @@ import { IAuditErrors } from "../models/audit";
2
2
  import { IConfigObj } from "../models/config";
3
3
  import { ICounter } from "../models/counter";
4
4
  import { IFindings } from "../models/findings";
5
+ import { IExercise } from "../models/exercise-obj";
5
6
  declare const _default: {
6
7
  isUrl: (url: string, errors: IAuditErrors[], counter: ICounter) => Promise<boolean>;
7
8
  checkForEmptySpaces: (str: string) => boolean;
8
9
  checkLearnpackClean: (configObj: IConfigObj, errors: IAuditErrors[]) => void;
9
10
  findInFile: (types: string[], content: string) => IFindings;
11
+ checkUrl: (config: IConfigObj, filePath: string, fileName: string, exercise: IExercise | undefined, errors: IAuditErrors[], warnings: IAuditErrors[], counter: ICounter | undefined) => Promise<boolean>;
12
+ writeFile: (content: string, filePath: string) => Promise<void>;
10
13
  showErrors: (errors: IAuditErrors[], counter: ICounter | undefined) => Promise<unknown>;
11
14
  showWarnings: (warnings: IAuditErrors[]) => Promise<unknown>;
12
15
  };