@silexlabs/silex 3.0.0-alpha.46 → 3.0.0-alpha.48
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/.env.default +1 -0
- package/dist/client/client/api.d.ts +1 -1
- package/dist/client/client/grapesjs/website-info.d.ts +2 -0
- package/dist/client/js/main.js +12 -12
- package/dist/client/types.d.ts +1 -1
- package/dist/plugins/client/types.d.ts +1 -1
- package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +11 -0
- package/dist/plugins/server/plugins/server/GitlabConnector.js +140 -30
- package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
- package/dist/plugins/server/server/config.js +1 -1
- package/dist/plugins/server/server/config.js.map +1 -1
- package/dist/plugins/server/types.d.ts +1 -1
- package/dist/server/server/cli.js +3 -0
- package/dist/server/server/config.js +1 -1
- package/package.json +1 -1
- package/src/ts/client/api.ts +2 -2
- package/src/ts/client/grapesjs/index.ts +2 -0
- package/src/ts/client/grapesjs/website-info.ts +39 -0
- package/src/ts/plugins/server/GitlabConnector.ts +151 -31
- package/src/ts/server/cli.ts +2 -0
- package/src/ts/server/config.ts +1 -1
- package/src/ts/types.ts +1 -1
|
@@ -21,7 +21,6 @@ import { ConnectorFile, ConnectorFileContent, StatusCallback, StorageConnector,
|
|
|
21
21
|
import { ApiError, ConnectorType, ConnectorUser, WebsiteData, WebsiteId, WebsiteMeta, WebsiteMetaFileContent } from '../../types'
|
|
22
22
|
import fetch from 'node-fetch'
|
|
23
23
|
import crypto, { createHash } from 'crypto'
|
|
24
|
-
import { PassThrough, Readable } from 'stream'
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
26
|
* Gitlab connector
|
|
@@ -34,6 +33,8 @@ export interface GitlabOptions {
|
|
|
34
33
|
clientSecret: string
|
|
35
34
|
branch: string
|
|
36
35
|
assetsFolder: string
|
|
36
|
+
metaRepo: string
|
|
37
|
+
metaRepoFile: string
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
interface GitlabToken {
|
|
@@ -50,6 +51,7 @@ interface GitlabToken {
|
|
|
50
51
|
scope: string
|
|
51
52
|
}
|
|
52
53
|
userId?: number
|
|
54
|
+
username?: string
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
interface GitlabSession {
|
|
@@ -65,7 +67,7 @@ interface GitlabAction {
|
|
|
65
67
|
interface GitlabWriteFile {
|
|
66
68
|
branch: string
|
|
67
69
|
commit_message: string
|
|
68
|
-
id
|
|
70
|
+
id?: string
|
|
69
71
|
actions?: GitlabAction[]
|
|
70
72
|
content?: string
|
|
71
73
|
file_path?: string
|
|
@@ -90,6 +92,16 @@ interface GitlabCreateBranch {
|
|
|
90
92
|
ref: string
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
interface MetaRepoFileContent {
|
|
96
|
+
websites: {
|
|
97
|
+
[websiteId: string]: {
|
|
98
|
+
meta: WebsiteMetaFileContent,
|
|
99
|
+
createdAt: string,
|
|
100
|
+
updatedAt: string,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
93
105
|
async function wait(ms: number) {
|
|
94
106
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|
95
107
|
}
|
|
@@ -108,6 +120,8 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
108
120
|
this.options = {
|
|
109
121
|
branch: 'main',
|
|
110
122
|
assetsFolder: '',
|
|
123
|
+
metaRepo: 'silex-meta',
|
|
124
|
+
metaRepoFile: 'websites.json',
|
|
111
125
|
...opts,
|
|
112
126
|
} as GitlabOptions
|
|
113
127
|
if(!this.options.clientId) throw new Error('Missing Gitlab client ID')
|
|
@@ -144,6 +158,36 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
144
158
|
})
|
|
145
159
|
}
|
|
146
160
|
|
|
161
|
+
/*
|
|
162
|
+
* Get the meta repo path for the current user
|
|
163
|
+
* The meta repo contains a JSON file which contains the list of websites
|
|
164
|
+
*/
|
|
165
|
+
private getMetaRepoPath(session: GitlabSession): string {
|
|
166
|
+
if(!session.gitlab?.username) throw new ApiError('Missing Gitlab user ID. User not logged in?', 401)
|
|
167
|
+
return encodeURIComponent(`${session.gitlab.username}/${this.options.metaRepo}`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Initialize the storage with a meta repo
|
|
172
|
+
*/
|
|
173
|
+
private async initStorage(session: GitlabSession): Promise<void> {
|
|
174
|
+
// Create the meta repo
|
|
175
|
+
try {
|
|
176
|
+
const project = await this.callApi(session, 'api/v4/projects/', 'POST', {
|
|
177
|
+
name: this.options.metaRepo,
|
|
178
|
+
}) as any
|
|
179
|
+
return this.createFile(session, this.getMetaRepoPath(session), this.options.metaRepoFile, JSON.stringify({
|
|
180
|
+
websites: {}
|
|
181
|
+
} as MetaRepoFileContent))
|
|
182
|
+
} catch (e) {
|
|
183
|
+
console.error('Could not init storage', e.statusCode, e.httpStatusCode, e)
|
|
184
|
+
throw e
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Call the Gitlab API with the user's token and handle errors
|
|
190
|
+
*/
|
|
147
191
|
private async callApi(session: GitlabSession, path: string, method: 'POST' | 'GET' | 'PUT' | 'DELETE' = 'GET', body: GitlabWriteFile | GitlabGetToken | GitlabWebsiteName | GitlabCreateBranch | null = null, params: any = {}): Promise<any> {
|
|
148
192
|
const token = session?.gitlab?.token
|
|
149
193
|
const tokenParam = token ? `access_token=${token.access_token}&` : ''
|
|
@@ -167,7 +211,7 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
167
211
|
throw e
|
|
168
212
|
} else {
|
|
169
213
|
// Useless error linked to the fact that the response is not JSON
|
|
170
|
-
|
|
214
|
+
console.error('Gitlab API error (3) - could not parse response', response.status, response.statusText, {url, method, body, params, text})
|
|
171
215
|
return text
|
|
172
216
|
}
|
|
173
217
|
}
|
|
@@ -196,12 +240,14 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
196
240
|
}
|
|
197
241
|
return await this.callApi(session, path, method, body as any, params)
|
|
198
242
|
} else {
|
|
199
|
-
|
|
200
|
-
|
|
243
|
+
const message = typeof refreshJson?.message === 'object' ? Object.entries(refreshJson.message).map(entry => entry.join(' ')).join(' ') : refreshJson?.message ?? refreshJson?.error ?? response.statusText
|
|
244
|
+
console.error('Gitlab API error (2) - could not refresh token', response.status, response.statusText, {message})
|
|
245
|
+
throw new ApiError(`Gitlab API error (2): ${message}`, response.status)
|
|
201
246
|
}
|
|
202
247
|
} else {
|
|
203
|
-
|
|
204
|
-
|
|
248
|
+
const message = typeof json?.message === 'object' ? Object.entries(json.message).map(entry => entry.join(' ')).join(' ') : json?.message ?? json?.error ?? response.statusText
|
|
249
|
+
console.error('Gitlab API error (1)', response.status, response.statusText, {message})
|
|
250
|
+
throw new ApiError(`Gitlab API error (1): ${message}`, response.status)
|
|
205
251
|
}
|
|
206
252
|
}
|
|
207
253
|
return json
|
|
@@ -281,7 +327,7 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
281
327
|
if(!session.gitlab?.codeVerifier) throw new ApiError('Missing code verifier', 401)
|
|
282
328
|
if(!session.gitlab?.codeChallenge) throw new ApiError('Missing code challenge', 401)
|
|
283
329
|
|
|
284
|
-
const
|
|
330
|
+
const response = await fetch('https://gitlab.com/oauth/token', {
|
|
285
331
|
method: 'POST',
|
|
286
332
|
headers: {
|
|
287
333
|
'Content-Type': 'application/json',
|
|
@@ -295,13 +341,16 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
295
341
|
code_verifier: session.gitlab.codeVerifier,
|
|
296
342
|
}),
|
|
297
343
|
})
|
|
298
|
-
.then((response) => response.json())
|
|
299
344
|
|
|
345
|
+
const token = await response.json()
|
|
346
|
+
|
|
347
|
+
// Store the token in the session
|
|
300
348
|
session.gitlab = { token, state: session.gitlab.state, codeVerifier: session.gitlab.codeVerifier, codeChallenge: session.gitlab.codeChallenge }
|
|
301
349
|
|
|
302
350
|
// We need to get the user ID for listWebsites
|
|
303
351
|
const user = await this.callApi(session, 'api/v4/user') as any
|
|
304
352
|
session.gitlab.userId = user.id
|
|
353
|
+
session.gitlab.username = user.username
|
|
305
354
|
}
|
|
306
355
|
|
|
307
356
|
async logout(session: GitlabSession): Promise<void> {
|
|
@@ -313,19 +362,43 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
313
362
|
return {
|
|
314
363
|
name: user.name,
|
|
315
364
|
email: user.email,
|
|
365
|
+
picture: user.avatar_url,
|
|
316
366
|
storage: await toConnectorData(session, this as StorageConnector),
|
|
317
367
|
}
|
|
318
368
|
}
|
|
319
369
|
|
|
320
370
|
async listWebsites(session: GitlabSession): Promise<WebsiteMeta[]> {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
371
|
+
try {
|
|
372
|
+
const result = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
|
|
373
|
+
ref: this.options.branch,
|
|
374
|
+
})
|
|
375
|
+
const { content } = result
|
|
376
|
+
const contentDecoded = Buffer.from(content, 'base64').toString('utf8')
|
|
377
|
+
const websites = (JSON.parse(contentDecoded) as MetaRepoFileContent).websites
|
|
378
|
+
console.log('listWebsites', {websites, contentDecoded, content, result})
|
|
379
|
+
return Object.entries(websites).map(([websiteId, {meta, createdAt, updatedAt}]) => ({
|
|
380
|
+
websiteId,
|
|
381
|
+
createdAt: new Date(createdAt),
|
|
382
|
+
updatedAt: new Date(updatedAt),
|
|
383
|
+
...meta,
|
|
384
|
+
}))
|
|
385
|
+
} catch (e) {
|
|
386
|
+
console.error('Could not list websites', e.statusCode, e.httpStatusCode, e.code)
|
|
387
|
+
if (e.statusCode === 404 || e.httpStatusCode === 404) {
|
|
388
|
+
await this.initStorage(session)
|
|
389
|
+
return []
|
|
390
|
+
} else {
|
|
391
|
+
throw e
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
//const projects = await this.callApi(session, `api/v4/users/${session.gitlab?.user?.id}/projects`) as any[]
|
|
395
|
+
//return projects.map(p => ({
|
|
396
|
+
// websiteId: p.id,
|
|
397
|
+
// name: p.name,
|
|
398
|
+
// createdAt: p.created_at,
|
|
399
|
+
// updatedAt: p.last_activity_at,
|
|
400
|
+
// connectorUserSettings: {},
|
|
401
|
+
//}))
|
|
329
402
|
}
|
|
330
403
|
|
|
331
404
|
async readWebsite(session: GitlabSession, websiteId: string): Promise<WebsiteData> {
|
|
@@ -343,9 +416,9 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
343
416
|
name: websiteMeta.name,
|
|
344
417
|
}) as any
|
|
345
418
|
await this.createFile(session, project.id, WEBSITE_DATA_FILE, JSON.stringify({} as WebsiteData))
|
|
346
|
-
await this.createFile(session, project.id, WEBSITE_META_DATA_FILE, JSON.stringify(websiteMeta))
|
|
419
|
+
//await this.createFile(session, project.id, WEBSITE_META_DATA_FILE, JSON.stringify(websiteMeta))
|
|
347
420
|
//await this.updateWebsite(session, project.id, {} as WebsiteData)
|
|
348
|
-
|
|
421
|
+
await this.setWebsiteMeta(session, project.id, websiteMeta)
|
|
349
422
|
return project.id
|
|
350
423
|
}
|
|
351
424
|
|
|
@@ -360,28 +433,75 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
360
433
|
}
|
|
361
434
|
|
|
362
435
|
async deleteWebsite(session: GitlabSession, websiteId: WebsiteId): Promise<void> {
|
|
436
|
+
// Delete repo
|
|
363
437
|
await this.callApi(session, `api/v4/projects/${websiteId}`, 'DELETE')
|
|
438
|
+
// Load the meta repo data
|
|
439
|
+
const file = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
|
|
440
|
+
ref: this.options.branch,
|
|
441
|
+
})
|
|
442
|
+
const metaRepo = JSON.parse(Buffer.from(file.content, 'base64').toString('utf8')) as MetaRepoFileContent
|
|
443
|
+
const data = metaRepo.websites[websiteId]
|
|
444
|
+
if(!data) throw new ApiError(`Website ${websiteId} not found`, 404)
|
|
445
|
+
// Update or create the website meta data
|
|
446
|
+
delete metaRepo.websites[websiteId]
|
|
447
|
+
// Save the meta repo data
|
|
448
|
+
const project = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'PUT', {
|
|
449
|
+
branch: this.options.branch,
|
|
450
|
+
commit_message: `Delete meta data of ${data.meta.name} (${websiteId}) from Silex`,
|
|
451
|
+
content: JSON.stringify(metaRepo),
|
|
452
|
+
file_path: this.options.metaRepoFile,
|
|
453
|
+
})
|
|
364
454
|
}
|
|
365
455
|
|
|
366
456
|
async getWebsiteMeta(session: GitlabSession, websiteId: WebsiteId): Promise<WebsiteMeta> {
|
|
367
|
-
const
|
|
457
|
+
const file = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
|
|
458
|
+
ref: this.options.branch,
|
|
459
|
+
})
|
|
460
|
+
const metaRepo = JSON.parse(Buffer.from(file.content, 'base64').toString('utf8')) as MetaRepoFileContent
|
|
461
|
+
if(!metaRepo.websites[websiteId]) throw new ApiError(`Website ${websiteId} not found`, 404)
|
|
368
462
|
return {
|
|
369
|
-
websiteId
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
connectorUserSettings: {},
|
|
463
|
+
websiteId,
|
|
464
|
+
createdAt: new Date(metaRepo.websites[websiteId].createdAt),
|
|
465
|
+
updatedAt: new Date(metaRepo.websites[websiteId].updatedAt),
|
|
466
|
+
...metaRepo.websites[websiteId].meta,
|
|
374
467
|
}
|
|
468
|
+
//const project = await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_META_DATA_FILE}`) as any
|
|
469
|
+
//return {
|
|
470
|
+
// websiteId: project.id,
|
|
471
|
+
// name: project.name,
|
|
472
|
+
// createdAt: project.created_at,
|
|
473
|
+
// updatedAt: project.last_activity_at,
|
|
474
|
+
// connectorUserSettings: {},
|
|
475
|
+
//}
|
|
375
476
|
}
|
|
376
477
|
|
|
377
478
|
async setWebsiteMeta(session: GitlabSession, websiteId: WebsiteId, websiteMeta: WebsiteMetaFileContent): Promise<void> {
|
|
378
|
-
|
|
479
|
+
// Load the meta repo data
|
|
480
|
+
const file = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
|
|
481
|
+
ref: this.options.branch,
|
|
482
|
+
})
|
|
483
|
+
const metaRepo = JSON.parse(Buffer.from(file.content, 'base64').toString('utf8')) as MetaRepoFileContent
|
|
484
|
+
// Update or create the website meta data
|
|
485
|
+
metaRepo.websites[websiteId] = {
|
|
486
|
+
updatedAt: new Date().toISOString(),
|
|
487
|
+
createdAt: metaRepo.websites[websiteId]?.createdAt ?? new Date().toISOString(),
|
|
488
|
+
meta: websiteMeta,
|
|
489
|
+
}
|
|
490
|
+
// Save the meta repo data
|
|
491
|
+
const project = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'PUT', {
|
|
379
492
|
branch: this.options.branch,
|
|
380
|
-
commit_message:
|
|
381
|
-
content: JSON.stringify(
|
|
382
|
-
file_path:
|
|
383
|
-
id: websiteId,
|
|
493
|
+
commit_message: `Update website meta data of ${websiteMeta.name} (${websiteId}) from Silex`,
|
|
494
|
+
content: JSON.stringify(metaRepo),
|
|
495
|
+
file_path: this.options.metaRepoFile,
|
|
384
496
|
})
|
|
497
|
+
|
|
498
|
+
//const project = await this.callApi(session, `api/v4/projects/${websiteId}/repository/files/${WEBSITE_META_DATA_FILE}`, 'PUT', {
|
|
499
|
+
// branch: this.options.branch,
|
|
500
|
+
// commit_message: 'Update website meta data from Silex',
|
|
501
|
+
// content: JSON.stringify(websiteMeta),
|
|
502
|
+
// file_path: WEBSITE_META_DATA_FILE,
|
|
503
|
+
// id: websiteId,
|
|
504
|
+
//})
|
|
385
505
|
}
|
|
386
506
|
|
|
387
507
|
async writeAssets(session: GitlabSession, websiteId: string, files: ConnectorFile[], status?: StatusCallback | undefined): Promise<void> {
|
|
@@ -393,7 +513,7 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
393
513
|
await this.updateFile(session, websiteId, this.getAssetPath(file.path), content, true)
|
|
394
514
|
} catch (e) {
|
|
395
515
|
// If the file does not exist, create it
|
|
396
|
-
if (e.statusCode === 404 || e.message.endsWith('A file with this name doesn\'t exist')) {
|
|
516
|
+
if (e.statusCode === 404 || e.httpStatusCode === 404 || e.message.endsWith('A file with this name doesn\'t exist')) {
|
|
397
517
|
await this.createFile(session, websiteId, file.path, content, true)
|
|
398
518
|
} else {
|
|
399
519
|
throw e
|
package/src/ts/server/cli.ts
CHANGED
|
@@ -21,6 +21,7 @@ import silex from './index'
|
|
|
21
21
|
import { parse } from 'cli'
|
|
22
22
|
|
|
23
23
|
const options = parse({
|
|
24
|
+
'url': ['', 'URL of your server', 'string'],
|
|
24
25
|
'host': ['', 'Sets the host param of the express module "cookie-session".', 'string'],
|
|
25
26
|
'port': [ 'p', 'Port to listen to', 'int'],
|
|
26
27
|
'protocol': ['', 'Sets the protocol param of the express module "cookie-session".', 'string'],
|
|
@@ -41,6 +42,7 @@ const options = parse({
|
|
|
41
42
|
'debug': [false, 'Debug mode, with live reload, source maps etc.', 'boolean'],
|
|
42
43
|
}, {})
|
|
43
44
|
|
|
45
|
+
if(options['url']) process.env.SILEX_URL = options['url']
|
|
44
46
|
if(options['host']) process.env.SILEX_HOST = options['host']
|
|
45
47
|
if(options.port) process.env.SILEX_PORT = options.port
|
|
46
48
|
if(options['protocol']) process.env.SILEX_PROTOCOL = options['protocol']
|
package/src/ts/server/config.ts
CHANGED
|
@@ -49,7 +49,7 @@ export class ServerConfig extends Config {
|
|
|
49
49
|
}
|
|
50
50
|
public port = process.env.SILEX_PORT
|
|
51
51
|
public debug = process.env.SILEX_DEBUG === 'true'
|
|
52
|
-
public url = `${process.env.SILEX_PROTOCOL}://${process.env.SILEX_HOST}:${process.env.SILEX_PORT}`
|
|
52
|
+
public url = process.env.SILEX_URL ?? `${process.env.SILEX_PROTOCOL}://${process.env.SILEX_HOST}:${process.env.SILEX_PORT}`
|
|
53
53
|
public userConfigPath: Plugin | undefined = process.env.SILEX_CONFIG
|
|
54
54
|
public configFilePath: Plugin = resolve(__dirname, '../../../.silex.js')
|
|
55
55
|
|
package/src/ts/types.ts
CHANGED
|
@@ -61,7 +61,7 @@ export type ApiWebsiteCreateBody = WebsiteMetaFileContent
|
|
|
61
61
|
export type ApiWebsiteCreateResponse = { message: string }
|
|
62
62
|
export type ApiWebsiteDeleteQuery = { websiteId: WebsiteId, connectorId?: ConnectorId }
|
|
63
63
|
export type ApiWebsiteMetaReadQuery = { websiteId: WebsiteId, connectorId?: ConnectorId }
|
|
64
|
-
export type ApiWebsiteMetaReadResponse =
|
|
64
|
+
export type ApiWebsiteMetaReadResponse = WebsiteMeta
|
|
65
65
|
export type ApiWebsiteMetaWriteQuery = { websiteId: WebsiteId, connectorId?: ConnectorId }
|
|
66
66
|
export type ApiWebsiteMetaWriteBody = WebsiteMetaFileContent
|
|
67
67
|
export type ApiWebsiteMetaWriteResponse = { message: string }
|