@learnpack/learnpack 2.1.26 → 2.1.28
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 +10 -10
- package/lib/commands/start.js +15 -4
- package/lib/managers/file.d.ts +1 -0
- package/lib/managers/file.js +8 -1
- package/lib/managers/server/routes.js +48 -14
- package/lib/managers/session.d.ts +1 -1
- package/lib/managers/session.js +39 -12
- package/lib/managers/socket.d.ts +1 -1
- package/lib/managers/socket.js +57 -43
- package/lib/models/action.d.ts +1 -1
- package/lib/models/config.d.ts +1 -1
- package/lib/models/exercise-obj.d.ts +3 -0
- package/lib/models/session.d.ts +4 -1
- package/lib/models/socket.d.ts +7 -6
- package/lib/models/status.d.ts +1 -1
- package/lib/utils/api.d.ts +2 -0
- package/lib/utils/api.js +51 -6
- package/oclif.manifest.json +1 -1
- package/package.json +3 -1
- package/src/commands/audit.ts +113 -113
- package/src/commands/clean.ts +10 -10
- package/src/commands/download.ts +18 -18
- package/src/commands/init.ts +39 -39
- package/src/commands/login.ts +13 -13
- package/src/commands/logout.ts +9 -9
- package/src/commands/publish.ts +25 -25
- package/src/commands/start.ts +101 -75
- package/src/commands/test.ts +34 -34
- package/src/managers/config/allowed_files.ts +2 -2
- package/src/managers/config/defaults.ts +2 -2
- package/src/managers/config/exercise.ts +79 -79
- package/src/managers/config/index.ts +145 -145
- package/src/managers/file.ts +74 -65
- package/src/managers/server/index.ts +32 -31
- package/src/managers/server/routes.ts +139 -90
- package/src/managers/session.ts +53 -24
- package/src/managers/socket.ts +92 -79
- package/src/models/action.ts +8 -1
- package/src/models/config-manager.ts +2 -2
- package/src/models/config.ts +7 -2
- package/src/models/exercise-obj.ts +6 -3
- package/src/models/plugin-config.ts +2 -2
- package/src/models/session.ts +5 -2
- package/src/models/socket.ts +12 -6
- package/src/models/status.ts +15 -14
- package/src/plugin/command/compile.ts +10 -10
- package/src/plugin/command/test.ts +14 -14
- package/src/plugin/index.ts +5 -5
- package/src/plugin/plugin.ts +26 -26
- package/src/plugin/utils.ts +23 -23
- package/src/utils/BaseCommand.ts +16 -16
- package/src/utils/api.ts +143 -91
- package/src/utils/audit.ts +93 -96
- package/src/utils/exercisesQueue.ts +15 -15
- package/src/utils/fileQueue.ts +85 -85
- package/src/utils/watcher.ts +14 -14
package/src/utils/audit.ts
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
import { IAuditErrors } from "../models/audit"
|
2
|
-
import { IConfigObj } from "../models/config"
|
3
|
-
import { ICounter } from "../models/counter"
|
4
|
-
import { IFindings } from "../models/findings"
|
5
|
-
import { IExercise } from "../models/exercise-obj"
|
6
|
-
import { IFrontmatter } from "../models/front-matter"
|
7
|
-
import Console from "./console"
|
8
|
-
import * as fs from "fs"
|
9
|
-
import * as path from "path"
|
1
|
+
import { IAuditErrors } from "../models/audit"
|
2
|
+
import { IConfigObj } from "../models/config"
|
3
|
+
import { ICounter } from "../models/counter"
|
4
|
+
import { IFindings } from "../models/findings"
|
5
|
+
import { IExercise } from "../models/exercise-obj"
|
6
|
+
import { IFrontmatter } from "../models/front-matter"
|
7
|
+
import Console from "./console"
|
8
|
+
import * as fs from "fs"
|
9
|
+
import * as path from "path"
|
10
10
|
|
11
11
|
// eslint-disable-next-line
|
12
12
|
const fetch = require("node-fetch");
|
@@ -19,39 +19,39 @@ const isUrl = async (
|
|
19
19
|
errors: IAuditErrors[],
|
20
20
|
counter: ICounter
|
21
21
|
) => {
|
22
|
-
const regexUrl = /(https?:\/\/[\w./-]+)/gm
|
23
|
-
counter.links.total
|
22
|
+
const regexUrl = /(https?:\/\/[\w./-]+)/gm
|
23
|
+
counter.links.total++
|
24
24
|
if (!regexUrl.test(url)) {
|
25
|
-
counter.links.error
|
25
|
+
counter.links.error++
|
26
26
|
errors.push({
|
27
27
|
exercise: undefined,
|
28
28
|
msg: `The repository value of the configuration file is not a link: ${url}`,
|
29
|
-
})
|
30
|
-
return false
|
29
|
+
})
|
30
|
+
return false
|
31
31
|
}
|
32
32
|
|
33
|
-
const res = await fetch(url, { method: "HEAD" })
|
33
|
+
const res = await fetch(url, { method: "HEAD" })
|
34
34
|
if (!res.ok) {
|
35
|
-
counter.links.error
|
35
|
+
counter.links.error++
|
36
36
|
errors.push({
|
37
37
|
exercise: undefined,
|
38
38
|
msg: `The link of the repository is broken: ${url}`,
|
39
|
-
})
|
39
|
+
})
|
40
40
|
}
|
41
41
|
|
42
|
-
return true
|
43
|
-
}
|
42
|
+
return true
|
43
|
+
}
|
44
44
|
|
45
45
|
const checkForEmptySpaces = (str: string) => {
|
46
|
-
const isEmpty = true
|
46
|
+
const isEmpty = true
|
47
47
|
for (const letter of str) {
|
48
48
|
if (letter !== " ") {
|
49
|
-
return false
|
49
|
+
return false
|
50
50
|
}
|
51
51
|
}
|
52
52
|
|
53
|
-
return isEmpty
|
54
|
-
}
|
53
|
+
return isEmpty
|
54
|
+
}
|
55
55
|
|
56
56
|
const checkLearnpackClean = (configObj: IConfigObj, errors: IAuditErrors[]) => {
|
57
57
|
if (
|
@@ -67,9 +67,9 @@ const checkLearnpackClean = (configObj: IConfigObj, errors: IAuditErrors[]) => {
|
|
67
67
|
errors.push({
|
68
68
|
exercise: undefined,
|
69
69
|
msg: "You have to run learnpack clean command",
|
70
|
-
})
|
70
|
+
})
|
71
71
|
}
|
72
|
-
}
|
72
|
+
}
|
73
73
|
|
74
74
|
const findInFile = (types: string[], content: string) => {
|
75
75
|
const regex: any = {
|
@@ -79,13 +79,13 @@ const findInFile = (types: string[], content: string) => {
|
|
79
79
|
markdownLinks: /(\s)+\[.*]\((https?:\/(\/[^)/]+)+\/?)\)/gm,
|
80
80
|
url: /(https?:\/\/[\w./-]+)/gm,
|
81
81
|
uploadcare: /https:\/\/ucarecdn.com\/(?:.*\/)*([\w./-]+)/gm,
|
82
|
-
}
|
82
|
+
}
|
83
83
|
|
84
|
-
const validTypes = Object.keys(regex)
|
84
|
+
const validTypes = Object.keys(regex)
|
85
85
|
if (!Array.isArray(types))
|
86
|
-
types = [types]
|
86
|
+
types = [types]
|
87
87
|
|
88
|
-
const findings: IFindings = {}
|
88
|
+
const findings: IFindings = {}
|
89
89
|
type findingsType =
|
90
90
|
| "relativeImages"
|
91
91
|
| "externalImages"
|
@@ -95,17 +95,17 @@ types = [types];
|
|
95
95
|
|
96
96
|
for (const type of types) {
|
97
97
|
if (!validTypes.includes(type))
|
98
|
-
throw new Error("Invalid type: " + type)
|
98
|
+
throw new Error("Invalid type: " + type)
|
99
99
|
else
|
100
|
-
findings[type as findingsType] = {}
|
100
|
+
findings[type as findingsType] = {}
|
101
101
|
}
|
102
102
|
|
103
103
|
for (const type of types) {
|
104
|
-
let m: RegExpExecArray
|
104
|
+
let m: RegExpExecArray
|
105
105
|
while ((m = regex[type].exec(content)) !== null) {
|
106
106
|
// This is necessary to avoid infinite loops with zero-width matches
|
107
107
|
if (m.index === regex.lastIndex) {
|
108
|
-
regex.lastIndex
|
108
|
+
regex.lastIndex++
|
109
109
|
}
|
110
110
|
|
111
111
|
// The result can be accessed through the `m`-variable.
|
@@ -116,12 +116,12 @@ findings[type as findingsType] = {};
|
|
116
116
|
absUrl: m[1],
|
117
117
|
mdUrl: m[2],
|
118
118
|
relUrl: m[6],
|
119
|
-
}
|
119
|
+
}
|
120
120
|
}
|
121
121
|
}
|
122
122
|
|
123
|
-
return findings
|
124
|
-
}
|
123
|
+
return findings
|
124
|
+
}
|
125
125
|
|
126
126
|
// This function checks that each of the url's are working.
|
127
127
|
const checkUrl = async (
|
@@ -134,40 +134,40 @@ const checkUrl = async (
|
|
134
134
|
counter: ICounter | undefined
|
135
135
|
) => {
|
136
136
|
if (!fs.existsSync(filePath))
|
137
|
-
return false
|
138
|
-
const content: string = fs.readFileSync(filePath).toString()
|
139
|
-
const isEmpty = checkForEmptySpaces(content)
|
137
|
+
return false
|
138
|
+
const content: string = fs.readFileSync(filePath).toString()
|
139
|
+
const isEmpty = checkForEmptySpaces(content)
|
140
140
|
if (isEmpty || !content)
|
141
141
|
errors.push({
|
142
142
|
exercise: exercise?.title!,
|
143
143
|
msg: `This file (${fileName}) doesn't have any content inside.`,
|
144
|
-
})
|
144
|
+
})
|
145
145
|
|
146
|
-
const frontmatter: IFrontmatter = fm(content)
|
146
|
+
const frontmatter: IFrontmatter = fm(content)
|
147
147
|
for (const attribute in frontmatter.attributes) {
|
148
148
|
if (
|
149
149
|
Object.prototype.hasOwnProperty.call(frontmatter.attributes, attribute) &&
|
150
150
|
(attribute === "intro" || attribute === "tutorial")
|
151
151
|
) {
|
152
|
-
counter && counter.links.total
|
152
|
+
counter && counter.links.total++
|
153
153
|
try {
|
154
154
|
// eslint-disable-next-line
|
155
155
|
let res = await fetch(frontmatter.attributes[attribute], {
|
156
156
|
method: "HEAD",
|
157
|
-
})
|
157
|
+
})
|
158
158
|
if (!res.ok) {
|
159
|
-
counter && counter.links.error
|
159
|
+
counter && counter.links.error++
|
160
160
|
errors.push({
|
161
161
|
exercise: exercise?.title!,
|
162
162
|
msg: `This link is broken (${res.ok}): ${frontmatter.attributes[attribute]}`,
|
163
|
-
})
|
163
|
+
})
|
164
164
|
}
|
165
165
|
} catch {
|
166
|
-
counter && counter.links.error
|
166
|
+
counter && counter.links.error++
|
167
167
|
errors.push({
|
168
168
|
exercise: exercise?.title,
|
169
169
|
msg: `This link is broken: ${frontmatter.attributes[attribute]}`,
|
170
|
-
})
|
170
|
+
})
|
171
171
|
}
|
172
172
|
}
|
173
173
|
}
|
@@ -176,7 +176,7 @@ return false;
|
|
176
176
|
const findings: IFindings = findInFile(
|
177
177
|
["relativeImages", "externalImages", "markdownLinks"],
|
178
178
|
content
|
179
|
-
)
|
179
|
+
)
|
180
180
|
type findingsType =
|
181
181
|
| "relativeImages"
|
182
182
|
| "externalImages"
|
@@ -185,25 +185,25 @@ return false;
|
|
185
185
|
| "uploadcare";
|
186
186
|
for (const finding in findings) {
|
187
187
|
if (Object.prototype.hasOwnProperty.call(findings, finding)) {
|
188
|
-
const obj = findings[finding as findingsType]
|
188
|
+
const obj = findings[finding as findingsType]
|
189
189
|
// Valdites all the relative path images.
|
190
190
|
if (finding === "relativeImages" && Object.keys(obj!).length > 0) {
|
191
191
|
for (const img in obj) {
|
192
192
|
if (Object.prototype.hasOwnProperty.call(obj, img)) {
|
193
193
|
// Validates if the image is in the assets folder.
|
194
|
-
counter && counter.images.total
|
194
|
+
counter && counter.images.total++
|
195
195
|
const relativePath = path
|
196
196
|
.relative(
|
197
197
|
exercise ? exercise.path.replace(/\\/gm, "/") : "./",
|
198
198
|
`${config!.config?.dirPath}/assets/${obj[img].relUrl}`
|
199
199
|
)
|
200
|
-
.replace(/\\/gm, "/")
|
200
|
+
.replace(/\\/gm, "/")
|
201
201
|
if (relativePath !== obj[img].absUrl.split("?").shift()) {
|
202
|
-
counter && counter.images.error
|
202
|
+
counter && counter.images.error++
|
203
203
|
errors.push({
|
204
204
|
exercise: exercise?.title,
|
205
205
|
msg: `This relative path (${obj[img].relUrl}) is not pointing to the assets folder.`,
|
206
|
-
})
|
206
|
+
})
|
207
207
|
}
|
208
208
|
|
209
209
|
if (
|
@@ -211,11 +211,11 @@ return false;
|
|
211
211
|
`${config!.config?.dirPath}/assets/${obj[img].relUrl}`
|
212
212
|
)
|
213
213
|
) {
|
214
|
-
counter && counter.images.error
|
214
|
+
counter && counter.images.error++
|
215
215
|
errors.push({
|
216
216
|
exercise: exercise?.title,
|
217
217
|
msg: `The file ${obj[img].relUrl} doesn't exist in the assets folder.`,
|
218
|
-
})
|
218
|
+
})
|
219
219
|
}
|
220
220
|
}
|
221
221
|
}
|
@@ -223,7 +223,7 @@ return false;
|
|
223
223
|
// Valdites all the aboslute path images.
|
224
224
|
for (const img in obj) {
|
225
225
|
if (Object.prototype.hasOwnProperty.call(obj, img)) {
|
226
|
-
counter && counter.images.total
|
226
|
+
counter && counter.images.total++
|
227
227
|
if (
|
228
228
|
fs.existsSync(
|
229
229
|
`${config!.config?.dirPath}/assets${obj[img].mdUrl
|
@@ -236,57 +236,57 @@ return false;
|
|
236
236
|
exercise ? exercise.path.replace(/\\/gm, "/") : "./",
|
237
237
|
`${config!.config?.dirPath}/assets/${obj[img].mdUrl}`
|
238
238
|
)
|
239
|
-
.replace(/\\/gm, "/")
|
239
|
+
.replace(/\\/gm, "/")
|
240
240
|
warnings.push({
|
241
241
|
exercise: exercise?.title,
|
242
242
|
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}".`,
|
243
|
-
})
|
243
|
+
})
|
244
244
|
}
|
245
245
|
|
246
246
|
try {
|
247
247
|
// eslint-disable-next-line
|
248
248
|
let res = await fetch(obj[img].absUrl, {
|
249
249
|
method: "HEAD",
|
250
|
-
})
|
250
|
+
})
|
251
251
|
if (!res.ok) {
|
252
|
-
counter && counter.images.error
|
252
|
+
counter && counter.images.error++
|
253
253
|
errors.push({
|
254
254
|
exercise: exercise?.title,
|
255
255
|
msg: `This link is broken: ${obj[img].absUrl}`,
|
256
|
-
})
|
256
|
+
})
|
257
257
|
}
|
258
258
|
} catch {
|
259
|
-
counter && counter.images.error
|
259
|
+
counter && counter.images.error++
|
260
260
|
errors.push({
|
261
261
|
exercise: exercise?.title,
|
262
262
|
msg: `This link is broken: ${obj[img].absUrl}`,
|
263
|
-
})
|
263
|
+
})
|
264
264
|
}
|
265
265
|
}
|
266
266
|
}
|
267
267
|
} else if (finding === "markdownLinks" && Object.keys(obj!).length > 0) {
|
268
268
|
for (const link in obj) {
|
269
269
|
if (Object.prototype.hasOwnProperty.call(obj, link)) {
|
270
|
-
counter && counter.links.total
|
270
|
+
counter && counter.links.total++
|
271
271
|
if (!obj[link].mdUrl.includes("twitter")) {
|
272
272
|
try {
|
273
273
|
// eslint-disable-next-line
|
274
274
|
let res = await fetch(obj[link].mdUrl, {
|
275
275
|
method: "HEAD",
|
276
|
-
})
|
276
|
+
})
|
277
277
|
if (res.status > 399 && res.status < 500) {
|
278
|
-
counter && counter.links.error
|
278
|
+
counter && counter.links.error++
|
279
279
|
errors.push({
|
280
280
|
exercise: exercise?.title,
|
281
281
|
msg: `This link is broken: ${obj[link].mdUrl}`,
|
282
|
-
})
|
282
|
+
})
|
283
283
|
}
|
284
284
|
} catch {
|
285
|
-
counter && counter.links.error
|
285
|
+
counter && counter.links.error++
|
286
286
|
errors.push({
|
287
287
|
exercise: exercise?.title,
|
288
288
|
msg: `This link is broken: ${obj[link].mdUrl}`,
|
289
|
-
})
|
289
|
+
})
|
290
290
|
}
|
291
291
|
}
|
292
292
|
}
|
@@ -295,37 +295,34 @@ return false;
|
|
295
295
|
}
|
296
296
|
}
|
297
297
|
|
298
|
-
return true
|
299
|
-
}
|
298
|
+
return true
|
299
|
+
}
|
300
300
|
|
301
301
|
// This function writes a given file with the given content.
|
302
302
|
const writeFile = async (content: string, filePath: string) => {
|
303
303
|
try {
|
304
|
-
await fs.promises.writeFile(filePath, content)
|
304
|
+
await fs.promises.writeFile(filePath, content)
|
305
305
|
} catch (error) {
|
306
306
|
if (error)
|
307
307
|
Console.error(
|
308
308
|
`We weren't able to write the file in this path "${filePath}".`,
|
309
309
|
error
|
310
|
-
)
|
310
|
+
)
|
311
311
|
}
|
312
|
-
}
|
312
|
+
}
|
313
313
|
|
314
314
|
// This function checks if there are errors, and show them in the console at the end.
|
315
|
-
const showErrors = (
|
316
|
-
errors: IAuditErrors[],
|
317
|
-
counter: ICounter | undefined
|
318
|
-
) => {
|
315
|
+
const showErrors = (errors: IAuditErrors[], counter: ICounter | undefined) => {
|
319
316
|
return new Promise((resolve, reject) => {
|
320
317
|
if (errors) {
|
321
318
|
if (errors.length > 0) {
|
322
|
-
Console.log("Checking for errors...")
|
319
|
+
Console.log("Checking for errors...")
|
323
320
|
for (const [i, error] of errors.entries())
|
324
321
|
Console.error(
|
325
322
|
`${i + 1}) ${error.msg} ${
|
326
323
|
error.exercise ? `(Exercise: ${error.exercise})` : ""
|
327
324
|
}`
|
328
|
-
)
|
325
|
+
)
|
329
326
|
if (counter) {
|
330
327
|
Console.error(
|
331
328
|
` We found ${errors.length} error${
|
@@ -335,53 +332,53 @@ const showErrors = (
|
|
335
332
|
} link, ${counter.readmeFiles} README files and ${
|
336
333
|
counter.exercises
|
337
334
|
} exercises.`
|
338
|
-
)
|
335
|
+
)
|
339
336
|
} else {
|
340
337
|
Console.error(
|
341
338
|
` We found ${errors.length} error${
|
342
339
|
errors.length > 1 ? "s" : ""
|
343
340
|
} related with the project integrity.`
|
344
|
-
)
|
341
|
+
)
|
345
342
|
}
|
346
343
|
|
347
|
-
process.exit(1)
|
344
|
+
process.exit(1)
|
348
345
|
} else {
|
349
346
|
if (counter) {
|
350
347
|
Console.success(
|
351
348
|
`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.`
|
352
|
-
)
|
349
|
+
)
|
353
350
|
} else {
|
354
|
-
Console.success(`We didn't find any errors in this repository.`)
|
351
|
+
Console.success(`We didn't find any errors in this repository.`)
|
355
352
|
}
|
356
353
|
|
357
|
-
process.exit(0)
|
354
|
+
process.exit(0)
|
358
355
|
}
|
359
356
|
} else {
|
360
|
-
reject("Failed")
|
357
|
+
reject("Failed")
|
361
358
|
}
|
362
|
-
})
|
363
|
-
}
|
359
|
+
})
|
360
|
+
}
|
364
361
|
|
365
362
|
// This function checks if there are warnings, and show them in the console at the end.
|
366
363
|
const showWarnings = (warnings: IAuditErrors[]) => {
|
367
364
|
return new Promise((resolve, reject) => {
|
368
365
|
if (warnings) {
|
369
366
|
if (warnings.length > 0) {
|
370
|
-
Console.log("Checking for warnings...")
|
367
|
+
Console.log("Checking for warnings...")
|
371
368
|
for (const [i, warning] of warnings.entries())
|
372
369
|
Console.warning(
|
373
370
|
`${i + 1}) ${warning.msg} ${
|
374
371
|
warning.exercise ? `File: ${warning.exercise}` : ""
|
375
372
|
}`
|
376
|
-
)
|
373
|
+
)
|
377
374
|
}
|
378
375
|
|
379
|
-
resolve("SUCCESS")
|
376
|
+
resolve("SUCCESS")
|
380
377
|
} else {
|
381
|
-
reject("Failed")
|
378
|
+
reject("Failed")
|
382
379
|
}
|
383
|
-
})
|
384
|
-
}
|
380
|
+
})
|
381
|
+
}
|
385
382
|
|
386
383
|
export default {
|
387
384
|
isUrl,
|
@@ -392,4 +389,4 @@ export default {
|
|
392
389
|
writeFile,
|
393
390
|
showErrors,
|
394
391
|
showWarnings,
|
395
|
-
}
|
392
|
+
}
|
@@ -1,11 +1,11 @@
|
|
1
|
-
import { IConfig } from "../models/config"
|
2
|
-
import { IExercise } from "../models/exercise-obj"
|
3
|
-
import { ISocket } from "../models/socket"
|
1
|
+
import { IConfig } from "../models/config"
|
2
|
+
import { IExercise } from "../models/exercise-obj"
|
3
|
+
import { ISocket } from "../models/socket"
|
4
4
|
|
5
5
|
class Exercise {
|
6
|
-
exercise: IExercise
|
6
|
+
exercise: IExercise
|
7
7
|
constructor(exercise: IExercise) {
|
8
|
-
this.exercise = exercise
|
8
|
+
this.exercise = exercise
|
9
9
|
}
|
10
10
|
|
11
11
|
test(sessionConfig: IConfig, config: IConfig, socket: ISocket) {
|
@@ -13,39 +13,39 @@ class Exercise {
|
|
13
13
|
socket.log(
|
14
14
|
"testing",
|
15
15
|
`Testing exercise ${this.exercise.slug} using ${this.exercise.language} engine`
|
16
|
-
)
|
16
|
+
)
|
17
17
|
|
18
18
|
sessionConfig.runHook("action", {
|
19
19
|
action: "test",
|
20
20
|
socket,
|
21
21
|
configuration: config,
|
22
22
|
exercise: this.exercise,
|
23
|
-
})
|
23
|
+
})
|
24
24
|
} else {
|
25
|
-
socket.onTestingFinished({ result: "success" })
|
25
|
+
socket.onTestingFinished({ result: "success" })
|
26
26
|
}
|
27
27
|
}
|
28
28
|
}
|
29
29
|
|
30
30
|
class ExercisesQueue {
|
31
|
-
exercises: IExercise[]
|
31
|
+
exercises: IExercise[]
|
32
32
|
constructor(exercises: any) {
|
33
33
|
this.exercises = exercises.map((exercise: IExercise) => {
|
34
|
-
return new Exercise(exercise)
|
35
|
-
})
|
34
|
+
return new Exercise(exercise)
|
35
|
+
})
|
36
36
|
}
|
37
37
|
|
38
38
|
pop() {
|
39
|
-
return this.exercises.shift()
|
39
|
+
return this.exercises.shift()
|
40
40
|
}
|
41
41
|
|
42
42
|
isEmpty() {
|
43
|
-
return this.size() === 0
|
43
|
+
return this.size() === 0
|
44
44
|
}
|
45
45
|
|
46
46
|
size() {
|
47
|
-
return this.exercises.length
|
47
|
+
return this.exercises.length
|
48
48
|
}
|
49
49
|
}
|
50
50
|
|
51
|
-
export default ExercisesQueue
|
51
|
+
export default ExercisesQueue
|