@learnpack/learnpack 2.1.25 → 2.1.26
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +16 -16
- package/bin/run +17 -17
- package/bin/run.cmd +3 -3
- package/lib/commands/audit.d.ts +6 -6
- package/lib/commands/audit.js +327 -302
- package/lib/commands/clean.d.ts +8 -8
- package/lib/commands/clean.js +25 -25
- package/lib/commands/download.d.ts +13 -13
- package/lib/commands/download.js +55 -55
- package/lib/commands/init.d.ts +9 -9
- package/lib/commands/init.js +123 -123
- package/lib/commands/login.d.ts +14 -14
- package/lib/commands/login.js +37 -37
- package/lib/commands/logout.d.ts +14 -14
- package/lib/commands/logout.js +37 -37
- package/lib/commands/publish.d.ts +14 -14
- package/lib/commands/publish.js +82 -82
- package/lib/commands/start.d.ts +7 -7
- package/lib/commands/start.js +165 -165
- package/lib/commands/test.d.ts +6 -6
- package/lib/commands/test.js +62 -62
- package/lib/index.d.ts +1 -1
- package/lib/index.js +4 -4
- package/lib/managers/config/allowed_files.d.ts +5 -5
- package/lib/managers/config/allowed_files.js +30 -30
- package/lib/managers/config/defaults.d.ts +39 -37
- package/lib/managers/config/defaults.js +40 -38
- package/lib/managers/config/exercise.d.ts +36 -36
- package/lib/managers/config/exercise.js +233 -230
- package/lib/managers/config/index.d.ts +3 -3
- package/lib/managers/config/index.js +320 -302
- package/lib/managers/file.d.ts +13 -13
- package/lib/managers/file.js +134 -134
- package/lib/managers/gitpod.d.ts +3 -3
- package/lib/managers/gitpod.js +67 -67
- package/lib/managers/server/index.d.ts +6 -6
- package/lib/managers/server/index.js +58 -51
- package/lib/managers/server/routes.d.ts +4 -4
- package/lib/managers/server/routes.js +167 -167
- package/lib/managers/session.d.ts +3 -3
- package/lib/managers/session.js +104 -104
- package/lib/managers/socket.d.ts +3 -3
- package/lib/managers/socket.js +164 -164
- package/lib/managers/test.js +84 -84
- package/lib/models/action.d.ts +2 -2
- package/lib/models/action.js +2 -2
- package/lib/models/audit.d.ts +15 -15
- package/lib/models/audit.js +2 -2
- package/lib/models/config-manager.d.ts +21 -21
- package/lib/models/config-manager.js +2 -2
- package/lib/models/config.d.ts +62 -60
- package/lib/models/config.js +2 -2
- package/lib/models/counter.d.ts +11 -11
- package/lib/models/counter.js +2 -2
- package/lib/models/errors.d.ts +15 -15
- package/lib/models/errors.js +2 -2
- package/lib/models/exercise-obj.d.ts +27 -27
- package/lib/models/exercise-obj.js +2 -2
- package/lib/models/file.d.ts +5 -5
- package/lib/models/file.js +2 -2
- package/lib/models/findings.d.ts +17 -17
- package/lib/models/findings.js +2 -2
- package/lib/models/flags.d.ts +10 -10
- package/lib/models/flags.js +2 -2
- package/lib/models/front-matter.d.ts +11 -11
- package/lib/models/front-matter.js +2 -2
- package/lib/models/gitpod-data.d.ts +16 -16
- package/lib/models/gitpod-data.js +2 -2
- package/lib/models/language.d.ts +4 -4
- package/lib/models/language.js +2 -2
- package/lib/models/package.d.ts +7 -7
- package/lib/models/package.js +2 -2
- package/lib/models/plugin-config.d.ts +16 -16
- package/lib/models/plugin-config.js +2 -2
- package/lib/models/session.d.ts +23 -23
- package/lib/models/session.js +2 -2
- package/lib/models/socket.d.ts +31 -31
- package/lib/models/socket.js +2 -2
- package/lib/models/status.d.ts +1 -1
- package/lib/models/status.js +2 -2
- package/lib/models/success-types.d.ts +1 -1
- package/lib/models/success-types.js +2 -2
- package/lib/plugin/command/compile.d.ts +6 -6
- package/lib/plugin/command/compile.js +18 -18
- package/lib/plugin/command/test.d.ts +6 -6
- package/lib/plugin/command/test.js +25 -25
- package/lib/plugin/index.d.ts +27 -27
- package/lib/plugin/index.js +7 -7
- package/lib/plugin/plugin.d.ts +8 -8
- package/lib/plugin/plugin.js +68 -68
- package/lib/plugin/utils.d.ts +16 -16
- package/lib/plugin/utils.js +58 -58
- package/lib/ui/download.d.ts +5 -5
- package/lib/ui/download.js +61 -61
- package/lib/utils/BaseCommand.d.ts +8 -8
- package/lib/utils/BaseCommand.js +41 -41
- package/lib/utils/SessionCommand.d.ts +10 -10
- package/lib/utils/SessionCommand.js +47 -47
- package/lib/utils/api.d.ts +12 -12
- package/lib/utils/api.js +173 -173
- package/lib/utils/audit.d.ts +16 -16
- package/lib/utils/audit.js +302 -302
- package/lib/utils/console.d.ts +12 -12
- package/lib/utils/console.js +19 -19
- package/lib/utils/errors.d.ts +17 -17
- package/lib/utils/errors.js +100 -100
- package/lib/utils/exercisesQueue.d.ts +9 -9
- package/lib/utils/exercisesQueue.js +38 -38
- package/lib/utils/fileQueue.d.ts +40 -40
- package/lib/utils/fileQueue.js +168 -168
- package/lib/utils/misc.d.ts +1 -1
- package/lib/utils/misc.js +23 -23
- package/lib/utils/validators.d.ts +5 -5
- package/lib/utils/validators.js +17 -17
- package/lib/utils/watcher.d.ts +2 -2
- package/lib/utils/watcher.js +23 -23
- package/oclif.manifest.json +1 -1
- package/package.json +138 -138
- package/src/commands/audit.ts +25 -0
- package/src/commands/clean.ts +29 -29
- package/src/commands/download.ts +62 -62
- package/src/commands/login.ts +42 -42
- package/src/commands/logout.ts +43 -43
- package/src/commands/publish.ts +107 -107
- package/src/commands/start.ts +238 -234
- package/src/commands/test.ts +85 -85
- package/src/index.ts +1 -1
- package/src/managers/config/allowed_files.ts +29 -29
- package/src/managers/config/defaults.ts +2 -0
- package/src/managers/config/exercise.ts +309 -302
- package/src/managers/config/index.ts +22 -1
- package/src/managers/file.ts +169 -169
- package/src/managers/gitpod.ts +84 -84
- package/src/managers/server/index.ts +77 -69
- package/src/managers/session.ts +118 -118
- package/src/managers/socket.ts +239 -239
- package/src/managers/test.ts +83 -83
- package/src/models/action.ts +3 -3
- package/src/models/config-manager.ts +23 -23
- package/src/models/config.ts +2 -0
- package/src/models/counter.ts +11 -11
- package/src/models/errors.ts +22 -22
- package/src/models/file.ts +5 -5
- package/src/models/findings.ts +18 -18
- package/src/models/flags.ts +10 -10
- package/src/models/front-matter.ts +11 -11
- package/src/models/gitpod-data.ts +19 -19
- package/src/models/language.ts +4 -4
- package/src/models/package.ts +7 -7
- package/src/models/plugin-config.ts +17 -17
- package/src/models/session.ts +26 -26
- package/src/models/socket.ts +48 -48
- package/src/models/status.ts +15 -15
- package/src/models/success-types.ts +1 -1
- package/src/plugin/command/compile.ts +17 -17
- package/src/plugin/command/test.ts +30 -30
- package/src/plugin/index.ts +6 -6
- package/src/plugin/plugin.ts +94 -94
- package/src/plugin/utils.ts +87 -87
- package/src/types/node-fetch.d.ts +1 -1
- package/src/ui/download.ts +71 -71
- package/src/utils/BaseCommand.ts +48 -48
- package/src/utils/SessionCommand.ts +48 -48
- package/src/utils/api.ts +194 -194
- package/src/utils/audit.ts +395 -395
- package/src/utils/console.ts +24 -24
- package/src/utils/errors.ts +117 -117
- package/src/utils/exercisesQueue.ts +51 -51
- package/src/utils/fileQueue.ts +198 -198
- package/src/utils/misc.ts +23 -23
- package/src/utils/templates/gitignore.txt +19 -19
- package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +24 -24
- package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +24 -24
- package/src/utils/templates/incremental/README.ejs +4 -4
- package/src/utils/templates/incremental/README.es.ejs +4 -4
- package/src/utils/templates/isolated/01-hello-world/README.es.md +26 -26
- package/src/utils/templates/isolated/01-hello-world/README.md +26 -26
- package/src/utils/templates/isolated/README.ejs +4 -4
- package/src/utils/templates/isolated/README.es.ejs +4 -4
- package/src/utils/templates/no-grading/README.ejs +4 -4
- package/src/utils/templates/no-grading/README.es.ejs +4 -4
- package/src/utils/validators.ts +18 -18
@@ -1,302 +1,309 @@
|
|
1
|
-
import * as p from "path";
|
2
|
-
// import frontMatter from 'front-matter'
|
3
|
-
import * as fs from "fs";
|
4
|
-
import Console from "../../utils/console";
|
5
|
-
import allowed from "./allowed_files";
|
6
|
-
|
7
|
-
import { IConfigObj } from "../../models/config";
|
8
|
-
import { IFile } from "../../models/file";
|
9
|
-
import { IExercise } from "../../models/exercise-obj";
|
10
|
-
|
11
|
-
// eslint-disable-next-line
|
12
|
-
const frontMatter = require("front-matter");
|
13
|
-
|
14
|
-
export const exercise = (
|
15
|
-
path: string,
|
16
|
-
position: number,
|
17
|
-
configObject: IConfigObj
|
18
|
-
): IExercise => {
|
19
|
-
const { config, exercises } = configObject;
|
20
|
-
let slug = p.basename(path);
|
21
|
-
|
22
|
-
if (!validateExerciseDirectoryName(slug)) {
|
23
|
-
Console.error(
|
24
|
-
`Exercise directory ${slug} has an invalid name, it has to start with two or three digits followed by words separated by underscors or hyphen (no white spaces). e.g: 01.12-hello-world`
|
25
|
-
);
|
26
|
-
}
|
27
|
-
|
28
|
-
// get all the files
|
29
|
-
const files = fs.readdirSync(path);
|
30
|
-
|
31
|
-
/**
|
32
|
-
* build the translation array like:
|
33
|
-
{
|
34
|
-
"us": "path/to/Readme.md",
|
35
|
-
"es": "path/to/Readme.es.md"
|
36
|
-
}
|
37
|
-
*/
|
38
|
-
const translations: { [key: string]: string } = {};
|
39
|
-
for (const file of files.filter(file =>
|
40
|
-
file.toLowerCase().includes("readme")
|
41
|
-
)) {
|
42
|
-
const parts = file.split(".");
|
43
|
-
|
44
|
-
if (parts.length === 3)
|
45
|
-
translations[parts[1]] = file;
|
46
|
-
else
|
47
|
-
translations.us = file;
|
48
|
-
}
|
49
|
-
|
50
|
-
// if the slug is a dot, it means there is not "exercises" folder, and its just a single README.md
|
51
|
-
if (slug === ".")
|
52
|
-
slug = "default-index";
|
53
|
-
|
54
|
-
const detected = detect(configObject, files);
|
55
|
-
|
56
|
-
const exerciseObj: IExercise = {
|
57
|
-
position,
|
58
|
-
path,
|
59
|
-
slug,
|
60
|
-
translations,
|
61
|
-
language: detected?.language,
|
62
|
-
entry: detected?.entry ? path + "/" + detected.entry : null, // full path to the exercise entry
|
63
|
-
title: slug || "Exercise",
|
64
|
-
graded: files.some(
|
65
|
-
file =>
|
66
|
-
file.toLowerCase().startsWith("test.") ||
|
67
|
-
file.toLowerCase().startsWith("tests.")
|
68
|
-
),
|
69
|
-
files: filterFiles(files, path),
|
70
|
-
// if the exercises was on the config before I may keep the status done
|
71
|
-
done:
|
72
|
-
Array.isArray(exercises) &&
|
73
|
-
typeof exercises[position] !== "undefined" &&
|
74
|
-
path.slice(Math.max(0, path.indexOf("exercises/") + 10)) ===
|
75
|
-
exercises[position].slug ?
|
76
|
-
exercises[position].done :
|
77
|
-
false,
|
78
|
-
getReadme: function (lang = null) {
|
79
|
-
if (lang === "us")
|
80
|
-
lang = null; // <-- english is default, no need to append it to the file name
|
81
|
-
|
82
|
-
if (!fs.existsSync(`${this.path}/README${lang ? "." + lang : ""}.md`)) {
|
83
|
-
Console.error(
|
84
|
-
`Language ${lang} not found for exercise ${slug}, switching to default language`
|
85
|
-
);
|
86
|
-
|
87
|
-
if (lang)
|
88
|
-
lang = null;
|
89
|
-
|
90
|
-
if (!fs.existsSync(`${this.path}/README${lang ? "." + lang : ""}.md`))
|
91
|
-
throw new Error(
|
92
|
-
"Readme file not found for exercise: " + this.path + "/README.md"
|
93
|
-
);
|
94
|
-
}
|
95
|
-
|
96
|
-
const content = fs.readFileSync(
|
97
|
-
`${this.path}/README${lang ? "." + lang : ""}.md`,
|
98
|
-
"utf8"
|
99
|
-
);
|
100
|
-
const attr = frontMatter(content);
|
101
|
-
return attr;
|
102
|
-
},
|
103
|
-
getFile: function (name: string) {
|
104
|
-
const file: IFile | undefined = this.files.find(
|
105
|
-
(f: IFile) => f.name === name
|
106
|
-
);
|
107
|
-
|
108
|
-
if (!file || !fs.existsSync(file.path)) {
|
109
|
-
throw new Error(`File not found: + ${file?.path}`);
|
110
|
-
} else if (fs.lstatSync(file.path).isDirectory()) {
|
111
|
-
return (
|
112
|
-
"Error: This is not a file to be read, but a directory: " + file.path
|
113
|
-
);
|
114
|
-
}
|
115
|
-
|
116
|
-
// get file content
|
117
|
-
const content = fs.readFileSync(file.path);
|
118
|
-
|
119
|
-
// create reset folder
|
120
|
-
if (!fs.existsSync(`${config?.dirPath}/resets`))
|
121
|
-
fs.mkdirSync(`${config?.dirPath}/resets`);
|
122
|
-
if (!fs.existsSync(`${config?.dirPath}/resets/` + this.slug)) {
|
123
|
-
fs.mkdirSync(`${config?.dirPath}/resets/` + this.slug);
|
124
|
-
|
125
|
-
fs.
|
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
|
-
return
|
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
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
!file.name.toLocaleLowerCase().includes("
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
!
|
196
|
-
file.name.
|
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
|
-
if (
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
hasFiles = files.filter(f => f.includes(".
|
232
|
-
if (hasFiles.length > 0)
|
233
|
-
return {
|
234
|
-
language: "
|
235
|
-
entry: hasFiles.find(f => config.entries.
|
236
|
-
};
|
237
|
-
|
238
|
-
hasFiles = files.filter(f => f.includes(".
|
239
|
-
if (hasFiles.length > 0)
|
240
|
-
return {
|
241
|
-
language: "
|
242
|
-
entry: hasFiles.find(f => config.entries.
|
243
|
-
};
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
if
|
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
|
-
"index.
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
1
|
+
import * as p from "path";
|
2
|
+
// import frontMatter from 'front-matter'
|
3
|
+
import * as fs from "fs";
|
4
|
+
import Console from "../../utils/console";
|
5
|
+
import allowed from "./allowed_files";
|
6
|
+
|
7
|
+
import { IConfigObj } from "../../models/config";
|
8
|
+
import { IFile } from "../../models/file";
|
9
|
+
import { IExercise } from "../../models/exercise-obj";
|
10
|
+
|
11
|
+
// eslint-disable-next-line
|
12
|
+
const frontMatter = require("front-matter");
|
13
|
+
|
14
|
+
export const exercise = (
|
15
|
+
path: string,
|
16
|
+
position: number,
|
17
|
+
configObject: IConfigObj
|
18
|
+
): IExercise => {
|
19
|
+
const { config, exercises } = configObject;
|
20
|
+
let slug = p.basename(path);
|
21
|
+
|
22
|
+
if (!validateExerciseDirectoryName(slug)) {
|
23
|
+
Console.error(
|
24
|
+
`Exercise directory ${slug} has an invalid name, it has to start with two or three digits followed by words separated by underscors or hyphen (no white spaces). e.g: 01.12-hello-world`
|
25
|
+
);
|
26
|
+
}
|
27
|
+
|
28
|
+
// get all the files
|
29
|
+
const files = fs.readdirSync(path);
|
30
|
+
|
31
|
+
/**
|
32
|
+
* build the translation array like:
|
33
|
+
{
|
34
|
+
"us": "path/to/Readme.md",
|
35
|
+
"es": "path/to/Readme.es.md"
|
36
|
+
}
|
37
|
+
*/
|
38
|
+
const translations: { [key: string]: string } = {};
|
39
|
+
for (const file of files.filter(file =>
|
40
|
+
file.toLowerCase().includes("readme")
|
41
|
+
)) {
|
42
|
+
const parts = file.split(".");
|
43
|
+
|
44
|
+
if (parts.length === 3)
|
45
|
+
translations[parts[1]] = file;
|
46
|
+
else
|
47
|
+
translations.us = file;
|
48
|
+
}
|
49
|
+
|
50
|
+
// if the slug is a dot, it means there is not "exercises" folder, and its just a single README.md
|
51
|
+
if (slug === ".")
|
52
|
+
slug = "default-index";
|
53
|
+
|
54
|
+
const detected = detect(configObject, files);
|
55
|
+
|
56
|
+
const exerciseObj: IExercise = {
|
57
|
+
position,
|
58
|
+
path,
|
59
|
+
slug,
|
60
|
+
translations,
|
61
|
+
language: detected?.language,
|
62
|
+
entry: detected?.entry ? path + "/" + detected.entry : null, // full path to the exercise entry
|
63
|
+
title: slug || "Exercise",
|
64
|
+
graded: files.some(
|
65
|
+
(file: any) =>
|
66
|
+
file.toLowerCase().startsWith("test.") ||
|
67
|
+
file.toLowerCase().startsWith("tests.")
|
68
|
+
),
|
69
|
+
files: filterFiles(files, path),
|
70
|
+
// if the exercises was on the config before I may keep the status done
|
71
|
+
done:
|
72
|
+
Array.isArray(exercises) &&
|
73
|
+
typeof exercises[position] !== "undefined" &&
|
74
|
+
path.slice(Math.max(0, path.indexOf("exercises/") + 10)) ===
|
75
|
+
exercises[position].slug ?
|
76
|
+
exercises[position].done :
|
77
|
+
false,
|
78
|
+
getReadme: function (lang = null) {
|
79
|
+
if (lang === "us")
|
80
|
+
lang = null; // <-- english is default, no need to append it to the file name
|
81
|
+
|
82
|
+
if (!fs.existsSync(`${this.path}/README${lang ? "." + lang : ""}.md`)) {
|
83
|
+
Console.error(
|
84
|
+
`Language ${lang} not found for exercise ${slug}, switching to default language`
|
85
|
+
);
|
86
|
+
|
87
|
+
if (lang)
|
88
|
+
lang = null;
|
89
|
+
|
90
|
+
if (!fs.existsSync(`${this.path}/README${lang ? "." + lang : ""}.md`))
|
91
|
+
throw new Error(
|
92
|
+
"Readme file not found for exercise: " + this.path + "/README.md"
|
93
|
+
);
|
94
|
+
}
|
95
|
+
|
96
|
+
const content = fs.readFileSync(
|
97
|
+
`${this.path}/README${lang ? "." + lang : ""}.md`,
|
98
|
+
"utf8"
|
99
|
+
);
|
100
|
+
const attr = frontMatter(content);
|
101
|
+
return attr;
|
102
|
+
},
|
103
|
+
getFile: function (name: string) {
|
104
|
+
const file: IFile | undefined = this.files.find(
|
105
|
+
(f: IFile) => f.name === name
|
106
|
+
);
|
107
|
+
|
108
|
+
if (!file || !fs.existsSync(file.path)) {
|
109
|
+
throw new Error(`File not found: + ${file?.path}`);
|
110
|
+
} else if (fs.lstatSync(file.path).isDirectory()) {
|
111
|
+
return (
|
112
|
+
"Error: This is not a file to be read, but a directory: " + file.path
|
113
|
+
);
|
114
|
+
}
|
115
|
+
|
116
|
+
// get file content
|
117
|
+
const content = fs.readFileSync(file.path);
|
118
|
+
|
119
|
+
// create reset folder
|
120
|
+
if (!fs.existsSync(`${config?.dirPath}/resets`))
|
121
|
+
fs.mkdirSync(`${config?.dirPath}/resets`);
|
122
|
+
if (!fs.existsSync(`${config?.dirPath}/resets/` + this.slug)) {
|
123
|
+
fs.mkdirSync(`${config?.dirPath}/resets/` + this.slug);
|
124
|
+
for (const _file of this.files) {
|
125
|
+
const fileContent = fs.readFileSync(_file.path);
|
126
|
+
if (
|
127
|
+
!fs.existsSync(
|
128
|
+
`${config?.dirPath}/resets/${this.slug}/${_file.name}`
|
129
|
+
)
|
130
|
+
) {
|
131
|
+
fs.writeFileSync(
|
132
|
+
`${config?.dirPath}/resets/${this.slug}/${_file.name}`,
|
133
|
+
fileContent
|
134
|
+
);
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
return content;
|
140
|
+
},
|
141
|
+
saveFile: function (name: string, content: string) {
|
142
|
+
const file: IFile | undefined = this.files.find(
|
143
|
+
(f: IFile) => f.name === name
|
144
|
+
);
|
145
|
+
|
146
|
+
if (file) {
|
147
|
+
if (!fs.existsSync(file.path)) {
|
148
|
+
throw new Error("File not found: " + file.path);
|
149
|
+
}
|
150
|
+
|
151
|
+
return fs.writeFileSync(file.path, content, "utf8");
|
152
|
+
}
|
153
|
+
},
|
154
|
+
getTestReport: function () {
|
155
|
+
const _path = `${configObject?.confPath?.base}/reports/${this.slug}.json`;
|
156
|
+
|
157
|
+
if (!fs.existsSync(_path))
|
158
|
+
return {};
|
159
|
+
|
160
|
+
const content = fs.readFileSync(_path);
|
161
|
+
const data = JSON.parse(`${content}`);
|
162
|
+
return data;
|
163
|
+
},
|
164
|
+
};
|
165
|
+
|
166
|
+
return exerciseObj;
|
167
|
+
};
|
168
|
+
|
169
|
+
export const validateExerciseDirectoryName = (str: string) => {
|
170
|
+
if (str === "./")
|
171
|
+
return true;
|
172
|
+
// TODO: Add nameValidationREgex from the config
|
173
|
+
const regex = /^(\d{2,3}(\.\d{1,2})?-([\dA-Za-z]+(-|_)?)+)$/;
|
174
|
+
return regex.test(str);
|
175
|
+
};
|
176
|
+
|
177
|
+
export const isCodable = (str: string) => {
|
178
|
+
const extension = p.extname(str);
|
179
|
+
return allowed.extensions.includes(extension.slice(1).toLowerCase());
|
180
|
+
};
|
181
|
+
|
182
|
+
const isNotConfiguration = (str: string) => {
|
183
|
+
return !allowed.names.includes(str);
|
184
|
+
};
|
185
|
+
|
186
|
+
export const shouldBeVisible = function (file: IFile) {
|
187
|
+
return (
|
188
|
+
// doest not have "test." on their name
|
189
|
+
!file.name.toLocaleLowerCase().includes("test.") &&
|
190
|
+
!file.name.toLocaleLowerCase().includes("tests.") &&
|
191
|
+
!file.name.toLocaleLowerCase().includes(".hide.") &&
|
192
|
+
// ignore hidden files
|
193
|
+
file.name.charAt(0) !== "." &&
|
194
|
+
// ignore learn.json and bc.json
|
195
|
+
!file.name.toLocaleLowerCase().includes("learn.json") &&
|
196
|
+
!file.name.toLocaleLowerCase().includes("bc.json") &&
|
197
|
+
// ignore images, videos, vectors, etc.
|
198
|
+
isCodable(file.name) &&
|
199
|
+
isNotConfiguration(file.name) &&
|
200
|
+
// readme's and directories
|
201
|
+
!file.name.toLowerCase().includes("readme.") &&
|
202
|
+
!isDirectory(file.path) &&
|
203
|
+
file.name.charAt(0) !== "_"
|
204
|
+
);
|
205
|
+
};
|
206
|
+
|
207
|
+
export const isDirectory = (source: string) => {
|
208
|
+
// if(path.basename(source) === path.basename(config.dirPath)) return false
|
209
|
+
return fs.lstatSync(source).isDirectory();
|
210
|
+
};
|
211
|
+
|
212
|
+
export const detect = (
|
213
|
+
configObject: IConfigObj | undefined,
|
214
|
+
files: Array<string>
|
215
|
+
) => {
|
216
|
+
if (!configObject) {
|
217
|
+
return;
|
218
|
+
}
|
219
|
+
|
220
|
+
const { config } = configObject;
|
221
|
+
|
222
|
+
if (!config)
|
223
|
+
throw new Error("No configuration found during the engine detection");
|
224
|
+
|
225
|
+
if (!config.entries)
|
226
|
+
throw new Error(
|
227
|
+
"No configuration found for entries, please add a 'entries' object with the default file name for your exercise entry file that is going to be used while compiling, for example: index.html for html, app.py for python3, etc."
|
228
|
+
);
|
229
|
+
// A language was found on the config object, but this language will only be used as last resort, learnpack will try to guess each exercise language independently based on file extension (js, jsx, html, etc.)
|
230
|
+
|
231
|
+
let hasFiles = files.filter(f => f.includes(".py"));
|
232
|
+
if (hasFiles.length > 0)
|
233
|
+
return {
|
234
|
+
language: "python3",
|
235
|
+
entry: hasFiles.find(f => config.entries.python3 === f),
|
236
|
+
};
|
237
|
+
|
238
|
+
hasFiles = files.filter(f => f.includes(".java"));
|
239
|
+
if (hasFiles.length > 0)
|
240
|
+
return {
|
241
|
+
language: "java",
|
242
|
+
entry: hasFiles.find(f => config.entries.java === f),
|
243
|
+
};
|
244
|
+
|
245
|
+
hasFiles = files.filter(f => f.includes(".jsx"));
|
246
|
+
if (hasFiles.length > 0)
|
247
|
+
return {
|
248
|
+
language: "react",
|
249
|
+
entry: hasFiles.find(f => config.entries.react === f),
|
250
|
+
};
|
251
|
+
const hasHTML = files.filter(f => f.includes("index.html"));
|
252
|
+
const hasIndexJS = files.find(f => f.includes("index.js"));
|
253
|
+
const hasJS = files.filter(f => f.includes(".js"));
|
254
|
+
// angular, vue, vanillajs needs to have at least 2 files (html,css,js),
|
255
|
+
// the test.js and the entry file in js
|
256
|
+
// if not its just another HTML
|
257
|
+
|
258
|
+
if (hasIndexJS && hasHTML.length > 0)
|
259
|
+
return {
|
260
|
+
language: "vanillajs",
|
261
|
+
entry: hasIndexJS,
|
262
|
+
};
|
263
|
+
if (hasHTML.length > 0)
|
264
|
+
return {
|
265
|
+
language: "html",
|
266
|
+
entry: hasHTML.find(f => config.entries.html === f),
|
267
|
+
};
|
268
|
+
if (hasJS.length > 0)
|
269
|
+
return {
|
270
|
+
language: "node",
|
271
|
+
entry: hasJS.find(f => config.entries.node === f),
|
272
|
+
};
|
273
|
+
|
274
|
+
return {
|
275
|
+
language: null,
|
276
|
+
entry: null,
|
277
|
+
};
|
278
|
+
};
|
279
|
+
|
280
|
+
export const filterFiles = (files: Array<string>, basePath = ".") =>
|
281
|
+
files
|
282
|
+
.map((ex: string) => ({
|
283
|
+
path: basePath + "/" + ex,
|
284
|
+
name: ex,
|
285
|
+
hidden: !shouldBeVisible({
|
286
|
+
name: ex,
|
287
|
+
path: basePath + "/" + ex,
|
288
|
+
} as IFile),
|
289
|
+
}))
|
290
|
+
.sort((f1, f2) => {
|
291
|
+
const score: { [key: string]: number } = {
|
292
|
+
// sorting priority
|
293
|
+
"index.html": 1,
|
294
|
+
"styles.css": 2,
|
295
|
+
"styles.scss": 2,
|
296
|
+
"style.css": 2,
|
297
|
+
"style.scss": 2,
|
298
|
+
"index.css": 2,
|
299
|
+
"index.scss": 2,
|
300
|
+
"index.js": 3,
|
301
|
+
};
|
302
|
+
return score[f1.name] < score[f2.name] ? -1 : 1;
|
303
|
+
});
|
304
|
+
|
305
|
+
export default {
|
306
|
+
exercise,
|
307
|
+
detect,
|
308
|
+
filterFiles,
|
309
|
+
};
|
@@ -57,6 +57,23 @@ const getGitpodAddress = () => {
|
|
57
57
|
return "http://localhost";
|
58
58
|
};
|
59
59
|
|
60
|
+
const getCodespacesNamespace = () => {
|
61
|
+
// https://orange-rotary-phone-wxpg49q5gcg4rp-3000.app.github.dev
|
62
|
+
const codespace_name = shell
|
63
|
+
.exec("echo $CODESPACE_NAME", { silent: true })
|
64
|
+
.stdout.replace(/(\r\n|\n|\r)/gm, "");
|
65
|
+
|
66
|
+
if (
|
67
|
+
!codespace_name ||
|
68
|
+
codespace_name === "" ||
|
69
|
+
codespace_name === undefined
|
70
|
+
) {
|
71
|
+
return null;
|
72
|
+
}
|
73
|
+
|
74
|
+
return codespace_name;
|
75
|
+
};
|
76
|
+
|
60
77
|
export default async ({
|
61
78
|
grading,
|
62
79
|
mode,
|
@@ -124,12 +141,17 @@ export default async ({
|
|
124
141
|
});
|
125
142
|
|
126
143
|
// auto detect agent (if possible)
|
144
|
+
const codespaces_workspace = getCodespacesNamespace();
|
127
145
|
if (shell.which("gp") && configObj && configObj.config) {
|
128
146
|
configObj.config.editor.agent = "vscode";
|
129
147
|
configObj.address = getGitpodAddress();
|
130
148
|
configObj.config.publicUrl = `https://${
|
131
149
|
configObj.config.port
|
132
150
|
}-${configObj.address?.slice(8)}`;
|
151
|
+
} else if (configObj.config && codespaces_workspace) {
|
152
|
+
configObj.config.editor.agent = "vscode";
|
153
|
+
configObj.address = `https://${codespaces_workspace}.github.dev`;
|
154
|
+
configObj.config.publicUrl = `https://${codespaces_workspace}-${configObj.config.port}.app.github.dev`;
|
133
155
|
} else if (configObj.config && !configObj.config.editor.agent) {
|
134
156
|
configObj.config.editor.agent = "localhost";
|
135
157
|
}
|
@@ -370,7 +392,6 @@ onChange();
|
|
370
392
|
configObj.config.translations = [
|
371
393
|
...new Set(configObj.config.translations),
|
372
394
|
];
|
373
|
-
|
374
395
|
fs.writeFileSync(
|
375
396
|
configObj.config.dirPath + "/config.json",
|
376
397
|
JSON.stringify(configObj, null, 4)
|