@learnpack/learnpack 1.0.0 → 2.0.0

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.
Files changed (103) hide show
  1. package/README.md +51 -398
  2. package/bin/run +14 -2
  3. package/oclif.manifest.json +1 -1
  4. package/package.json +135 -111
  5. package/src/commands/audit.ts +462 -0
  6. package/src/commands/clean.ts +29 -0
  7. package/src/commands/download.ts +62 -0
  8. package/src/commands/init.ts +169 -0
  9. package/src/commands/login.ts +42 -0
  10. package/src/commands/logout.ts +43 -0
  11. package/src/commands/publish.ts +107 -0
  12. package/src/commands/start.ts +229 -0
  13. package/src/commands/{test.js → test.ts} +19 -21
  14. package/src/index.ts +1 -0
  15. package/src/managers/config/allowed_files.ts +29 -0
  16. package/src/managers/config/defaults.ts +33 -0
  17. package/src/managers/config/exercise.ts +295 -0
  18. package/src/managers/config/index.ts +411 -0
  19. package/src/managers/file.ts +169 -0
  20. package/src/managers/gitpod.ts +84 -0
  21. package/src/managers/server/{index.js → index.ts} +26 -19
  22. package/src/managers/server/routes.ts +250 -0
  23. package/src/managers/session.ts +118 -0
  24. package/src/managers/socket.ts +239 -0
  25. package/src/managers/test.ts +83 -0
  26. package/src/models/action.ts +3 -0
  27. package/src/models/audit-errors.ts +4 -0
  28. package/src/models/config-manager.ts +23 -0
  29. package/src/models/config.ts +74 -0
  30. package/src/models/counter.ts +11 -0
  31. package/src/models/errors.ts +22 -0
  32. package/src/models/exercise-obj.ts +26 -0
  33. package/src/models/file.ts +5 -0
  34. package/src/models/findings.ts +18 -0
  35. package/src/models/flags.ts +10 -0
  36. package/src/models/front-matter.ts +11 -0
  37. package/src/models/gitpod-data.ts +19 -0
  38. package/src/models/language.ts +4 -0
  39. package/src/models/package.ts +7 -0
  40. package/src/models/plugin-config.ts +17 -0
  41. package/src/models/session.ts +26 -0
  42. package/src/models/socket.ts +48 -0
  43. package/src/models/status.ts +15 -0
  44. package/src/models/success-types.ts +1 -0
  45. package/src/plugin/command/compile.ts +17 -0
  46. package/src/plugin/command/test.ts +30 -0
  47. package/src/plugin/index.ts +6 -0
  48. package/src/plugin/plugin.ts +94 -0
  49. package/src/plugin/utils.ts +87 -0
  50. package/src/types/node-fetch.d.ts +1 -0
  51. package/src/ui/download.ts +71 -0
  52. package/src/utils/BaseCommand.ts +48 -0
  53. package/src/utils/SessionCommand.ts +48 -0
  54. package/src/utils/api.ts +194 -0
  55. package/src/utils/audit.ts +162 -0
  56. package/src/utils/console.ts +24 -0
  57. package/src/utils/errors.ts +117 -0
  58. package/src/utils/{exercisesQueue.js → exercisesQueue.ts} +12 -6
  59. package/src/utils/fileQueue.ts +198 -0
  60. package/src/utils/misc.ts +23 -0
  61. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +2 -4
  62. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +1 -2
  63. package/src/utils/templates/isolated/01-hello-world/README.es.md +1 -2
  64. package/src/utils/templates/isolated/01-hello-world/README.md +1 -2
  65. package/src/utils/templates/isolated/README.ejs +1 -1
  66. package/src/utils/templates/isolated/README.es.ejs +1 -1
  67. package/src/utils/validators.ts +18 -0
  68. package/src/utils/watcher.ts +27 -0
  69. package/plugin/command/compile.js +0 -17
  70. package/plugin/command/test.js +0 -29
  71. package/plugin/index.js +0 -6
  72. package/plugin/plugin.js +0 -71
  73. package/plugin/utils.js +0 -78
  74. package/src/commands/audit.js +0 -243
  75. package/src/commands/clean.js +0 -27
  76. package/src/commands/download.js +0 -52
  77. package/src/commands/hello.js +0 -20
  78. package/src/commands/init.js +0 -133
  79. package/src/commands/login.js +0 -45
  80. package/src/commands/logout.js +0 -39
  81. package/src/commands/publish.js +0 -78
  82. package/src/commands/start.js +0 -169
  83. package/src/index.js +0 -1
  84. package/src/managers/config/allowed_files.js +0 -12
  85. package/src/managers/config/defaults.js +0 -32
  86. package/src/managers/config/exercise.js +0 -212
  87. package/src/managers/config/index.js +0 -342
  88. package/src/managers/file.js +0 -137
  89. package/src/managers/server/routes.js +0 -151
  90. package/src/managers/session.js +0 -83
  91. package/src/managers/socket.js +0 -185
  92. package/src/managers/test.js +0 -77
  93. package/src/ui/download.js +0 -48
  94. package/src/utils/BaseCommand.js +0 -34
  95. package/src/utils/SessionCommand.js +0 -46
  96. package/src/utils/api.js +0 -164
  97. package/src/utils/audit.js +0 -114
  98. package/src/utils/console.js +0 -16
  99. package/src/utils/errors.js +0 -90
  100. package/src/utils/fileQueue.js +0 -194
  101. package/src/utils/misc.js +0 -26
  102. package/src/utils/validators.js +0 -15
  103. package/src/utils/watcher.js +0 -24
@@ -1,114 +0,0 @@
1
- const Console = require('./console')
2
- const fetch = require('node-fetch')
3
- const fs = require('fs')
4
-
5
- module.exports = {
6
- // This function checks if a url is valid.
7
- isUrl: async (url, errors, counter) => {
8
- let regex_url = /(https?:\/\/[a-zA-Z_\-.\/0-9]+)/gm
9
- counter.links.total++
10
- if (!regex_url.exec(url)) {
11
- counter.links.error++
12
- errors.push({ exercise: null, msg: `The repository value of the configuration file is not a link: ${url}` })
13
- return false;
14
- }
15
- let res = await fetch(url, { method: "HEAD" });
16
- if (!res.ok) {
17
- counter.links.error++
18
- errors.push({ exercise: null, msg: `The link of the repository is broken: ${url}` })
19
- }
20
- return true;
21
- },
22
- checkForEmptySpaces: (str) => {
23
- let isEmpty = true;
24
- for(let letter of str){
25
- if (letter !== " ") {
26
- isEmpty = false;
27
- return isEmpty;
28
- }
29
- }
30
- return isEmpty;
31
- },
32
- checkLearnpackClean: (configObj, errors) => {
33
- if(fs.existsSync(configObj.config.outputPath) || fs.existsSync(configObj.config.dirPath + "/_app") || fs.existsSync(configObj.config.dirPath + "/reports") || fs.existsSync(configObj.config.dirPath + "/resets") || fs.existsSync(configObj.config.dirPath + "/app.tar.gz") || fs.existsSync(configObj.config.dirPath + "/config.json") || fs.existsSync(configObj.config.dirPath + "/vscode_queue.json")) {
34
- errors.push({ exercise: null, msg: `You have to run learnpack clean command` })
35
- }
36
- },
37
- findInFile: (types, content) => {
38
- const regex = {
39
- relative_images: /!\[.*\]\s*\((((\.\/)?(\.{2}\/){1,5})(.*\/)*(.[^\/\s]*\.[a-zA-Z]{2,4})[^\s]*)\)/gm,
40
- external_images: /!\[.*\]\((https?:\/(\/{1}[^/)]+)+\/?)\)/gm,
41
- markdown_links: /(\s)+\[.*\]\((https?:\/(\/{1}[^/)]+)+\/?)\)/gm,
42
- url: /(https?:\/\/[a-zA-Z_\-.\/0-9]+)/gm,
43
- uploadcare: /https:\/\/ucarecdn.com\/(?:.*\/)*([a-zA-Z_\-.\/0-9]+)/gm
44
- }
45
-
46
- const validTypes = Object.keys(regex);
47
- if (!Array.isArray(types)) types = [types];
48
-
49
- let findings = {}
50
-
51
- types.forEach(type => {
52
- if (!validTypes.includes(type)) throw Error("Invalid type: " + type)
53
- else findings[type] = {};
54
- });
55
-
56
- types.forEach(type => {
57
-
58
- let count = 0;
59
- let m;
60
- while ((m = regex[type].exec(content)) !== null) {
61
- // This is necessary to avoid infinite loops with zero-width matches
62
- if (m.index === regex.lastIndex) {
63
- regex.lastIndex++;
64
- }
65
-
66
- // The result can be accessed through the `m`-variable.
67
- // m.forEach((match, groupIndex) => values.push(match));
68
- count++;
69
-
70
- findings[type][m[0]] = {
71
- content: m[0],
72
- absUrl: m[1],
73
- mdUrl: m[2],
74
- relUrl: m[6]
75
- }
76
- }
77
- })
78
-
79
- return findings;
80
- },
81
- // This function checks if there are errors, and show them in the console at the end.
82
- showErrors: (errors, counter) => {
83
- return new Promise((resolve, reject) => {
84
- if (errors) {
85
- if (errors.length > 0) {
86
- Console.log("Checking for errors...")
87
- errors.forEach((error, i) => Console.error(`${i + 1}) ${error.msg} ${error.exercise != null ? `(Exercise: ${error.exercise})` : ""}`))
88
- Console.error(` We found ${errors.length} errors among ${counter.images.total} images, ${counter.links.total} link, ${counter.readmeFiles} README files and ${counter.exercises} exercises.`)
89
- process.exit(1)
90
- } else {
91
- Console.success(`We didn't find any errors in this repository among ${counter.images.total} images, ${counter.links.total} link, ${counter.readmeFiles} README files and ${counter.exercises} exercises.`)
92
- process.exit(0)
93
- }
94
- resolve("SUCCESS")
95
- } else {
96
- reject("Failed")
97
- }
98
- })
99
- },
100
- // This function checks if there are warnings, and show them in the console at the end.
101
- showWarnings: (warnings) => {
102
- return new Promise((resolve, reject) => {
103
- if (warnings) {
104
- if (warnings.length > 0) {
105
- Console.log("Checking for warnings...")
106
- warnings.forEach((warning, i) => Console.warning(`${i + 1}) ${warning.msg} ${warning.exercise ? `File: ${warning.exercise}` : ""}`))
107
- }
108
- resolve("SUCCESS")
109
- } else {
110
- reject("Failed")
111
- }
112
- })
113
- }
114
- }
@@ -1,16 +0,0 @@
1
- const chalk = require("chalk")
2
-
3
- module.exports = {
4
- // _debug: true,
5
- _debug: process.env.DEBUG == 'true',
6
- startDebug: function(){ this._debug = true; },
7
- log: (msg, ...args) => console.log(chalk.gray(msg), ...args),
8
- error: (msg, ...args) => console.log(chalk.red('⨉ '+msg), ...args),
9
- success: (msg, ...args) => console.log(chalk.green('✓ '+msg), ...args),
10
- info: (msg, ...args) => console.log(chalk.blue('ⓘ '+msg), ...args),
11
- help: (msg) => console.log(`${chalk.white.bold('⚠ help:')} ${chalk.white(msg)}`),
12
- warning: (msg) => console.log(`${chalk.yellow('⚠ warning:')} ${chalk.yellow(msg)}`),
13
- debug(...args){
14
- this._debug && console.log(chalk.magentaBright(`⚠ debug: `), args)
15
- }
16
- }
@@ -1,90 +0,0 @@
1
- const fetch = require("node-fetch");
2
- const Console = require("./console");
3
- let solutions = null;
4
- const uknown = {
5
- video: "https://www.youtube.com/watch?v=gD1Sa99GiE4",
6
- message: "Uknown internal error",
7
- slug: "uknown",
8
- gif: "https://github.com/breatheco-de/breathecode-cli/blob/master/docs/errors/uknown.gif?raw=true"
9
- };
10
-
11
- const getSolution = (slug=null) => {
12
-
13
- if(!slug) Console.debug(`Getting solution templates from the learnpack repository`);
14
- else Console.debug(`Getting solution for ${slug}`, solutions);
15
-
16
-
17
- if(!solutions){
18
- Console.debug("Fetching for errors.json on github");
19
- fetch('https://raw.githubusercontent.com/breatheco-de/breathecode-cli/master/docs/errors/errors.json')
20
- .then(r => r.json()).then(_s => solutions = _s);
21
- return uknown;
22
- }
23
- if(typeof solutions[slug] === "undefined" || !slug) return uknown;
24
- else return solutions[slug];
25
- }
26
- const ValidationError = (error) => {
27
- const message = error.message || error;
28
- const _err = new Error(message);
29
- _err.status = 400;
30
- _err.type = 'validation-error';
31
-
32
- const sol = getSolution(error.slug);
33
- _err.video = sol.video;
34
- _err.gif = sol.gif;
35
- _err.message = typeof message === "string" ? message : sol.message;
36
- return _err;
37
- }
38
- const NotFoundError = (error) => {
39
- const message = error.message || error;
40
- const _err = new Error(message);
41
- _err.status = 400;
42
- _err.type = 'not-found-error';
43
-
44
- const sol = getSolution(error.slug);
45
- _err.video = sol.video;
46
- _err.gif = sol.gif;
47
- _err.message = typeof message === "string" ? message : sol.message;
48
- return _err;
49
- }
50
- const CompilerError = (error) => {
51
- const message = error.message || error;
52
- const _err = new Error(message);
53
- _err.status = 400;
54
- _err.type = 'compiler-error';
55
-
56
- const sol = getSolution(error.slug);
57
- _err.video = sol.video;
58
- _err.gif = sol.gif;
59
- _err.message = typeof message === "string" ? message : sol.message;
60
- return _err;
61
- }
62
- const TestingError = (error) => {
63
- const message = error.message || error;
64
- const _err = new Error(message);
65
- _err.status = 400;
66
- _err.type = 'testing-error';
67
- return _err;
68
- }
69
- const AuthError = (error) => {
70
- const message = error.message || error;
71
- const _err = new Error(message);
72
- _err.status = 403;
73
- _err.type = 'auth-error';
74
- return _err;
75
- }
76
- const InternalError = (error) => {
77
- const message = error.message || error;
78
- const _err = new Error(message);
79
- _err.status = 500;
80
- _err.type = 'internal-error';
81
-
82
- const sol = getSolution(error.slug);
83
- _err.video = sol.video;
84
- _err.gif = sol.gif;
85
- _err.message = typeof message === "string" ? message : sol.message;
86
- return _err;
87
- }
88
-
89
- getSolution();
90
- module.exports = { ValidationError, CompilerError, TestingError, NotFoundError, InternalError, AuthError };
@@ -1,194 +0,0 @@
1
- const logger = require('../utils/console')
2
- const fs = require("fs")
3
- const em = require('events')
4
- const XXH = require('xxhashjs')
5
-
6
- // possible events to dispatch
7
- let events = {
8
- START_EXERCISE: "start_exercise",
9
- INIT: "initializing",
10
- RUNNING: "configuration_loaded",
11
- END: "connection_ended",
12
- RESET_EXERCISE: "reset_exercise",
13
- OPEN_FILES: "open_files",
14
- OPEN_WINDOW: "open_window",
15
- INSTRUCTIONS_CLOSED: "instructions_closed"
16
- }
17
-
18
- let options = {
19
- path: null,
20
- create: false
21
- }
22
- let lastHash = null
23
- let watcher = null // subscribe to file and listen to changes
24
- let actions = null // action queue
25
-
26
- const loadDispatcher = (opts) => {
27
-
28
- actions = [{ name: "initializing", time: now() }]
29
- logger.debug(`Loading from ${opts.path}`)
30
-
31
- let exists = fs.existsSync(opts.path);
32
- if(opts.create){
33
- if(exists) actions.push({ name: "reset", time: now() })
34
- fs.writeFileSync(opts.path, JSON.stringify(actions), { flag: "w"})
35
- exists = true
36
- }
37
-
38
- if(!exists) throw Error(`Invalid queue path, missing file at: ${opts.path}`)
39
-
40
- let incomingActions = []
41
- try{
42
- const content = fs.readFileSync(opts.path, 'utf-8')
43
- incomingActions = JSON.parse(content)
44
- if(!Array.isArray(incomingActions)) incomingActions = []
45
- }
46
- catch(error){
47
- incomingActions = []
48
- logger.debug(`Error loading VSCode Actions file`)
49
- }
50
-
51
- logger.debug(`Actions load `, incomingActions)
52
- return incomingActions
53
- }
54
-
55
-
56
- const enqueue = (name, data) => {
57
-
58
-
59
- if(!Object.values(events).includes(name)){
60
- logger.debug(`Invalid event ${name}`)
61
- throw Error(`Invalid action ${name}`)
62
- }
63
-
64
- if(!actions) actions = []
65
-
66
- actions.push({ name, time: now(), data: data })
67
- logger.debug(`EMIT -> ${name}:Exporting changes to ${options.path}`)
68
-
69
- return fs.writeFileSync(options.path, JSON.stringify(actions))
70
- }
71
- const now = () => {
72
- const hrTime = process.hrtime()
73
- return hrTime[0] * 1000000 + hrTime[1] / 1000
74
- }
75
- const loadFile = (filePath) => {
76
-
77
- if(!fs.existsSync(filePath)) throw Error(`No queue.json file to load on ${filePath}`);
78
-
79
- const content = fs.readFileSync(filePath, 'utf8')
80
- const newHash = XXH.h32( content, 0xABCD ).toString(16);
81
- const isUpdated = lastHash != newHash
82
- lastHash = newHash
83
- const incomingActions = JSON.parse(content)
84
- return { isUpdated, incomingActions }
85
- }
86
-
87
- const dequeue = () => {
88
-
89
- // first time dequeue loads
90
- if(!actions) actions = []
91
-
92
- const { isUpdated, incomingActions } = loadFile(options.path, 'utf8')
93
-
94
- if(!isUpdated){
95
-
96
- /**
97
- * make sure no tasks are executed from the queue by matching both
98
- * queues (the incoming with current one)
99
- */
100
- actions = incomingActions
101
- logger.debug(`No new actions to process: ${actions.length}/${incomingActions.length}`)
102
- return null
103
- }
104
-
105
- // do i need to reset actions to zero?
106
- if(actions.length > 0 && actions[0].time != incomingActions[0].time){
107
- actions = []
108
- }
109
-
110
- let action = incomingActions[actions.length]
111
- logger.debug("Dequeing action ", action)
112
- actions.push(action)
113
- return action
114
- }
115
-
116
- const pull = (callback) => {
117
- logger.debug("Pulling actions")
118
- let incoming = dequeue()
119
- while(incoming){
120
- callback(incoming)
121
- incoming = dequeue()
122
- }
123
- }
124
-
125
- const reset = (callback) => {
126
- logger.debug("Queue reseted")
127
- actions = []
128
- if(fs.existsSync(options.path)){
129
- const success = fs.writeFileSync(options.path, "[]")
130
- if(success) callback()
131
- }
132
- }
133
-
134
- const onPull = (callback) => {
135
-
136
- const chokidar = require('chokidar')
137
-
138
- logger.debug("Starting to listen...")
139
- try{
140
- loadFile(options.path)
141
- }catch{
142
- logger.debug("No previeues queue file, waiting for it to be created...")
143
- }
144
-
145
- if(!watcher){
146
- logger.debug(`Watching ${options.path}`)
147
- watcher = chokidar.watch(`${options.path}`, {
148
- persistent: true
149
- })
150
- }
151
- else logger.debug("Already watching queue path")
152
-
153
- watcher
154
- .on('add', path => pull(callback))
155
- .on('change', path => pull(callback))
156
-
157
- return true
158
- }
159
-
160
- const onReset = (callback) => {
161
-
162
- const chokidar = require('chokidar')
163
-
164
- if(!watcher){
165
- logger.debug(`Watching ${options.path}`)
166
- watcher = chokidar.watch(`${options.path}`, {
167
- persistent: true
168
- })
169
- }
170
-
171
- watcher.on('unlink', path => reset(callback))
172
-
173
- return true
174
- }
175
-
176
-
177
- module.exports = {
178
- events,
179
- dispatcher: (opts = {}) => {
180
- if(!actions){
181
- options = { ...options, ...opts }
182
- logger.debug("Initializing queue dispatcher", options)
183
- actions = loadDispatcher(options)
184
- }
185
- return { enqueue, events }
186
- },
187
- listener: (opts = {}) => {
188
- if(!actions){
189
- options = { ...options, ...opts }
190
- logger.debug("Initializing queue listener", options)
191
- }
192
- return { onPull, onReset, events }
193
- }
194
- }
package/src/utils/misc.js DELETED
@@ -1,26 +0,0 @@
1
- const prioritizeHTMLFile = (entryFiles) => {
2
- let files = [];
3
-
4
- // Find the html file and put it as latest in the files array
5
- // in order to keep the html file opened in vscode plugin
6
- const index = entryFiles.findIndex((file) => {
7
- return /.*\.html$/.test(file);
8
- });
9
-
10
- if (index !== -1) {
11
- for (let i = 0; i < entryFiles.length; i++) {
12
- if (i !== index) {
13
- files.push(entryFiles[i]);
14
- }
15
- }
16
- files.push(entryFiles[index]);
17
- } else {
18
- files = entryFiles;
19
- }
20
-
21
- return files;
22
- };
23
-
24
- module.exports = {
25
- prioritizeHTMLFile,
26
- };
@@ -1,15 +0,0 @@
1
-
2
- function validURL(str) {
3
-
4
- if(!str || !str.includes("github.com")) return false;
5
-
6
- var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
7
- '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
8
- '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
9
- '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
10
- '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
11
- '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
12
- return !!pattern.test(str);
13
- }
14
-
15
- module.exports = { validURL }
@@ -1,24 +0,0 @@
1
- const chokidar = require('chokidar');
2
- const debounce = require('debounce');
3
-
4
- module.exports = (path) => new Promise((resolve, reject) => {
5
- const watcher = chokidar.watch(path, {
6
- ignored: (_path, _stats) => {
7
- return _stats && !_stats.isDirectory();
8
- },
9
- persistent: true,
10
- depth: 1,
11
- ignoreInitial: true
12
- });
13
-
14
- const onChange = (eventname, filename) =>{
15
- resolve(eventname, filename);
16
- }
17
- watcher.on('all', debounce(onChange, 500, true));
18
- // watcher.on('all', onChange);
19
-
20
- process.on('SIGINT', function() {
21
- watcher.close();
22
- process.exit();
23
- });
24
- });