@learnpack/learnpack 4.0.12 → 4.0.13
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 +16 -35
- package/lib/commands/publish.d.ts +6 -9
- package/lib/commands/publish.js +137 -59
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/publish.ts +181 -107
- package/src/managers/session.ts +145 -145
- package/lib/commands/build.d.ts +0 -11
- package/lib/commands/build.js +0 -160
- package/src/commands/build.ts +0 -181
package/src/commands/publish.ts
CHANGED
@@ -1,107 +1,181 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
static
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
const
|
47
|
-
if (
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if (!
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
)
|
80
|
-
}
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
}
|
103
|
-
}
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
1
|
+
/* eslint-disable arrow-parens */
|
2
|
+
/* eslint-disable unicorn/no-array-for-each */
|
3
|
+
import { flags } from "@oclif/command"
|
4
|
+
import SessionCommand from "../utils/SessionCommand"
|
5
|
+
import SessionManager from "../managers/session"
|
6
|
+
import * as fs from "fs"
|
7
|
+
import * as path from "path"
|
8
|
+
import * as archiver from "archiver"
|
9
|
+
import axios from "axios"
|
10
|
+
import FormData = require("form-data")
|
11
|
+
import Console from "../utils/console"
|
12
|
+
|
13
|
+
// const RIGOBOT_HOST = "https://rigobot-test-cca7d841c9d8.herokuapp.com"
|
14
|
+
const RIGOBOT_HOST =
|
15
|
+
// "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
|
16
|
+
"https://rigobot.herokuapp.com"
|
17
|
+
const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
|
18
|
+
|
19
|
+
export default class BuildCommand extends SessionCommand {
|
20
|
+
static description =
|
21
|
+
"Builds the project by copying necessary files and directories into a zip file"
|
22
|
+
|
23
|
+
static flags = {
|
24
|
+
help: flags.help({ char: "h" }),
|
25
|
+
}
|
26
|
+
|
27
|
+
async init() {
|
28
|
+
const { flags } = this.parse(BuildCommand)
|
29
|
+
await this.initSession(flags)
|
30
|
+
}
|
31
|
+
|
32
|
+
async run() {
|
33
|
+
const buildDir = path.join(process.cwd(), "build")
|
34
|
+
const sessionPayload = await SessionManager.getPayload()
|
35
|
+
if (!sessionPayload || !sessionPayload.rigobot) {
|
36
|
+
Console.error(
|
37
|
+
"You must be logged in to upload a LearnPack packge, please run: \n$ learnpack login"
|
38
|
+
)
|
39
|
+
return
|
40
|
+
}
|
41
|
+
|
42
|
+
const rigoToken = sessionPayload.rigobot.key
|
43
|
+
// const rigoToken = "417d612d226a1606ad3a4e94b1881a9f0124b667"
|
44
|
+
|
45
|
+
// Read learn.json to get the slug
|
46
|
+
const learnJsonPath = path.join(process.cwd(), "learn.json")
|
47
|
+
if (!fs.existsSync(learnJsonPath)) {
|
48
|
+
this.error("learn.json not found")
|
49
|
+
}
|
50
|
+
|
51
|
+
const learnJson = JSON.parse(fs.readFileSync(learnJsonPath, "utf-8"))
|
52
|
+
|
53
|
+
const zipFilePath = path.join(process.cwd(), `${learnJson.slug}.zip`)
|
54
|
+
|
55
|
+
// Ensure build directory exists
|
56
|
+
if (!fs.existsSync(buildDir)) {
|
57
|
+
fs.mkdirSync(buildDir)
|
58
|
+
}
|
59
|
+
|
60
|
+
// Copy config.json
|
61
|
+
const configPath = path.join(process.cwd(), ".learn", "config.json")
|
62
|
+
if (fs.existsSync(configPath)) {
|
63
|
+
fs.copyFileSync(configPath, path.join(buildDir, "config.json"))
|
64
|
+
} else {
|
65
|
+
this.error("config.json not found")
|
66
|
+
}
|
67
|
+
|
68
|
+
// Copy .learn/assets directory
|
69
|
+
const assetsDir = path.join(process.cwd(), ".learn", "assets")
|
70
|
+
if (fs.existsSync(assetsDir)) {
|
71
|
+
this.copyDirectory(assetsDir, path.join(buildDir, ".learn", "assets"))
|
72
|
+
} else {
|
73
|
+
this.error(".learn/assets directory not found")
|
74
|
+
}
|
75
|
+
|
76
|
+
// Copy .learn/_app directory files to the same level as config.json
|
77
|
+
const appDir = path.join(process.cwd(), ".learn", "_app")
|
78
|
+
if (fs.existsSync(appDir)) {
|
79
|
+
this.copyDirectory(appDir, buildDir)
|
80
|
+
} else {
|
81
|
+
this.error(".learn/_app directory not found")
|
82
|
+
}
|
83
|
+
|
84
|
+
// Copy exercises directory
|
85
|
+
const exercisesDir = path.join(process.cwd(), "exercises")
|
86
|
+
const learnExercisesDir = path.join(process.cwd(), ".learn", "exercises")
|
87
|
+
|
88
|
+
if (fs.existsSync(exercisesDir)) {
|
89
|
+
this.copyDirectory(exercisesDir, path.join(buildDir, "exercises"))
|
90
|
+
} else if (fs.existsSync(learnExercisesDir)) {
|
91
|
+
this.copyDirectory(learnExercisesDir, path.join(buildDir, "exercises"))
|
92
|
+
} else {
|
93
|
+
this.error("exercises directory not found in either location")
|
94
|
+
}
|
95
|
+
|
96
|
+
// Copy learn.json
|
97
|
+
fs.copyFileSync(learnJsonPath, path.join(buildDir, "learn.json"))
|
98
|
+
|
99
|
+
// Create zip file
|
100
|
+
const output = fs.createWriteStream(zipFilePath)
|
101
|
+
const archive = archiver("zip", {
|
102
|
+
zlib: { level: 9 },
|
103
|
+
})
|
104
|
+
|
105
|
+
output.on("close", async () => {
|
106
|
+
this.log(
|
107
|
+
`Build completed: ${zipFilePath} (${archive.pointer()} total bytes)`
|
108
|
+
)
|
109
|
+
// Remove build directory after zip is created
|
110
|
+
this.removeDirectory(buildDir)
|
111
|
+
console.log("Zip file saved in project root")
|
112
|
+
|
113
|
+
const formData = new FormData()
|
114
|
+
formData.append("file", fs.createReadStream(zipFilePath))
|
115
|
+
formData.append("config", JSON.stringify(learnJson))
|
116
|
+
|
117
|
+
try {
|
118
|
+
const res = await axios.post(uploadZipEndpont, formData, {
|
119
|
+
headers: {
|
120
|
+
...formData.getHeaders(),
|
121
|
+
Authorization: `Token ${rigoToken}`,
|
122
|
+
},
|
123
|
+
})
|
124
|
+
console.log(res.data)
|
125
|
+
} catch (error) {
|
126
|
+
if (axios.isAxiosError(error)) {
|
127
|
+
if (error.response && error.response.status === 403) {
|
128
|
+
console.error("Error 403:", error.response.data.error)
|
129
|
+
} else if (error.response && error.response.status === 400) {
|
130
|
+
console.error(error.response.data.error)
|
131
|
+
} else {
|
132
|
+
console.error("Error uploading file:", error.message)
|
133
|
+
}
|
134
|
+
} else {
|
135
|
+
console.error("Error uploading file:", error)
|
136
|
+
}
|
137
|
+
}
|
138
|
+
})
|
139
|
+
|
140
|
+
archive.on("error", (err: any) => {
|
141
|
+
throw err
|
142
|
+
})
|
143
|
+
|
144
|
+
archive.pipe(output)
|
145
|
+
archive.directory(buildDir, false)
|
146
|
+
await archive.finalize()
|
147
|
+
}
|
148
|
+
|
149
|
+
copyDirectory(src: string, dest: string) {
|
150
|
+
if (!fs.existsSync(dest)) {
|
151
|
+
fs.mkdirSync(dest, { recursive: true })
|
152
|
+
}
|
153
|
+
|
154
|
+
const entries = fs.readdirSync(src, { withFileTypes: true })
|
155
|
+
|
156
|
+
for (const entry of entries) {
|
157
|
+
const srcPath = path.join(src, entry.name)
|
158
|
+
const destPath = path.join(dest, entry.name)
|
159
|
+
|
160
|
+
if (entry.isDirectory()) {
|
161
|
+
this.copyDirectory(srcPath, destPath)
|
162
|
+
} else {
|
163
|
+
fs.copyFileSync(srcPath, destPath)
|
164
|
+
}
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
removeDirectory(dir: string) {
|
169
|
+
if (fs.existsSync(dir)) {
|
170
|
+
fs.readdirSync(dir).forEach((file) => {
|
171
|
+
const currentPath = path.join(dir, file)
|
172
|
+
if (fs.lstatSync(currentPath).isDirectory()) {
|
173
|
+
this.removeDirectory(currentPath)
|
174
|
+
} else {
|
175
|
+
fs.unlinkSync(currentPath)
|
176
|
+
}
|
177
|
+
})
|
178
|
+
fs.rmdirSync(dir)
|
179
|
+
}
|
180
|
+
}
|
181
|
+
}
|
package/src/managers/session.ts
CHANGED
@@ -1,145 +1,145 @@
|
|
1
|
-
import Console from "../utils/console"
|
2
|
-
import api from "../utils/api"
|
3
|
-
|
4
|
-
import v from "validator"
|
5
|
-
import { ValidationError, InternalError } from "../utils/errors"
|
6
|
-
|
7
|
-
import * as fs from "fs"
|
8
|
-
import cli from "cli-ux"
|
9
|
-
import * as storage from "node-persist"
|
10
|
-
|
11
|
-
import { IPayload, ISession, IStartProps } from "../models/session"
|
12
|
-
import { IConfigObj } from "../models/config"
|
13
|
-
import TelemetryManager from "./telemetry"
|
14
|
-
|
15
|
-
const Session: ISession = {
|
16
|
-
sessionStarted: false,
|
17
|
-
token: null,
|
18
|
-
config: null,
|
19
|
-
currentCohort: null,
|
20
|
-
initialize: async function () {
|
21
|
-
if (!this.sessionStarted) {
|
22
|
-
if (!this.config) {
|
23
|
-
throw InternalError("Configuration not found")
|
24
|
-
}
|
25
|
-
|
26
|
-
if (!fs.existsSync(this.config.dirPath)) {
|
27
|
-
fs.mkdirSync(this.config.dirPath)
|
28
|
-
}
|
29
|
-
|
30
|
-
await storage.init({ dir: `${this.config.dirPath}/.session` })
|
31
|
-
this.sessionStarted = true
|
32
|
-
}
|
33
|
-
|
34
|
-
return true
|
35
|
-
},
|
36
|
-
|
37
|
-
setRigoToken: async function (token: string) {
|
38
|
-
await this.initialize()
|
39
|
-
const payload = await storage.getItem("bc-payload")
|
40
|
-
await storage.setItem("bc-payload", {
|
41
|
-
...payload,
|
42
|
-
rigobot: { key: token },
|
43
|
-
})
|
44
|
-
Console.debug("Rigobot token successfuly set")
|
45
|
-
return true
|
46
|
-
},
|
47
|
-
setPayload: async function (value: IPayload) {
|
48
|
-
await this.initialize()
|
49
|
-
await storage.setItem("bc-payload", { token: this.token, ...value })
|
50
|
-
Console.debug("Payload successfuly found and set for " + value.email)
|
51
|
-
return true
|
52
|
-
},
|
53
|
-
getPayload: async function () {
|
54
|
-
await this.initialize()
|
55
|
-
let payload = null
|
56
|
-
try {
|
57
|
-
payload = await storage.getItem("bc-payload")
|
58
|
-
} catch {
|
59
|
-
Console.debug("Error retriving session payload")
|
60
|
-
}
|
61
|
-
|
62
|
-
return payload
|
63
|
-
},
|
64
|
-
isActive: function () {
|
65
|
-
/* if (this.token) {
|
66
|
-
return true
|
67
|
-
} else {
|
68
|
-
return false
|
69
|
-
} */
|
70
|
-
return !!this.token
|
71
|
-
},
|
72
|
-
get: async function (configObj?: IConfigObj) {
|
73
|
-
if (configObj && configObj.config) {
|
74
|
-
this.config = configObj.config
|
75
|
-
}
|
76
|
-
|
77
|
-
await this.sync()
|
78
|
-
if (!this.isActive()) {
|
79
|
-
return null
|
80
|
-
}
|
81
|
-
|
82
|
-
const payload = await this.getPayload()
|
83
|
-
|
84
|
-
return {
|
85
|
-
payload,
|
86
|
-
token: this.token,
|
87
|
-
}
|
88
|
-
},
|
89
|
-
login: async function () {
|
90
|
-
const email = await cli.prompt("What is your email?")
|
91
|
-
if (!v.isEmail(email)) {
|
92
|
-
throw ValidationError("Invalid email")
|
93
|
-
}
|
94
|
-
|
95
|
-
const password = await cli.prompt("What is your password?", {
|
96
|
-
type: "hide",
|
97
|
-
})
|
98
|
-
|
99
|
-
const data = await api.login(email, password)
|
100
|
-
if (data) {
|
101
|
-
// cli.log(data)
|
102
|
-
this.start({ token: data.token, payload: data })
|
103
|
-
}
|
104
|
-
},
|
105
|
-
loginWeb: async function (email, password) {
|
106
|
-
if (!v.isEmail(email)) {
|
107
|
-
throw ValidationError("Invalid email")
|
108
|
-
}
|
109
|
-
|
110
|
-
const data = await api.login(email, password)
|
111
|
-
if (data) {
|
112
|
-
this.start({ token: data.token, payload: data })
|
113
|
-
TelemetryManager.setStudent({
|
114
|
-
user_id: data.user_id,
|
115
|
-
email: data.email,
|
116
|
-
token: data.token,
|
117
|
-
})
|
118
|
-
return data
|
119
|
-
}
|
120
|
-
},
|
121
|
-
sync: async function () {
|
122
|
-
const payload = await this.getPayload()
|
123
|
-
if (payload) {
|
124
|
-
this.token = payload.token
|
125
|
-
}
|
126
|
-
},
|
127
|
-
start: async function ({ token, payload = null }: IStartProps) {
|
128
|
-
if (!token) {
|
129
|
-
throw new Error("A token and email is needed to start a session")
|
130
|
-
}
|
131
|
-
|
132
|
-
this.token = token
|
133
|
-
|
134
|
-
if (payload && (await this.setPayload(payload))) {
|
135
|
-
Console.success(`Successfully logged in as ${payload.email}`)
|
136
|
-
}
|
137
|
-
},
|
138
|
-
destroy: async function () {
|
139
|
-
await storage.clear()
|
140
|
-
this.token = null
|
141
|
-
Console.success("You have logged out")
|
142
|
-
},
|
143
|
-
}
|
144
|
-
|
145
|
-
export default Session
|
1
|
+
import Console from "../utils/console"
|
2
|
+
import api from "../utils/api"
|
3
|
+
|
4
|
+
import v from "validator"
|
5
|
+
import { ValidationError, InternalError } from "../utils/errors"
|
6
|
+
|
7
|
+
import * as fs from "fs"
|
8
|
+
import cli from "cli-ux"
|
9
|
+
import * as storage from "node-persist"
|
10
|
+
|
11
|
+
import { IPayload, ISession, IStartProps } from "../models/session"
|
12
|
+
import { IConfigObj } from "../models/config"
|
13
|
+
import TelemetryManager from "./telemetry"
|
14
|
+
|
15
|
+
const Session: ISession = {
|
16
|
+
sessionStarted: false,
|
17
|
+
token: null,
|
18
|
+
config: null,
|
19
|
+
currentCohort: null,
|
20
|
+
initialize: async function () {
|
21
|
+
if (!this.sessionStarted) {
|
22
|
+
if (!this.config) {
|
23
|
+
throw InternalError("Configuration not found")
|
24
|
+
}
|
25
|
+
|
26
|
+
if (!fs.existsSync(this.config.dirPath)) {
|
27
|
+
fs.mkdirSync(this.config.dirPath)
|
28
|
+
}
|
29
|
+
|
30
|
+
await storage.init({ dir: `${this.config.dirPath}/.session` })
|
31
|
+
this.sessionStarted = true
|
32
|
+
}
|
33
|
+
|
34
|
+
return true
|
35
|
+
},
|
36
|
+
|
37
|
+
setRigoToken: async function (token: string) {
|
38
|
+
await this.initialize()
|
39
|
+
const payload = await storage.getItem("bc-payload")
|
40
|
+
await storage.setItem("bc-payload", {
|
41
|
+
...payload,
|
42
|
+
rigobot: { key: token },
|
43
|
+
})
|
44
|
+
Console.debug("Rigobot token successfuly set")
|
45
|
+
return true
|
46
|
+
},
|
47
|
+
setPayload: async function (value: IPayload) {
|
48
|
+
await this.initialize()
|
49
|
+
await storage.setItem("bc-payload", { token: this.token, ...value })
|
50
|
+
Console.debug("Payload successfuly found and set for " + value.email)
|
51
|
+
return true
|
52
|
+
},
|
53
|
+
getPayload: async function () {
|
54
|
+
await this.initialize()
|
55
|
+
let payload = null
|
56
|
+
try {
|
57
|
+
payload = await storage.getItem("bc-payload")
|
58
|
+
} catch {
|
59
|
+
Console.debug("Error retriving session payload")
|
60
|
+
}
|
61
|
+
|
62
|
+
return payload
|
63
|
+
},
|
64
|
+
isActive: function () {
|
65
|
+
/* if (this.token) {
|
66
|
+
return true
|
67
|
+
} else {
|
68
|
+
return false
|
69
|
+
} */
|
70
|
+
return !!this.token
|
71
|
+
},
|
72
|
+
get: async function (configObj?: IConfigObj) {
|
73
|
+
if (configObj && configObj.config) {
|
74
|
+
this.config = configObj.config
|
75
|
+
}
|
76
|
+
|
77
|
+
await this.sync()
|
78
|
+
if (!this.isActive()) {
|
79
|
+
return null
|
80
|
+
}
|
81
|
+
|
82
|
+
const payload = await this.getPayload()
|
83
|
+
|
84
|
+
return {
|
85
|
+
payload,
|
86
|
+
token: this.token,
|
87
|
+
}
|
88
|
+
},
|
89
|
+
login: async function () {
|
90
|
+
const email = await cli.prompt("What is your email?")
|
91
|
+
if (!v.isEmail(email)) {
|
92
|
+
throw ValidationError("Invalid email")
|
93
|
+
}
|
94
|
+
|
95
|
+
const password = await cli.prompt("What is your password?", {
|
96
|
+
type: "hide",
|
97
|
+
})
|
98
|
+
|
99
|
+
const data = await api.login(email, password)
|
100
|
+
if (data) {
|
101
|
+
// cli.log(data)
|
102
|
+
this.start({ token: data.token, payload: data })
|
103
|
+
}
|
104
|
+
},
|
105
|
+
loginWeb: async function (email, password) {
|
106
|
+
if (!v.isEmail(email)) {
|
107
|
+
throw ValidationError("Invalid email")
|
108
|
+
}
|
109
|
+
|
110
|
+
const data = await api.login(email, password)
|
111
|
+
if (data) {
|
112
|
+
this.start({ token: data.token, payload: data })
|
113
|
+
TelemetryManager.setStudent({
|
114
|
+
user_id: data.user_id,
|
115
|
+
email: data.email,
|
116
|
+
token: data.token,
|
117
|
+
})
|
118
|
+
return data
|
119
|
+
}
|
120
|
+
},
|
121
|
+
sync: async function () {
|
122
|
+
const payload = await this.getPayload()
|
123
|
+
if (payload) {
|
124
|
+
this.token = payload.token
|
125
|
+
}
|
126
|
+
},
|
127
|
+
start: async function ({ token, payload = null }: IStartProps) {
|
128
|
+
if (!token) {
|
129
|
+
throw new Error("A token and email is needed to start a session")
|
130
|
+
}
|
131
|
+
|
132
|
+
this.token = token
|
133
|
+
|
134
|
+
if (payload && (await this.setPayload(payload))) {
|
135
|
+
Console.success(`Successfully logged in as ${payload.email}`)
|
136
|
+
}
|
137
|
+
},
|
138
|
+
destroy: async function () {
|
139
|
+
await storage.clear()
|
140
|
+
this.token = null
|
141
|
+
Console.success("You have logged out")
|
142
|
+
},
|
143
|
+
}
|
144
|
+
|
145
|
+
export default Session
|
package/lib/commands/build.d.ts
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
import SessionCommand from "../utils/SessionCommand";
|
2
|
-
export default class BuildCommand extends SessionCommand {
|
3
|
-
static description: string;
|
4
|
-
static flags: {
|
5
|
-
help: import("@oclif/parser/lib/flags").IBooleanFlag<void>;
|
6
|
-
};
|
7
|
-
init(): Promise<void>;
|
8
|
-
run(): Promise<void>;
|
9
|
-
copyDirectory(src: string, dest: string): void;
|
10
|
-
removeDirectory(dir: string): void;
|
11
|
-
}
|