@learnpack/learnpack 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,87 @@
1
+ import * as chalk from "chalk";
2
+
3
+ const getMatches = (reg: RegExp, content: string) => {
4
+ const 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)
9
+ reg.lastIndex++;
10
+
11
+ // The result can be accessed through the `m`-variable.
12
+ inputs.push(m[1] || null);
13
+ }
14
+
15
+ return inputs;
16
+ };
17
+
18
+ const cleanStdout = (buffer: string, inputs: string[]) => {
19
+ if (Array.isArray(inputs))
20
+ for (let i = 0; i < inputs.length; i++)
21
+ if (inputs[i])
22
+ buffer = buffer.replace(inputs[i], "");
23
+
24
+ return buffer;
25
+ };
26
+
27
+ const indent = (string: string, options: any, count = 1) => {
28
+ options = {
29
+ indent: " ",
30
+ includeEmptyLines: false,
31
+ ...options,
32
+ };
33
+
34
+ if (typeof string !== "string") {
35
+ throw new TypeError(
36
+ `Expected \`input\` to be a \`string\`, got \`${typeof string}\``
37
+ );
38
+ }
39
+
40
+ if (typeof count !== "number") {
41
+ throw new TypeError(
42
+ `Expected \`count\` to be a \`number\`, got \`${typeof count}\``
43
+ );
44
+ }
45
+
46
+ if (count < 0) {
47
+ throw new RangeError(
48
+ `Expected \`count\` to be at least 0, got \`${count}\``
49
+ );
50
+ }
51
+
52
+ if (typeof options.indent !== "string") {
53
+ throw new TypeError(
54
+ `Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\``
55
+ );
56
+ }
57
+
58
+ if (count === 0) {
59
+ return string;
60
+ }
61
+
62
+ const regex = options.includeEmptyLines ? /^/gm : /^(?!\s*$)/gm;
63
+
64
+ return string.replace(regex, options.indent.repeat(count));
65
+ };
66
+
67
+ const Console = {
68
+ // _debug: true,
69
+ _debug: process.env.DEBUG === "true",
70
+ startDebug: function () {
71
+ this._debug = true;
72
+ },
73
+ log: (msg: string, ...args: any[]) => console.log(chalk.gray(msg), ...args),
74
+ error: (msg: string, ...args: any[]) =>
75
+ console.log(chalk.red("⨉ " + msg), ...args),
76
+ success: (msg: string, ...args: any[]) =>
77
+ console.log(chalk.green("✓ " + msg), ...args),
78
+ info: (msg: string, ...args: any[]) =>
79
+ console.log(chalk.blue("ⓘ " + msg), ...args),
80
+ help: (msg: string) =>
81
+ console.log(`${chalk.white.bold("⚠ help:")} ${chalk.white(msg)}`),
82
+ debug(...args: any[]) {
83
+ this._debug && console.log(chalk.magentaBright(`⚠ debug: `), args);
84
+ },
85
+ };
86
+
87
+ export default { getMatches, cleanStdout, indent, Console };
@@ -0,0 +1 @@
1
+ declare module '*';
@@ -0,0 +1,71 @@
1
+ import {prompt} from 'enquirer'
2
+ import Console from '../utils/console'
3
+ import api from '../utils/api'
4
+ // import fetch from 'node-fetch'
5
+
6
+ import {ILanguage} from '../models/language'
7
+ import {IPackage} from '../models/package'
8
+
9
+ export const askPackage = async () => {
10
+ Console.info('No package was specified')
11
+ const languages = await api.getLangs()
12
+
13
+ return new Promise((resolve, reject) => {
14
+ if (languages.length === 0) {
15
+ // reject(new Error('No categories available'))
16
+ reject('No categories available')
17
+ // return null;
18
+ }
19
+
20
+ // let packages = []
21
+ prompt([
22
+ {
23
+ type: 'select',
24
+ name: 'lang',
25
+ message: 'What language do you want to practice?',
26
+ choices: languages.map((l: ILanguage) => ({
27
+ message: l.title,
28
+ name: l.slug,
29
+ })),
30
+ },
31
+ ])
32
+ .then(({lang}: any) => {
33
+ return (async () => {
34
+ const response = await api.getAllPackages({lang})
35
+ const packages = response.results
36
+ if (packages.length === 0) {
37
+ const error = new Error(`No packages found for language ${lang}`)
38
+ Console.error(error.message) // TODO: Look this
39
+ return error
40
+ }
41
+
42
+ return prompt([
43
+ {
44
+ type: 'select',
45
+ name: 'pack',
46
+ message: 'Choose one of the packages available',
47
+ choices: packages.map((l: IPackage) => ({
48
+ message: `${l.title}, difficulty: ${l.difficulty}, downloads: ${
49
+ l.downloads
50
+ } ${
51
+ l.skills.length > 0 ? `(Skills: ${l.skills.join(',')})` : ''
52
+ }`,
53
+ name: l.slug,
54
+ })),
55
+ },
56
+ ])
57
+ })()
58
+ })
59
+ .then((resp: any) => {
60
+ if (!resp)
61
+ reject(resp.message || resp)
62
+ else
63
+ resolve(resp.pack)
64
+ })
65
+ .catch(error => {
66
+ Console.error(error.message || error)
67
+ })
68
+ })
69
+ }
70
+
71
+ export default {askPackage}
@@ -0,0 +1,48 @@
1
+ import { Command } from "@oclif/command";
2
+ import Console from "./console";
3
+ import { createInterface } from "readline";
4
+ // import SessionManager from '../managers/session'
5
+
6
+ class BaseCommand extends Command {
7
+ async catch(err: any) {
8
+ Console.debug("COMMAND CATCH", err);
9
+
10
+ throw err;
11
+ }
12
+
13
+ async init() {
14
+ const { flags, args } = this.parse(BaseCommand);
15
+ Console.debug("COMMAND INIT");
16
+ Console.debug("These are your flags: ", flags);
17
+ Console.debug("These are your args: ", args);
18
+
19
+ // quick fix for listening to the process termination on windows
20
+ if (process.platform === "win32") {
21
+ const rl = createInterface({
22
+ input: process.stdin,
23
+ output: process.stdout,
24
+ });
25
+
26
+ rl.on("SIGINT", function () {
27
+ // process.emit('SIGINT')
28
+ // process.emit('SIGINT')
29
+ });
30
+ }
31
+
32
+ process.on("SIGINT", function () {
33
+ Console.debug("Terminated (SIGINT)");
34
+ process.exit();
35
+ });
36
+ }
37
+
38
+ async finally() {
39
+ Console.debug("COMMAND FINALLY");
40
+ // called after run and catch regardless of whether or not the command errored
41
+ }
42
+
43
+ async run() {
44
+ // console.log('running my command')
45
+ }
46
+ }
47
+
48
+ export default BaseCommand;
@@ -0,0 +1,48 @@
1
+ // import { flags } from "@oclif/command";
2
+ import BaseCommand from './BaseCommand'
3
+ import Console from './console'
4
+ import SessionManager from '../managers/session'
5
+ import configManager from '../managers/config/index'
6
+ import {AuthError} from './errors'
7
+ import {IConfigManager} from '../models/config-manager'
8
+
9
+ export default class SessionCommand extends BaseCommand {
10
+ session: any = null
11
+ configManager: IConfigManager | null = null
12
+ static flags: any
13
+
14
+ async initSession(flags: any, _private = false) {
15
+ try {
16
+ if (!this.configManager) {
17
+ await this.buildConfig(flags)
18
+ }
19
+
20
+ this.session = await SessionManager.get(this.configManager?.get())
21
+ if (this.session) {
22
+ Console.debug(`Session open for ${this.session.payload.email}.`)
23
+ } else {
24
+ if (_private)
25
+ throw AuthError(
26
+ 'You need to log in, run the following command to continue: $ learnpack login',
27
+ )
28
+ Console.debug('No active session available', _private)
29
+ }
30
+ } catch (error) {
31
+ Console.error((error as TypeError).message)
32
+ }
33
+ }
34
+
35
+ async buildConfig(flags: any) {
36
+ this.configManager = await configManager(flags)
37
+ }
38
+
39
+ async catch(err: any) {
40
+ Console.debug('COMMAND CATCH', err)
41
+ throw err
42
+ }
43
+ }
44
+
45
+ // SessionCommand.description = `Describe the command here
46
+ // ...
47
+ // Extra documentation goes here
48
+ // `
@@ -0,0 +1,194 @@
1
+ import Console from "../utils/console";
2
+ import * as storage from "node-persist";
3
+ import cli from "cli-ux";
4
+ const HOST = "https://learnpack.herokuapp.com";
5
+
6
+ // eslint-disable-next-line
7
+ const _fetch = require("node-fetch");
8
+
9
+ interface IHeaders {
10
+ "Content-Type"?: string;
11
+ Authorization?: string;
12
+ }
13
+
14
+ interface IOptions {
15
+ headers?: IHeaders;
16
+ method?: string;
17
+ body?: string;
18
+ }
19
+
20
+ const fetch = async (url: string, options: IOptions = {}) => {
21
+ const headers: IHeaders = { "Content-Type": "application/json" };
22
+ let session = null;
23
+ try {
24
+ session = await storage.getItem("bc-payload");
25
+ if (session.token && session.token !== "" && !url.includes("/token"))
26
+ headers.Authorization = "Token " + session.token;
27
+ } catch {}
28
+
29
+ try {
30
+ const resp = await _fetch(url, {
31
+ ...options,
32
+ headers: { ...headers, ...options.headers },
33
+ } as any);
34
+
35
+ if (resp.status >= 200 && resp.status < 300)
36
+ return await resp.json();
37
+ if (resp.status === 401)
38
+ throw APIError("Invalid authentication credentials", 401);
39
+ else if (resp.status === 404)
40
+ throw APIError("Package not found", 404);
41
+ else if (resp.status >= 500)
42
+ throw APIError("Impossible to connect with the server", 500);
43
+ else if (resp.status >= 400) {
44
+ const error = await resp.json();
45
+ if (error.detail || error.error) {
46
+ throw APIError(error.detail || error.error);
47
+ } else if (error.nonFieldErrors) {
48
+ throw APIError(error.nonFieldErrors[0], error);
49
+ } else if (typeof error === "object") {
50
+ if (Object.keys(error).length > 0) {
51
+ const key = error[Object.keys(error)[0]];
52
+ throw APIError(`${key}: ${error[key][0]}`, error);
53
+ }
54
+ } else {
55
+ throw APIError("Uknown error");
56
+ }
57
+ } else
58
+ throw APIError("Uknown error");
59
+ } catch (error) {
60
+ Console.error((error as TypeError).message);
61
+ throw error;
62
+ }
63
+ };
64
+
65
+ const login = async (identification: string, password: string) => {
66
+ try {
67
+ cli.action.start("Looking for credentials...");
68
+ await cli.wait(1000);
69
+ const data = await fetch(`${HOST}/v1/auth/token/`, {
70
+ body: JSON.stringify({ identification, password }),
71
+ method: "post",
72
+ });
73
+ cli.action.stop("ready");
74
+ return data;
75
+ } catch (error) {
76
+ Console.error((error as TypeError).message);
77
+ Console.debug(error);
78
+ }
79
+ };
80
+
81
+ const publish = async (config: any) => {
82
+ const keys = [
83
+ "difficulty",
84
+ "language",
85
+ "skills",
86
+ "technologies",
87
+ "slug",
88
+ "repository",
89
+ "author",
90
+ "title",
91
+ ];
92
+
93
+ const payload: { [key: string]: string } = {};
94
+ for (const k of keys)
95
+ config[k] ? (payload[k] = config[k]) : null;
96
+ try {
97
+ console.log("Package to publish:", payload);
98
+ cli.action.start("Updating package information...");
99
+ await cli.wait(1000);
100
+ const data = await fetch(`${HOST}/v1/package/${config.slug}`, {
101
+ method: "PUT",
102
+ body: JSON.stringify(payload),
103
+ });
104
+ cli.action.stop("ready");
105
+ return data;
106
+ } catch (error) {
107
+ console.log("payload", payload);
108
+ Console.error((error as TypeError).message);
109
+ Console.debug(error);
110
+ throw error;
111
+ }
112
+ };
113
+
114
+ const update = async (config: any) => {
115
+ try {
116
+ cli.action.start("Updating package information...");
117
+ await cli.wait(1000);
118
+ const data = await fetch(`${HOST}/v1/package/`, {
119
+ method: "POST",
120
+ body: JSON.stringify(config),
121
+ });
122
+ cli.action.stop("ready");
123
+ return data;
124
+ } catch (error) {
125
+ Console.error((error as any).message);
126
+ Console.debug(error);
127
+ throw error;
128
+ }
129
+ };
130
+
131
+ const getPackage = async (slug: string) => {
132
+ try {
133
+ cli.action.start("Downloading package information...");
134
+ await cli.wait(1000);
135
+ const data = await fetch(`${HOST}/v1/package/${slug}`);
136
+ cli.action.stop("ready");
137
+ return data;
138
+ } catch (error) {
139
+ if ((error as any).status === 404)
140
+ Console.error(`Package ${slug} does not exist`);
141
+ else
142
+ Console.error(`Package ${slug} does not exist`);
143
+ Console.debug(error);
144
+ throw error;
145
+ }
146
+ };
147
+
148
+ const getLangs = async () => {
149
+ try {
150
+ cli.action.start("Downloading language options...");
151
+ await cli.wait(1000);
152
+ const data = await fetch(`${HOST}/v1/package/language`);
153
+ cli.action.stop("ready");
154
+ return data;
155
+ } catch (error) {
156
+ if ((error as any).status === 404)
157
+ Console.error("Package slug does not exist");
158
+ else
159
+ Console.error("Package slug does not exist");
160
+ Console.debug(error);
161
+ throw error;
162
+ }
163
+ };
164
+
165
+ const getAllPackages = async ({
166
+ lang = "",
167
+ slug = "",
168
+ }: {
169
+ lang?: string;
170
+ slug?: string;
171
+ }) => {
172
+ try {
173
+ cli.action.start("Downloading packages...");
174
+ await cli.wait(1000);
175
+ const data = await fetch(
176
+ `${HOST}/v1/package/all?limit=100&language=${lang}&slug=${slug}`
177
+ );
178
+ cli.action.stop("ready");
179
+ return data;
180
+ } catch (error) {
181
+ Console.error(`Package ${slug} does not exist`);
182
+ Console.debug(error);
183
+ throw error;
184
+ }
185
+ };
186
+
187
+ const APIError = (error: TypeError | string, code?: number) => {
188
+ const message: string = (error as TypeError).message || (error as string);
189
+ const _err = new Error(message) as any;
190
+ _err.status = code || 400;
191
+ return _err;
192
+ };
193
+
194
+ export default { login, publish, update, getPackage, getLangs, getAllPackages };
@@ -0,0 +1,162 @@
1
+ import {IAuditErrors} from '../models/audit-errors'
2
+ import {IConfigObj} from '../models/config'
3
+ import {ICounter} from '../models/counter'
4
+ import {IFindings} from '../models/findings'
5
+ import Console from './console'
6
+
7
+ // eslint-disable-next-line
8
+ const fetch = require("node-fetch");
9
+ import * as fs from 'fs'
10
+
11
+ export default {
12
+ // This function checks if a url is valid.
13
+ isUrl: async (url: string, errors: IAuditErrors[], counter: ICounter) => {
14
+ const regexUrl = /(https?:\/\/[\w./-]+)/gm
15
+ counter.links.total++
16
+ if (!regexUrl.test(url)) {
17
+ counter.links.error++
18
+ errors.push({
19
+ exercise: undefined,
20
+ msg: `The repository value of the configuration file is not a link: ${url}`,
21
+ })
22
+ return false
23
+ }
24
+
25
+ const res = await fetch(url, {method: 'HEAD'})
26
+ if (!res.ok) {
27
+ counter.links.error++
28
+ errors.push({
29
+ exercise: undefined,
30
+ msg: `The link of the repository is broken: ${url}`,
31
+ })
32
+ }
33
+
34
+ return true
35
+ },
36
+ checkForEmptySpaces: (str: string) => {
37
+ const isEmpty = true
38
+ for (const letter of str) {
39
+ if (letter !== ' ') {
40
+ return false
41
+ }
42
+ }
43
+
44
+ return isEmpty
45
+ },
46
+ checkLearnpackClean: (configObj: IConfigObj, errors: IAuditErrors[]) => {
47
+ if (
48
+ (configObj.config?.outputPath &&
49
+ fs.existsSync(configObj.config?.outputPath)) ||
50
+ fs.existsSync(`${configObj.config?.dirPath}/_app`) ||
51
+ fs.existsSync(`${configObj.config?.dirPath}/reports`) ||
52
+ fs.existsSync(`${configObj.config?.dirPath}/resets`) ||
53
+ fs.existsSync(`${configObj.config?.dirPath}/app.tar.gz`) ||
54
+ fs.existsSync(`${configObj.config?.dirPath}/config.json`) ||
55
+ fs.existsSync(`${configObj.config?.dirPath}/vscode_queue.json`)
56
+ ) {
57
+ errors.push({
58
+ exercise: undefined,
59
+ msg: 'You have to run learnpack clean command',
60
+ })
61
+ }
62
+ },
63
+ findInFile: (types: string[], content: string) => {
64
+ const regex: any = {
65
+ relativeImages:
66
+ /!\[.*]\s*\((((\.\/)?(\.{2}\/){1,5})(.*\/)*(.[^\s/]*\.[A-Za-z]{2,4})\S*)\)/gm,
67
+ externalImages: /!\[.*]\((https?:\/(\/[^)/]+)+\/?)\)/gm,
68
+ markdownLinks: /(\s)+\[.*]\((https?:\/(\/[^)/]+)+\/?)\)/gm,
69
+ url: /(https?:\/\/[\w./-]+)/gm,
70
+ uploadcare: /https:\/\/ucarecdn.com\/(?:.*\/)*([\w./-]+)/gm,
71
+ }
72
+
73
+ const validTypes = Object.keys(regex)
74
+ if (!Array.isArray(types))
75
+ types = [types]
76
+
77
+ const findings: IFindings = {}
78
+ type findingsType =
79
+ | 'relativeImages'
80
+ | 'externalImages'
81
+ | 'markdownLinks'
82
+ | 'url'
83
+ | 'uploadcare';
84
+
85
+ for (const type of types) {
86
+ if (!validTypes.includes(type))
87
+ throw new Error('Invalid type: ' + type)
88
+ else
89
+ findings[type as findingsType] = {}
90
+ }
91
+
92
+ for (const type of types) {
93
+ let m: RegExpExecArray
94
+ while ((m = regex[type].exec(content)) !== null) {
95
+ // This is necessary to avoid infinite loops with zero-width matches
96
+ if (m.index === regex.lastIndex) {
97
+ regex.lastIndex++
98
+ }
99
+
100
+ // The result can be accessed through the `m`-variable.
101
+ // m.forEach((match, groupIndex) => values.push(match));
102
+
103
+ findings[type as findingsType]![m[0]] = {
104
+ content: m[0],
105
+ absUrl: m[1],
106
+ mdUrl: m[2],
107
+ relUrl: m[6],
108
+ }
109
+ }
110
+ }
111
+
112
+ return findings
113
+ },
114
+ // This function checks if there are errors, and show them in the console at the end.
115
+ showErrors: (errors: IAuditErrors[], counter: ICounter) => {
116
+ return new Promise((resolve, reject) => {
117
+ if (errors) {
118
+ if (errors.length > 0) {
119
+ Console.log('Checking for errors...')
120
+ for (const [i, error] of errors.entries())
121
+ Console.error(
122
+ `${i + 1}) ${error.msg} ${
123
+ error.exercise ? `(Exercise: ${error.exercise})` : ''
124
+ }`,
125
+ )
126
+
127
+ Console.error(
128
+ ` We found ${errors.length} errors among ${counter.images.total} images, ${counter.links.total} link, ${counter.readmeFiles} README files and ${counter.exercises} exercises.`,
129
+ )
130
+ process.exit(1)
131
+ } else {
132
+ Console.success(
133
+ `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.`,
134
+ )
135
+ process.exit(0)
136
+ }
137
+ } else {
138
+ reject('Failed')
139
+ }
140
+ })
141
+ },
142
+ // This function checks if there are warnings, and show them in the console at the end.
143
+ showWarnings: (warnings: IAuditErrors[]) => {
144
+ return new Promise((resolve, reject) => {
145
+ if (warnings) {
146
+ if (warnings.length > 0) {
147
+ Console.log('Checking for warnings...')
148
+ for (const [i, warning] of warnings.entries())
149
+ Console.warning(
150
+ `${i + 1}) ${warning.msg} ${
151
+ warning.exercise ? `File: ${warning.exercise}` : ''
152
+ }`,
153
+ )
154
+ }
155
+
156
+ resolve('SUCCESS')
157
+ } else {
158
+ reject('Failed')
159
+ }
160
+ })
161
+ },
162
+ }
@@ -0,0 +1,24 @@
1
+ import * as chalk from 'chalk'
2
+
3
+ export default {
4
+ // _debug: true,
5
+ _debug: process.env.DEBUG === 'true',
6
+ startDebug: function () {
7
+ this._debug = true
8
+ },
9
+ log: (msg: string | Array<string>, ...args: Array<any>) =>
10
+ console.log(chalk.gray(msg), ...args),
11
+ error: (msg: string, ...args: Array<any>) =>
12
+ console.log(chalk.red('⨉ ' + msg), ...args),
13
+ success: (msg: string, ...args: Array<any>) =>
14
+ console.log(chalk.green('✓ ' + msg), ...args),
15
+ info: (msg: string, ...args: Array<any>) =>
16
+ console.log(chalk.blue('ⓘ ' + msg), ...args),
17
+ help: (msg: string) =>
18
+ console.log(`${chalk.white.bold('⚠ help:')} ${chalk.white(msg)}`),
19
+ debug(...args: Array<any>) {
20
+ this._debug && console.log(chalk.magentaBright('⚠ debug: '), args)
21
+ },
22
+ warning: (msg: string) =>
23
+ console.log(`${chalk.yellow('⚠ warning:')} ${chalk.yellow(msg)}`),
24
+ }