@learnpack/learnpack 5.0.37 → 5.0.39
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 +17 -13
- package/lib/commands/audit.js +34 -13
- package/lib/commands/init.js +10 -0
- package/lib/commands/publish.d.ts +3 -1
- package/lib/commands/publish.js +36 -7
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/audit.ts +36 -14
- package/src/commands/init.ts +13 -0
- package/src/commands/publish.ts +358 -312
- package/src/utils/templates/incremental/.github/workflows/learnpack-audit.yml +1 -1
- package/src/utils/templates/isolated/.github/workflows/learnpack-audit.yml +1 -1
package/README.md
CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @learnpack/learnpack
|
|
21
21
|
$ learnpack COMMAND
|
22
22
|
running command...
|
23
23
|
$ learnpack (-v|--version|version)
|
24
|
-
@learnpack/learnpack/5.0.
|
24
|
+
@learnpack/learnpack/5.0.39 win32-x64 node-v22.14.0
|
25
25
|
$ learnpack --help [COMMAND]
|
26
26
|
USAGE
|
27
27
|
$ learnpack COMMAND
|
@@ -58,6 +58,9 @@ learnpack audit is the command in charge of creating an auditory of the reposito
|
|
58
58
|
USAGE
|
59
59
|
$ learnpack audit
|
60
60
|
|
61
|
+
OPTIONS
|
62
|
+
-s, --strict strict mode
|
63
|
+
|
61
64
|
DESCRIPTION
|
62
65
|
...
|
63
66
|
learnpack audit checks for the following information in a repository:
|
@@ -76,7 +79,7 @@ DESCRIPTION
|
|
76
79
|
12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
|
77
80
|
```
|
78
81
|
|
79
|
-
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
82
|
+
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\audit.ts)_
|
80
83
|
|
81
84
|
## `learnpack breakToken`
|
82
85
|
|
@@ -91,7 +94,7 @@ OPTIONS
|
|
91
94
|
-y, --yes Skip all prompts and initialize an empty project
|
92
95
|
```
|
93
96
|
|
94
|
-
_See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
97
|
+
_See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\breakToken.ts)_
|
95
98
|
|
96
99
|
## `learnpack clean`
|
97
100
|
|
@@ -106,7 +109,7 @@ DESCRIPTION
|
|
106
109
|
Extra documentation goes here
|
107
110
|
```
|
108
111
|
|
109
|
-
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
112
|
+
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\clean.ts)_
|
110
113
|
|
111
114
|
## `learnpack download [PACKAGE]`
|
112
115
|
|
@@ -124,7 +127,7 @@ DESCRIPTION
|
|
124
127
|
Extra documentation goes here
|
125
128
|
```
|
126
129
|
|
127
|
-
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
130
|
+
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\download.ts)_
|
128
131
|
|
129
132
|
## `learnpack help [COMMAND]`
|
130
133
|
|
@@ -156,7 +159,7 @@ OPTIONS
|
|
156
159
|
-y, --yes Skip all prompts and initialize an empty project
|
157
160
|
```
|
158
161
|
|
159
|
-
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
162
|
+
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\init.ts)_
|
160
163
|
|
161
164
|
## `learnpack login [PACKAGE]`
|
162
165
|
|
@@ -174,7 +177,7 @@ DESCRIPTION
|
|
174
177
|
Extra documentation goes here
|
175
178
|
```
|
176
179
|
|
177
|
-
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
180
|
+
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\login.ts)_
|
178
181
|
|
179
182
|
## `learnpack logout [PACKAGE]`
|
180
183
|
|
@@ -192,7 +195,7 @@ DESCRIPTION
|
|
192
195
|
Extra documentation goes here
|
193
196
|
```
|
194
197
|
|
195
|
-
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
198
|
+
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\logout.ts)_
|
196
199
|
|
197
200
|
## `learnpack plugins`
|
198
201
|
|
@@ -320,10 +323,11 @@ USAGE
|
|
320
323
|
$ learnpack publish
|
321
324
|
|
322
325
|
OPTIONS
|
323
|
-
-h, --help
|
326
|
+
-h, --help show CLI help
|
327
|
+
-s, --strict strict mode
|
324
328
|
```
|
325
329
|
|
326
|
-
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
330
|
+
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\publish.ts)_
|
327
331
|
|
328
332
|
## `learnpack start`
|
329
333
|
|
@@ -345,7 +349,7 @@ OPTIONS
|
|
345
349
|
-y, --yes Skip all prompts and initialize an empty project
|
346
350
|
```
|
347
351
|
|
348
|
-
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
352
|
+
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\start.ts)_
|
349
353
|
|
350
354
|
## `learnpack test [EXERCISESLUG]`
|
351
355
|
|
@@ -362,7 +366,7 @@ OPTIONS
|
|
362
366
|
-y, --yes Skip all prompts and initialize an empty project
|
363
367
|
```
|
364
368
|
|
365
|
-
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
369
|
+
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\test.ts)_
|
366
370
|
|
367
371
|
## `learnpack translate`
|
368
372
|
|
@@ -376,7 +380,7 @@ OPTIONS
|
|
376
380
|
-y, --yes Skip all prompts and initialize an empty project
|
377
381
|
```
|
378
382
|
|
379
|
-
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
383
|
+
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.39/src\commands\translate.ts)_
|
380
384
|
<!-- commandsstop -->
|
381
385
|
|
382
386
|
> > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
|
package/lib/commands/audit.js
CHANGED
@@ -6,6 +6,7 @@ const console_1 = require("../utils/console");
|
|
6
6
|
const audit_1 = require("../utils/audit");
|
7
7
|
const SessionCommand_1 = require("../utils/SessionCommand");
|
8
8
|
const path = require("path");
|
9
|
+
const command_1 = require("@oclif/command");
|
9
10
|
// eslint-disable-next-line
|
10
11
|
const fetch = require("node-fetch");
|
11
12
|
class AuditCommand extends SessionCommand_1.default {
|
@@ -15,6 +16,8 @@ class AuditCommand extends SessionCommand_1.default {
|
|
15
16
|
}
|
16
17
|
async run() {
|
17
18
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
19
|
+
const { flags } = this.parse(AuditCommand);
|
20
|
+
const strict = flags.strict;
|
18
21
|
console_1.default.log("Running command audit...");
|
19
22
|
// Get configuration object.
|
20
23
|
let config = (_a = this.configManager) === null || _a === void 0 ? void 0 : _a.get();
|
@@ -87,12 +90,19 @@ class AuditCommand extends SessionCommand_1.default {
|
|
87
90
|
msg: "The difficulty property is not in the configuration object",
|
88
91
|
});
|
89
92
|
// check if the bugs_link property is in the configuration object
|
90
|
-
if (!((_h = config.config) === null || _h === void 0 ? void 0 : _h.bugsLink))
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
if (!((_h = config.config) === null || _h === void 0 ? void 0 : _h.bugsLink)) {
|
94
|
+
if (strict)
|
95
|
+
errors.push({
|
96
|
+
exercise: undefined,
|
97
|
+
msg: "The bugsLink property is not in the configuration object",
|
98
|
+
});
|
99
|
+
else
|
100
|
+
warnings.push({
|
101
|
+
exercise: undefined,
|
102
|
+
msg: "The bugsLink property is not in the configuration object",
|
103
|
+
});
|
104
|
+
}
|
105
|
+
// check if the video_solutions property is in the configuration object
|
96
106
|
if (((_j = config.config) === null || _j === void 0 ? void 0 : _j.videoSolutions) === undefined)
|
97
107
|
warnings.push({
|
98
108
|
exercise: undefined,
|
@@ -100,11 +110,18 @@ class AuditCommand extends SessionCommand_1.default {
|
|
100
110
|
});
|
101
111
|
// These two lines check if the 'repository' property is inside the configuration object.
|
102
112
|
console_1.default.debug("Checking if the repository property is inside the configuration object...");
|
103
|
-
if (!((_k = config.config) === null || _k === void 0 ? void 0 : _k.repository))
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
113
|
+
if (!((_k = config.config) === null || _k === void 0 ? void 0 : _k.repository)) {
|
114
|
+
if (strict)
|
115
|
+
errors.push({
|
116
|
+
exercise: undefined,
|
117
|
+
msg: "The repository property is not in the configuration object",
|
118
|
+
});
|
119
|
+
else
|
120
|
+
warnings.push({
|
121
|
+
exercise: undefined,
|
122
|
+
msg: "The repository property is not in the configuration object",
|
123
|
+
});
|
124
|
+
}
|
108
125
|
else
|
109
126
|
audit_1.default.isUrl((_l = config.config) === null || _l === void 0 ? void 0 : _l.repository, errors, counter);
|
110
127
|
// These two lines check if the 'description' property is inside the configuration object.
|
@@ -117,7 +134,7 @@ class AuditCommand extends SessionCommand_1.default {
|
|
117
134
|
if (!((_o = config.config) === null || _o === void 0 ? void 0 : _o.projectType))
|
118
135
|
errors.push({
|
119
136
|
exercise: undefined,
|
120
|
-
msg: "The projectType property mandatory in the configuration object",
|
137
|
+
msg: "The projectType property is mandatory in the configuration object",
|
121
138
|
});
|
122
139
|
if (errors.length === 0)
|
123
140
|
console_1.default.log("The config file is ok");
|
@@ -346,6 +363,10 @@ learnpack audit checks for the following information in a repository:
|
|
346
363
|
12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
|
347
364
|
`;
|
348
365
|
AuditCommand.flags = {
|
349
|
-
|
366
|
+
strict: command_1.flags.boolean({
|
367
|
+
char: "s",
|
368
|
+
description: "strict mode",
|
369
|
+
default: false,
|
370
|
+
}),
|
350
371
|
};
|
351
372
|
exports.default = AuditCommand;
|
package/lib/commands/init.js
CHANGED
@@ -25,6 +25,7 @@ const PARAMS = {
|
|
25
25
|
max_fkgl: 8,
|
26
26
|
max_words: 200,
|
27
27
|
max_rewrite_attempts: 3,
|
28
|
+
max_title_length: 25,
|
28
29
|
};
|
29
30
|
const whichTargetAudience = async () => {
|
30
31
|
const res = await prompts([
|
@@ -133,6 +134,9 @@ const initializeInteractiveCreation = async (rigoToken, courseInfo) => {
|
|
133
134
|
duration,
|
134
135
|
};
|
135
136
|
};
|
137
|
+
process.emitWarning = (warning) => {
|
138
|
+
console_1.default.debug("A Warning was emitted by Node.js: ", warning);
|
139
|
+
};
|
136
140
|
const appendContentIndex = async () => {
|
137
141
|
const choices = await prompts([
|
138
142
|
{
|
@@ -292,6 +296,12 @@ const getChoices = async (empty) => {
|
|
292
296
|
name: "title",
|
293
297
|
initial: "My Interactive Tutorial",
|
294
298
|
message: "Title for your tutorial? Press enter to leave as it is",
|
299
|
+
validate: (value) => {
|
300
|
+
if (value.length > PARAMS.max_title_length) {
|
301
|
+
return `Title must be less than ${PARAMS.max_title_length} characters`;
|
302
|
+
}
|
303
|
+
return true;
|
304
|
+
},
|
295
305
|
},
|
296
306
|
{
|
297
307
|
type: "text",
|
@@ -1,11 +1,13 @@
|
|
1
1
|
import SessionCommand from "../utils/SessionCommand";
|
2
|
-
|
2
|
+
declare class BuildCommand extends SessionCommand {
|
3
3
|
static description: string;
|
4
4
|
static flags: {
|
5
5
|
help: import("@oclif/parser/lib/flags").IBooleanFlag<void>;
|
6
|
+
strict: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
|
6
7
|
};
|
7
8
|
init(): Promise<void>;
|
8
9
|
run(): Promise<void>;
|
9
10
|
copyDirectory(src: string, dest: string): void;
|
10
11
|
removeDirectory(dir: string): void;
|
11
12
|
}
|
13
|
+
export default BuildCommand;
|
package/lib/commands/publish.js
CHANGED
@@ -15,14 +15,17 @@ const console_1 = require("../utils/console");
|
|
15
15
|
const file_1 = require("../managers/file");
|
16
16
|
const api_1 = require("../utils/api");
|
17
17
|
const prompts = require("prompts");
|
18
|
+
const rigoActions_1 = require("../utils/rigoActions");
|
18
19
|
const RIGOBOT_HOST = "https://rigobot.herokuapp.com";
|
19
20
|
// const RIGOBOT_HOST =
|
20
21
|
// "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
|
21
22
|
const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload";
|
22
|
-
const runAudit = () => {
|
23
|
+
const runAudit = (strict) => {
|
23
24
|
try {
|
24
25
|
console_1.default.info("Running learnpack audit before publishing...");
|
25
|
-
(0, child_process_1.execSync)(
|
26
|
+
(0, child_process_1.execSync)(`learnpack audit ${strict ? "--strict" : ""}`, {
|
27
|
+
stdio: "inherit",
|
28
|
+
});
|
26
29
|
}
|
27
30
|
catch (error) {
|
28
31
|
console_1.default.error("Failed to audit with learnpack");
|
@@ -67,13 +70,21 @@ class BuildCommand extends SessionCommand_1.default {
|
|
67
70
|
async run() {
|
68
71
|
var _a, _b, _c;
|
69
72
|
const buildDir = path.join(process.cwd(), "build");
|
73
|
+
const { flags } = this.parse(BuildCommand);
|
74
|
+
const strict = flags.strict;
|
75
|
+
console_1.default.debug("Strict mode: ", strict);
|
70
76
|
// this.configManager?.clean()
|
71
77
|
const configObject = (_a = this.configManager) === null || _a === void 0 ? void 0 : _a.get();
|
72
78
|
let sessionPayload = await session_1.default.getPayload();
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
79
|
+
const sessionExists = sessionPayload && sessionPayload.rigobot;
|
80
|
+
const isValidToken = sessionExists && sessionPayload.rigobot.key ?
|
81
|
+
await (0, rigoActions_1.isValidRigoToken)(sessionPayload.rigobot.key) :
|
82
|
+
false;
|
83
|
+
const isValidBreathecodeToken = sessionExists && sessionPayload.token ?
|
84
|
+
await api_1.default.validateToken(sessionPayload.token) :
|
85
|
+
false;
|
86
|
+
if (!sessionExists || !isValidBreathecodeToken || !isValidToken) {
|
87
|
+
console_1.default.info("Almost there! First you need to login with 4Geeks.com to use AI Generation tool for creators. You can create a new account here: https://4geeks.com/creators");
|
77
88
|
try {
|
78
89
|
sessionPayload = await session_1.default.login();
|
79
90
|
}
|
@@ -83,11 +94,16 @@ class BuildCommand extends SessionCommand_1.default {
|
|
83
94
|
}
|
84
95
|
}
|
85
96
|
const rigoToken = sessionPayload.rigobot.key;
|
97
|
+
const consumable = await (0, api_1.getConsumable)(sessionPayload.token, "learnpack-publish");
|
98
|
+
if (consumable.count === 0) {
|
99
|
+
console_1.default.error("It seems you cannot publish tutorials. Make sure you creator subscription is up to date here: https://4geeks.com/profile/subscriptions. If you believe there is an issue you can always contact support@4geeks.com");
|
100
|
+
process.exit(1);
|
101
|
+
}
|
86
102
|
if (configObject) {
|
87
103
|
console_1.default.info("Cleaning configuration files");
|
88
104
|
(_b = this.configManager) === null || _b === void 0 ? void 0 : _b.clean();
|
89
105
|
// build exerises
|
90
|
-
runAudit();
|
106
|
+
runAudit(strict);
|
91
107
|
console_1.default.debug("Building exercises");
|
92
108
|
(_c = this.configManager) === null || _c === void 0 ? void 0 : _c.buildIndex();
|
93
109
|
}
|
@@ -256,5 +272,18 @@ class BuildCommand extends SessionCommand_1.default {
|
|
256
272
|
BuildCommand.description = "Builds the project by copying necessary files and directories into a zip file";
|
257
273
|
BuildCommand.flags = {
|
258
274
|
help: command_1.flags.help({ char: "h" }),
|
275
|
+
strict: command_1.flags.boolean({
|
276
|
+
char: "s",
|
277
|
+
description: "strict mode",
|
278
|
+
default: false,
|
279
|
+
}),
|
280
|
+
};
|
281
|
+
BuildCommand.flags = {
|
282
|
+
strict: command_1.flags.boolean({
|
283
|
+
char: "s",
|
284
|
+
description: "strict mode",
|
285
|
+
default: false,
|
286
|
+
}),
|
287
|
+
help: command_1.flags.help({ char: "h" }),
|
259
288
|
};
|
260
289
|
exports.default = BuildCommand;
|
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"5.0.
|
1
|
+
{"version":"5.0.39","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false}},"args":[]},"breakToken":{"id":"breakToken","description":"Break the token","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false},"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[]}}}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@learnpack/learnpack",
|
3
3
|
"description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
|
4
|
-
"version": "5.0.
|
4
|
+
"version": "5.0.39",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
package/src/commands/audit.ts
CHANGED
@@ -10,6 +10,7 @@ import { IFrontmatter } from "../models/front-matter"
|
|
10
10
|
import { IAuditErrors } from "../models/audit"
|
11
11
|
import { ICounter } from "../models/counter"
|
12
12
|
import { IFindings } from "../models/findings"
|
13
|
+
import { flags } from "@oclif/command"
|
13
14
|
|
14
15
|
// eslint-disable-next-line
|
15
16
|
const fetch = require("node-fetch")
|
@@ -21,6 +22,9 @@ class AuditCommand extends SessionCommand {
|
|
21
22
|
}
|
22
23
|
|
23
24
|
async run() {
|
25
|
+
const { flags }: { flags: { strict: boolean } } = this.parse(AuditCommand)
|
26
|
+
|
27
|
+
const strict = flags.strict
|
24
28
|
Console.log("Running command audit...")
|
25
29
|
|
26
30
|
// Get configuration object.
|
@@ -114,12 +118,20 @@ class AuditCommand extends SessionCommand {
|
|
114
118
|
msg: "The difficulty property is not in the configuration object",
|
115
119
|
})
|
116
120
|
// check if the bugs_link property is in the configuration object
|
117
|
-
if (!config!.config?.bugsLink)
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
121
|
+
if (!config!.config?.bugsLink) {
|
122
|
+
if (strict)
|
123
|
+
errors.push({
|
124
|
+
exercise: undefined,
|
125
|
+
msg: "The bugsLink property is not in the configuration object",
|
126
|
+
})
|
127
|
+
else
|
128
|
+
warnings.push({
|
129
|
+
exercise: undefined,
|
130
|
+
msg: "The bugsLink property is not in the configuration object",
|
131
|
+
})
|
132
|
+
}
|
133
|
+
|
134
|
+
// check if the video_solutions property is in the configuration object
|
123
135
|
if (config!.config?.videoSolutions === undefined)
|
124
136
|
warnings.push({
|
125
137
|
exercise: undefined,
|
@@ -130,12 +142,18 @@ class AuditCommand extends SessionCommand {
|
|
130
142
|
Console.debug(
|
131
143
|
"Checking if the repository property is inside the configuration object..."
|
132
144
|
)
|
133
|
-
if (!config!.config?.repository)
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
145
|
+
if (!config!.config?.repository) {
|
146
|
+
if (strict)
|
147
|
+
errors.push({
|
148
|
+
exercise: undefined,
|
149
|
+
msg: "The repository property is not in the configuration object",
|
150
|
+
})
|
151
|
+
else
|
152
|
+
warnings.push({
|
153
|
+
exercise: undefined,
|
154
|
+
msg: "The repository property is not in the configuration object",
|
155
|
+
})
|
156
|
+
} else
|
139
157
|
Audit.isUrl(config!.config?.repository, errors, counter)
|
140
158
|
|
141
159
|
// These two lines check if the 'description' property is inside the configuration object.
|
@@ -150,7 +168,7 @@ Audit.isUrl(config!.config?.repository, errors, counter)
|
|
150
168
|
if (!config!.config?.projectType)
|
151
169
|
errors.push({
|
152
170
|
exercise: undefined,
|
153
|
-
msg: "The projectType property mandatory in the configuration object",
|
171
|
+
msg: "The projectType property is mandatory in the configuration object",
|
154
172
|
})
|
155
173
|
|
156
174
|
if (errors.length === 0)
|
@@ -443,7 +461,11 @@ learnpack audit checks for the following information in a repository:
|
|
443
461
|
`
|
444
462
|
|
445
463
|
AuditCommand.flags = {
|
446
|
-
|
464
|
+
strict: flags.boolean({
|
465
|
+
char: "s",
|
466
|
+
description: "strict mode",
|
467
|
+
default: false,
|
468
|
+
}),
|
447
469
|
}
|
448
470
|
|
449
471
|
export default AuditCommand
|
package/src/commands/init.ts
CHANGED
@@ -47,6 +47,7 @@ const PARAMS = {
|
|
47
47
|
max_fkgl: 8,
|
48
48
|
max_words: 200,
|
49
49
|
max_rewrite_attempts: 3,
|
50
|
+
max_title_length: 25,
|
50
51
|
}
|
51
52
|
|
52
53
|
const whichTargetAudience = async () => {
|
@@ -221,6 +222,10 @@ const initializeInteractiveCreation = async (
|
|
221
222
|
}
|
222
223
|
}
|
223
224
|
|
225
|
+
process.emitWarning = (warning: string) => {
|
226
|
+
Console.debug("A Warning was emitted by Node.js: ", warning)
|
227
|
+
}
|
228
|
+
|
224
229
|
const appendContentIndex = async () => {
|
225
230
|
const choices = await prompts([
|
226
231
|
{
|
@@ -425,9 +430,17 @@ const getChoices = async (empty: boolean) => {
|
|
425
430
|
},
|
426
431
|
{
|
427
432
|
type: "text",
|
433
|
+
|
428
434
|
name: "title",
|
429
435
|
initial: "My Interactive Tutorial",
|
430
436
|
message: "Title for your tutorial? Press enter to leave as it is",
|
437
|
+
validate: (value: string) => {
|
438
|
+
if (value.length > PARAMS.max_title_length) {
|
439
|
+
return `Title must be less than ${PARAMS.max_title_length} characters`
|
440
|
+
}
|
441
|
+
|
442
|
+
return true
|
443
|
+
},
|
431
444
|
},
|
432
445
|
{
|
433
446
|
type: "text",
|
package/src/commands/publish.ts
CHANGED
@@ -1,312 +1,358 @@
|
|
1
|
-
/* eslint-disable arrow-parens */
|
2
|
-
/* eslint-disable unicorn/no-array-for-each */
|
3
|
-
import { execSync } from "child_process"
|
4
|
-
import { flags } from "@oclif/command"
|
5
|
-
import SessionCommand from "../utils/SessionCommand"
|
6
|
-
import SessionManager from "../managers/session"
|
7
|
-
import * as fs from "fs"
|
8
|
-
import * as path from "path"
|
9
|
-
import * as archiver from "archiver"
|
10
|
-
import axios from "axios"
|
11
|
-
import FormData = require("form-data")
|
12
|
-
import Console from "../utils/console"
|
13
|
-
import {
|
14
|
-
decompress,
|
15
|
-
downloadEditor,
|
16
|
-
checkIfDirectoryExists,
|
17
|
-
} from "../managers/file"
|
18
|
-
import api, { TAcademy } from "../utils/api"
|
19
|
-
import * as prompts from "prompts"
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
}
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
static
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
const
|
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
|
-
const
|
131
|
-
|
132
|
-
const
|
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
|
-
const
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
fs.mkdirSync(
|
172
|
-
}
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
} else {
|
206
|
-
|
207
|
-
}
|
208
|
-
|
209
|
-
// Copy
|
210
|
-
const
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
this.
|
215
|
-
}
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
-
|
1
|
+
/* eslint-disable arrow-parens */
|
2
|
+
/* eslint-disable unicorn/no-array-for-each */
|
3
|
+
import { execSync } from "child_process"
|
4
|
+
import { flags } from "@oclif/command"
|
5
|
+
import SessionCommand from "../utils/SessionCommand"
|
6
|
+
import SessionManager from "../managers/session"
|
7
|
+
import * as fs from "fs"
|
8
|
+
import * as path from "path"
|
9
|
+
import * as archiver from "archiver"
|
10
|
+
import axios from "axios"
|
11
|
+
import FormData = require("form-data")
|
12
|
+
import Console from "../utils/console"
|
13
|
+
import {
|
14
|
+
decompress,
|
15
|
+
downloadEditor,
|
16
|
+
checkIfDirectoryExists,
|
17
|
+
} from "../managers/file"
|
18
|
+
import api, { getConsumable, TAcademy } from "../utils/api"
|
19
|
+
import * as prompts from "prompts"
|
20
|
+
import { isValidRigoToken } from "../utils/rigoActions"
|
21
|
+
|
22
|
+
const RIGOBOT_HOST = "https://rigobot.herokuapp.com"
|
23
|
+
// const RIGOBOT_HOST =
|
24
|
+
// "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
|
25
|
+
const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
|
26
|
+
|
27
|
+
const runAudit = (strict: boolean) => {
|
28
|
+
try {
|
29
|
+
Console.info("Running learnpack audit before publishing...")
|
30
|
+
execSync(`learnpack audit ${strict ? "--strict" : ""}`, {
|
31
|
+
stdio: "inherit",
|
32
|
+
})
|
33
|
+
} catch (error) {
|
34
|
+
Console.error("Failed to audit with learnpack")
|
35
|
+
|
36
|
+
// Si `execSync` lanza un error, capturamos el mensaje de error
|
37
|
+
if (error instanceof Error) {
|
38
|
+
Console.error(error.message)
|
39
|
+
} else {
|
40
|
+
Console.error("Unknown error occurred")
|
41
|
+
}
|
42
|
+
|
43
|
+
// Detener la ejecución del comando si `learnpack publish` falla
|
44
|
+
process.exit(1)
|
45
|
+
}
|
46
|
+
|
47
|
+
// Continuar con el proceso de build solo si `learnpack publish` fue exitoso
|
48
|
+
Console.info(
|
49
|
+
"Learnpack publish completed successfully. Proceeding with build..."
|
50
|
+
)
|
51
|
+
}
|
52
|
+
|
53
|
+
const selectAcademy = async (academies: TAcademy[]) => {
|
54
|
+
if (academies.length === 0) {
|
55
|
+
return null
|
56
|
+
}
|
57
|
+
|
58
|
+
if (academies.length === 1) {
|
59
|
+
return academies[0]
|
60
|
+
}
|
61
|
+
|
62
|
+
// prompts the user to select an academy to upload the assets
|
63
|
+
Console.info("In which academy do you want to publish the asset?")
|
64
|
+
const response = await prompts({
|
65
|
+
type: "select",
|
66
|
+
name: "academy",
|
67
|
+
message: "Select an academy",
|
68
|
+
choices: academies.map((academy) => ({
|
69
|
+
title: academy.name,
|
70
|
+
value: academy,
|
71
|
+
})),
|
72
|
+
})
|
73
|
+
return response.academy
|
74
|
+
}
|
75
|
+
|
76
|
+
class BuildCommand extends SessionCommand {
|
77
|
+
static description =
|
78
|
+
"Builds the project by copying necessary files and directories into a zip file"
|
79
|
+
|
80
|
+
static flags = {
|
81
|
+
help: flags.help({ char: "h" }),
|
82
|
+
strict: flags.boolean({
|
83
|
+
char: "s",
|
84
|
+
description: "strict mode",
|
85
|
+
default: false,
|
86
|
+
}),
|
87
|
+
}
|
88
|
+
|
89
|
+
async init() {
|
90
|
+
const { flags } = this.parse(BuildCommand)
|
91
|
+
await this.initSession(flags)
|
92
|
+
}
|
93
|
+
|
94
|
+
async run() {
|
95
|
+
const buildDir = path.join(process.cwd(), "build")
|
96
|
+
|
97
|
+
const { flags }: { flags: { strict: boolean } } = this.parse(BuildCommand)
|
98
|
+
const strict = flags.strict
|
99
|
+
Console.debug("Strict mode: ", strict)
|
100
|
+
// this.configManager?.clean()
|
101
|
+
|
102
|
+
const configObject = this.configManager?.get()
|
103
|
+
|
104
|
+
let sessionPayload = await SessionManager.getPayload()
|
105
|
+
|
106
|
+
const sessionExists = sessionPayload && sessionPayload.rigobot
|
107
|
+
|
108
|
+
const isValidToken =
|
109
|
+
sessionExists && sessionPayload.rigobot.key ?
|
110
|
+
await isValidRigoToken(sessionPayload.rigobot.key) :
|
111
|
+
false
|
112
|
+
|
113
|
+
const isValidBreathecodeToken =
|
114
|
+
sessionExists && sessionPayload.token ?
|
115
|
+
await api.validateToken(sessionPayload.token) :
|
116
|
+
false
|
117
|
+
|
118
|
+
if (!sessionExists || !isValidBreathecodeToken || !isValidToken) {
|
119
|
+
Console.info(
|
120
|
+
"Almost there! First you need to login with 4Geeks.com to use AI Generation tool for creators. You can create a new account here: https://4geeks.com/creators"
|
121
|
+
)
|
122
|
+
try {
|
123
|
+
sessionPayload = await SessionManager.login()
|
124
|
+
} catch (error) {
|
125
|
+
Console.error("Error trying to authenticate")
|
126
|
+
Console.error((error as TypeError).message || (error as string))
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
const rigoToken = sessionPayload.rigobot.key
|
131
|
+
|
132
|
+
const consumable = await getConsumable(
|
133
|
+
sessionPayload.token,
|
134
|
+
"learnpack-publish"
|
135
|
+
)
|
136
|
+
|
137
|
+
if (consumable.count === 0) {
|
138
|
+
Console.error(
|
139
|
+
"It seems you cannot publish tutorials. Make sure you creator subscription is up to date here: https://4geeks.com/profile/subscriptions. If you believe there is an issue you can always contact support@4geeks.com"
|
140
|
+
)
|
141
|
+
process.exit(1)
|
142
|
+
}
|
143
|
+
|
144
|
+
if (configObject) {
|
145
|
+
Console.info("Cleaning configuration files")
|
146
|
+
this.configManager?.clean()
|
147
|
+
// build exerises
|
148
|
+
runAudit(strict)
|
149
|
+
|
150
|
+
Console.debug("Building exercises")
|
151
|
+
this.configManager?.buildIndex()
|
152
|
+
}
|
153
|
+
|
154
|
+
// const academies = await api.listUserAcademies(sessionPayload.token)
|
155
|
+
// // console.log(academies, "academies")
|
156
|
+
// const academy = await selectAcademy(academies)
|
157
|
+
// console.log(academy, "academy")
|
158
|
+
|
159
|
+
// Read learn.json to get the slug
|
160
|
+
const learnJsonPath = path.join(process.cwd(), "learn.json")
|
161
|
+
if (!fs.existsSync(learnJsonPath)) {
|
162
|
+
this.error("learn.json not found")
|
163
|
+
}
|
164
|
+
|
165
|
+
const learnJson = JSON.parse(fs.readFileSync(learnJsonPath, "utf-8"))
|
166
|
+
|
167
|
+
const zipFilePath = path.join(process.cwd(), `${learnJson.slug}.zip`)
|
168
|
+
|
169
|
+
// Ensure build directory exists
|
170
|
+
if (!fs.existsSync(buildDir)) {
|
171
|
+
fs.mkdirSync(buildDir)
|
172
|
+
}
|
173
|
+
|
174
|
+
if (configObject) {
|
175
|
+
const { config } = configObject
|
176
|
+
const appAlreadyExists = checkIfDirectoryExists(`${config?.dirPath}/_app`)
|
177
|
+
|
178
|
+
if (!appAlreadyExists) {
|
179
|
+
// download app and decompress
|
180
|
+
await downloadEditor(
|
181
|
+
config?.editor.version,
|
182
|
+
`${config?.dirPath}/app.tar.gz`
|
183
|
+
)
|
184
|
+
|
185
|
+
Console.info("Decompressing LearnPack UI, this may take a minute...")
|
186
|
+
await decompress(
|
187
|
+
`${config?.dirPath}/app.tar.gz`,
|
188
|
+
`${config?.dirPath}/_app/`
|
189
|
+
)
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
// Copy config.json
|
194
|
+
const configPath = path.join(process.cwd(), ".learn", "config.json")
|
195
|
+
if (fs.existsSync(configPath)) {
|
196
|
+
fs.copyFileSync(configPath, path.join(buildDir, "config.json"))
|
197
|
+
} else {
|
198
|
+
this.error("config.json not found")
|
199
|
+
}
|
200
|
+
|
201
|
+
// Copy .learn/assets directory, if it exists else create it
|
202
|
+
const assetsDir = path.join(process.cwd(), ".learn", "assets")
|
203
|
+
if (fs.existsSync(assetsDir)) {
|
204
|
+
this.copyDirectory(assetsDir, path.join(buildDir, ".learn", "assets"))
|
205
|
+
} else {
|
206
|
+
fs.mkdirSync(path.join(buildDir, ".learn", "assets"), { recursive: true })
|
207
|
+
}
|
208
|
+
|
209
|
+
// Copy .learn/_app directory files to the same level as config.json
|
210
|
+
const appDir = path.join(process.cwd(), ".learn", "_app")
|
211
|
+
if (fs.existsSync(appDir)) {
|
212
|
+
this.copyDirectory(appDir, buildDir)
|
213
|
+
} else {
|
214
|
+
this.error(".learn/_app directory not found")
|
215
|
+
}
|
216
|
+
|
217
|
+
// After copying the _app directory
|
218
|
+
const indexHtmlPath = path.join(appDir, "index.html")
|
219
|
+
const buildIndexHtmlPath = path.join(buildDir, "index.html")
|
220
|
+
|
221
|
+
if (fs.existsSync(indexHtmlPath)) {
|
222
|
+
let indexHtmlContent = fs.readFileSync(indexHtmlPath, "utf-8")
|
223
|
+
|
224
|
+
const description = learnJson.description.us || "LearnPack is awesome!"
|
225
|
+
const title =
|
226
|
+
learnJson.title.us || "LearnPack: Interactive Learning as a Service"
|
227
|
+
|
228
|
+
const previewUrl =
|
229
|
+
learnJson.preview ||
|
230
|
+
"https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/public/learnpack.svg"
|
231
|
+
// Replace placeholders and the <title>Old title </title> tag for a new tag with the title
|
232
|
+
indexHtmlContent = indexHtmlContent
|
233
|
+
.replace(/{{description}}/g, description)
|
234
|
+
.replace(/<title>.*<\/title>/, `<title>${title}</title>`)
|
235
|
+
.replace(/{{title}}/g, title)
|
236
|
+
.replace(/{{preview}}/g, previewUrl)
|
237
|
+
|
238
|
+
// Write the modified content to the build directory
|
239
|
+
fs.writeFileSync(buildIndexHtmlPath, indexHtmlContent)
|
240
|
+
} else {
|
241
|
+
this.error("index.html not found in _app directory")
|
242
|
+
}
|
243
|
+
|
244
|
+
// Copy exercises directory
|
245
|
+
const exercisesDir = path.join(process.cwd(), "exercises")
|
246
|
+
const learnExercisesDir = path.join(process.cwd(), ".learn", "exercises")
|
247
|
+
|
248
|
+
if (fs.existsSync(exercisesDir)) {
|
249
|
+
this.copyDirectory(exercisesDir, path.join(buildDir, "exercises"))
|
250
|
+
} else if (fs.existsSync(learnExercisesDir)) {
|
251
|
+
this.copyDirectory(learnExercisesDir, path.join(buildDir, "exercises"))
|
252
|
+
} else {
|
253
|
+
this.error("exercises directory not found in either location")
|
254
|
+
}
|
255
|
+
|
256
|
+
// Copy learn.json
|
257
|
+
fs.copyFileSync(learnJsonPath, path.join(buildDir, "learn.json"))
|
258
|
+
|
259
|
+
// Create zip file
|
260
|
+
const output = fs.createWriteStream(zipFilePath)
|
261
|
+
const archive = archiver("zip", {
|
262
|
+
zlib: { level: 9 },
|
263
|
+
})
|
264
|
+
|
265
|
+
output.on("close", async () => {
|
266
|
+
this.log(
|
267
|
+
`Build completed: ${zipFilePath} (${archive.pointer()} total bytes)`
|
268
|
+
)
|
269
|
+
// Remove build directory after zip is created
|
270
|
+
|
271
|
+
Console.debug("Zip file saved in project root")
|
272
|
+
|
273
|
+
const formData = new FormData()
|
274
|
+
formData.append("file", fs.createReadStream(zipFilePath))
|
275
|
+
formData.append("config", JSON.stringify(learnJson))
|
276
|
+
|
277
|
+
try {
|
278
|
+
const res = await axios.post(uploadZipEndpont, formData, {
|
279
|
+
headers: {
|
280
|
+
...formData.getHeaders(),
|
281
|
+
Authorization: `Token ${rigoToken}`,
|
282
|
+
},
|
283
|
+
})
|
284
|
+
console.log(res.data)
|
285
|
+
// Remove the zip file after uploading
|
286
|
+
fs.unlinkSync(zipFilePath)
|
287
|
+
this.removeDirectory(buildDir)
|
288
|
+
} catch (error) {
|
289
|
+
if (axios.isAxiosError(error)) {
|
290
|
+
if (error.response && error.response.status === 403) {
|
291
|
+
console.error("Error 403:", error.response.data.error)
|
292
|
+
} else if (error.response && error.response.status === 400) {
|
293
|
+
console.error(error.response.data.error)
|
294
|
+
} else {
|
295
|
+
console.error("Error uploading file:", error)
|
296
|
+
}
|
297
|
+
} else {
|
298
|
+
console.error("Error uploading file:", error)
|
299
|
+
}
|
300
|
+
|
301
|
+
fs.unlinkSync(zipFilePath)
|
302
|
+
this.removeDirectory(buildDir)
|
303
|
+
}
|
304
|
+
})
|
305
|
+
|
306
|
+
archive.on("error", (err: any) => {
|
307
|
+
throw err
|
308
|
+
})
|
309
|
+
|
310
|
+
archive.pipe(output)
|
311
|
+
archive.directory(buildDir, false)
|
312
|
+
await archive.finalize()
|
313
|
+
}
|
314
|
+
|
315
|
+
copyDirectory(src: string, dest: string) {
|
316
|
+
if (!fs.existsSync(dest)) {
|
317
|
+
fs.mkdirSync(dest, { recursive: true })
|
318
|
+
}
|
319
|
+
|
320
|
+
const entries = fs.readdirSync(src, { withFileTypes: true })
|
321
|
+
|
322
|
+
for (const entry of entries) {
|
323
|
+
const srcPath = path.join(src, entry.name)
|
324
|
+
const destPath = path.join(dest, entry.name)
|
325
|
+
|
326
|
+
if (entry.isDirectory()) {
|
327
|
+
this.copyDirectory(srcPath, destPath)
|
328
|
+
} else {
|
329
|
+
fs.copyFileSync(srcPath, destPath)
|
330
|
+
}
|
331
|
+
}
|
332
|
+
}
|
333
|
+
|
334
|
+
removeDirectory(dir: string) {
|
335
|
+
if (fs.existsSync(dir)) {
|
336
|
+
fs.readdirSync(dir).forEach((file) => {
|
337
|
+
const currentPath = path.join(dir, file)
|
338
|
+
if (fs.lstatSync(currentPath).isDirectory()) {
|
339
|
+
this.removeDirectory(currentPath)
|
340
|
+
} else {
|
341
|
+
fs.unlinkSync(currentPath)
|
342
|
+
}
|
343
|
+
})
|
344
|
+
fs.rmdirSync(dir)
|
345
|
+
}
|
346
|
+
}
|
347
|
+
}
|
348
|
+
|
349
|
+
BuildCommand.flags = {
|
350
|
+
strict: flags.boolean({
|
351
|
+
char: "s",
|
352
|
+
description: "strict mode",
|
353
|
+
default: false,
|
354
|
+
}),
|
355
|
+
help: flags.help({ char: "h" }),
|
356
|
+
}
|
357
|
+
|
358
|
+
export default BuildCommand
|