@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 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.37 win32-x64 node-v22.14.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.37/src\commands\audit.ts)_
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.37/src\commands\breakToken.ts)_
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.37/src\commands\clean.ts)_
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.37/src\commands\download.ts)_
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.37/src\commands\init.ts)_
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.37/src\commands\login.ts)_
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.37/src\commands\logout.ts)_
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 show CLI 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.37/src\commands\publish.ts)_
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.37/src\commands\start.ts)_
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.37/src\commands\test.ts)_
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.37/src\commands\translate.ts)_
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
@@ -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
- errors.push({
92
- exercise: undefined,
93
- msg: "The bugsLink property is not in the configuration object",
94
- });
95
- // check if the video_solutions property is in the configuration object
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
- errors.push({
105
- exercise: undefined,
106
- msg: "The repository property is not in the configuration object",
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
- // name: flags.string({char: 'n', description: 'name to print'}),
366
+ strict: command_1.flags.boolean({
367
+ char: "s",
368
+ description: "strict mode",
369
+ default: false,
370
+ }),
350
371
  };
351
372
  exports.default = AuditCommand;
@@ -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
- export default class BuildCommand extends SessionCommand {
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;
@@ -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)("learnpack audit", { stdio: "inherit" });
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
- if (!sessionPayload ||
74
- !sessionPayload.rigobot ||
75
- (sessionPayload.token && !(await api_1.default.validateToken(sessionPayload.token)))) {
76
- console_1.default.error("You must be logged in to upload a LearnPack package");
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;
@@ -1 +1 @@
1
- {"version":"5.0.37","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":{},"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":{"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":[]}}}
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.37",
4
+ "version": "5.0.39",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -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
- errors.push({
119
- exercise: undefined,
120
- msg: "The bugsLink property is not in the configuration object",
121
- })
122
- // check if the video_solutions property is in the configuration object
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
- errors.push({
135
- exercise: undefined,
136
- msg: "The repository property is not in the configuration object",
137
- })
138
- else
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
- // name: flags.string({char: 'n', description: 'name to print'}),
464
+ strict: flags.boolean({
465
+ char: "s",
466
+ description: "strict mode",
467
+ default: false,
468
+ }),
447
469
  }
448
470
 
449
471
  export default AuditCommand
@@ -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",
@@ -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
- const RIGOBOT_HOST = "https://rigobot.herokuapp.com"
22
- // const RIGOBOT_HOST =
23
- // "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
24
- const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
25
-
26
- const runAudit = () => {
27
- try {
28
- Console.info("Running learnpack audit before publishing...")
29
- execSync("learnpack audit", { stdio: "inherit" })
30
- } catch (error) {
31
- Console.error("Failed to audit with learnpack")
32
-
33
- // Si `execSync` lanza un error, capturamos el mensaje de error
34
- if (error instanceof Error) {
35
- Console.error(error.message)
36
- } else {
37
- Console.error("Unknown error occurred")
38
- }
39
-
40
- // Detener la ejecución del comando si `learnpack publish` falla
41
- process.exit(1)
42
- }
43
-
44
- // Continuar con el proceso de build solo si `learnpack publish` fue exitoso
45
- Console.info(
46
- "Learnpack publish completed successfully. Proceeding with build..."
47
- )
48
- }
49
-
50
- const selectAcademy = async (academies: TAcademy[]) => {
51
- if (academies.length === 0) {
52
- return null
53
- }
54
-
55
- if (academies.length === 1) {
56
- return academies[0]
57
- }
58
-
59
- // prompts the user to select an academy to upload the assets
60
- Console.info("In which academy do you want to publish the asset?")
61
- const response = await prompts({
62
- type: "select",
63
- name: "academy",
64
- message: "Select an academy",
65
- choices: academies.map((academy) => ({
66
- title: academy.name,
67
- value: academy,
68
- })),
69
- })
70
- return response.academy
71
- }
72
-
73
- export default class BuildCommand extends SessionCommand {
74
- static description =
75
- "Builds the project by copying necessary files and directories into a zip file"
76
-
77
- static flags = {
78
- help: flags.help({ char: "h" }),
79
- }
80
-
81
- async init() {
82
- const { flags } = this.parse(BuildCommand)
83
- await this.initSession(flags)
84
- }
85
-
86
- async run() {
87
- const buildDir = path.join(process.cwd(), "build")
88
- // this.configManager?.clean()
89
-
90
- const configObject = this.configManager?.get()
91
-
92
- let sessionPayload = await SessionManager.getPayload()
93
- if (
94
- !sessionPayload ||
95
- !sessionPayload.rigobot ||
96
- (sessionPayload.token && !(await api.validateToken(sessionPayload.token)))
97
- ) {
98
- Console.error("You must be logged in to upload a LearnPack package")
99
- try {
100
- sessionPayload = await SessionManager.login()
101
- } catch (error) {
102
- Console.error("Error trying to authenticate")
103
- Console.error((error as TypeError).message || (error as string))
104
- }
105
- }
106
-
107
- const rigoToken = sessionPayload.rigobot.key
108
-
109
- if (configObject) {
110
- Console.info("Cleaning configuration files")
111
- this.configManager?.clean()
112
- // build exerises
113
- runAudit()
114
-
115
- Console.debug("Building exercises")
116
- this.configManager?.buildIndex()
117
- }
118
-
119
- // const academies = await api.listUserAcademies(sessionPayload.token)
120
- // // console.log(academies, "academies")
121
- // const academy = await selectAcademy(academies)
122
- // console.log(academy, "academy")
123
-
124
- // Read learn.json to get the slug
125
- const learnJsonPath = path.join(process.cwd(), "learn.json")
126
- if (!fs.existsSync(learnJsonPath)) {
127
- this.error("learn.json not found")
128
- }
129
-
130
- const learnJson = JSON.parse(fs.readFileSync(learnJsonPath, "utf-8"))
131
-
132
- const zipFilePath = path.join(process.cwd(), `${learnJson.slug}.zip`)
133
-
134
- // Ensure build directory exists
135
- if (!fs.existsSync(buildDir)) {
136
- fs.mkdirSync(buildDir)
137
- }
138
-
139
- if (configObject) {
140
- const { config } = configObject
141
- const appAlreadyExists = checkIfDirectoryExists(`${config?.dirPath}/_app`)
142
-
143
- if (!appAlreadyExists) {
144
- // download app and decompress
145
- await downloadEditor(
146
- config?.editor.version,
147
- `${config?.dirPath}/app.tar.gz`
148
- )
149
-
150
- Console.info("Decompressing LearnPack UI, this may take a minute...")
151
- await decompress(
152
- `${config?.dirPath}/app.tar.gz`,
153
- `${config?.dirPath}/_app/`
154
- )
155
- }
156
- }
157
-
158
- // Copy config.json
159
- const configPath = path.join(process.cwd(), ".learn", "config.json")
160
- if (fs.existsSync(configPath)) {
161
- fs.copyFileSync(configPath, path.join(buildDir, "config.json"))
162
- } else {
163
- this.error("config.json not found")
164
- }
165
-
166
- // Copy .learn/assets directory, if it exists else create it
167
- const assetsDir = path.join(process.cwd(), ".learn", "assets")
168
- if (fs.existsSync(assetsDir)) {
169
- this.copyDirectory(assetsDir, path.join(buildDir, ".learn", "assets"))
170
- } else {
171
- fs.mkdirSync(path.join(buildDir, ".learn", "assets"), { recursive: true })
172
- }
173
-
174
- // Copy .learn/_app directory files to the same level as config.json
175
- const appDir = path.join(process.cwd(), ".learn", "_app")
176
- if (fs.existsSync(appDir)) {
177
- this.copyDirectory(appDir, buildDir)
178
- } else {
179
- this.error(".learn/_app directory not found")
180
- }
181
-
182
- // After copying the _app directory
183
- const indexHtmlPath = path.join(appDir, "index.html")
184
- const buildIndexHtmlPath = path.join(buildDir, "index.html")
185
-
186
- if (fs.existsSync(indexHtmlPath)) {
187
- let indexHtmlContent = fs.readFileSync(indexHtmlPath, "utf-8")
188
-
189
- const description = learnJson.description.us || "LearnPack is awesome!"
190
- const title =
191
- learnJson.title.us || "LearnPack: Interactive Learning as a Service"
192
-
193
- const previewUrl =
194
- learnJson.preview ||
195
- "https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/public/learnpack.svg"
196
- // Replace placeholders and the <title>Old title </title> tag for a new tag with the title
197
- indexHtmlContent = indexHtmlContent
198
- .replace(/{{description}}/g, description)
199
- .replace(/<title>.*<\/title>/, `<title>${title}</title>`)
200
- .replace(/{{title}}/g, title)
201
- .replace(/{{preview}}/g, previewUrl)
202
-
203
- // Write the modified content to the build directory
204
- fs.writeFileSync(buildIndexHtmlPath, indexHtmlContent)
205
- } else {
206
- this.error("index.html not found in _app directory")
207
- }
208
-
209
- // Copy exercises directory
210
- const exercisesDir = path.join(process.cwd(), "exercises")
211
- const learnExercisesDir = path.join(process.cwd(), ".learn", "exercises")
212
-
213
- if (fs.existsSync(exercisesDir)) {
214
- this.copyDirectory(exercisesDir, path.join(buildDir, "exercises"))
215
- } else if (fs.existsSync(learnExercisesDir)) {
216
- this.copyDirectory(learnExercisesDir, path.join(buildDir, "exercises"))
217
- } else {
218
- this.error("exercises directory not found in either location")
219
- }
220
-
221
- // Copy learn.json
222
- fs.copyFileSync(learnJsonPath, path.join(buildDir, "learn.json"))
223
-
224
- // Create zip file
225
- const output = fs.createWriteStream(zipFilePath)
226
- const archive = archiver("zip", {
227
- zlib: { level: 9 },
228
- })
229
-
230
- output.on("close", async () => {
231
- this.log(
232
- `Build completed: ${zipFilePath} (${archive.pointer()} total bytes)`
233
- )
234
- // Remove build directory after zip is created
235
-
236
- Console.debug("Zip file saved in project root")
237
-
238
- const formData = new FormData()
239
- formData.append("file", fs.createReadStream(zipFilePath))
240
- formData.append("config", JSON.stringify(learnJson))
241
-
242
- try {
243
- const res = await axios.post(uploadZipEndpont, formData, {
244
- headers: {
245
- ...formData.getHeaders(),
246
- Authorization: `Token ${rigoToken}`,
247
- },
248
- })
249
- console.log(res.data)
250
- // Remove the zip file after uploading
251
- fs.unlinkSync(zipFilePath)
252
- this.removeDirectory(buildDir)
253
- } catch (error) {
254
- if (axios.isAxiosError(error)) {
255
- if (error.response && error.response.status === 403) {
256
- console.error("Error 403:", error.response.data.error)
257
- } else if (error.response && error.response.status === 400) {
258
- console.error(error.response.data.error)
259
- } else {
260
- console.error("Error uploading file:", error)
261
- }
262
- } else {
263
- console.error("Error uploading file:", error)
264
- }
265
-
266
- fs.unlinkSync(zipFilePath)
267
- this.removeDirectory(buildDir)
268
- }
269
- })
270
-
271
- archive.on("error", (err: any) => {
272
- throw err
273
- })
274
-
275
- archive.pipe(output)
276
- archive.directory(buildDir, false)
277
- await archive.finalize()
278
- }
279
-
280
- copyDirectory(src: string, dest: string) {
281
- if (!fs.existsSync(dest)) {
282
- fs.mkdirSync(dest, { recursive: true })
283
- }
284
-
285
- const entries = fs.readdirSync(src, { withFileTypes: true })
286
-
287
- for (const entry of entries) {
288
- const srcPath = path.join(src, entry.name)
289
- const destPath = path.join(dest, entry.name)
290
-
291
- if (entry.isDirectory()) {
292
- this.copyDirectory(srcPath, destPath)
293
- } else {
294
- fs.copyFileSync(srcPath, destPath)
295
- }
296
- }
297
- }
298
-
299
- removeDirectory(dir: string) {
300
- if (fs.existsSync(dir)) {
301
- fs.readdirSync(dir).forEach((file) => {
302
- const currentPath = path.join(dir, file)
303
- if (fs.lstatSync(currentPath).isDirectory()) {
304
- this.removeDirectory(currentPath)
305
- } else {
306
- fs.unlinkSync(currentPath)
307
- }
308
- })
309
- fs.rmdirSync(dir)
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
@@ -26,4 +26,4 @@ jobs:
26
26
  with:
27
27
  node-version: ${{ matrix.node-version }}
28
28
  - run: npm install @learnpack/learnpack@latest -g
29
- - run: learnpack audit
29
+ - run: learnpack audit --strict
@@ -26,4 +26,4 @@ jobs:
26
26
  with:
27
27
  node-version: ${{ matrix.node-version }}
28
28
  - run: npm install @learnpack/learnpack@latest -g
29
- - run: learnpack audit
29
+ - run: learnpack audit --strict