@learnpack/learnpack 5.0.21 → 5.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/lib/commands/init.js +69 -33
- package/lib/utils/audit.d.ts +1 -1
- package/lib/utils/audit.js +67 -89
- package/lib/utils/rigoActions.d.ts +28 -1
- package/lib/utils/rigoActions.js +80 -26
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/init.ts +97 -44
- package/src/commands/translate.ts +0 -1
- package/src/utils/audit.ts +372 -393
- package/src/utils/rigoActions.ts +137 -34
package/src/utils/audit.ts
CHANGED
@@ -1,393 +1,372 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
import {
|
4
|
-
import {
|
5
|
-
import {
|
6
|
-
import {
|
7
|
-
import
|
8
|
-
import
|
9
|
-
import
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
// eslint-disable-next-line
|
14
|
-
const
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
fs.existsSync(`${configObj.config?.dirPath}/
|
63
|
-
fs.existsSync(`${configObj.config?.dirPath}/
|
64
|
-
fs.existsSync(`${configObj.config?.dirPath}/
|
65
|
-
fs.existsSync(`${configObj.config?.dirPath}/
|
66
|
-
fs.existsSync(`${configObj.config?.dirPath}/
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
| "
|
94
|
-
| "
|
95
|
-
| "
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
const
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
for (const img in obj) {
|
226
|
-
if (Object.prototype.hasOwnProperty.call(obj, img)) {
|
227
|
-
counter && counter.images.total++
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
)
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
.
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
}
|
245
|
-
}
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
} else {
|
358
|
-
reject("Failed")
|
359
|
-
}
|
360
|
-
})
|
361
|
-
}
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
}`
|
374
|
-
)
|
375
|
-
}
|
376
|
-
|
377
|
-
resolve("SUCCESS")
|
378
|
-
} else {
|
379
|
-
reject("Failed")
|
380
|
-
}
|
381
|
-
})
|
382
|
-
}
|
383
|
-
|
384
|
-
export default {
|
385
|
-
isUrl,
|
386
|
-
checkForEmptySpaces,
|
387
|
-
checkLearnpackClean,
|
388
|
-
findInFile,
|
389
|
-
checkUrl,
|
390
|
-
writeFile,
|
391
|
-
showErrors,
|
392
|
-
showWarnings,
|
393
|
-
}
|
1
|
+
/* eslint-disable no-await-in-loop, @typescript-eslint/no-non-null-asserted-optional-chain, no-promise-executor-return */
|
2
|
+
|
3
|
+
import { IAuditErrors } from "../models/audit"
|
4
|
+
import { IConfigObj } from "../models/config"
|
5
|
+
import { ICounter } from "../models/counter"
|
6
|
+
import { IFindings } from "../models/findings"
|
7
|
+
import { IExercise } from "../models/exercise-obj"
|
8
|
+
import { IFrontmatter } from "../models/front-matter"
|
9
|
+
import Console from "./console"
|
10
|
+
import * as fs from "fs"
|
11
|
+
import * as path from "path"
|
12
|
+
|
13
|
+
// eslint-disable-next-line
|
14
|
+
const fetch = require("node-fetch")
|
15
|
+
// eslint-disable-next-line
|
16
|
+
const fm = require("front-matter")
|
17
|
+
|
18
|
+
// This function checks if a url is valid.
|
19
|
+
const isUrl = async (
|
20
|
+
url: string,
|
21
|
+
errors: IAuditErrors[],
|
22
|
+
counter: ICounter
|
23
|
+
) => {
|
24
|
+
const regexUrl = /(https?:\/\/[\w./-]+)/gm
|
25
|
+
counter.links.total++
|
26
|
+
if (!regexUrl.test(url)) {
|
27
|
+
counter.links.error++
|
28
|
+
errors.push({
|
29
|
+
exercise: undefined,
|
30
|
+
msg: `The repository value of the configuration file is not a link: ${url}`,
|
31
|
+
})
|
32
|
+
return false
|
33
|
+
}
|
34
|
+
|
35
|
+
const res = await fetch(url, { method: "HEAD" })
|
36
|
+
if (!res.ok) {
|
37
|
+
counter.links.error++
|
38
|
+
errors.push({
|
39
|
+
exercise: undefined,
|
40
|
+
msg: `The link of the repository is broken: ${url}`,
|
41
|
+
})
|
42
|
+
}
|
43
|
+
|
44
|
+
return true
|
45
|
+
}
|
46
|
+
|
47
|
+
const checkForEmptySpaces = (str: string) => {
|
48
|
+
const isEmpty = true
|
49
|
+
for (const letter of str) {
|
50
|
+
if (letter !== " ") {
|
51
|
+
return false
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
return isEmpty
|
56
|
+
}
|
57
|
+
|
58
|
+
const checkLearnpackClean = (configObj: IConfigObj, errors: IAuditErrors[]) => {
|
59
|
+
if (
|
60
|
+
(configObj.config?.outputPath &&
|
61
|
+
fs.existsSync(configObj.config?.outputPath)) ||
|
62
|
+
fs.existsSync(`${configObj.config?.dirPath}/_app`) ||
|
63
|
+
fs.existsSync(`${configObj.config?.dirPath}/reports`) ||
|
64
|
+
fs.existsSync(`${configObj.config?.dirPath}/resets`) ||
|
65
|
+
fs.existsSync(`${configObj.config?.dirPath}/app.tar.gz`) ||
|
66
|
+
fs.existsSync(`${configObj.config?.dirPath}/config.json`) ||
|
67
|
+
fs.existsSync(`${configObj.config?.dirPath}/vscode_queue.json`) ||
|
68
|
+
fs.existsSync(`${configObj.config?.dirPath}/telemetry.json`)
|
69
|
+
) {
|
70
|
+
errors.push({
|
71
|
+
exercise: undefined,
|
72
|
+
msg: "You have to run learnpack clean command",
|
73
|
+
})
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
const findInFile = (types: string[], content: string) => {
|
78
|
+
const regex: any = {
|
79
|
+
relativeImages:
|
80
|
+
/!\[.*]\s*\((((\.\/)?(\.{2}\/){1,5})(.*\/)*(.[^\s/]*\.[A-Za-z]{2,4})\S*)\)/gm,
|
81
|
+
externalImages: /!\[.*]\((https?:\/(\/[^)/]+)+\/?)\)/gm,
|
82
|
+
markdownLinks: /(\s)+\[.*]\((https?:\/(\/[^)/]+)+\/?)\)/gm,
|
83
|
+
url: /(https?:\/\/[\w./-]+)/gm,
|
84
|
+
uploadcare: /https:\/\/ucarecdn.com\/(?:.*\/)*([\w./-]+)/gm,
|
85
|
+
}
|
86
|
+
|
87
|
+
const validTypes = Object.keys(regex)
|
88
|
+
if (!Array.isArray(types))
|
89
|
+
types = [types]
|
90
|
+
|
91
|
+
const findings: IFindings = {}
|
92
|
+
type findingsType =
|
93
|
+
| "relativeImages"
|
94
|
+
| "externalImages"
|
95
|
+
| "markdownLinks"
|
96
|
+
| "url"
|
97
|
+
| "uploadcare"
|
98
|
+
|
99
|
+
for (const type of types) {
|
100
|
+
if (!validTypes.includes(type))
|
101
|
+
throw new Error("Invalid type: " + type)
|
102
|
+
else
|
103
|
+
findings[type as findingsType] = {}
|
104
|
+
}
|
105
|
+
|
106
|
+
for (const type of types) {
|
107
|
+
let m: RegExpExecArray
|
108
|
+
while ((m = regex[type].exec(content)) !== null) {
|
109
|
+
// This is necessary to avoid infinite loops with zero-width matches
|
110
|
+
if (m.index === regex.lastIndex) {
|
111
|
+
regex.lastIndex++
|
112
|
+
}
|
113
|
+
|
114
|
+
// The result can be accessed through the `m`-variable.
|
115
|
+
// m.forEach((match, groupIndex) => values.push(match));
|
116
|
+
|
117
|
+
findings[type as findingsType]![m[0]] = {
|
118
|
+
content: m[0],
|
119
|
+
absUrl: m[1],
|
120
|
+
mdUrl: m[2],
|
121
|
+
relUrl: m[6],
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
return findings
|
127
|
+
}
|
128
|
+
|
129
|
+
const checkLinkWithRetry = async (
|
130
|
+
url: string,
|
131
|
+
retries = 3,
|
132
|
+
delay = 1000
|
133
|
+
): Promise<{ isValid: boolean; status?: number }> => {
|
134
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
135
|
+
try {
|
136
|
+
let res = await fetch(url, { method: "HEAD" })
|
137
|
+
|
138
|
+
if (res.status === 429) {
|
139
|
+
await new Promise(resolve => setTimeout(resolve, delay))
|
140
|
+
delay *= 2 // Exponential backoff
|
141
|
+
continue
|
142
|
+
}
|
143
|
+
|
144
|
+
if (res.status === 403) {
|
145
|
+
return { isValid: false, status: 403 }
|
146
|
+
}
|
147
|
+
|
148
|
+
if (!res.ok) {
|
149
|
+
res = await fetch(url, { method: "GET" })
|
150
|
+
|
151
|
+
if (!res.ok) {
|
152
|
+
return { isValid: false, status: res.status }
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
return { isValid: true }
|
157
|
+
} catch (error) {
|
158
|
+
console.debug(`Error checking link ${url}:`, error)
|
159
|
+
return { isValid: false, status: 429 }
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
return { isValid: false, status: 429 }
|
164
|
+
}
|
165
|
+
|
166
|
+
const checkUrl = async (
|
167
|
+
config: IConfigObj,
|
168
|
+
filePath: string,
|
169
|
+
fileName: string,
|
170
|
+
exercise: IExercise | undefined,
|
171
|
+
errors: IAuditErrors[],
|
172
|
+
warnings: IAuditErrors[],
|
173
|
+
counter: ICounter | undefined
|
174
|
+
) => {
|
175
|
+
if (!fs.existsSync(filePath))
|
176
|
+
return false
|
177
|
+
const content: string = fs.readFileSync(filePath).toString()
|
178
|
+
const isEmpty = checkForEmptySpaces(content)
|
179
|
+
if (isEmpty || !content)
|
180
|
+
errors.push({
|
181
|
+
exercise: exercise?.title!,
|
182
|
+
msg: `This file (${fileName}) doesn't have any content inside.`,
|
183
|
+
})
|
184
|
+
|
185
|
+
const frontmatter: IFrontmatter = fm(content)
|
186
|
+
for (const attribute in frontmatter.attributes) {
|
187
|
+
if (
|
188
|
+
Object.prototype.hasOwnProperty.call(frontmatter.attributes, attribute) &&
|
189
|
+
(attribute === "intro" || attribute === "tutorial")
|
190
|
+
) {
|
191
|
+
counter && counter.links.total++
|
192
|
+
const url = frontmatter.attributes[attribute]
|
193
|
+
|
194
|
+
const { isValid, status } = await checkLinkWithRetry(url)
|
195
|
+
|
196
|
+
if (!isValid) {
|
197
|
+
if (status === 429 || status === 403) {
|
198
|
+
warnings.push({
|
199
|
+
exercise: exercise?.title!,
|
200
|
+
msg: `Warning: This link might be temporarily inaccessible (${status}): ${url}`,
|
201
|
+
})
|
202
|
+
} else {
|
203
|
+
counter && counter.links.error++
|
204
|
+
errors.push({
|
205
|
+
exercise: exercise?.title!,
|
206
|
+
msg: `This link is broken: ${url}`,
|
207
|
+
})
|
208
|
+
}
|
209
|
+
}
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
// Check URLs in README files
|
214
|
+
const findings: IFindings = findInFile(
|
215
|
+
["relativeImages", "externalImages", "markdownLinks"],
|
216
|
+
content
|
217
|
+
)
|
218
|
+
type findingsType = "relativeImages" | "externalImages" | "markdownLinks"
|
219
|
+
|
220
|
+
for (const finding in findings) {
|
221
|
+
if (Object.prototype.hasOwnProperty.call(findings, finding)) {
|
222
|
+
const obj = findings[finding as findingsType]
|
223
|
+
|
224
|
+
if (finding === "externalImages" && Object.keys(obj!).length > 0) {
|
225
|
+
for (const img in obj) {
|
226
|
+
if (Object.prototype.hasOwnProperty.call(obj, img)) {
|
227
|
+
counter && counter.images.total++
|
228
|
+
const url = obj[img].absUrl
|
229
|
+
|
230
|
+
const { isValid, status } = await checkLinkWithRetry(url)
|
231
|
+
|
232
|
+
if (!isValid) {
|
233
|
+
if (status === 429 || status === 403) {
|
234
|
+
warnings.push({
|
235
|
+
exercise: exercise?.title,
|
236
|
+
msg: `Warning: This image link might be temporarily inaccessible (${status}): ${url}`,
|
237
|
+
})
|
238
|
+
} else {
|
239
|
+
counter && counter.images.error++
|
240
|
+
errors.push({
|
241
|
+
exercise: exercise?.title,
|
242
|
+
msg: `This image link is broken: ${url}`,
|
243
|
+
})
|
244
|
+
}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
}
|
248
|
+
} else if (finding === "markdownLinks" && Object.keys(obj!).length > 0) {
|
249
|
+
for (const link in obj) {
|
250
|
+
if (Object.prototype.hasOwnProperty.call(obj, link)) {
|
251
|
+
counter && counter.links.total++
|
252
|
+
const url = obj[link].mdUrl
|
253
|
+
|
254
|
+
if (!url.includes("twitter")) {
|
255
|
+
const { isValid, status } = await checkLinkWithRetry(url)
|
256
|
+
|
257
|
+
if (!isValid) {
|
258
|
+
if (status === 429 || status === 403) {
|
259
|
+
warnings.push({
|
260
|
+
exercise: exercise?.title,
|
261
|
+
msg: `Warning: This markdown link might be temporarily inaccessible (${status}): ${url}`,
|
262
|
+
})
|
263
|
+
} else {
|
264
|
+
counter && counter.links.error++
|
265
|
+
errors.push({
|
266
|
+
exercise: exercise?.title,
|
267
|
+
msg: `This markdown link is broken: ${url}`,
|
268
|
+
})
|
269
|
+
}
|
270
|
+
}
|
271
|
+
}
|
272
|
+
}
|
273
|
+
}
|
274
|
+
}
|
275
|
+
}
|
276
|
+
}
|
277
|
+
|
278
|
+
return true
|
279
|
+
}
|
280
|
+
|
281
|
+
// This function writes a file in the given path.
|
282
|
+
const writeFile = (filePath: string, content: string) => {
|
283
|
+
try {
|
284
|
+
fs.writeFileSync(filePath, content)
|
285
|
+
} catch (error) {
|
286
|
+
if (error)
|
287
|
+
Console.error(
|
288
|
+
`We weren't able to write the file in this path "${filePath}".`,
|
289
|
+
error
|
290
|
+
)
|
291
|
+
}
|
292
|
+
}
|
293
|
+
|
294
|
+
// This function checks if there are errors, and show them in the console at the end.
|
295
|
+
const showErrors = (errors: IAuditErrors[], counter: ICounter | undefined) => {
|
296
|
+
return new Promise((resolve, reject) => {
|
297
|
+
if (errors) {
|
298
|
+
if (errors.length > 0) {
|
299
|
+
Console.log("Checking for errors...")
|
300
|
+
for (const [i, error] of errors.entries())
|
301
|
+
Console.error(
|
302
|
+
`${i + 1}) ${error.msg} ${
|
303
|
+
error.exercise ? `(Exercise: ${error.exercise})` : ""
|
304
|
+
}`
|
305
|
+
)
|
306
|
+
if (counter) {
|
307
|
+
Console.error(
|
308
|
+
` We found ${errors.length} error${
|
309
|
+
errors.length > 1 ? "s" : ""
|
310
|
+
} among ${counter.images.total} images, ${
|
311
|
+
counter.links.total
|
312
|
+
} link, ${counter.readmeFiles} README files and ${
|
313
|
+
counter.exercises
|
314
|
+
} exercises.`
|
315
|
+
)
|
316
|
+
} else {
|
317
|
+
Console.error(
|
318
|
+
` We found ${errors.length} error${
|
319
|
+
errors.length > 1 ? "s" : ""
|
320
|
+
} related with the project integrity.`
|
321
|
+
)
|
322
|
+
}
|
323
|
+
|
324
|
+
process.exit(1)
|
325
|
+
} else {
|
326
|
+
if (counter) {
|
327
|
+
Console.success(
|
328
|
+
`We didn't find any errors in this repository among ${counter.images.total} images, ${counter.links.total} link, ${counter.readmeFiles} README files and ${counter.exercises} exercises.`
|
329
|
+
)
|
330
|
+
} else {
|
331
|
+
Console.success(`We didn't find any errors in this repository.`)
|
332
|
+
}
|
333
|
+
|
334
|
+
process.exit(0)
|
335
|
+
}
|
336
|
+
} else {
|
337
|
+
reject("Failed")
|
338
|
+
}
|
339
|
+
})
|
340
|
+
}
|
341
|
+
|
342
|
+
// This function checks if there are warnings, and show them in the console at the end.
|
343
|
+
const showWarnings = (warnings: IAuditErrors[]) => {
|
344
|
+
return new Promise((resolve, reject) => {
|
345
|
+
if (warnings) {
|
346
|
+
if (warnings.length > 0) {
|
347
|
+
Console.log("Checking for warnings...")
|
348
|
+
for (const [i, warning] of warnings.entries())
|
349
|
+
Console.warning(
|
350
|
+
`${i + 1}) ${warning.msg} ${
|
351
|
+
warning.exercise ? `File: ${warning.exercise}` : ""
|
352
|
+
}`
|
353
|
+
)
|
354
|
+
}
|
355
|
+
|
356
|
+
resolve("SUCCESS")
|
357
|
+
} else {
|
358
|
+
reject("Failed")
|
359
|
+
}
|
360
|
+
})
|
361
|
+
}
|
362
|
+
|
363
|
+
export default {
|
364
|
+
isUrl,
|
365
|
+
checkForEmptySpaces,
|
366
|
+
checkLearnpackClean,
|
367
|
+
findInFile,
|
368
|
+
checkUrl,
|
369
|
+
writeFile,
|
370
|
+
showErrors,
|
371
|
+
showWarnings,
|
372
|
+
}
|