@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
@@ -0,0 +1,169 @@
|
|
1
|
+
import { flags } from "@oclif/command";
|
2
|
+
import BaseCommand from "../utils/BaseCommand";
|
3
|
+
|
4
|
+
// eslint-disable-next-line
|
5
|
+
import * as fs from "fs-extra";
|
6
|
+
import * as prompts from "prompts";
|
7
|
+
import cli from "cli-ux";
|
8
|
+
import * as eta from "eta";
|
9
|
+
|
10
|
+
import Console from "../utils/console";
|
11
|
+
import { ValidationError } from "../utils/errors";
|
12
|
+
import defaults from "../managers/config/defaults";
|
13
|
+
|
14
|
+
import * as path from "path";
|
15
|
+
|
16
|
+
class InitComand extends BaseCommand {
|
17
|
+
static description =
|
18
|
+
"Create a new learning package: Book, Tutorial or Exercise";
|
19
|
+
|
20
|
+
static flags = {
|
21
|
+
...BaseCommand.flags,
|
22
|
+
grading: flags.help({ char: "h" }),
|
23
|
+
};
|
24
|
+
|
25
|
+
async run() {
|
26
|
+
const { flags } = this.parse(InitComand);
|
27
|
+
|
28
|
+
// if the folder/file .learn or .breathecode aleady exists
|
29
|
+
await alreadyInitialized();
|
30
|
+
|
31
|
+
const choices = await prompts([
|
32
|
+
{
|
33
|
+
type: "select",
|
34
|
+
name: "grading",
|
35
|
+
message: "Is the auto-grading going to be isolated or incremental?",
|
36
|
+
choices: [
|
37
|
+
{
|
38
|
+
title: "Incremental: Build on top of each other like a tutorial",
|
39
|
+
value: "incremental",
|
40
|
+
},
|
41
|
+
{ title: "Isolated: Small separated exercises", value: "isolated" },
|
42
|
+
{
|
43
|
+
title: "No grading: No feedback or testing whatsoever",
|
44
|
+
value: null,
|
45
|
+
},
|
46
|
+
],
|
47
|
+
},
|
48
|
+
{
|
49
|
+
type: "text",
|
50
|
+
name: "title",
|
51
|
+
initial: "My Interactive Tutorial",
|
52
|
+
message: "Title for your tutorial? Press enter to leave as it is",
|
53
|
+
},
|
54
|
+
{
|
55
|
+
type: "text",
|
56
|
+
name: "description",
|
57
|
+
initial: "",
|
58
|
+
message: "Description for your tutorial? Press enter to leave blank",
|
59
|
+
},
|
60
|
+
{
|
61
|
+
type: "select",
|
62
|
+
name: "difficulty",
|
63
|
+
message: "How difficulty will be to complete the tutorial?",
|
64
|
+
choices: [
|
65
|
+
{ title: "Begginer (no previous experience)", value: "beginner" },
|
66
|
+
{ title: "Easy (just a bit of experience required)", value: "easy" },
|
67
|
+
{
|
68
|
+
title: "Intermediate (you need experience)",
|
69
|
+
value: "intermediate",
|
70
|
+
},
|
71
|
+
{ title: "Hard (master the topic)", value: "hard" },
|
72
|
+
],
|
73
|
+
},
|
74
|
+
{
|
75
|
+
type: "text",
|
76
|
+
name: "duration",
|
77
|
+
initial: "1",
|
78
|
+
message: "How many hours avg it takes to complete (number)?",
|
79
|
+
validate: (value: string) => {
|
80
|
+
const n = Math.floor(Number(value));
|
81
|
+
return (
|
82
|
+
n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0
|
83
|
+
);
|
84
|
+
},
|
85
|
+
},
|
86
|
+
]);
|
87
|
+
|
88
|
+
const packageInfo = {
|
89
|
+
...defaults.config,
|
90
|
+
grading: choices.grading,
|
91
|
+
difficulty: choices.difficulty,
|
92
|
+
duration: parseInt(choices.duration),
|
93
|
+
description: choices.description,
|
94
|
+
title: choices.title,
|
95
|
+
slug: choices.title.toLowerCase().replace(/ /g, "-").replace(/[^\w-]+/g, ""),
|
96
|
+
};
|
97
|
+
|
98
|
+
cli.action.start("Initializing package");
|
99
|
+
|
100
|
+
const languages = ["en", "es"];
|
101
|
+
|
102
|
+
const templatesDir = path.resolve(
|
103
|
+
__dirname,
|
104
|
+
"../utils/templates/" + choices.grading || "no-grading"
|
105
|
+
);
|
106
|
+
if (!fs.existsSync(templatesDir))
|
107
|
+
throw ValidationError(`Template ${templatesDir} does not exists`);
|
108
|
+
await fs.copySync(templatesDir, "./");
|
109
|
+
|
110
|
+
// Creating README files
|
111
|
+
// eslint-disable-next-line
|
112
|
+
languages.forEach((language) => {
|
113
|
+
const readmeFilename = `README${language !== "en" ? `.${language}` : ""}`;
|
114
|
+
fs.writeFileSync(
|
115
|
+
`./${readmeFilename}.md`,
|
116
|
+
eta.render(
|
117
|
+
fs.readFileSync(
|
118
|
+
path.resolve(__dirname, `${templatesDir}/${readmeFilename}.ejs`),
|
119
|
+
"utf-8"
|
120
|
+
),
|
121
|
+
packageInfo
|
122
|
+
)
|
123
|
+
);
|
124
|
+
if (fs.existsSync(`./${readmeFilename}.ejs`))
|
125
|
+
fs.removeSync(`./${readmeFilename}.ejs`);
|
126
|
+
});
|
127
|
+
|
128
|
+
if (!fs.existsSync("./.gitignore"))
|
129
|
+
fs.copyFile(
|
130
|
+
path.resolve(__dirname, "../utils/templates/gitignore.txt"),
|
131
|
+
"./.gitignore"
|
132
|
+
);
|
133
|
+
fs.writeFileSync("./learn.json", JSON.stringify(packageInfo, null, 2));
|
134
|
+
|
135
|
+
cli.action.stop();
|
136
|
+
Console.success(`😋 Package initialized successfully`);
|
137
|
+
Console.help(
|
138
|
+
`Start the exercises by running the following command on your terminal: $ learnpack start`
|
139
|
+
);
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
const alreadyInitialized = () =>
|
144
|
+
new Promise((resolve, reject) => {
|
145
|
+
fs.readdir("./", function (err: any, files: any) {
|
146
|
+
files = files.filter((f: any) =>
|
147
|
+
[".learn", "learn.json", "bc.json", ".breathecode"].includes(f)
|
148
|
+
);
|
149
|
+
if (err) {
|
150
|
+
reject(ValidationError(err.message));
|
151
|
+
throw ValidationError(err.message);
|
152
|
+
} else if (files.length > 0) {
|
153
|
+
reject(
|
154
|
+
ValidationError(
|
155
|
+
"It seems the package is already initialized because we've found the following files: " +
|
156
|
+
files.join(",")
|
157
|
+
)
|
158
|
+
);
|
159
|
+
throw ValidationError(
|
160
|
+
"It seems the package is already initialized because we've found the following files: " +
|
161
|
+
files.join(",")
|
162
|
+
);
|
163
|
+
}
|
164
|
+
|
165
|
+
resolve(false);
|
166
|
+
});
|
167
|
+
});
|
168
|
+
|
169
|
+
export default InitComand;
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import SessionCommand from "../utils/SessionCommand";
|
2
|
+
import SessionManager from "../managers/session";
|
3
|
+
import Console from "../utils/console";
|
4
|
+
|
5
|
+
class LoginCommand extends SessionCommand {
|
6
|
+
static description = `Describe the command here
|
7
|
+
...
|
8
|
+
Extra documentation goes here
|
9
|
+
`;
|
10
|
+
|
11
|
+
static flags: any = {
|
12
|
+
// name: flags.string({char: 'n', description: 'name to print'}),
|
13
|
+
};
|
14
|
+
|
15
|
+
static args = [
|
16
|
+
{
|
17
|
+
name: "package", // name of arg to show in help and reference with args[name]
|
18
|
+
required: false, // make the arg required with `required: true`
|
19
|
+
description:
|
20
|
+
"The unique string that identifies this package on learnpack", // help description
|
21
|
+
hidden: false, // hide this arg from help
|
22
|
+
},
|
23
|
+
];
|
24
|
+
|
25
|
+
async init() {
|
26
|
+
const { flags } = this.parse(LoginCommand);
|
27
|
+
await this.initSession(flags);
|
28
|
+
}
|
29
|
+
|
30
|
+
async run() {
|
31
|
+
/* const {flags, args} = */ this.parse(LoginCommand);
|
32
|
+
|
33
|
+
try {
|
34
|
+
await SessionManager.login();
|
35
|
+
} catch (error) {
|
36
|
+
Console.error("Error trying to authenticate");
|
37
|
+
Console.error((error as TypeError).message || (error as string));
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
export default LoginCommand;
|
@@ -0,0 +1,43 @@
|
|
1
|
+
// import {Command, flags} from '@oclif/command'
|
2
|
+
// import { prompt } from "enquirer"
|
3
|
+
// import fetch from 'node-fetch'
|
4
|
+
import SessionCommand from '../utils/SessionCommand'
|
5
|
+
import SessionManager from '../managers/session'
|
6
|
+
// import Console from '../utils/console'
|
7
|
+
// import { replace } from 'node-emoji'
|
8
|
+
// import { validURL } from "../utils/validators"
|
9
|
+
// const BaseCommand from '../utils/BaseCommand');
|
10
|
+
|
11
|
+
class LogoutCommand extends SessionCommand {
|
12
|
+
static description = `Describe the command here
|
13
|
+
...
|
14
|
+
Extra documentation goes here
|
15
|
+
`
|
16
|
+
|
17
|
+
static flags: any = {
|
18
|
+
// name: flags.string({char: 'n', description: 'name to print'}),
|
19
|
+
}
|
20
|
+
|
21
|
+
static args = [
|
22
|
+
{
|
23
|
+
name: 'package', // name of arg to show in help and reference with args[name]
|
24
|
+
required: false, // make the arg required with `required: true`
|
25
|
+
description:
|
26
|
+
'The unique string that identifies this package on learnpack', // help description
|
27
|
+
hidden: false, // hide this arg from help
|
28
|
+
},
|
29
|
+
]
|
30
|
+
|
31
|
+
async init() {
|
32
|
+
const {flags} = this.parse(LogoutCommand)
|
33
|
+
await this.initSession(flags)
|
34
|
+
}
|
35
|
+
|
36
|
+
async run() {
|
37
|
+
// const {flags, args} = this.parse(LogoutCommand)
|
38
|
+
|
39
|
+
SessionManager.destroy()
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
export default LogoutCommand
|
@@ -0,0 +1,107 @@
|
|
1
|
+
import { prompt } from "enquirer";
|
2
|
+
import SessionCommand from "../utils/SessionCommand";
|
3
|
+
import Console from "../utils/console";
|
4
|
+
import api from "../utils/api";
|
5
|
+
import { validURL } from "../utils/validators";
|
6
|
+
|
7
|
+
// eslint-disable-next-line
|
8
|
+
const fetch = require("node-fetch");
|
9
|
+
|
10
|
+
class PublishCommand extends SessionCommand {
|
11
|
+
static description = `Describe the command here
|
12
|
+
...
|
13
|
+
Extra documentation goes here
|
14
|
+
`;
|
15
|
+
|
16
|
+
static flags: any = {
|
17
|
+
// name: flags.string({char: 'n', description: 'name to print'}),
|
18
|
+
};
|
19
|
+
|
20
|
+
static args = [
|
21
|
+
{
|
22
|
+
name: "package", // name of arg to show in help and reference with args[name]
|
23
|
+
required: false, // make the arg required with `required: true`
|
24
|
+
description:
|
25
|
+
"The unique string that identifies this package on learnpack", // help description
|
26
|
+
hidden: false, // hide this arg from help
|
27
|
+
},
|
28
|
+
];
|
29
|
+
|
30
|
+
async init() {
|
31
|
+
const { flags } = this.parse(PublishCommand);
|
32
|
+
await this.initSession(flags, true);
|
33
|
+
}
|
34
|
+
|
35
|
+
async run() {
|
36
|
+
const { flags, args } = this.parse(PublishCommand);
|
37
|
+
|
38
|
+
// avoid annonymus sessions
|
39
|
+
// eslint-disable-next-line
|
40
|
+
if (!this.session) return;
|
41
|
+
|
42
|
+
Console.info(
|
43
|
+
`Session found for ${this.session.payload.email}, publishing the package...`
|
44
|
+
);
|
45
|
+
|
46
|
+
const configObject = this.configManager?.get();
|
47
|
+
if (
|
48
|
+
configObject?.config?.slug === undefined ||
|
49
|
+
!configObject.config?.slug
|
50
|
+
) {
|
51
|
+
throw new Error(
|
52
|
+
"The package is missing a slug (unique name identifier), please check your learn.json file and make sure it has a 'slug'"
|
53
|
+
);
|
54
|
+
}
|
55
|
+
|
56
|
+
if (!validURL(configObject?.config?.repository ?? "")) {
|
57
|
+
throw new Error(
|
58
|
+
"The package has a missing or invalid 'repository' on the configuration file, it needs to be a Github URL"
|
59
|
+
);
|
60
|
+
} else {
|
61
|
+
const validateResp = await fetch(configObject.config?.repository, {
|
62
|
+
method: "HEAD",
|
63
|
+
});
|
64
|
+
if (!validateResp.ok || validateResp.status !== 200) {
|
65
|
+
throw new Error(
|
66
|
+
`The specified repository URL on the configuration file does not exist or its private, only public repositories are allowed at the moment: ${configObject.config?.repository}`
|
67
|
+
);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
// start watching for file changes
|
72
|
+
try {
|
73
|
+
await api.publish({
|
74
|
+
...configObject,
|
75
|
+
author: this.session.payload.user_id,
|
76
|
+
});
|
77
|
+
Console.success(
|
78
|
+
`Package updated and published successfully: ${configObject.config?.slug}`
|
79
|
+
);
|
80
|
+
} catch (error) {
|
81
|
+
if ((error as any).status === 404) {
|
82
|
+
const answer = await prompt([
|
83
|
+
{
|
84
|
+
type: "confirm",
|
85
|
+
name: "create",
|
86
|
+
message: `Package with slug ${configObject.config?.slug} does not exist, do you want to create it?`,
|
87
|
+
},
|
88
|
+
]);
|
89
|
+
if (answer) {
|
90
|
+
await api.update({
|
91
|
+
...configObject,
|
92
|
+
author: this.session.payload.user_id,
|
93
|
+
});
|
94
|
+
Console.success(
|
95
|
+
`Package created and published successfully: ${configObject.config?.slug}`
|
96
|
+
);
|
97
|
+
} else {
|
98
|
+
Console.error("No answer from server");
|
99
|
+
}
|
100
|
+
} else {
|
101
|
+
Console.error((error as TypeError).message);
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
export default PublishCommand;
|
@@ -0,0 +1,229 @@
|
|
1
|
+
// import path from "path";
|
2
|
+
import { flags } from "@oclif/command";
|
3
|
+
import SessionCommand from "../utils/SessionCommand";
|
4
|
+
import Console from "../utils/console";
|
5
|
+
import socket from "../managers/socket";
|
6
|
+
import queue from "../utils/fileQueue";
|
7
|
+
import { decompress, downloadEditor } from "../managers/file";
|
8
|
+
import { prioritizeHTMLFile } from "../utils/misc";
|
9
|
+
|
10
|
+
import createServer from "../managers/server";
|
11
|
+
|
12
|
+
import { IGitpodData } from "../models/gitpod-data";
|
13
|
+
import { IExercise, IExerciseData } from "../models/exercise-obj";
|
14
|
+
|
15
|
+
export default class StartCommand extends SessionCommand {
|
16
|
+
static description = "Runs a small server with all the exercise instructions";
|
17
|
+
|
18
|
+
static flags = {
|
19
|
+
...SessionCommand.flags,
|
20
|
+
port: flags.string({ char: "p", description: "server port" }),
|
21
|
+
host: flags.string({ char: "h", description: "server host" }),
|
22
|
+
disableGrading: flags.boolean({
|
23
|
+
char: "D",
|
24
|
+
description: "disble grading functionality",
|
25
|
+
default: false,
|
26
|
+
}),
|
27
|
+
// disableGrading: flags.boolean({char: 'dg', description: 'disble grading functionality', default: false }),
|
28
|
+
watch: flags.boolean({
|
29
|
+
char: "w",
|
30
|
+
description: "Watch for file changes",
|
31
|
+
default: false,
|
32
|
+
}),
|
33
|
+
editor: flags.string({
|
34
|
+
char: "e",
|
35
|
+
description: "[standalone, gitpod]",
|
36
|
+
options: ["standalone", "gitpod"],
|
37
|
+
}),
|
38
|
+
version: flags.string({
|
39
|
+
char: "v",
|
40
|
+
description: "E.g: 1.0.1",
|
41
|
+
default: undefined,
|
42
|
+
}),
|
43
|
+
grading: flags.string({
|
44
|
+
char: "g",
|
45
|
+
description: "[isolated, incremental]",
|
46
|
+
options: ["isolated", "incremental"],
|
47
|
+
}),
|
48
|
+
debug: flags.boolean({
|
49
|
+
char: "d",
|
50
|
+
description: "debugger mode for more verbage",
|
51
|
+
default: false,
|
52
|
+
}),
|
53
|
+
};
|
54
|
+
|
55
|
+
// 🛑 IMPORTANT
|
56
|
+
// Every command that will use the configManager needs this init method
|
57
|
+
async init() {
|
58
|
+
const { flags } = this.parse(StartCommand);
|
59
|
+
await this.initSession(flags);
|
60
|
+
}
|
61
|
+
|
62
|
+
async run() {
|
63
|
+
// get configuration object
|
64
|
+
const configObject = this.configManager?.get();
|
65
|
+
const config = configObject?.config;
|
66
|
+
|
67
|
+
if (configObject) {
|
68
|
+
const { config } = configObject;
|
69
|
+
|
70
|
+
// build exerises
|
71
|
+
this.configManager?.buildIndex();
|
72
|
+
|
73
|
+
Console.debug(
|
74
|
+
`Grading: ${config?.grading} ${
|
75
|
+
config?.disabledActions?.includes("test") ? "(disabled)" : ""
|
76
|
+
}, editor: ${config?.editor.mode} ${config?.editor.version}, for ${
|
77
|
+
Array.isArray(configObject?.exercises) ? configObject?.exercises.length : 0
|
78
|
+
} exercises found`
|
79
|
+
);
|
80
|
+
|
81
|
+
// download app and decompress
|
82
|
+
await downloadEditor(
|
83
|
+
config?.editor.version,
|
84
|
+
`${config?.dirPath}/app.tar.gz`
|
85
|
+
);
|
86
|
+
|
87
|
+
Console.info("Decompressing LearnPack UI, this may take a minute...");
|
88
|
+
await decompress(
|
89
|
+
`${config?.dirPath}/app.tar.gz`,
|
90
|
+
`${config?.dirPath}/_app/`
|
91
|
+
);
|
92
|
+
|
93
|
+
// listen to socket commands
|
94
|
+
if (config && this.configManager) {
|
95
|
+
const server = await createServer(configObject, this.configManager);
|
96
|
+
|
97
|
+
const dispatcher = queue.dispatcher({
|
98
|
+
create: true,
|
99
|
+
path: `${config.dirPath}/vscode_queue.json`,
|
100
|
+
});
|
101
|
+
|
102
|
+
socket.start(config, server, false);
|
103
|
+
|
104
|
+
socket.on("open", (data: IGitpodData) => {
|
105
|
+
Console.debug("Opening these files: ", data);
|
106
|
+
const files = prioritizeHTMLFile(data.files);
|
107
|
+
dispatcher.enqueue(dispatcher.events.OPEN_FILES, files);
|
108
|
+
socket.ready("Ready to compile...");
|
109
|
+
});
|
110
|
+
|
111
|
+
socket.on("open_window", (data: IGitpodData) => {
|
112
|
+
Console.debug("Opening window: ", data);
|
113
|
+
dispatcher.enqueue(dispatcher.events.OPEN_WINDOW, data);
|
114
|
+
socket.ready("Ready to compile...");
|
115
|
+
});
|
116
|
+
|
117
|
+
socket.on("reset", (exercise: IExerciseData) => {
|
118
|
+
try {
|
119
|
+
this.configManager?.reset(exercise.exerciseSlug);
|
120
|
+
dispatcher.enqueue(
|
121
|
+
dispatcher.events.RESET_EXERCISE,
|
122
|
+
exercise.exerciseSlug
|
123
|
+
);
|
124
|
+
socket.ready("Ready to compile...");
|
125
|
+
} catch (error) {
|
126
|
+
socket.error(
|
127
|
+
"compiler-error",
|
128
|
+
(error as TypeError).message ||
|
129
|
+
"There was an error reseting the exercise"
|
130
|
+
);
|
131
|
+
setTimeout(() => socket.ready("Ready to compile..."), 2000);
|
132
|
+
}
|
133
|
+
});
|
134
|
+
// socket.on("preview", (data) => {
|
135
|
+
// Console.debug("Preview triggered, removing the 'preview' action ")
|
136
|
+
// socket.removeAllowed("preview")
|
137
|
+
// socket.log('ready',['Ready to compile...'])
|
138
|
+
// })
|
139
|
+
|
140
|
+
socket.on("build", async (data: IExerciseData) => {
|
141
|
+
const exercise = this.configManager?.getExercise(data.exerciseSlug);
|
142
|
+
|
143
|
+
if (!exercise?.language) {
|
144
|
+
socket.error(
|
145
|
+
"compiler-error",
|
146
|
+
"Impossible to detect language to build for " +
|
147
|
+
data.exerciseSlug +
|
148
|
+
"..."
|
149
|
+
);
|
150
|
+
return;
|
151
|
+
}
|
152
|
+
|
153
|
+
socket.log(
|
154
|
+
"compiling",
|
155
|
+
"Building exercise " +
|
156
|
+
data.exerciseSlug +
|
157
|
+
" with " +
|
158
|
+
exercise.language +
|
159
|
+
"..."
|
160
|
+
);
|
161
|
+
await this.config.runHook("action", {
|
162
|
+
action: "compile",
|
163
|
+
socket,
|
164
|
+
configuration: config,
|
165
|
+
exercise,
|
166
|
+
});
|
167
|
+
});
|
168
|
+
|
169
|
+
socket.on("test", async (data: IExerciseData) => {
|
170
|
+
const exercise = this.configManager?.getExercise(data.exerciseSlug);
|
171
|
+
|
172
|
+
if (!exercise?.language) {
|
173
|
+
socket.error(
|
174
|
+
"compiler-error",
|
175
|
+
"Impossible to detect engine language for testing for " +
|
176
|
+
data.exerciseSlug +
|
177
|
+
"..."
|
178
|
+
);
|
179
|
+
return;
|
180
|
+
}
|
181
|
+
|
182
|
+
if (
|
183
|
+
config?.disabledActions!.includes("test") ||
|
184
|
+
config?.disableGrading
|
185
|
+
) {
|
186
|
+
socket.ready("Grading is disabled on configuration");
|
187
|
+
return true;
|
188
|
+
}
|
189
|
+
|
190
|
+
socket.log(
|
191
|
+
"testing",
|
192
|
+
"Testing your exercise using the " + exercise.language + " engine."
|
193
|
+
);
|
194
|
+
|
195
|
+
await this.config.runHook("action", {
|
196
|
+
action: "test",
|
197
|
+
socket,
|
198
|
+
configuration: config,
|
199
|
+
exercise,
|
200
|
+
});
|
201
|
+
this.configManager?.save();
|
202
|
+
|
203
|
+
return true;
|
204
|
+
});
|
205
|
+
|
206
|
+
const terminate = () => {
|
207
|
+
Console.debug("Terminating Learnpack...");
|
208
|
+
server.terminate(() => {
|
209
|
+
this.configManager?.noCurrentExercise();
|
210
|
+
dispatcher.enqueue(dispatcher.events.END);
|
211
|
+
process.exit();
|
212
|
+
});
|
213
|
+
};
|
214
|
+
|
215
|
+
server.on("close", terminate);
|
216
|
+
process.on("SIGINT", terminate);
|
217
|
+
process.on("SIGTERM", terminate);
|
218
|
+
process.on("SIGHUP", terminate);
|
219
|
+
|
220
|
+
// finish the server startup
|
221
|
+
setTimeout(() => dispatcher.enqueue(dispatcher.events.RUNNING), 1000);
|
222
|
+
|
223
|
+
// start watching for file changes
|
224
|
+
// eslint-disable-next-line
|
225
|
+
if (StartCommand.flags.watch) this.configManager.watchIndex(_exercises => socket.reload(null, _exercises));
|
226
|
+
}
|
227
|
+
}
|
228
|
+
}
|
229
|
+
}
|
@@ -1,42 +1,40 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
import Console from "../utils/console";
|
2
|
+
import SessionCommand from "../utils/SessionCommand";
|
3
|
+
import socket from "../managers/socket";
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
import createServer from "../managers/server";
|
6
|
+
import ExercisesQueue from "../utils/exercisesQueue";
|
7
|
+
import { IExercise } from "../models/exercise-obj";
|
7
8
|
|
8
9
|
class TestCommand extends SessionCommand {
|
9
10
|
async init() {
|
10
11
|
const { flags } = this.parse(TestCommand);
|
11
12
|
await this.initSession(flags);
|
12
13
|
}
|
14
|
+
|
13
15
|
async run() {
|
14
16
|
const {
|
15
17
|
args: { exerciseSlug },
|
16
18
|
} = this.parse(TestCommand);
|
17
19
|
|
18
20
|
// Build exercises index
|
19
|
-
this.configManager
|
21
|
+
this.configManager?.buildIndex();
|
20
22
|
|
21
|
-
let exercises = [];
|
23
|
+
let exercises: IExercise[] | undefined = [];
|
22
24
|
|
23
25
|
// test all exercises
|
24
|
-
|
25
|
-
exercises = this.configManager.getAllExercises();
|
26
|
-
} else {
|
27
|
-
exercises = [this.configManager.getExercise(exerciseSlug)];
|
28
|
-
}
|
26
|
+
!exerciseSlug ? exercises = this.configManager?.getAllExercises() : exercises = [this.configManager!.getExercise(exerciseSlug)];
|
29
27
|
|
30
28
|
const exercisesQueue = new ExercisesQueue(exercises);
|
31
29
|
|
32
|
-
const configObject = this.configManager
|
30
|
+
const configObject = this.configManager?.get();
|
33
31
|
|
34
32
|
let hasFailed = false;
|
35
33
|
let failedTestsCount = 0;
|
36
34
|
let successTestsCount = 0;
|
37
|
-
|
35
|
+
const testsToRunCount = exercisesQueue.size();
|
38
36
|
|
39
|
-
configObject
|
37
|
+
configObject!.config!.testingFinishedCallback = ({ result }) => {
|
40
38
|
if (result === "failed") {
|
41
39
|
hasFailed = true;
|
42
40
|
failedTestsCount++;
|
@@ -57,17 +55,17 @@ class TestCommand extends SessionCommand {
|
|
57
55
|
|
58
56
|
process.exit(hasFailed ? 1 : 0);
|
59
57
|
} else {
|
60
|
-
exercisesQueue.pop()
|
58
|
+
exercisesQueue.pop()!.test!(this.config, config!, socket);
|
61
59
|
}
|
62
60
|
};
|
63
61
|
|
64
|
-
const
|
62
|
+
const config = configObject?.config;
|
65
63
|
|
66
|
-
const server = await createServer(configObject
|
64
|
+
const server = await createServer(configObject!, this.configManager!, true);
|
67
65
|
|
68
|
-
socket.start(config
|
66
|
+
socket.start(config!, server, true);
|
69
67
|
|
70
|
-
exercisesQueue.pop()
|
68
|
+
exercisesQueue.pop()!.test!(this.config, config!, socket);
|
71
69
|
}
|
72
70
|
}
|
73
71
|
|
@@ -82,4 +80,4 @@ TestCommand.args = [
|
|
82
80
|
},
|
83
81
|
];
|
84
82
|
|
85
|
-
|
83
|
+
export default TestCommand;
|
package/src/index.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export * from '@oclif/command'
|