@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.
- package/README.md +51 -398
- package/bin/run +14 -2
- package/oclif.manifest.json +1 -1
- package/package.json +135 -111
- package/src/commands/audit.ts +462 -0
- package/src/commands/clean.ts +29 -0
- package/src/commands/download.ts +62 -0
- package/src/commands/init.ts +169 -0
- package/src/commands/login.ts +42 -0
- package/src/commands/logout.ts +43 -0
- package/src/commands/publish.ts +107 -0
- package/src/commands/start.ts +229 -0
- package/src/commands/{test.js → test.ts} +19 -21
- package/src/index.ts +1 -0
- package/src/managers/config/allowed_files.ts +29 -0
- package/src/managers/config/defaults.ts +33 -0
- package/src/managers/config/exercise.ts +295 -0
- package/src/managers/config/index.ts +411 -0
- package/src/managers/file.ts +169 -0
- package/src/managers/gitpod.ts +84 -0
- package/src/managers/server/{index.js → index.ts} +26 -19
- package/src/managers/server/routes.ts +250 -0
- package/src/managers/session.ts +118 -0
- package/src/managers/socket.ts +239 -0
- package/src/managers/test.ts +83 -0
- package/src/models/action.ts +3 -0
- package/src/models/audit-errors.ts +4 -0
- package/src/models/config-manager.ts +23 -0
- package/src/models/config.ts +74 -0
- package/src/models/counter.ts +11 -0
- package/src/models/errors.ts +22 -0
- package/src/models/exercise-obj.ts +26 -0
- package/src/models/file.ts +5 -0
- package/src/models/findings.ts +18 -0
- package/src/models/flags.ts +10 -0
- package/src/models/front-matter.ts +11 -0
- package/src/models/gitpod-data.ts +19 -0
- package/src/models/language.ts +4 -0
- package/src/models/package.ts +7 -0
- package/src/models/plugin-config.ts +17 -0
- package/src/models/session.ts +26 -0
- package/src/models/socket.ts +48 -0
- package/src/models/status.ts +15 -0
- package/src/models/success-types.ts +1 -0
- package/src/plugin/command/compile.ts +17 -0
- package/src/plugin/command/test.ts +30 -0
- package/src/plugin/index.ts +6 -0
- package/src/plugin/plugin.ts +94 -0
- package/src/plugin/utils.ts +87 -0
- package/src/types/node-fetch.d.ts +1 -0
- package/src/ui/download.ts +71 -0
- package/src/utils/BaseCommand.ts +48 -0
- package/src/utils/SessionCommand.ts +48 -0
- package/src/utils/api.ts +194 -0
- package/src/utils/audit.ts +162 -0
- package/src/utils/console.ts +24 -0
- package/src/utils/errors.ts +117 -0
- package/src/utils/{exercisesQueue.js → exercisesQueue.ts} +12 -6
- package/src/utils/fileQueue.ts +198 -0
- package/src/utils/misc.ts +23 -0
- package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +2 -4
- package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +1 -2
- package/src/utils/templates/isolated/01-hello-world/README.es.md +1 -2
- package/src/utils/templates/isolated/01-hello-world/README.md +1 -2
- package/src/utils/templates/isolated/README.ejs +1 -1
- package/src/utils/templates/isolated/README.es.ejs +1 -1
- package/src/utils/validators.ts +18 -0
- package/src/utils/watcher.ts +27 -0
- package/plugin/command/compile.js +0 -17
- package/plugin/command/test.js +0 -29
- package/plugin/index.js +0 -6
- package/plugin/plugin.js +0 -71
- package/plugin/utils.js +0 -78
- package/src/commands/audit.js +0 -243
- package/src/commands/clean.js +0 -27
- package/src/commands/download.js +0 -52
- package/src/commands/hello.js +0 -20
- package/src/commands/init.js +0 -133
- package/src/commands/login.js +0 -45
- package/src/commands/logout.js +0 -39
- package/src/commands/publish.js +0 -78
- package/src/commands/start.js +0 -169
- package/src/index.js +0 -1
- package/src/managers/config/allowed_files.js +0 -12
- package/src/managers/config/defaults.js +0 -32
- package/src/managers/config/exercise.js +0 -212
- package/src/managers/config/index.js +0 -342
- package/src/managers/file.js +0 -137
- package/src/managers/server/routes.js +0 -151
- package/src/managers/session.js +0 -83
- package/src/managers/socket.js +0 -185
- package/src/managers/test.js +0 -77
- package/src/ui/download.js +0 -48
- package/src/utils/BaseCommand.js +0 -34
- package/src/utils/SessionCommand.js +0 -46
- package/src/utils/api.js +0 -164
- package/src/utils/audit.js +0 -114
- package/src/utils/console.js +0 -16
- package/src/utils/errors.js +0 -90
- package/src/utils/fileQueue.js +0 -194
- package/src/utils/misc.js +0 -26
- package/src/utils/validators.js +0 -15
- package/src/utils/watcher.js +0 -24
package/plugin/utils.js
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
const chalk = require("chalk")
|
2
|
-
|
3
|
-
const getMatches = (reg, content) => {
|
4
|
-
let inputs = [];
|
5
|
-
let m;
|
6
|
-
while ((m = reg.exec(content)) !== null) {
|
7
|
-
// This is necessary to avoid infinite loops with zero-width matches
|
8
|
-
if (m.index === reg.lastIndex) reg.lastIndex++;
|
9
|
-
|
10
|
-
// The result can be accessed through the `m`-variable.
|
11
|
-
inputs.push(m[1] || null);
|
12
|
-
}
|
13
|
-
return inputs;
|
14
|
-
}
|
15
|
-
|
16
|
-
const cleanStdout = (buffer, inputs) => {
|
17
|
-
if(Array.isArray(inputs))
|
18
|
-
for(let i = 0; i < inputs.length; i++)
|
19
|
-
if(inputs[i]) buffer = buffer.replace(inputs[i],'');
|
20
|
-
|
21
|
-
return buffer;
|
22
|
-
}
|
23
|
-
|
24
|
-
const indent = (string, count = 1, options) => {
|
25
|
-
options = {
|
26
|
-
indent: ' ',
|
27
|
-
includeEmptyLines: false,
|
28
|
-
...options
|
29
|
-
};
|
30
|
-
|
31
|
-
if (typeof string !== 'string') {
|
32
|
-
throw new TypeError(
|
33
|
-
`Expected \`input\` to be a \`string\`, got \`${typeof string}\``
|
34
|
-
);
|
35
|
-
}
|
36
|
-
|
37
|
-
if (typeof count !== 'number') {
|
38
|
-
throw new TypeError(
|
39
|
-
`Expected \`count\` to be a \`number\`, got \`${typeof count}\``
|
40
|
-
);
|
41
|
-
}
|
42
|
-
|
43
|
-
if (count < 0) {
|
44
|
-
throw new RangeError(
|
45
|
-
`Expected \`count\` to be at least 0, got \`${count}\``
|
46
|
-
);
|
47
|
-
}
|
48
|
-
|
49
|
-
if (typeof options.indent !== 'string') {
|
50
|
-
throw new TypeError(
|
51
|
-
`Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\``
|
52
|
-
);
|
53
|
-
}
|
54
|
-
|
55
|
-
if (count === 0) {
|
56
|
-
return string;
|
57
|
-
}
|
58
|
-
|
59
|
-
const regex = options.includeEmptyLines ? /^/gm : /^(?!\s*$)/gm;
|
60
|
-
|
61
|
-
return string.replace(regex, options.indent.repeat(count));
|
62
|
-
};
|
63
|
-
|
64
|
-
const Console = {
|
65
|
-
// _debug: true,
|
66
|
-
_debug: process.env.DEBUG == 'true',
|
67
|
-
startDebug: function(){ this._debug = true; },
|
68
|
-
log: (msg, ...args) => console.log(chalk.gray(msg), ...args),
|
69
|
-
error: (msg, ...args) => console.log(chalk.red('⨉ '+msg), ...args),
|
70
|
-
success: (msg, ...args) => console.log(chalk.green('✓ '+msg), ...args),
|
71
|
-
info: (msg, ...args) => console.log(chalk.blue('ⓘ '+msg), ...args),
|
72
|
-
help: (msg) => console.log(`${chalk.white.bold('⚠ help:')} ${chalk.white(msg)}`),
|
73
|
-
debug(...args){
|
74
|
-
this._debug && console.log(chalk.magentaBright(`⚠ debug: `), args)
|
75
|
-
}
|
76
|
-
}
|
77
|
-
|
78
|
-
module.exports = { getMatches, cleanStdout, indent, Console };
|
package/src/commands/audit.js
DELETED
@@ -1,243 +0,0 @@
|
|
1
|
-
const fs = require('fs')
|
2
|
-
const fetch = require('node-fetch')
|
3
|
-
const {validateExerciseDirectoryName} = require('../managers/config/exercise.js')
|
4
|
-
const { flags } = require('@oclif/command')
|
5
|
-
const Console = require('../utils/console')
|
6
|
-
const { isUrl, findInFile, checkLearnpackClean, checkForEmptySpaces, showErrors, showWarnings } = require('../utils/audit')
|
7
|
-
const SessionCommand = require('../utils/SessionCommand');
|
8
|
-
const fm = require("front-matter")
|
9
|
-
const path = require('path')
|
10
|
-
|
11
|
-
class AuditCommand extends SessionCommand {
|
12
|
-
async init() {
|
13
|
-
const { flags } = this.parse(AuditCommand)
|
14
|
-
await this.initSession(flags)
|
15
|
-
}
|
16
|
-
async run() {
|
17
|
-
const { flags } = this.parse(AuditCommand)
|
18
|
-
|
19
|
-
Console.log("Running command audit...")
|
20
|
-
|
21
|
-
// Get configuration object.
|
22
|
-
const config = this.configManager.get();
|
23
|
-
|
24
|
-
let errors = []
|
25
|
-
let warnings = []
|
26
|
-
let counter = {
|
27
|
-
images: {
|
28
|
-
error: 0,
|
29
|
-
total: 0,
|
30
|
-
},
|
31
|
-
links: {
|
32
|
-
error: 0,
|
33
|
-
total: 0,
|
34
|
-
},
|
35
|
-
exercises: 0,
|
36
|
-
readmeFiles: 0
|
37
|
-
}
|
38
|
-
|
39
|
-
// Checks if learnpack clean has been run
|
40
|
-
checkLearnpackClean(config, errors)
|
41
|
-
|
42
|
-
// Build exercises if they are not built yet.
|
43
|
-
if (!this.configManager.get().exercises) this.configManager.buildIndex()
|
44
|
-
|
45
|
-
// Check if the exercises folder has some files within any ./exercise
|
46
|
-
const exercisesPath = config.config.exercisesPath
|
47
|
-
fs.readdir(exercisesPath, (err, files) => {
|
48
|
-
if (err) {
|
49
|
-
return console.log('Unable to scan directory: ' + err);
|
50
|
-
}
|
51
|
-
//listing all files using forEach
|
52
|
-
files.forEach(function (file) {
|
53
|
-
// Do whatever you want to do with the file
|
54
|
-
let filePath = path.join(exercisesPath, file)
|
55
|
-
if (fs.statSync(filePath).isFile()) warnings.push({ exercise: file, msg: `This file is not inside any exercise folder.` })
|
56
|
-
});
|
57
|
-
})
|
58
|
-
|
59
|
-
// This function checks that each of the url's are working.
|
60
|
-
const checkUrl = async (file, exercise) => {
|
61
|
-
if (!fs.existsSync(file.path)) return false
|
62
|
-
const content = fs.readFileSync(file.path).toString();
|
63
|
-
let isEmpty = checkForEmptySpaces(content);
|
64
|
-
if (isEmpty === true || content == false) errors.push({ exercise: exercise.title, msg: `This file (${file.name}) doesn't have any content inside.` })
|
65
|
-
const frontmatter = fm(content).attributes
|
66
|
-
for (const attribute in frontmatter) {
|
67
|
-
if (attribute === "intro" || attribute === "tutorial") {
|
68
|
-
counter.links.total++
|
69
|
-
try {
|
70
|
-
let res = await fetch(frontmatter[attribute], { method: "HEAD" });
|
71
|
-
if (!res.ok) {
|
72
|
-
counter.links.error++;
|
73
|
-
errors.push({ exercise: exercise.title, msg: `This link is broken (${res.ok}): ${frontmatter[attribute]}` })
|
74
|
-
}
|
75
|
-
}
|
76
|
-
catch (error) {
|
77
|
-
counter.links.error++;
|
78
|
-
errors.push({ exercise: exercise.title, msg: `This link is broken: ${frontmatter[attribute]}` })
|
79
|
-
}
|
80
|
-
}
|
81
|
-
}
|
82
|
-
|
83
|
-
// Check url's of each README file.
|
84
|
-
const findings = findInFile(["relative_images", "external_images", "markdown_links"], content);
|
85
|
-
for (const finding in findings) {
|
86
|
-
let obj = findings[finding];
|
87
|
-
// Valdites all the relative path images.
|
88
|
-
if (finding === "relative_images" && Object.keys(obj).length > 0) {
|
89
|
-
for (const img in obj) {
|
90
|
-
// Validates if the image is in the assets folder.
|
91
|
-
counter.images.total++
|
92
|
-
let relativePath = path.relative(exercise.path.replace(/\\/gm, "/"), `${config.config.dirPath}/assets/${obj[img].relUrl}`).replace(/\\/gm, "/")
|
93
|
-
if (relativePath != obj[img].absUrl.split("?").shift()) {
|
94
|
-
counter.images.error++;
|
95
|
-
errors.push({ exercise: exercise.title, msg: `This relative path (${obj[img].relUrl}) is not pointing to the assets folder.` })
|
96
|
-
}
|
97
|
-
if (!fs.existsSync(`${config.config.dirPath}/assets/${obj[img].relUrl}`)) {
|
98
|
-
counter.images.error++;
|
99
|
-
errors.push({ exercise: exercise.title, msg: `The file ${obj[img].relUrl} doesn't exist in the assets folder.` })
|
100
|
-
}
|
101
|
-
}
|
102
|
-
} else if (finding === "external_images" && Object.keys(obj).length > 0) {
|
103
|
-
// Valdites all the aboslute path images.
|
104
|
-
for (const img in obj) {
|
105
|
-
counter.images.total++
|
106
|
-
try {
|
107
|
-
let res = await fetch(obj[img].absUrl, { method: "HEAD" });
|
108
|
-
if (!res.ok) {
|
109
|
-
counter.images.error++;
|
110
|
-
errors.push({ exercise: exercise.title, msg: `This link is broken: ${obj[img].absUrl}` })
|
111
|
-
}
|
112
|
-
}
|
113
|
-
catch (error) {
|
114
|
-
counter.images.error++;
|
115
|
-
errors.push({ exercise: exercise.title, msg: `This link is broken: ${obj[img].absUrl}` })
|
116
|
-
}
|
117
|
-
}
|
118
|
-
} else if (finding === "markdown_links" && Object.keys(obj).length > 0) {
|
119
|
-
for (const link in obj) {
|
120
|
-
counter.links.total++
|
121
|
-
try {
|
122
|
-
let res = await fetch(obj[link].mdUrl, { method: "HEAD" });
|
123
|
-
if (res.status > 399 && res.status < 200) {
|
124
|
-
counter.links.error++;
|
125
|
-
errors.push({ exercise: exercise.title, msg: `This link is broken: ${obj[link].mdUrl}` })
|
126
|
-
}
|
127
|
-
}
|
128
|
-
catch (error) {
|
129
|
-
counter.links.error++;
|
130
|
-
errors.push({ exercise: exercise.title, msg: `This link is broken: ${obj[link].mdUrl}` })
|
131
|
-
}
|
132
|
-
}
|
133
|
-
}
|
134
|
-
}
|
135
|
-
return true
|
136
|
-
}
|
137
|
-
|
138
|
-
// This function is being created because the find method doesn't work with promises.
|
139
|
-
const find = async (file, lang, exercise) => {
|
140
|
-
if (file.name === lang) {
|
141
|
-
await checkUrl(file, exercise)
|
142
|
-
return true
|
143
|
-
}
|
144
|
-
return false
|
145
|
-
}
|
146
|
-
|
147
|
-
Console.info(' Checking if the config file is fine...')
|
148
|
-
// These two lines check if the 'slug' property is inside the configuration object.
|
149
|
-
Console.debug("Checking if the slug property is inside the configuration object...")
|
150
|
-
if (!config.config.slug) errors.push({ exercise: null, msg: "The slug property is not in the configuration object" })
|
151
|
-
|
152
|
-
// These two lines check if the 'repository' property is inside the configuration object.
|
153
|
-
Console.debug("Checking if the repository property is inside the configuration object...")
|
154
|
-
if (!config.config.repository) errors.push({ exercise: null, msg: "The repository property is not in the configuration object" })
|
155
|
-
else isUrl(config.config.repository, errors, counter)
|
156
|
-
|
157
|
-
// These two lines check if the 'description' property is inside the configuration object.
|
158
|
-
Console.debug("Checking if the description property is inside the configuration object...")
|
159
|
-
if (!config.config.description) errors.push({ exercise: null, msg: "The description property is not in the configuration object" })
|
160
|
-
|
161
|
-
if (errors.length == 0) Console.log("The config file is ok")
|
162
|
-
|
163
|
-
// Validates if images and links are working at every README file.
|
164
|
-
const exercises = config.exercises
|
165
|
-
let readmeFiles = []
|
166
|
-
|
167
|
-
if (exercises.length > 0) {
|
168
|
-
Console.info(' Checking if the images are working...')
|
169
|
-
for (const index in exercises) {
|
170
|
-
let exercise = exercises[index]
|
171
|
-
if (!validateExerciseDirectoryName(exercise.title)) errors.push({exercise: exercise.title, msg: `The exercise ${exercise.title} has an invalid name.`})
|
172
|
-
let readmeFilesCount = { exercise: exercise.title, count: 0 };
|
173
|
-
if (Object.keys(exercise.translations).length == 0) errors.push({ exercise: exercise.title, msg: `The exercise ${exercise.title} doesn't have a README.md file.` })
|
174
|
-
|
175
|
-
if (exercise.language == "python3" || exercise.language == "python") {
|
176
|
-
exercise.files.map(f => f).find(f => {
|
177
|
-
if (f.path.includes('test.py') || f.path.includes('tests.py')) {
|
178
|
-
const content = fs.readFileSync(f.path).toString();
|
179
|
-
let isEmpty = checkForEmptySpaces(content);
|
180
|
-
if (isEmpty === true || content == false) errors.push({ exercise: exercise.title, msg: `This file (${f.name}) doesn't have any content inside.` })
|
181
|
-
}
|
182
|
-
});
|
183
|
-
}
|
184
|
-
else {
|
185
|
-
exercise.files.map(f => f).find(f => {
|
186
|
-
if (f.path.includes('test.js') || f.path.includes('tests.js')) {
|
187
|
-
const content = fs.readFileSync(f.path).toString();
|
188
|
-
let isEmpty = checkForEmptySpaces(content);
|
189
|
-
if (isEmpty === true || content == false) errors.push({ exercise: exercise.title, msg: `This file (${f.name}) doesn't have any content inside.` })
|
190
|
-
}
|
191
|
-
});
|
192
|
-
}
|
193
|
-
|
194
|
-
for (const lang in exercise.translations) {
|
195
|
-
let files = []
|
196
|
-
for (const file of exercise.files) {
|
197
|
-
let found = await find(file, exercise.translations[lang], exercise)
|
198
|
-
if (found == true) readmeFilesCount = { ...readmeFilesCount, count: readmeFilesCount.count + 1 }
|
199
|
-
files.push(found)
|
200
|
-
}
|
201
|
-
if (!files.includes(true)) errors.push({ exercise: exercise.title, msg: `This exercise doesn't have a README.md file.` })
|
202
|
-
|
203
|
-
}
|
204
|
-
readmeFiles.push(readmeFilesCount)
|
205
|
-
}
|
206
|
-
} else errors.push({ exercise: null, msg: "The exercises array is empty." })
|
207
|
-
|
208
|
-
Console.log(`${counter.images.total - counter.images.error} images ok from ${counter.images.total}`)
|
209
|
-
|
210
|
-
Console.info(" Checking if important files are missing... (README's, translations, gitignore...)")
|
211
|
-
// Check if all the exercises has the same ammount of README's, this way we can check if they have the same ammount of translations.
|
212
|
-
let files = [];
|
213
|
-
readmeFiles.map((item, i, arr) => {
|
214
|
-
if (item.count !== arr[0].count) files.push(` ${item.exercise}`)
|
215
|
-
})
|
216
|
-
if (files.length > 0) {
|
217
|
-
files = files.join()
|
218
|
-
warnings.push({ exercise: null, msg: `These exercises are missing translations: ${files}` })
|
219
|
-
}
|
220
|
-
|
221
|
-
// Checks if the .gitignore file exists.
|
222
|
-
if (!fs.existsSync(`.gitignore`)) warnings.push(".gitignore file doesn't exist")
|
223
|
-
|
224
|
-
counter.exercises = exercises.length;
|
225
|
-
readmeFiles.forEach((readme) => {
|
226
|
-
counter.readmeFiles += readme.count
|
227
|
-
})
|
228
|
-
|
229
|
-
await showWarnings(warnings)
|
230
|
-
await showErrors(errors, counter)
|
231
|
-
}
|
232
|
-
}
|
233
|
-
|
234
|
-
AuditCommand.description = `Check if the configuration object has slug, description and repository property
|
235
|
-
...
|
236
|
-
Extra documentation goes here
|
237
|
-
`
|
238
|
-
|
239
|
-
AuditCommand.flags = {
|
240
|
-
// name: flags.string({char: 'n', description: 'name to print'}),
|
241
|
-
}
|
242
|
-
|
243
|
-
module.exports = AuditCommand
|
package/src/commands/clean.js
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
const {flags} = require('@oclif/command')
|
2
|
-
const Console = require('../utils/console')
|
3
|
-
const SessionCommand = require('../utils/SessionCommand')
|
4
|
-
class CleanCommand extends SessionCommand {
|
5
|
-
async init() {
|
6
|
-
const {flags} = this.parse(CleanCommand)
|
7
|
-
await this.initSession(flags)
|
8
|
-
}
|
9
|
-
async run() {
|
10
|
-
const {flags} = this.parse(CleanCommand)
|
11
|
-
|
12
|
-
this.configManager.clean()
|
13
|
-
|
14
|
-
Console.success("Package cleaned successfully, ready to publish")
|
15
|
-
}
|
16
|
-
}
|
17
|
-
|
18
|
-
CleanCommand.description = `Clean the configuration object
|
19
|
-
...
|
20
|
-
Extra documentation goes here
|
21
|
-
`
|
22
|
-
|
23
|
-
CleanCommand.flags = {
|
24
|
-
// name: flags.string({char: 'n', description: 'name to print'}),
|
25
|
-
}
|
26
|
-
|
27
|
-
module.exports = CleanCommand
|
package/src/commands/download.js
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
const {Command, flags} = require('@oclif/command')
|
2
|
-
const fetch = require('node-fetch');
|
3
|
-
const { clone } = require('../managers/file.js')
|
4
|
-
const Console = require('../utils/console')
|
5
|
-
const api = require('../utils/api')
|
6
|
-
const getRepoInfo = require('git-repo-info')
|
7
|
-
const { askPackage } = require('../ui/download')
|
8
|
-
|
9
|
-
class DownloadCommand extends Command {
|
10
|
-
// async init() {
|
11
|
-
// const {flags} = this.parse(DownloadCommand)
|
12
|
-
// await this.initSession(flags)
|
13
|
-
// }
|
14
|
-
async run() {
|
15
|
-
const {flags, args} = this.parse(DownloadCommand)
|
16
|
-
// start watching for file changes
|
17
|
-
let _package = args.package
|
18
|
-
if(!_package) _package = await askPackage()
|
19
|
-
|
20
|
-
if(!_package) return null
|
21
|
-
|
22
|
-
try{
|
23
|
-
_package = _package.pack;
|
24
|
-
clone(_package.repository)
|
25
|
-
.then(result => {
|
26
|
-
Console.success(`Successfully downloaded`)
|
27
|
-
const folder = _package.repository.substring(_package.repository.lastIndexOf('/') + 1).split(".")[0];
|
28
|
-
Console.info(`You can now CD into the folder like this: $ cd ${folder}`)
|
29
|
-
})
|
30
|
-
.catch(error => Console.error(error.message || error))
|
31
|
-
}
|
32
|
-
catch(error){}
|
33
|
-
}
|
34
|
-
}
|
35
|
-
|
36
|
-
DownloadCommand.description = `Describe the command here
|
37
|
-
...
|
38
|
-
Extra documentation goes here
|
39
|
-
`
|
40
|
-
DownloadCommand.flags = {
|
41
|
-
// name: flags.string({char: 'n', description: 'name to print'}),
|
42
|
-
}
|
43
|
-
DownloadCommand.args =[
|
44
|
-
{
|
45
|
-
name: 'package', // name of arg to show in help and reference with args[name]
|
46
|
-
required: false, // make the arg required with `required: true`
|
47
|
-
description: 'The unique string that identifies this package on learnpack', // help description
|
48
|
-
hidden: false // hide this arg from help
|
49
|
-
}
|
50
|
-
]
|
51
|
-
|
52
|
-
module.exports = DownloadCommand
|
package/src/commands/hello.js
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
const {Command, flags} = require('@oclif/command')
|
2
|
-
|
3
|
-
class HelloCommand extends Command {
|
4
|
-
async run() {
|
5
|
-
const {flags} = this.parse(HelloCommand)
|
6
|
-
const name = flags.name || 'world'
|
7
|
-
this.log(`hello ${name} from ./src/commands/hello.js`)
|
8
|
-
}
|
9
|
-
}
|
10
|
-
|
11
|
-
HelloCommand.description = `Describe the command here
|
12
|
-
...
|
13
|
-
Extra documentation goes here
|
14
|
-
`
|
15
|
-
|
16
|
-
HelloCommand.flags = {
|
17
|
-
name: flags.string({char: 'n', description: 'name to print'}),
|
18
|
-
}
|
19
|
-
|
20
|
-
module.exports = HelloCommand
|
package/src/commands/init.js
DELETED
@@ -1,133 +0,0 @@
|
|
1
|
-
const {flags} = require('@oclif/command')
|
2
|
-
const BaseCommand = require('../utils/BaseCommand')
|
3
|
-
|
4
|
-
const fs = require('fs-extra')
|
5
|
-
const prompts = require('prompts')
|
6
|
-
const cli = require("cli-ux").default
|
7
|
-
const eta = require("eta")
|
8
|
-
|
9
|
-
const Console = require('../utils/console')
|
10
|
-
const { ValidationError } = require('../utils/errors')
|
11
|
-
let defaults = require('../managers/config/defaults.js')
|
12
|
-
|
13
|
-
const path = require('path')
|
14
|
-
const { resolve } = require('path')
|
15
|
-
|
16
|
-
class InitComand extends BaseCommand {
|
17
|
-
async run() {
|
18
|
-
const {flags} = this.parse(InitComand)
|
19
|
-
|
20
|
-
try{
|
21
|
-
// if the folder/file .learn or .breathecode aleady exists
|
22
|
-
await alreadyInitialized();
|
23
|
-
}
|
24
|
-
catch(error){
|
25
|
-
Console.error(error.message)
|
26
|
-
return false
|
27
|
-
}
|
28
|
-
|
29
|
-
let choices = await prompts([
|
30
|
-
{
|
31
|
-
type: 'select',
|
32
|
-
name: 'grading',
|
33
|
-
message: 'Is the auto-grading going to be isolated or incremental?',
|
34
|
-
choices: [
|
35
|
-
{ title: 'Incremental: Build on top of each other like a tutorial', value: 'incremental' },
|
36
|
-
{ title: 'Isolated: Small isolated exercises', value: 'isolated' },
|
37
|
-
{ title: 'No grading: No feedback or testing whatsoever', value: null },
|
38
|
-
],
|
39
|
-
},{
|
40
|
-
type: 'text',
|
41
|
-
name: 'title',
|
42
|
-
initial: 'My Interactive Tutorial',
|
43
|
-
message: 'Title for your tutorial? Press enter to leave as it is'
|
44
|
-
},{
|
45
|
-
type: 'text',
|
46
|
-
name: 'description',
|
47
|
-
initial: '',
|
48
|
-
message: 'Description for your tutorial? Press enter to leave blank'
|
49
|
-
},{
|
50
|
-
type: 'select',
|
51
|
-
name: 'difficulty',
|
52
|
-
message: 'How difficulty will be to complete the tutorial?',
|
53
|
-
choices: [
|
54
|
-
{ title: 'Begginer (no previous experience)', value: 'beginner' },
|
55
|
-
{ title: 'Easy (just a bit of experience required)', value: 'easy' },
|
56
|
-
{ title: 'Intermediate (you need experience)', value: 'intermediate' },
|
57
|
-
{ title: 'Hard (master the topic)', value: 'hard' },
|
58
|
-
],
|
59
|
-
},{
|
60
|
-
type: 'text',
|
61
|
-
name: 'duration',
|
62
|
-
initial: "1",
|
63
|
-
message: 'How many hours avg it takes to complete (number)?',
|
64
|
-
validate: value => {
|
65
|
-
var n = Math.floor(Number(value))
|
66
|
-
return n !== Infinity && String(n) === value && n >= 0
|
67
|
-
}
|
68
|
-
}
|
69
|
-
])
|
70
|
-
|
71
|
-
const packageInfo = {
|
72
|
-
...defaults.config,
|
73
|
-
grading: choices.grading,
|
74
|
-
difficulty: choices.difficulty,
|
75
|
-
duration: parseInt(choices.duration),
|
76
|
-
description: choices.description,
|
77
|
-
title: choices.title,
|
78
|
-
slug: choices.title.toLowerCase().replace(/ /g,'-').replace(/[^\w-]+/g,'')
|
79
|
-
}
|
80
|
-
|
81
|
-
cli.action.start('Initializing package')
|
82
|
-
|
83
|
-
const languages = ['en', 'es']
|
84
|
-
|
85
|
-
try{
|
86
|
-
const templatesDir = path.resolve(__dirname,"../utils/templates/"+choices.grading || "no-grading")
|
87
|
-
if(!fs.existsSync(templatesDir)) throw ValidationError(`Template ${templatesDir} does not exists`)
|
88
|
-
await fs.copySync(templatesDir, './')
|
89
|
-
|
90
|
-
// Creating README files
|
91
|
-
languages.forEach((language) => {
|
92
|
-
const readmeFilename = `README${language !== 'en' ? `.${language}` : ''}`
|
93
|
-
fs.writeFileSync(`./${readmeFilename}.md`, eta.render(fs.readFileSync(path.resolve(__dirname,`${templatesDir}/${readmeFilename}.ejs`),'utf-8'), packageInfo))
|
94
|
-
if(fs.existsSync(`./${readmeFilename}.ejs`)) fs.removeSync(`./${readmeFilename}.ejs`)
|
95
|
-
})
|
96
|
-
|
97
|
-
if(!fs.existsSync('./.gitignore')) fs.copyFile(path.resolve(__dirname,'../utils/templates/gitignore.txt'), './.gitignore')
|
98
|
-
fs.writeFileSync('./learn.json', JSON.stringify(packageInfo, null, 2))
|
99
|
-
}
|
100
|
-
catch(error){
|
101
|
-
Console.error(error.message || error)
|
102
|
-
return false
|
103
|
-
}
|
104
|
-
|
105
|
-
cli.action.stop()
|
106
|
-
Console.success(`😋 Package initialized successfully`)
|
107
|
-
Console.help(`Start the exercises by running the following command on your terminal: $ learnpack start`)
|
108
|
-
|
109
|
-
}
|
110
|
-
}
|
111
|
-
|
112
|
-
InitComand.description = 'Create a new learning package: Book, Tutorial or Exercise'
|
113
|
-
InitComand.flags = {
|
114
|
-
...BaseCommand.flags,
|
115
|
-
grading: flags.help({char:'h'}),
|
116
|
-
}
|
117
|
-
|
118
|
-
const alreadyInitialized = () => new Promise((resolve, reject) => {
|
119
|
-
fs.readdir('./', function(err, files) {
|
120
|
-
files = files.filter(f => ['.learn', 'learn.json', 'bc.json', '.breathecode', '.gitignore'].includes(f))
|
121
|
-
if (err) {
|
122
|
-
reject(ValidationError(err.message))
|
123
|
-
return true
|
124
|
-
} else if (files.length > 0){
|
125
|
-
reject(ValidationError("It seems the package is already initialized because we've found the following files: "+files.join(',')))
|
126
|
-
return true
|
127
|
-
}
|
128
|
-
|
129
|
-
resolve(false)
|
130
|
-
})
|
131
|
-
})
|
132
|
-
|
133
|
-
module.exports = InitComand
|
package/src/commands/login.js
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
const {Command, flags} = require('@oclif/command')
|
2
|
-
const { prompt } = require("enquirer")
|
3
|
-
const fetch = require('node-fetch');
|
4
|
-
const SessionCommand = require('../utils/SessionCommand')
|
5
|
-
const SessionManager = require('../managers/session.js')
|
6
|
-
const Console = require('../utils/console');
|
7
|
-
const { replace } = require('node-emoji');
|
8
|
-
const { validURL } = require("../utils/validators")
|
9
|
-
// const BaseCommand = require('../utils/BaseCommand');
|
10
|
-
|
11
|
-
class PublishCommand extends SessionCommand {
|
12
|
-
async init() {
|
13
|
-
const {flags} = this.parse(PublishCommand)
|
14
|
-
await this.initSession(flags)
|
15
|
-
}
|
16
|
-
async run() {
|
17
|
-
const {flags, args} = this.parse(PublishCommand)
|
18
|
-
|
19
|
-
try{
|
20
|
-
await SessionManager.login();
|
21
|
-
}
|
22
|
-
catch(error){
|
23
|
-
Console.error("Error trying to authenticate")
|
24
|
-
Console.error(error.message || error)
|
25
|
-
}
|
26
|
-
}
|
27
|
-
}
|
28
|
-
|
29
|
-
PublishCommand.description = `Describe the command here
|
30
|
-
...
|
31
|
-
Extra documentation goes here
|
32
|
-
`
|
33
|
-
PublishCommand.flags = {
|
34
|
-
// name: flags.string({char: 'n', description: 'name to print'}),
|
35
|
-
}
|
36
|
-
PublishCommand.args =[
|
37
|
-
{
|
38
|
-
name: 'package', // name of arg to show in help and reference with args[name]
|
39
|
-
required: false, // make the arg required with `required: true`
|
40
|
-
description: 'The unique string that identifies this package on learnpack', // help description
|
41
|
-
hidden: false // hide this arg from help
|
42
|
-
}
|
43
|
-
]
|
44
|
-
|
45
|
-
module.exports = PublishCommand
|
package/src/commands/logout.js
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
const {Command, flags} = require('@oclif/command')
|
2
|
-
const { prompt } = require("enquirer")
|
3
|
-
const fetch = require('node-fetch');
|
4
|
-
const SessionCommand = require('../utils/SessionCommand')
|
5
|
-
const SessionManager = require('../managers/session.js')
|
6
|
-
const Console = require('../utils/console');
|
7
|
-
const { replace } = require('node-emoji');
|
8
|
-
const { validURL } = require("../utils/validators")
|
9
|
-
// const BaseCommand = require('../utils/BaseCommand');
|
10
|
-
|
11
|
-
class PublishCommand extends SessionCommand {
|
12
|
-
async init() {
|
13
|
-
const {flags} = this.parse(PublishCommand)
|
14
|
-
await this.initSession(flags)
|
15
|
-
}
|
16
|
-
async run() {
|
17
|
-
const {flags, args} = this.parse(PublishCommand)
|
18
|
-
|
19
|
-
SessionManager.destroy();
|
20
|
-
}
|
21
|
-
}
|
22
|
-
|
23
|
-
PublishCommand.description = `Describe the command here
|
24
|
-
...
|
25
|
-
Extra documentation goes here
|
26
|
-
`
|
27
|
-
PublishCommand.flags = {
|
28
|
-
// name: flags.string({char: 'n', description: 'name to print'}),
|
29
|
-
}
|
30
|
-
PublishCommand.args =[
|
31
|
-
{
|
32
|
-
name: 'package', // name of arg to show in help and reference with args[name]
|
33
|
-
required: false, // make the arg required with `required: true`
|
34
|
-
description: 'The unique string that identifies this package on learnpack', // help description
|
35
|
-
hidden: false // hide this arg from help
|
36
|
-
}
|
37
|
-
]
|
38
|
-
|
39
|
-
module.exports = PublishCommand
|