@learnpack/learnpack 5.0.27 → 5.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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.27 win32-x64 node-v20.16.0
24
+ @learnpack/learnpack/5.0.28 win32-x64 node-v20.16.0
25
25
  $ learnpack --help [COMMAND]
26
26
  USAGE
27
27
  $ learnpack COMMAND
@@ -33,6 +33,7 @@ USAGE
33
33
 
34
34
  <!-- commands -->
35
35
  * [`learnpack audit`](#learnpack-audit)
36
+ * [`learnpack breakToken`](#learnpack-breaktoken)
36
37
  * [`learnpack clean`](#learnpack-clean)
37
38
  * [`learnpack download [PACKAGE]`](#learnpack-download-package)
38
39
  * [`learnpack help [COMMAND]`](#learnpack-help-command)
@@ -75,7 +76,22 @@ DESCRIPTION
75
76
  12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
76
77
  ```
77
78
 
78
- _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.27/src\commands\audit.ts)_
79
+ _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\audit.ts)_
80
+
81
+ ## `learnpack breakToken`
82
+
83
+ Break the token
84
+
85
+ ```
86
+ USAGE
87
+ $ learnpack breakToken
88
+
89
+ OPTIONS
90
+ -h, --grading show CLI help
91
+ -y, --yes Skip all prompts and initialize an empty project
92
+ ```
93
+
94
+ _See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\breakToken.ts)_
79
95
 
80
96
  ## `learnpack clean`
81
97
 
@@ -90,7 +106,7 @@ DESCRIPTION
90
106
  Extra documentation goes here
91
107
  ```
92
108
 
93
- _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.27/src\commands\clean.ts)_
109
+ _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\clean.ts)_
94
110
 
95
111
  ## `learnpack download [PACKAGE]`
96
112
 
@@ -108,7 +124,7 @@ DESCRIPTION
108
124
  Extra documentation goes here
109
125
  ```
110
126
 
111
- _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.27/src\commands\download.ts)_
127
+ _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\download.ts)_
112
128
 
113
129
  ## `learnpack help [COMMAND]`
114
130
 
@@ -140,7 +156,7 @@ OPTIONS
140
156
  -y, --yes Skip all prompts and initialize an empty project
141
157
  ```
142
158
 
143
- _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.27/src\commands\init.ts)_
159
+ _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\init.ts)_
144
160
 
145
161
  ## `learnpack login [PACKAGE]`
146
162
 
@@ -158,7 +174,7 @@ DESCRIPTION
158
174
  Extra documentation goes here
159
175
  ```
160
176
 
161
- _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.27/src\commands\login.ts)_
177
+ _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\login.ts)_
162
178
 
163
179
  ## `learnpack logout [PACKAGE]`
164
180
 
@@ -176,7 +192,7 @@ DESCRIPTION
176
192
  Extra documentation goes here
177
193
  ```
178
194
 
179
- _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.27/src\commands\logout.ts)_
195
+ _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\logout.ts)_
180
196
 
181
197
  ## `learnpack plugins`
182
198
 
@@ -307,7 +323,7 @@ OPTIONS
307
323
  -h, --help show CLI help
308
324
  ```
309
325
 
310
- _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.27/src\commands\publish.ts)_
326
+ _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\publish.ts)_
311
327
 
312
328
  ## `learnpack start`
313
329
 
@@ -329,7 +345,7 @@ OPTIONS
329
345
  -y, --yes Skip all prompts and initialize an empty project
330
346
  ```
331
347
 
332
- _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.27/src\commands\start.ts)_
348
+ _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\start.ts)_
333
349
 
334
350
  ## `learnpack test [EXERCISESLUG]`
335
351
 
@@ -346,7 +362,7 @@ OPTIONS
346
362
  -y, --yes Skip all prompts and initialize an empty project
347
363
  ```
348
364
 
349
- _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.27/src\commands\test.ts)_
365
+ _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\test.ts)_
350
366
 
351
367
  ## `learnpack translate`
352
368
 
@@ -360,7 +376,7 @@ OPTIONS
360
376
  -y, --yes Skip all prompts and initialize an empty project
361
377
  ```
362
378
 
363
- _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.27/src\commands\translate.ts)_
379
+ _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\translate.ts)_
364
380
  <!-- commandsstop -->
365
381
 
366
382
  > > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
@@ -0,0 +1,10 @@
1
+ import BaseCommand from "../utils/BaseCommand";
2
+ declare class BreakTokenCommand extends BaseCommand {
3
+ static description: string;
4
+ static flags: {
5
+ grading: import("@oclif/parser/lib/flags").IBooleanFlag<void>;
6
+ yes: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
10
+ export default BreakTokenCommand;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const command_1 = require("@oclif/command");
4
+ const BaseCommand_1 = require("../utils/BaseCommand");
5
+ const session_1 = require("../managers/session");
6
+ class BreakTokenCommand extends BaseCommand_1.default {
7
+ async run() {
8
+ const { flags } = this.parse(BreakTokenCommand);
9
+ await session_1.default.breakToken();
10
+ process.exit(0);
11
+ }
12
+ }
13
+ BreakTokenCommand.description = "Break the token";
14
+ BreakTokenCommand.flags = Object.assign(Object.assign({}, BaseCommand_1.default.flags), { grading: command_1.flags.help({ char: "h" }) });
15
+ exports.default = BreakTokenCommand;
@@ -7,11 +7,12 @@ const fs = require("fs-extra");
7
7
  const prompts = require("prompts");
8
8
  const cli_ux_1 = require("cli-ux");
9
9
  const eta = require("eta");
10
+ const api_1 = require("../utils/api");
10
11
  const console_1 = require("../utils/console");
11
12
  const errors_1 = require("../utils/errors");
12
13
  const path = require("path");
13
14
  const rigoActions_1 = require("../utils/rigoActions");
14
- const api_1 = require("../utils/api");
15
+ const api_2 = require("../utils/api");
15
16
  const creatorUtilities_1 = require("../utils/creatorUtilities");
16
17
  const session_1 = require("../managers/session");
17
18
  const initializeInteractiveCreation = async (rigoToken, courseInfo) => {
@@ -65,7 +66,9 @@ const initializeInteractiveCreation = async (rigoToken, courseInfo) => {
65
66
  const handleAILogic = async (tutorialDir, packageInfo) => {
66
67
  fs.removeSync(path.join(tutorialDir, "exercises", "01-hello-world"));
67
68
  let sessionPayload = await session_1.default.getPayload();
68
- if (!sessionPayload || !sessionPayload.rigobot) {
69
+ if (!sessionPayload ||
70
+ !sessionPayload.rigobot ||
71
+ (sessionPayload.token && !(await api_1.default.validateToken(sessionPayload.token)))) {
69
72
  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");
70
73
  try {
71
74
  sessionPayload = await session_1.default.login();
@@ -76,10 +79,10 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
76
79
  }
77
80
  }
78
81
  const rigoToken = sessionPayload.rigobot.key;
79
- const consumables = await (0, api_1.getConsumables)(sessionPayload.token, "ai-generation");
80
- if (consumables.ai_generation === 0) {
81
- console_1.default.error("It seems you cannot generate tutorials with AI. 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");
82
- process.exit(1);
82
+ const consumable = await (0, api_2.getConsumable)(sessionPayload.token, "ai-generation");
83
+ if (consumable.count === 0) {
84
+ console_1.default.error("It seems you cannot generate tutorials with AI. 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");
85
+ // process.exit(1)
83
86
  }
84
87
  const isCreator = await (0, rigoActions_1.hasCreatorPermission)(rigoToken);
85
88
  if (!isCreator) {
@@ -13,6 +13,8 @@ const axios_1 = require("axios");
13
13
  const FormData = require("form-data");
14
14
  const console_1 = require("../utils/console");
15
15
  const file_1 = require("../managers/file");
16
+ const api_1 = require("../utils/api");
17
+ const prompts = require("prompts");
16
18
  const RIGOBOT_HOST = "https://rigobot.herokuapp.com";
17
19
  // const RIGOBOT_HOST =
18
20
  // "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
@@ -37,6 +39,26 @@ const runAudit = () => {
37
39
  // Continuar con el proceso de build solo si `learnpack publish` fue exitoso
38
40
  console_1.default.info("Learnpack publish completed successfully. Proceeding with build...");
39
41
  };
42
+ const selectAcademy = async (academies) => {
43
+ if (academies.length === 0) {
44
+ return null;
45
+ }
46
+ if (academies.length === 1) {
47
+ return academies[0];
48
+ }
49
+ // prompts the user to select an academy to upload the assets
50
+ console_1.default.info("In which academy do you want to publish the asset?");
51
+ const response = await prompts({
52
+ type: "select",
53
+ name: "academy",
54
+ message: "Select an academy",
55
+ choices: academies.map((academy) => ({
56
+ title: academy.name,
57
+ value: academy,
58
+ })),
59
+ });
60
+ return response.academy;
61
+ };
40
62
  class BuildCommand extends SessionCommand_1.default {
41
63
  async init() {
42
64
  const { flags } = this.parse(BuildCommand);
@@ -48,7 +70,9 @@ class BuildCommand extends SessionCommand_1.default {
48
70
  // this.configManager?.clean()
49
71
  const configObject = (_a = this.configManager) === null || _a === void 0 ? void 0 : _a.get();
50
72
  let sessionPayload = await session_1.default.getPayload();
51
- if (!sessionPayload || !sessionPayload.rigobot) {
73
+ if (!sessionPayload ||
74
+ !sessionPayload.rigobot ||
75
+ (sessionPayload.token && !(await api_1.default.validateToken(sessionPayload.token)))) {
52
76
  console_1.default.error("You must be logged in to upload a LearnPack package");
53
77
  try {
54
78
  sessionPayload = await session_1.default.login();
@@ -67,7 +91,10 @@ class BuildCommand extends SessionCommand_1.default {
67
91
  console_1.default.debug("Building exercises");
68
92
  (_c = this.configManager) === null || _c === void 0 ? void 0 : _c.buildIndex();
69
93
  }
70
- // const rigoToken = "417d612d226a1606ad3a4e94b1881a9f0124b667"
94
+ // const academies = await api.listUserAcademies(sessionPayload.token)
95
+ // console.log(academies, "academies")
96
+ // const academy = await selectAcademy(academies)
97
+ // console.log(academy, "academy")
71
98
  // Read learn.json to get the slug
72
99
  const learnJsonPath = path.join(process.cwd(), "learn.json");
73
100
  if (!fs.existsSync(learnJsonPath)) {
@@ -138,5 +138,13 @@ const Session = {
138
138
  this.token = null;
139
139
  console_1.default.success("You have logged out");
140
140
  },
141
+ breakToken: async function () {
142
+ const payload = await this.getPayload();
143
+ if (payload) {
144
+ this.token = "asdasdasdasd";
145
+ await storage.setItem("bc-payload", Object.assign(Object.assign({}, payload), { token: "asdasdsad" }));
146
+ console_1.default.success("Token broken successfully");
147
+ }
148
+ },
141
149
  };
142
150
  exports.default = Session;
@@ -31,5 +31,6 @@ export interface ISession {
31
31
  sync: () => Promise<void>;
32
32
  start: ({ token, payload }: IStartProps) => Promise<void>;
33
33
  destroy: () => Promise<void>;
34
+ breakToken: () => Promise<void>;
34
35
  }
35
36
  export {};
@@ -1,6 +1,14 @@
1
1
  type TConsumableSlug = "ai-conversation-message" | "ai-compilation" | "ai-tutorial-generation" | "ai-generation";
2
2
  export declare const countConsumables: (consumables: any, consumableSlug?: TConsumableSlug) => any;
3
- export declare const getConsumables: (token: string, consumableSlug?: TConsumableSlug) => Promise<any>;
3
+ export declare const getConsumable: (token: string, consumableSlug?: TConsumableSlug) => Promise<any>;
4
+ export interface TAcademy {
5
+ id: number;
6
+ name: string;
7
+ slug: string;
8
+ timezone: string;
9
+ }
10
+ export declare const listUserAcademies: (breathecodeToken: string) => Promise<TAcademy[]>;
11
+ export declare const validateToken: (token: string) => Promise<any>;
4
12
  declare const _default: {
5
13
  login: (identification: string, password: string) => Promise<any>;
6
14
  publish: (config: any) => Promise<any>;
@@ -13,5 +21,7 @@ declare const _default: {
13
21
  }) => Promise<any>;
14
22
  sendBatchTelemetry: (url: string, body: any) => Promise<void>;
15
23
  sendStreamTelemetry: (url: string, body: object) => Promise<void>;
24
+ listUserAcademies: (breathecodeToken: string) => Promise<TAcademy[]>;
25
+ validateToken: (token: string) => Promise<any>;
16
26
  };
17
27
  export default _default;
package/lib/utils/api.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getConsumables = exports.countConsumables = void 0;
3
+ exports.validateToken = exports.listUserAcademies = exports.getConsumable = exports.countConsumables = void 0;
4
4
  const console_1 = require("../utils/console");
5
5
  const storage = require("node-persist");
6
6
  const cli_ux_1 = require("cli-ux");
@@ -63,13 +63,11 @@ const login = async (identification, password) => {
63
63
  cli_ux_1.default.action.start(`Looking for credentials with ${identification}`);
64
64
  await cli_ux_1.default.wait(1000);
65
65
  const url = `${HOST}/v1/auth/login/`;
66
- const data = await fetch(url, {
67
- body: JSON.stringify({
68
- email: identification,
69
- password: password,
70
- }),
71
- method: "post",
66
+ const res = await axios_1.default.post(url, {
67
+ email: identification,
68
+ password: password,
72
69
  });
70
+ const data = res.data;
73
71
  cli_ux_1.default.action.stop("ready");
74
72
  let rigoPayload = null;
75
73
  try {
@@ -258,22 +256,60 @@ const countConsumables = (consumables, consumableSlug = "ai-tutorial-generation"
258
256
  return consumable ? consumable.balance.unit : 0;
259
257
  };
260
258
  exports.countConsumables = countConsumables;
261
- const getConsumables = async (token, consumableSlug = "ai-generation") => {
259
+ const getConsumable = async (token, consumableSlug = "ai-generation") => {
262
260
  const url = `${HOST}/v1/payments/me/service/consumable?virtual=true`;
263
261
  const headers = {
264
262
  Authorization: `Token ${token}`,
265
263
  };
266
264
  try {
267
265
  const response = await axios_1.default.get(url, { headers });
268
- const ai_tutorial_generation = (0, exports.countConsumables)(response.data, consumableSlug);
269
- return { ai_tutorial_generation };
266
+ const count = (0, exports.countConsumables)(response.data, consumableSlug);
267
+ return { count };
270
268
  }
271
269
  catch (error) {
272
270
  console.error("Error fetching consumables:", error);
273
271
  throw error;
274
272
  }
275
273
  };
276
- exports.getConsumables = getConsumables;
274
+ exports.getConsumable = getConsumable;
275
+ const listUserAcademies = async (breathecodeToken) => {
276
+ const url = "https://breathecode.herokuapp.com/v1/auth/user/me";
277
+ try {
278
+ const response = await axios_1.default.get(url, {
279
+ headers: {
280
+ Authorization: `Token ${breathecodeToken}`,
281
+ },
282
+ });
283
+ const data = response.data;
284
+ const academiesMap = new Map();
285
+ for (const role of data.roles) {
286
+ const academy = role.academy;
287
+ if (!academiesMap.has(academy.id)) {
288
+ academiesMap.set(academy.id, academy);
289
+ }
290
+ }
291
+ return [...academiesMap.values()];
292
+ }
293
+ catch (error) {
294
+ console.error("Failed to fetch user academies:", error);
295
+ return [];
296
+ }
297
+ };
298
+ exports.listUserAcademies = listUserAcademies;
299
+ const validateToken = async (token) => {
300
+ const url = "https://breathecode.herokuapp.com/v1/auth/user/me";
301
+ const headers = {
302
+ Authorization: `Token ${token}`,
303
+ };
304
+ try {
305
+ const response = await axios_1.default.get(url, { headers });
306
+ return response.data;
307
+ }
308
+ catch (_a) {
309
+ return false;
310
+ }
311
+ };
312
+ exports.validateToken = validateToken;
277
313
  exports.default = {
278
314
  login,
279
315
  publish,
@@ -283,4 +319,6 @@ exports.default = {
283
319
  getAllPackages,
284
320
  sendBatchTelemetry,
285
321
  sendStreamTelemetry,
322
+ listUserAcademies: exports.listUserAcademies,
323
+ validateToken: exports.validateToken,
286
324
  };
@@ -1 +1 @@
1
- {"version":"5.0.27","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":[]},"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.28","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":[]}}}
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.27",
4
+ "version": "5.0.28",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -0,0 +1,25 @@
1
+ import { flags } from "@oclif/command"
2
+ import BaseCommand from "../utils/BaseCommand"
3
+ // eslint-disable-next-line
4
+ import * as fs from "fs-extra"
5
+ import Console from "../utils/console"
6
+
7
+ import SessionManager from "../managers/session"
8
+
9
+ class BreakTokenCommand extends BaseCommand {
10
+ static description = "Break the token"
11
+
12
+ static flags = {
13
+ ...BaseCommand.flags,
14
+ grading: flags.help({ char: "h" }),
15
+ }
16
+
17
+ async run() {
18
+ const { flags } = this.parse(BreakTokenCommand)
19
+
20
+ await SessionManager.breakToken()
21
+ process.exit(0)
22
+ }
23
+ }
24
+
25
+ export default BreakTokenCommand
@@ -20,7 +20,7 @@ import {
20
20
  readmeCreator,
21
21
  createPreviewReadme,
22
22
  } from "../utils/rigoActions"
23
- import { getConsumables } from "../utils/api"
23
+ import { getConsumable } from "../utils/api"
24
24
  import {
25
25
  checkReadingTime,
26
26
  PackageInfo,
@@ -98,7 +98,11 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
98
98
 
99
99
  let sessionPayload = await SessionManager.getPayload()
100
100
 
101
- if (!sessionPayload || !sessionPayload.rigobot) {
101
+ if (
102
+ !sessionPayload ||
103
+ !sessionPayload.rigobot ||
104
+ (sessionPayload.token && !(await api.validateToken(sessionPayload.token)))
105
+ ) {
102
106
  Console.info(
103
107
  "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"
104
108
  )
@@ -112,16 +116,13 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
112
116
 
113
117
  const rigoToken = sessionPayload.rigobot.key
114
118
 
115
- const consumables = await getConsumables(
116
- sessionPayload.token,
117
- "ai-generation"
118
- )
119
+ const consumable = await getConsumable(sessionPayload.token, "ai-generation")
119
120
 
120
- if (consumables.ai_generation === 0) {
121
+ if (consumable.count === 0) {
121
122
  Console.error(
122
- "It seems you cannot generate tutorials with AI. 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"
123
+ "It seems you cannot generate tutorials with AI. 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"
123
124
  )
124
- process.exit(1)
125
+ // process.exit(1)
125
126
  }
126
127
 
127
128
  const isCreator = await hasCreatorPermission(rigoToken)
@@ -15,6 +15,8 @@ import {
15
15
  downloadEditor,
16
16
  checkIfDirectoryExists,
17
17
  } from "../managers/file"
18
+ import api, { TAcademy } from "../utils/api"
19
+ import * as prompts from "prompts"
18
20
 
19
21
  const RIGOBOT_HOST = "https://rigobot.herokuapp.com"
20
22
  // const RIGOBOT_HOST =
@@ -45,6 +47,29 @@ const runAudit = () => {
45
47
  )
46
48
  }
47
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
+
48
73
  export default class BuildCommand extends SessionCommand {
49
74
  static description =
50
75
  "Builds the project by copying necessary files and directories into a zip file"
@@ -65,7 +90,11 @@ export default class BuildCommand extends SessionCommand {
65
90
  const configObject = this.configManager?.get()
66
91
 
67
92
  let sessionPayload = await SessionManager.getPayload()
68
- if (!sessionPayload || !sessionPayload.rigobot) {
93
+ if (
94
+ !sessionPayload ||
95
+ !sessionPayload.rigobot ||
96
+ (sessionPayload.token && !(await api.validateToken(sessionPayload.token)))
97
+ ) {
69
98
  Console.error("You must be logged in to upload a LearnPack package")
70
99
  try {
71
100
  sessionPayload = await SessionManager.login()
@@ -87,7 +116,10 @@ export default class BuildCommand extends SessionCommand {
87
116
  this.configManager?.buildIndex()
88
117
  }
89
118
 
90
- // const rigoToken = "417d612d226a1606ad3a4e94b1881a9f0124b667"
119
+ // const academies = await api.listUserAcademies(sessionPayload.token)
120
+ // console.log(academies, "academies")
121
+ // const academy = await selectAcademy(academies)
122
+ // console.log(academy, "academy")
91
123
 
92
124
  // Read learn.json to get the slug
93
125
  const learnJsonPath = path.join(process.cwd(), "learn.json")
@@ -167,6 +167,17 @@ const Session: ISession = {
167
167
  this.token = null
168
168
  Console.success("You have logged out")
169
169
  },
170
+ breakToken: async function () {
171
+ const payload = await this.getPayload()
172
+ if (payload) {
173
+ this.token = "asdasdasdasd"
174
+ await storage.setItem("bc-payload", {
175
+ ...payload,
176
+ token: "asdasdsad",
177
+ })
178
+ Console.success("Token broken successfully")
179
+ }
180
+ },
170
181
  }
171
182
 
172
183
  export default Session
@@ -35,4 +35,5 @@ export interface ISession {
35
35
  sync: () => Promise<void>
36
36
  start: ({ token, payload }: IStartProps) => Promise<void>
37
37
  destroy: () => Promise<void>
38
+ breakToken: () => Promise<void>
38
39
  }
package/src/utils/api.ts CHANGED
@@ -79,13 +79,12 @@ const login = async (identification: string, password: string) => {
79
79
  await cli.wait(1000)
80
80
  const url = `${HOST}/v1/auth/login/`
81
81
 
82
- const data = await fetch(url, {
83
- body: JSON.stringify({
84
- email: identification,
85
- password: password,
86
- }),
87
- method: "post",
82
+ const res = await axios.post(url, {
83
+ email: identification,
84
+ password: password,
88
85
  })
86
+ const data = res.data
87
+
89
88
  cli.action.stop("ready")
90
89
  let rigoPayload = null
91
90
  try {
@@ -320,7 +319,7 @@ export const countConsumables = (
320
319
  return consumable ? consumable.balance.unit : 0
321
320
  }
322
321
 
323
- export const getConsumables = async (
322
+ export const getConsumable = async (
324
323
  token: string,
325
324
  consumableSlug: TConsumableSlug = "ai-generation"
326
325
  ): Promise<any> => {
@@ -333,18 +332,65 @@ export const getConsumables = async (
333
332
  try {
334
333
  const response = await axios.get(url, { headers })
335
334
 
336
- const ai_tutorial_generation = countConsumables(
337
- response.data,
338
- consumableSlug
339
- )
335
+ const count = countConsumables(response.data, consumableSlug)
340
336
 
341
- return { ai_tutorial_generation }
337
+ return { count }
342
338
  } catch (error) {
343
339
  console.error("Error fetching consumables:", error)
344
340
  throw error
345
341
  }
346
342
  }
347
343
 
344
+ export interface TAcademy {
345
+ id: number
346
+ name: string
347
+ slug: string
348
+ timezone: string
349
+ }
350
+
351
+ export const listUserAcademies = async (
352
+ breathecodeToken: string
353
+ ): Promise<TAcademy[]> => {
354
+ const url = "https://breathecode.herokuapp.com/v1/auth/user/me"
355
+
356
+ try {
357
+ const response = await axios.get(url, {
358
+ headers: {
359
+ Authorization: `Token ${breathecodeToken}`,
360
+ },
361
+ })
362
+
363
+ const data = response.data
364
+
365
+ const academiesMap = new Map<number, TAcademy>()
366
+ for (const role of data.roles) {
367
+ const academy = role.academy
368
+ if (!academiesMap.has(academy.id)) {
369
+ academiesMap.set(academy.id, academy)
370
+ }
371
+ }
372
+
373
+ return [...academiesMap.values()]
374
+ } catch (error) {
375
+ console.error("Failed to fetch user academies:", error)
376
+ return []
377
+ }
378
+ }
379
+
380
+ export const validateToken = async (token: string) => {
381
+ const url = "https://breathecode.herokuapp.com/v1/auth/user/me"
382
+ const headers = {
383
+ Authorization: `Token ${token}`,
384
+ }
385
+
386
+ try {
387
+ const response = await axios.get(url, { headers })
388
+ return response.data
389
+ } catch {
390
+ return false
391
+ }
392
+ }
393
+
348
394
  export default {
349
395
  login,
350
396
  publish,
@@ -354,4 +400,6 @@ export default {
354
400
  getAllPackages,
355
401
  sendBatchTelemetry,
356
402
  sendStreamTelemetry,
403
+ listUserAcademies,
404
+ validateToken,
357
405
  }
@@ -1,147 +1,147 @@
1
- // eslint-disable-next-line
2
- const frontMatter = require("front-matter")
3
- import * as path from "path"
4
-
5
- import * as yaml from "js-yaml"
6
-
7
- type TEstimateReadingTimeReturns = {
8
- minutes: number
9
- words: number
10
- }
11
-
12
- export const estimateReadingTime = (
13
- text: string,
14
- wordsPerMinute = 150
15
- ): TEstimateReadingTimeReturns => {
16
- const words = text.trim().split(/\s+/).length
17
- const minutes = words / wordsPerMinute
18
-
19
- if (minutes < 1) {
20
- if (words === 0)
21
- return {
22
- minutes: 1,
23
- words,
24
- }
25
- } else {
26
- return {
27
- minutes,
28
- words,
29
- }
30
- }
31
-
32
- return {
33
- minutes: 1,
34
- words,
35
- }
36
- }
37
-
38
- export type PackageInfo = {
39
- grading: string
40
- difficulty: string
41
- duration: number
42
- description: {
43
- us: string
44
- }
45
- title: {
46
- us: string
47
- }
48
- }
49
-
50
- export function checkReadingTime(
51
- markdown: string,
52
- wordsPerMinute = 150
53
- ): { newMarkdown: string; exceedsThreshold: boolean } {
54
- const parsed = frontMatter(markdown)
55
- const readingTime = estimateReadingTime(parsed.body, wordsPerMinute)
56
- let attributes = parsed.attributes ? parsed.attributes : {}
57
-
58
- if (typeof parsed.attributes !== "object") {
59
- attributes = {}
60
- }
61
-
62
- const updatedAttributes = {
63
- ...attributes,
64
- readingTime,
65
- }
66
-
67
- // Convert the front matter back to a proper YAML string
68
- const yamlFrontMatter = yaml.dump(updatedAttributes).trim()
69
-
70
- // Reconstruct the markdown with the front matter
71
- const newMarkdown = `---\n${yamlFrontMatter}\n---\n\n${parsed.body}`
72
-
73
- return {
74
- newMarkdown,
75
- exceedsThreshold: readingTime.minutes > wordsPerMinute,
76
- }
77
- }
78
-
79
- const slugify = (text: string) => {
80
- return text
81
- .toString()
82
- .normalize("NFD")
83
- .replace(/[\u0300-\u036F]/g, "")
84
- .toLowerCase()
85
- .trim()
86
- .replace(/\s+/g, "-")
87
- .replace(/[^\w-]+/g, "")
88
- }
89
-
90
- export const getExInfo = (title: string) => {
91
- // Example title: '1.0 - Introduction to AI [READ: Small introduction to important concepts such as AI, machine learning, and their applications]'
92
- let [exNumber, exTitle] = title.split(" - ")
93
-
94
- // Extract kind and description
95
- const kindMatch = exTitle.match(/\[(.*?):(.*?)]/)
96
- const kind = kindMatch ? kindMatch[1].trim().toLowerCase() : "read"
97
- const description = kindMatch ? kindMatch[2].trim() : ""
98
-
99
- exNumber = exNumber.trim()
100
- // Clean title
101
- exTitle = exTitle.replace(kindMatch?.[0] || "", "").trim()
102
- exTitle = slugify(exTitle)
103
-
104
- return {
105
- exNumber,
106
- kind,
107
- description,
108
- exTitle,
109
- }
110
- }
111
-
112
- export function extractImagesFromMarkdown(markdown: string) {
113
- const imageRegex = /!\[([^\]]*)]\(([^)]+)\)/g
114
- const images = []
115
- let match
116
-
117
- while ((match = imageRegex.exec(markdown)) !== null) {
118
- const altText = match[1]
119
- const url = match[2]
120
- images.push({ alt: altText, url: url })
121
- }
122
-
123
- return images
124
- }
125
-
126
- export function getFilenameFromUrl(url: string) {
127
- return path.basename(url)
128
- }
129
-
130
- export const makePackageInfo = (choices: any) => {
131
- const packageInfo = {
132
- grading: choices.grading,
133
- difficulty: choices.difficulty,
134
- duration: parseInt(choices.duration),
135
- description: {
136
- us: choices.description,
137
- },
138
- title: {
139
- us: choices.title,
140
- },
141
- slug: choices.title
142
- .toLowerCase()
143
- .replace(/ /g, "-")
144
- .replace(/[^\w-]+/g, ""),
145
- }
146
- return packageInfo
147
- }
1
+ // eslint-disable-next-line
2
+ const frontMatter = require("front-matter")
3
+ import * as path from "path"
4
+
5
+ import * as yaml from "js-yaml"
6
+
7
+ type TEstimateReadingTimeReturns = {
8
+ minutes: number
9
+ words: number
10
+ }
11
+
12
+ export const estimateReadingTime = (
13
+ text: string,
14
+ wordsPerMinute = 150
15
+ ): TEstimateReadingTimeReturns => {
16
+ const words = text.trim().split(/\s+/).length
17
+ const minutes = words / wordsPerMinute
18
+
19
+ if (minutes < 1) {
20
+ if (words === 0)
21
+ return {
22
+ minutes: 1,
23
+ words,
24
+ }
25
+ } else {
26
+ return {
27
+ minutes,
28
+ words,
29
+ }
30
+ }
31
+
32
+ return {
33
+ minutes: 1,
34
+ words,
35
+ }
36
+ }
37
+
38
+ export type PackageInfo = {
39
+ grading: string
40
+ difficulty: string
41
+ duration: number
42
+ description: {
43
+ us: string
44
+ }
45
+ title: {
46
+ us: string
47
+ }
48
+ }
49
+
50
+ export function checkReadingTime(
51
+ markdown: string,
52
+ wordsPerMinute = 150
53
+ ): { newMarkdown: string; exceedsThreshold: boolean } {
54
+ const parsed = frontMatter(markdown)
55
+ const readingTime = estimateReadingTime(parsed.body, wordsPerMinute)
56
+ let attributes = parsed.attributes ? parsed.attributes : {}
57
+
58
+ if (typeof parsed.attributes !== "object") {
59
+ attributes = {}
60
+ }
61
+
62
+ const updatedAttributes = {
63
+ ...attributes,
64
+ readingTime,
65
+ }
66
+
67
+ // Convert the front matter back to a proper YAML string
68
+ const yamlFrontMatter = yaml.dump(updatedAttributes).trim()
69
+
70
+ // Reconstruct the markdown with the front matter
71
+ const newMarkdown = `---\n${yamlFrontMatter}\n---\n\n${parsed.body}`
72
+
73
+ return {
74
+ newMarkdown,
75
+ exceedsThreshold: readingTime.minutes > wordsPerMinute,
76
+ }
77
+ }
78
+
79
+ const slugify = (text: string) => {
80
+ return text
81
+ .toString()
82
+ .normalize("NFD")
83
+ .replace(/[\u0300-\u036F]/g, "")
84
+ .toLowerCase()
85
+ .trim()
86
+ .replace(/\s+/g, "-")
87
+ .replace(/[^\w-]+/g, "")
88
+ }
89
+
90
+ export const getExInfo = (title: string) => {
91
+ // Example title: '1.0 - Introduction to AI [READ: Small introduction to important concepts such as AI, machine learning, and their applications]'
92
+ let [exNumber, exTitle] = title.split(" - ")
93
+
94
+ // Extract kind and description
95
+ const kindMatch = exTitle.match(/\[(.*?):(.*?)]/)
96
+ const kind = kindMatch ? kindMatch[1].trim().toLowerCase() : "read"
97
+ const description = kindMatch ? kindMatch[2].trim() : ""
98
+
99
+ exNumber = exNumber.trim()
100
+ // Clean title
101
+ exTitle = exTitle.replace(kindMatch?.[0] || "", "").trim()
102
+ exTitle = slugify(exTitle)
103
+
104
+ return {
105
+ exNumber,
106
+ kind,
107
+ description,
108
+ exTitle,
109
+ }
110
+ }
111
+
112
+ export function extractImagesFromMarkdown(markdown: string) {
113
+ const imageRegex = /!\[([^\]]*)]\(([^)]+)\)/g
114
+ const images = []
115
+ let match
116
+
117
+ while ((match = imageRegex.exec(markdown)) !== null) {
118
+ const altText = match[1]
119
+ const url = match[2]
120
+ images.push({ alt: altText, url: url })
121
+ }
122
+
123
+ return images
124
+ }
125
+
126
+ export function getFilenameFromUrl(url: string) {
127
+ return path.basename(url)
128
+ }
129
+
130
+ export const makePackageInfo = (choices: any) => {
131
+ const packageInfo = {
132
+ grading: choices.grading,
133
+ difficulty: choices.difficulty,
134
+ duration: parseInt(choices.duration),
135
+ description: {
136
+ us: choices.description,
137
+ },
138
+ title: {
139
+ us: choices.title,
140
+ },
141
+ slug: choices.title
142
+ .toLowerCase()
143
+ .replace(/ /g, "-")
144
+ .replace(/[^\w-]+/g, ""),
145
+ }
146
+ return packageInfo
147
+ }