@learnpack/learnpack 2.0.19 → 2.0.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. package/README.md +10 -10
  2. package/lib/commands/audit.d.ts +6 -0
  3. package/lib/commands/audit.js +369 -0
  4. package/lib/commands/clean.d.ts +8 -0
  5. package/lib/commands/clean.js +25 -0
  6. package/lib/commands/download.d.ts +13 -0
  7. package/lib/commands/download.js +55 -0
  8. package/lib/commands/init.d.ts +9 -0
  9. package/lib/commands/init.js +117 -0
  10. package/lib/commands/login.d.ts +14 -0
  11. package/lib/commands/login.js +37 -0
  12. package/lib/commands/logout.d.ts +14 -0
  13. package/lib/commands/logout.js +37 -0
  14. package/lib/commands/publish.d.ts +14 -0
  15. package/lib/commands/publish.js +82 -0
  16. package/lib/commands/start.d.ts +7 -0
  17. package/lib/commands/start.js +165 -0
  18. package/lib/commands/test.d.ts +6 -0
  19. package/lib/commands/test.js +62 -0
  20. package/lib/managers/config/allowed_files.d.ts +5 -0
  21. package/lib/managers/config/allowed_files.js +30 -0
  22. package/lib/managers/config/defaults.d.ts +34 -0
  23. package/lib/managers/config/defaults.js +35 -0
  24. package/lib/managers/config/exercise.d.ts +36 -0
  25. package/lib/managers/config/exercise.js +230 -0
  26. package/lib/managers/config/index.d.ts +3 -0
  27. package/lib/managers/config/index.js +307 -0
  28. package/lib/managers/file.d.ts +13 -0
  29. package/lib/managers/file.js +134 -0
  30. package/lib/managers/gitpod.d.ts +3 -0
  31. package/lib/managers/gitpod.js +67 -0
  32. package/lib/managers/server/index.d.ts +6 -0
  33. package/lib/managers/server/index.js +51 -0
  34. package/lib/managers/server/routes.d.ts +4 -0
  35. package/lib/managers/server/routes.js +160 -0
  36. package/lib/managers/session.d.ts +3 -0
  37. package/lib/managers/session.js +104 -0
  38. package/lib/managers/socket.d.ts +3 -0
  39. package/lib/managers/socket.js +164 -0
  40. package/lib/managers/test.d.ts +0 -0
  41. package/lib/managers/test.js +84 -0
  42. package/lib/models/action.d.ts +2 -0
  43. package/lib/models/action.js +2 -0
  44. package/lib/models/audit-errors.d.ts +4 -0
  45. package/lib/models/audit-errors.js +2 -0
  46. package/lib/models/config-manager.d.ts +21 -0
  47. package/lib/models/config-manager.js +2 -0
  48. package/lib/models/config.d.ts +57 -0
  49. package/lib/models/config.js +2 -0
  50. package/lib/models/counter.d.ts +11 -0
  51. package/lib/models/counter.js +2 -0
  52. package/lib/models/errors.d.ts +15 -0
  53. package/lib/models/errors.js +2 -0
  54. package/lib/models/exercise-obj.d.ts +27 -0
  55. package/lib/models/exercise-obj.js +2 -0
  56. package/lib/models/file.d.ts +5 -0
  57. package/lib/models/file.js +2 -0
  58. package/lib/models/findings.d.ts +17 -0
  59. package/lib/models/findings.js +2 -0
  60. package/lib/models/flags.d.ts +10 -0
  61. package/lib/models/flags.js +2 -0
  62. package/lib/models/front-matter.d.ts +11 -0
  63. package/lib/models/front-matter.js +2 -0
  64. package/lib/models/gitpod-data.d.ts +16 -0
  65. package/lib/models/gitpod-data.js +2 -0
  66. package/lib/models/language.d.ts +4 -0
  67. package/lib/models/language.js +2 -0
  68. package/lib/models/package.d.ts +7 -0
  69. package/lib/models/package.js +2 -0
  70. package/lib/models/plugin-config.d.ts +16 -0
  71. package/lib/models/plugin-config.js +2 -0
  72. package/lib/models/session.d.ts +23 -0
  73. package/lib/models/session.js +2 -0
  74. package/lib/models/socket.d.ts +31 -0
  75. package/lib/models/socket.js +2 -0
  76. package/lib/models/status.d.ts +1 -0
  77. package/lib/models/status.js +2 -0
  78. package/lib/models/success-types.d.ts +1 -0
  79. package/lib/models/success-types.js +2 -0
  80. package/lib/plugin/command/compile.d.ts +6 -0
  81. package/lib/plugin/command/compile.js +18 -0
  82. package/lib/plugin/command/test.d.ts +6 -0
  83. package/lib/plugin/command/test.js +25 -0
  84. package/lib/plugin/index.d.ts +27 -0
  85. package/lib/plugin/index.js +7 -0
  86. package/lib/plugin/plugin.d.ts +8 -0
  87. package/lib/plugin/plugin.js +68 -0
  88. package/lib/plugin/utils.d.ts +16 -0
  89. package/lib/plugin/utils.js +58 -0
  90. package/lib/ui/download.d.ts +5 -0
  91. package/lib/ui/download.js +61 -0
  92. package/lib/utils/BaseCommand.d.ts +8 -0
  93. package/lib/utils/BaseCommand.js +41 -0
  94. package/lib/utils/SessionCommand.d.ts +10 -0
  95. package/lib/utils/SessionCommand.js +47 -0
  96. package/lib/utils/api.d.ts +12 -0
  97. package/lib/utils/api.js +173 -0
  98. package/lib/utils/audit.d.ts +13 -0
  99. package/lib/utils/audit.js +129 -0
  100. package/lib/utils/console.d.ts +12 -0
  101. package/lib/utils/console.js +19 -0
  102. package/lib/utils/errors.d.ts +17 -0
  103. package/lib/utils/errors.js +100 -0
  104. package/lib/utils/exercisesQueue.d.ts +9 -0
  105. package/lib/utils/exercisesQueue.js +38 -0
  106. package/lib/utils/fileQueue.d.ts +40 -0
  107. package/lib/utils/fileQueue.js +168 -0
  108. package/lib/utils/misc.d.ts +1 -0
  109. package/lib/utils/misc.js +23 -0
  110. package/lib/utils/validators.d.ts +5 -0
  111. package/lib/utils/validators.js +17 -0
  112. package/lib/utils/watcher.d.ts +2 -0
  113. package/lib/utils/watcher.js +24 -0
  114. package/oclif.manifest.json +1 -1
  115. package/package.json +1 -1
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = {
4
+ config: {
5
+ port: 3000,
6
+ editor: {
7
+ mode: null,
8
+ agent: null,
9
+ version: null,
10
+ },
11
+ dirPath: './.learn',
12
+ configPath: './learn.json',
13
+ outputPath: './.learn/dist',
14
+ publicPath: '/preview',
15
+ publicUrl: null,
16
+ language: 'auto',
17
+ grading: 'isolated',
18
+ exercisesPath: './',
19
+ webpackTemplate: null,
20
+ disableGrading: false,
21
+ disabledActions: [],
22
+ actions: [],
23
+ entries: {
24
+ html: 'index.html',
25
+ vanillajs: 'index.js',
26
+ react: 'app.jsx',
27
+ node: 'app.js',
28
+ python3: 'app.py',
29
+ java: 'app.java',
30
+ },
31
+ },
32
+ address: 'http://localhost',
33
+ currentExercise: null,
34
+ exercises: [],
35
+ };
@@ -0,0 +1,36 @@
1
+ import { IConfigObj } from "../../models/config";
2
+ import { IFile } from "../../models/file";
3
+ import { IExercise } from "../../models/exercise-obj";
4
+ export declare const exercise: (path: string, position: number, configObject: IConfigObj) => IExercise;
5
+ export declare const validateExerciseDirectoryName: (str: string) => boolean;
6
+ export declare const isCodable: (str: string) => boolean;
7
+ export declare const shouldBeVisible: (file: IFile) => boolean;
8
+ export declare const isDirectory: (source: string) => boolean;
9
+ export declare const detect: (configObject: IConfigObj | undefined, files: Array<string>) => {
10
+ language: string;
11
+ entry: string | undefined;
12
+ } | {
13
+ language: null;
14
+ entry: null;
15
+ } | undefined;
16
+ export declare const filterFiles: (files: Array<string>, basePath?: string) => {
17
+ path: string;
18
+ name: string;
19
+ hidden: boolean;
20
+ }[];
21
+ declare const _default: {
22
+ exercise: (path: string, position: number, configObject: IConfigObj) => IExercise;
23
+ detect: (configObject: IConfigObj | undefined, files: string[]) => {
24
+ language: string;
25
+ entry: string | undefined;
26
+ } | {
27
+ language: null;
28
+ entry: null;
29
+ } | undefined;
30
+ filterFiles: (files: string[], basePath?: string) => {
31
+ path: string;
32
+ name: string;
33
+ hidden: boolean;
34
+ }[];
35
+ };
36
+ export default _default;
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.filterFiles = exports.detect = exports.isDirectory = exports.shouldBeVisible = exports.isCodable = exports.validateExerciseDirectoryName = exports.exercise = void 0;
4
+ const p = require("path");
5
+ // import frontMatter from 'front-matter'
6
+ const fs = require("fs");
7
+ const console_1 = require("../../utils/console");
8
+ const allowed_files_1 = require("./allowed_files");
9
+ // eslint-disable-next-line
10
+ const frontMatter = require("front-matter");
11
+ exports.exercise = (path, position, configObject) => {
12
+ const { config, exercises } = configObject;
13
+ let slug = p.basename(path);
14
+ if (!exports.validateExerciseDirectoryName(slug)) {
15
+ console_1.default.error(`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`);
16
+ }
17
+ // get all the files
18
+ const files = fs.readdirSync(path);
19
+ /**
20
+ * build the translation array like:
21
+ {
22
+ "us": "path/to/Readme.md",
23
+ "es": "path/to/Readme.es.md"
24
+ }
25
+ */
26
+ const translations = {};
27
+ for (const file of files.filter(file => file.toLowerCase().includes("readme"))) {
28
+ const parts = file.split(".");
29
+ if (parts.length === 3)
30
+ translations[parts[1]] = file;
31
+ else
32
+ translations.us = file;
33
+ }
34
+ // if the slug is a dot, it means there is not "exercises" folder, and its just a single README.md
35
+ if (slug === ".")
36
+ slug = "default-index";
37
+ const detected = exports.detect(configObject, files);
38
+ const exerciseObj = {
39
+ position,
40
+ path,
41
+ slug,
42
+ translations,
43
+ language: detected === null || detected === void 0 ? void 0 : detected.language,
44
+ entry: (detected === null || detected === void 0 ? void 0 : detected.entry) ? path + "/" + detected.entry : null,
45
+ title: slug || "Exercise",
46
+ graded: files.some(file => file.toLowerCase().startsWith("test.") ||
47
+ file.toLowerCase().startsWith("tests.")),
48
+ files: exports.filterFiles(files, path),
49
+ // if the exercises was on the config before I may keep the status done
50
+ done: Array.isArray(exercises) &&
51
+ typeof exercises[position] !== "undefined" &&
52
+ path.slice(Math.max(0, path.indexOf("exercises/") + 10)) ===
53
+ exercises[position].slug ?
54
+ exercises[position].done :
55
+ false,
56
+ getReadme: function (lang = null) {
57
+ if (lang === "us")
58
+ lang = null; // <-- english is default, no need to append it to the file name
59
+ if (!fs.existsSync(`${this.path}/README${lang ? "." + lang : ""}.md`)) {
60
+ console_1.default.error(`Language ${lang} not found for exercise ${slug}, switching to default language`);
61
+ if (lang)
62
+ lang = null;
63
+ if (!fs.existsSync(`${this.path}/README${lang ? "." + lang : ""}.md`))
64
+ throw new Error("Readme file not found for exercise: " + this.path + "/README.md");
65
+ }
66
+ const content = fs.readFileSync(`${this.path}/README${lang ? "." + lang : ""}.md`, "utf8");
67
+ const attr = frontMatter(content);
68
+ return attr;
69
+ },
70
+ getFile: function (name) {
71
+ const file = this.files.find((f) => f.name === name);
72
+ if (!file || !fs.existsSync(file.path)) {
73
+ throw new Error(`File not found: + ${file === null || file === void 0 ? void 0 : file.path}`);
74
+ }
75
+ else if (fs.lstatSync(file.path).isDirectory()) {
76
+ return ("Error: This is not a file to be read, but a directory: " + file.path);
77
+ }
78
+ // get file content
79
+ const content = fs.readFileSync(file.path);
80
+ // create reset folder
81
+ if (!fs.existsSync(`${config === null || config === void 0 ? void 0 : config.dirPath}/resets`))
82
+ fs.mkdirSync(`${config === null || config === void 0 ? void 0 : config.dirPath}/resets`);
83
+ if (!fs.existsSync(`${config === null || config === void 0 ? void 0 : config.dirPath}/resets/` + this.slug)) {
84
+ fs.mkdirSync(`${config === null || config === void 0 ? void 0 : config.dirPath}/resets/` + this.slug);
85
+ if (!fs.existsSync(`${config === null || config === void 0 ? void 0 : config.dirPath}/resets/${this.slug}/${name}`)) {
86
+ fs.writeFileSync(`${config === null || config === void 0 ? void 0 : config.dirPath}/resets/${this.slug}/${name}`, content);
87
+ }
88
+ }
89
+ return content;
90
+ },
91
+ saveFile: function (name, content) {
92
+ const file = this.files.find((f) => f.name === name);
93
+ if (file) {
94
+ if (!fs.existsSync(file.path)) {
95
+ throw new Error("File not found: " + file.path);
96
+ }
97
+ return fs.writeFileSync(file.path, content, "utf8");
98
+ }
99
+ },
100
+ getTestReport: function () {
101
+ var _a;
102
+ const _path = `${(_a = configObject === null || configObject === void 0 ? void 0 : configObject.confPath) === null || _a === void 0 ? void 0 : _a.base}/reports/${this.slug}.json`;
103
+ if (!fs.existsSync(_path))
104
+ return {};
105
+ const content = fs.readFileSync(_path);
106
+ const data = JSON.parse(`${content}`);
107
+ return data;
108
+ },
109
+ };
110
+ return exerciseObj;
111
+ };
112
+ exports.validateExerciseDirectoryName = (str) => {
113
+ if (str === "./")
114
+ return true;
115
+ // TODO: Add nameValidationREgex from the config
116
+ const regex = /^(\d{2,3}(\.\d{1,2})?-([\dA-Za-z]+(-|_)?)+)$/;
117
+ return regex.test(str);
118
+ };
119
+ exports.isCodable = (str) => {
120
+ const extension = p.extname(str);
121
+ return allowed_files_1.default.extensions.includes(extension.slice(1).toLowerCase());
122
+ };
123
+ const isNotConfiguration = (str) => {
124
+ return !allowed_files_1.default.names.includes(str);
125
+ };
126
+ exports.shouldBeVisible = function (file) {
127
+ return (
128
+ // doest not have "test." on their name
129
+ !file.name.toLocaleLowerCase().includes("test.") &&
130
+ !file.name.toLocaleLowerCase().includes("tests.") &&
131
+ !file.name.toLocaleLowerCase().includes(".hide.") &&
132
+ // ignore hidden files
133
+ file.name.charAt(0) !== "." &&
134
+ // ignore learn.json and bc.json
135
+ !file.name.toLocaleLowerCase().includes("learn.json") &&
136
+ !file.name.toLocaleLowerCase().includes("bc.json") &&
137
+ // ignore images, videos, vectors, etc.
138
+ exports.isCodable(file.name) &&
139
+ isNotConfiguration(file.name) &&
140
+ // readme's and directories
141
+ !file.name.toLowerCase().includes("readme.") &&
142
+ !exports.isDirectory(file.path) &&
143
+ file.name.charAt(0) !== "_");
144
+ };
145
+ exports.isDirectory = (source) => {
146
+ // if(path.basename(source) === path.basename(config.dirPath)) return false
147
+ return fs.lstatSync(source).isDirectory();
148
+ };
149
+ exports.detect = (configObject, files) => {
150
+ if (!configObject) {
151
+ return;
152
+ }
153
+ const { config } = configObject;
154
+ if (!config)
155
+ throw new Error("No configuration found during the engine detection");
156
+ if (!config.entries)
157
+ throw new Error("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.");
158
+ // 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.)
159
+ let hasFiles = files.filter(f => f.includes(".py"));
160
+ if (hasFiles.length > 0)
161
+ return {
162
+ language: "python3",
163
+ entry: hasFiles.find(f => config.entries.python3 === f),
164
+ };
165
+ hasFiles = files.filter(f => f.includes(".java"));
166
+ if (hasFiles.length > 0)
167
+ return {
168
+ language: "java",
169
+ entry: hasFiles.find(f => config.entries.java === f),
170
+ };
171
+ hasFiles = files.filter(f => f.includes(".jsx"));
172
+ if (hasFiles.length > 0)
173
+ return {
174
+ language: "react",
175
+ entry: hasFiles.find(f => config.entries.react === f),
176
+ };
177
+ const hasHTML = files.filter(f => f.includes("index.html"));
178
+ const hasIndexJS = files.find(f => f.includes("index.js"));
179
+ const hasJS = files.filter(f => f.includes(".js"));
180
+ // angular, vue, vanillajs needs to have at least 2 files (html,css,js),
181
+ // the test.js and the entry file in js
182
+ // if not its just another HTML
183
+ if (hasIndexJS && hasHTML.length > 0)
184
+ return {
185
+ language: "vanillajs",
186
+ entry: hasIndexJS,
187
+ };
188
+ if (hasHTML.length > 0)
189
+ return {
190
+ language: "html",
191
+ entry: hasHTML.find(f => config.entries.html === f),
192
+ };
193
+ if (hasJS.length > 0)
194
+ return {
195
+ language: "node",
196
+ entry: hasJS.find(f => config.entries.node === f),
197
+ };
198
+ return {
199
+ language: null,
200
+ entry: null,
201
+ };
202
+ };
203
+ exports.filterFiles = (files, basePath = ".") => files
204
+ .map((ex) => ({
205
+ path: basePath + "/" + ex,
206
+ name: ex,
207
+ hidden: !exports.shouldBeVisible({
208
+ name: ex,
209
+ path: basePath + "/" + ex,
210
+ }),
211
+ }))
212
+ .sort((f1, f2) => {
213
+ const score = {
214
+ // sorting priority
215
+ "index.html": 1,
216
+ "styles.css": 2,
217
+ "styles.scss": 2,
218
+ "style.css": 2,
219
+ "style.scss": 2,
220
+ "index.css": 2,
221
+ "index.scss": 2,
222
+ "index.js": 3,
223
+ };
224
+ return score[f1.name] < score[f2.name] ? -1 : 1;
225
+ });
226
+ exports.default = {
227
+ exercise: exports.exercise,
228
+ detect: exports.detect,
229
+ filterFiles: exports.filterFiles,
230
+ };
@@ -0,0 +1,3 @@
1
+ import { IConfigManagerAttributes, IConfigManager } from "../../models/config-manager";
2
+ declare const _default: ({ grading, mode, disableGrading, version, }: IConfigManagerAttributes) => Promise<IConfigManager>;
3
+ export default _default;
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const path = require("path");
4
+ const fs = require("fs");
5
+ const shell = require("shelljs");
6
+ const console_1 = require("../../utils/console");
7
+ const watcher_1 = require("../../utils/watcher");
8
+ const errors_1 = require("../../utils/errors");
9
+ const defaults_1 = require("./defaults");
10
+ const exercise_1 = require("./exercise");
11
+ const file_1 = require("../file");
12
+ /* exercise folder name standard */
13
+ // eslint-disable-next-line
14
+ const fetch = require("node-fetch");
15
+ // eslint-disable-next-line
16
+ const chalk = require("chalk");
17
+ /* exercise folder name standard */
18
+ const getConfigPath = () => {
19
+ const possibleFileNames = [
20
+ "learn.json",
21
+ ".learn/learn.json",
22
+ "bc.json",
23
+ ".breathecode/bc.json",
24
+ ];
25
+ const config = possibleFileNames.find(file => fs.existsSync(file)) || null;
26
+ if (config && fs.existsSync(".breathecode"))
27
+ return { config, base: ".breathecode" };
28
+ if (config === null)
29
+ throw errors_1.NotFoundError("learn.json file not found on current folder, is this a learnpack package?");
30
+ return { config, base: ".learn" };
31
+ };
32
+ const getExercisesPath = (base) => {
33
+ const possibleFileNames = ["./exercises", base + "/exercises", "./"];
34
+ return possibleFileNames.find(file => fs.existsSync(file)) || null;
35
+ };
36
+ const getGitpodAddress = () => {
37
+ if (shell.exec("gp -h", { silent: true }).code === 0) {
38
+ return shell
39
+ .exec("gp url", { silent: true })
40
+ .stdout.replace(/(\r\n|\n|\r)/gm, "");
41
+ }
42
+ console_1.default.debug("Gitpod command line tool not found");
43
+ return "http://localhost";
44
+ };
45
+ exports.default = async ({ grading, mode, disableGrading, version, }) => {
46
+ var _a, _b;
47
+ const confPath = getConfigPath();
48
+ console_1.default.debug("This is the config path: ", confPath);
49
+ let configObj = {};
50
+ if (confPath) {
51
+ const bcContent = fs.readFileSync(confPath.config);
52
+ let hiddenBcContent = {};
53
+ if (fs.existsSync(confPath.base + "/config.json")) {
54
+ hiddenBcContent = fs.readFileSync(confPath.base + "/config.json");
55
+ hiddenBcContent = JSON.parse(hiddenBcContent);
56
+ if (!hiddenBcContent)
57
+ throw new Error(`Invalid ${confPath.base}/config.json syntax: Unable to parse.`);
58
+ }
59
+ const jsonConfig = JSON.parse(`${bcContent}`);
60
+ if (!jsonConfig)
61
+ throw new Error(`Invalid ${confPath.config} syntax: Unable to parse.`);
62
+ let session;
63
+ // add using id to the installation
64
+ if (!jsonConfig.session) {
65
+ session = Math.floor(Math.random() * 10000000000000000000);
66
+ }
67
+ else {
68
+ session = jsonConfig.session;
69
+ delete jsonConfig.session;
70
+ }
71
+ configObj = deepMerge(hiddenBcContent, {
72
+ config: jsonConfig,
73
+ session: session,
74
+ });
75
+ console_1.default.debug("Content from the configuration .json ", configObj);
76
+ }
77
+ else {
78
+ throw errors_1.ValidationError("No learn.json file has been found, make sure you are in the folder");
79
+ }
80
+ configObj = deepMerge(defaults_1.default || {}, configObj, {
81
+ config: {
82
+ grading: grading || ((_a = configObj.config) === null || _a === void 0 ? void 0 : _a.grading),
83
+ configPath: confPath.config,
84
+ },
85
+ });
86
+ if (configObj.config) {
87
+ configObj.config.outputPath = confPath.base + "/dist";
88
+ }
89
+ console_1.default.debug("This is your configuration object: ", Object.assign(Object.assign({}, configObj), { exercises: configObj.exercises ?
90
+ configObj.exercises.map(e => e.slug) :
91
+ [] }));
92
+ // auto detect agent (if possible)
93
+ if (shell.which("gp") && configObj && configObj.config) {
94
+ configObj.config.editor.agent = "gitpod";
95
+ configObj.address = getGitpodAddress();
96
+ configObj.config.publicUrl = `https://${configObj.config.port}-${(_b = configObj.address) === null || _b === void 0 ? void 0 : _b.slice(8)}`;
97
+ }
98
+ else if (configObj.config && !configObj.config.editor.agent) {
99
+ configObj.config.editor.agent = "localhost";
100
+ }
101
+ if (configObj.config && !configObj.config.publicUrl)
102
+ configObj.config.publicUrl = `${configObj.address}:${configObj.config.port}`;
103
+ // Assign default editor mode if not set already
104
+ if (configObj.config && mode !== null) {
105
+ configObj.config.editor.mode = mode || "vscode";
106
+ }
107
+ if (configObj.config && !configObj.config.editor.mode)
108
+ configObj.config.editor.mode =
109
+ configObj.config.editor.agent === "localhost" ? "standalone" : "preview";
110
+ if (version && configObj.config)
111
+ configObj.config.editor.version = version;
112
+ else if (configObj.config && configObj.config.editor.version === null) {
113
+ console_1.default.debug("Config version not found, downloading default.");
114
+ const resp = await fetch("https://raw.githubusercontent.com/learnpack/coding-ide/learnpack/package.json");
115
+ const packageJSON = await resp.json();
116
+ configObj.config.editor.version = packageJSON.version || "1.0.72";
117
+ }
118
+ if (configObj.config) {
119
+ configObj.config.dirPath = "./" + confPath.base;
120
+ configObj.config.exercisesPath = getExercisesPath(confPath.base) || "./";
121
+ }
122
+ return {
123
+ validLanguages: {},
124
+ get: () => configObj,
125
+ validateEngine: function (language, server, socket) {
126
+ // eslint-disable-next-line
127
+ const alias = (_l) => {
128
+ const map = {
129
+ python3: "python",
130
+ };
131
+ if (map[_l])
132
+ return map[_l];
133
+ return _l;
134
+ };
135
+ // decode aliases
136
+ language = alias(language);
137
+ if (this.validLanguages[language])
138
+ return true;
139
+ console_1.default.debug(`Validating engine for ${language} compilation`);
140
+ let result = shell.exec("learnpack plugins", { silent: true });
141
+ if (result.code === 0 &&
142
+ result.stdout.includes(`learnpack-${language}`)) {
143
+ this.validLanguages[language] = true;
144
+ return true;
145
+ }
146
+ console_1.default.info(`Language engine for ${language} not found, installing...`);
147
+ result = shell.exec(`learnpack plugins:install learnpack-${language}`, {
148
+ silent: true,
149
+ });
150
+ if (result.code === 0) {
151
+ socket.log("compiling", "Installing the python compiler, you will have to reset the exercises after installation by writing on your terminal: $ learnpack run");
152
+ console_1.default.info(`Successfully installed the ${language} exercise engine, \n please start learnpack again by running the following command: \n ${chalk.white("$ learnpack start")}\n\n `);
153
+ server.terminate();
154
+ return false;
155
+ }
156
+ this.validLanguages[language] = false;
157
+ socket.error(`Error installing ${language} exercise engine`);
158
+ console_1.default.error(`Error installing ${language} exercise engine`);
159
+ console_1.default.log(result.stdout);
160
+ throw errors_1.InternalError(`Error installing ${language} exercise engine`);
161
+ },
162
+ clean: () => {
163
+ if (configObj.config) {
164
+ if (configObj.config.outputPath) {
165
+ file_1.rmSync(configObj.config.outputPath);
166
+ }
167
+ file_1.rmSync(configObj.config.dirPath + "/_app");
168
+ file_1.rmSync(configObj.config.dirPath + "/reports");
169
+ file_1.rmSync(configObj.config.dirPath + "/.session");
170
+ file_1.rmSync(configObj.config.dirPath + "/resets");
171
+ // clean tag gz
172
+ if (fs.existsSync(configObj.config.dirPath + "/app.tar.gz"))
173
+ fs.unlinkSync(configObj.config.dirPath + "/app.tar.gz");
174
+ if (fs.existsSync(configObj.config.dirPath + "/config.json"))
175
+ fs.unlinkSync(configObj.config.dirPath + "/config.json");
176
+ if (fs.existsSync(configObj.config.dirPath + "/vscode_queue.json"))
177
+ fs.unlinkSync(configObj.config.dirPath + "/vscode_queue.json");
178
+ }
179
+ },
180
+ getExercise: slug => {
181
+ console_1.default.debug("ExercisePath Slug", slug);
182
+ const exercise = (configObj.exercises || []).find(ex => ex.slug === slug);
183
+ if (!exercise)
184
+ throw errors_1.ValidationError(`Exercise ${slug} not found`);
185
+ return exercise;
186
+ },
187
+ getAllExercises: () => {
188
+ return configObj.exercises;
189
+ },
190
+ startExercise: function (slug) {
191
+ const exercise = this.getExercise(slug);
192
+ // set config.json with current exercise
193
+ configObj.currentExercise = exercise.slug;
194
+ this.save();
195
+ // eslint-disable-next-line
196
+ exercise.files.forEach((f) => {
197
+ if (configObj.config) {
198
+ const _path = configObj.config.outputPath + "/" + f.name;
199
+ if (f.hidden === false && fs.existsSync(_path))
200
+ fs.unlinkSync(_path);
201
+ }
202
+ });
203
+ return exercise;
204
+ },
205
+ noCurrentExercise: function () {
206
+ configObj.currentExercise = null;
207
+ this.save();
208
+ },
209
+ reset: slug => {
210
+ var _a;
211
+ if (configObj.config &&
212
+ !fs.existsSync(`${configObj.config.dirPath}/resets/` + slug))
213
+ throw errors_1.ValidationError("Could not find the original files for " + slug);
214
+ const exercise = (configObj.exercises || []).find(ex => ex.slug === slug);
215
+ if (!exercise)
216
+ throw errors_1.ValidationError(`Exercise ${slug} not found on the configuration`);
217
+ if (configObj.config) {
218
+ for (const fileName of fs.readdirSync(`${configObj.config.dirPath}/resets/${slug}/`)) {
219
+ const content = fs.readFileSync(`${(_a = configObj.config) === null || _a === void 0 ? void 0 : _a.dirPath}/resets/${slug}/${fileName}`);
220
+ fs.writeFileSync(`${exercise.path}/${fileName}`, content);
221
+ }
222
+ }
223
+ },
224
+ buildIndex: function () {
225
+ var _a, _b;
226
+ console_1.default.info("Building the exercise index...");
227
+ const isDirectory = (source) => {
228
+ var _a;
229
+ const name = path.basename(source);
230
+ if (name === path.basename(((_a = configObj === null || configObj === void 0 ? void 0 : configObj.config) === null || _a === void 0 ? void 0 : _a.dirPath) || ""))
231
+ return false;
232
+ // ignore folders that start with a dot
233
+ if (name.charAt(0) === "." || name.charAt(0) === "_")
234
+ return false;
235
+ return fs.lstatSync(source).isDirectory();
236
+ };
237
+ const getDirectories = (source) => fs
238
+ .readdirSync(source)
239
+ .map(name => path.join(source, name))
240
+ .filter(isDirectory);
241
+ // add the .learn folder
242
+ if (!fs.existsSync(confPath.base))
243
+ fs.mkdirSync(confPath.base);
244
+ // add the outout folder where webpack will publish the the html/css/js files
245
+ if (configObj.config &&
246
+ configObj.config.outputPath &&
247
+ !fs.existsSync(configObj.config.outputPath))
248
+ fs.mkdirSync(configObj.config.outputPath);
249
+ // TODO: we could use npm library front-mater to read the title of the exercises from the README.md
250
+ const grupedByDirectory = getDirectories(((_a = configObj === null || configObj === void 0 ? void 0 : configObj.config) === null || _a === void 0 ? void 0 : _a.exercisesPath) || "");
251
+ configObj.exercises =
252
+ grupedByDirectory.length > 0 ?
253
+ grupedByDirectory.map((path, position) => exercise_1.exercise(path, position, configObj)) :
254
+ [exercise_1.exercise(((_b = configObj === null || configObj === void 0 ? void 0 : configObj.config) === null || _b === void 0 ? void 0 : _b.exercisesPath) || "", 0, configObj)];
255
+ this.save();
256
+ },
257
+ watchIndex: function (onChange) {
258
+ var _a;
259
+ if (configObj.config && !configObj.config.exercisesPath)
260
+ throw errors_1.ValidationError("No exercises directory to watch: " + configObj.config.exercisesPath);
261
+ this.buildIndex();
262
+ watcher_1.default(((_a = configObj === null || configObj === void 0 ? void 0 : configObj.config) === null || _a === void 0 ? void 0 : _a.exercisesPath) || "")
263
+ .then(( /* eventname, filename */) => {
264
+ console_1.default.debug("Changes detected on your exercises");
265
+ this.buildIndex();
266
+ if (onChange)
267
+ onChange();
268
+ })
269
+ .catch(error => {
270
+ throw error;
271
+ });
272
+ },
273
+ save: () => {
274
+ console_1.default.debug("Saving configuration with: ", configObj);
275
+ // remove the duplicates form the actions array
276
+ // configObj.config.actions = [...new Set(configObj.config.actions)];
277
+ if (configObj.config) {
278
+ configObj.config.translations = [
279
+ ...new Set(configObj.config.translations),
280
+ ];
281
+ fs.writeFileSync(configObj.config.dirPath + "/config.json", JSON.stringify(configObj, null, 4));
282
+ }
283
+ },
284
+ };
285
+ };
286
+ function deepMerge(...sources) {
287
+ let acc = {};
288
+ for (const source of sources) {
289
+ if (Array.isArray(source)) {
290
+ if (!Array.isArray(acc)) {
291
+ acc = [];
292
+ }
293
+ acc = [...source];
294
+ }
295
+ else if (source instanceof Object) {
296
+ // eslint-disable-next-line
297
+ for (let [key, value] of Object.entries(source)) {
298
+ if (value instanceof Object && key in acc) {
299
+ value = deepMerge(acc[key], value);
300
+ }
301
+ if (value !== undefined)
302
+ acc = Object.assign(Object.assign({}, acc), { [key]: value });
303
+ }
304
+ }
305
+ }
306
+ return acc;
307
+ }
@@ -0,0 +1,13 @@
1
+ export declare const decompress: (sourcePath: string, destinationPath: string) => Promise<unknown>;
2
+ export declare const downloadEditor: (version: string | undefined, destination: string) => Promise<unknown>;
3
+ export declare const download: (url: string, dest: string) => Promise<unknown>;
4
+ export declare const clone: (repository?: string, folder?: string) => Promise<unknown>;
5
+ export declare const rmSync: (path: string) => void;
6
+ declare const _default: {
7
+ download: (url: string, dest: string) => Promise<unknown>;
8
+ decompress: (sourcePath: string, destinationPath: string) => Promise<unknown>;
9
+ downloadEditor: (version: string | undefined, destination: string) => Promise<unknown>;
10
+ clone: (repository?: string, folder?: string) => Promise<unknown>;
11
+ rmSync: (path: string) => void;
12
+ };
13
+ export default _default;