@silexlabs/silex 3.0.0-alpha.96 → 3.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/.env.default +2 -1
- package/README.md +31 -9
- package/dist/client/client/api.d.ts +5 -0
- package/dist/client/client/assetUrl.d.ts +3 -0
- package/dist/client/client/config.d.ts +31 -0
- package/dist/client/client/events.d.ts +7 -1
- package/dist/client/client/grapesjs/PublicationManager.d.ts +2 -1
- package/dist/client/client/grapesjs/css-props.d.ts +6 -0
- package/dist/client/client/grapesjs/img.d.ts +6 -0
- package/dist/client/client/grapesjs/index.d.ts +2 -2
- package/dist/client/client/grapesjs/new-page-dialog.d.ts +1 -1
- package/dist/client/client/grapesjs/page-panel.d.ts +1 -1
- package/dist/client/client/grapesjs/project-bar.d.ts +16 -0
- package/dist/client/client/grapesjs/resize-panel.d.ts +1 -0
- package/dist/client/client/grapesjs/rich-text.d.ts +4 -1
- package/dist/client/client/grapesjs/semantic.d.ts +1 -1
- package/dist/client/client/grapesjs/settings-sections.d.ts +15 -0
- package/dist/client/client/grapesjs/settings.d.ts +3 -1
- package/dist/client/client/grapesjs/symbolDialogs.d.ts +1 -1
- package/dist/client/client/grapesjs/view-buttons.d.ts +1 -0
- package/dist/client/client/index.d.ts +10 -0
- package/dist/client/client/publication-transformers.d.ts +4 -3
- package/dist/client/client/publish-custom-code-block.d.ts +4 -0
- package/dist/client/client/publish-fonts.d.ts +4 -0
- package/dist/client/client/utils.d.ts +4 -0
- package/dist/client/constants.d.ts +1 -0
- package/dist/client/css/admin.css +273 -32
- package/dist/client/index.html +2 -1
- package/dist/client/js/main.js +164 -123
- package/dist/client/js/main.js.LICENSE.txt +15 -1
- package/dist/client/types.d.ts +12 -7
- package/dist/plugins/client/client/events.d.ts +7 -1
- package/dist/plugins/client/client/events.js +7 -1
- package/dist/plugins/client/client/events.js.map +1 -1
- package/dist/plugins/client/plugins/client/template.js +108 -58
- package/dist/plugins/client/plugins/client/template.js.map +1 -1
- package/dist/plugins/client/types.d.ts +12 -7
- package/dist/plugins/client/types.js +6 -0
- package/dist/plugins/client/types.js.map +1 -1
- package/dist/plugins/server/constants.d.ts +1 -0
- package/dist/plugins/server/constants.js +2 -1
- package/dist/plugins/server/constants.js.map +1 -1
- package/dist/plugins/server/plugins/server/FtpConnector.d.ts +1 -0
- package/dist/plugins/server/plugins/server/FtpConnector.js +12 -1
- package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
- package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +3 -0
- package/dist/plugins/server/plugins/server/GitlabConnector.js +72 -35
- package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
- package/dist/plugins/server/server/config.js +3 -3
- package/dist/plugins/server/server/config.js.map +1 -1
- package/dist/plugins/server/server/connectors/FsHosting.d.ts +1 -0
- package/dist/plugins/server/server/connectors/FsHosting.js +13 -1
- package/dist/plugins/server/server/connectors/FsHosting.js.map +1 -1
- package/dist/plugins/server/server/connectors/FsStorage.d.ts +2 -1
- package/dist/plugins/server/server/connectors/FsStorage.js +29 -25
- package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
- package/dist/plugins/server/server/connectors/connectors.d.ts +1 -0
- package/dist/plugins/server/server/jobs.js +4 -2
- package/dist/plugins/server/server/jobs.js.map +1 -1
- package/dist/plugins/server/types.d.ts +12 -7
- package/dist/plugins/server/types.js +7 -1
- package/dist/plugins/server/types.js.map +1 -1
- package/dist/server/constants.js +2 -1
- package/dist/server/server/api/publicationApi.js +3 -5
- package/dist/server/server/api/websiteApi.js +26 -7
- package/dist/server/server/cli.js +9 -6
- package/dist/server/server/config.js +3 -3
- package/dist/server/server/connectors/FsHosting.js +13 -1
- package/dist/server/server/connectors/FsStorage.js +28 -25
- package/dist/server/server/jobs.js +4 -2
- package/dist/server/types.js +7 -1
- package/package.json +35 -31
- package/public/assets/favicon.ico +0 -0
- package/public/assets/favicon.png +0 -0
- package/public/assets/logo-silex-small.png +0 -0
- package/src/css/_variables.scss +5 -3
- package/src/css/dialog.scss +22 -1
- package/src/css/fonts.scss +31 -0
- package/src/css/footer.scss +1 -1
- package/src/css/grapesjs-plugins.scss +34 -0
- package/src/css/grapesjs.scss +26 -11
- package/src/css/index.scss +1 -0
- package/src/css/project-bar.scss +11 -7
- package/src/css/silex.scss +58 -5
- package/src/html/index.pug +2 -2
- package/src/ts/client/api.ts +14 -9
- package/src/ts/client/assetUrl.test.ts +145 -42
- package/src/ts/client/assetUrl.ts +29 -10
- package/src/ts/client/config.ts +85 -4
- package/src/ts/client/events.ts +15 -9
- package/src/ts/client/grapesjs/PublicationManager.ts +71 -38
- package/src/ts/client/grapesjs/PublicationUi.ts +13 -11
- package/src/ts/client/grapesjs/css-props.ts +544 -0
- package/src/ts/client/grapesjs/eleventy.ts +2 -5
- package/src/ts/client/grapesjs/footer.ts +24 -9
- package/src/ts/client/grapesjs/img.ts +147 -0
- package/src/ts/client/grapesjs/index.ts +63 -25
- package/src/ts/client/grapesjs/internal-links.test.ts +2 -1
- package/src/ts/client/grapesjs/internal-links.ts +0 -1
- package/src/ts/client/grapesjs/new-page-dialog.ts +2 -3
- package/src/ts/client/grapesjs/page-panel.ts +28 -3
- package/src/ts/client/grapesjs/project-bar.ts +83 -64
- package/src/ts/client/grapesjs/resize-panel.ts +20 -0
- package/src/ts/client/grapesjs/rich-text.ts +21 -13
- package/src/ts/client/grapesjs/semantic.ts +3 -3
- package/src/ts/client/grapesjs/settings-sections.ts +117 -0
- package/src/ts/client/grapesjs/settings.ts +120 -77
- package/src/ts/client/grapesjs/symbolDialogs.ts +2 -3
- package/src/ts/client/grapesjs/view-buttons.ts +42 -0
- package/src/ts/client/index.ts +13 -6
- package/src/ts/client/publication-transformers.test.ts +13 -5
- package/src/ts/client/publication-transformers.ts +60 -47
- package/{dist/plugins/client/client/utils.js → src/ts/client/publish-custom-code-block.ts} +14 -8
- package/{dist/plugins/client/page.js → src/ts/client/publish-fonts.ts} +19 -14
- package/src/ts/client/utils.ts +18 -0
- package/src/ts/constants.ts +1 -0
- package/src/ts/plugins/client/template.ts +112 -60
- package/src/ts/plugins/server/FtpConnector.ts +14 -2
- package/src/ts/plugins/server/GitlabConnector.ts +71 -32
- package/src/ts/server/api/publicationApi.ts +5 -5
- package/src/ts/server/api/websiteApi.ts +51 -17
- package/src/ts/server/cli.ts +7 -5
- package/src/ts/server/config.ts +4 -4
- package/src/ts/server/connectors/FsHosting.ts +11 -5
- package/src/ts/server/connectors/FsStorage.ts +33 -4
- package/src/ts/server/connectors/connectors.ts +1 -1
- package/src/ts/server/jobs.ts +4 -2
- package/src/ts/types.ts +10 -7
- package/dist/plugins/client/client/api.d.ts +0 -85
- package/dist/plugins/client/client/api.js +0 -114
- package/dist/plugins/client/client/api.js.map +0 -1
- package/dist/plugins/client/client/assetUrl.d.ts +0 -51
- package/dist/plugins/client/client/assetUrl.js +0 -224
- package/dist/plugins/client/client/assetUrl.js.map +0 -1
- package/dist/plugins/client/client/config.d.ts +0 -58
- package/dist/plugins/client/client/config.js +0 -90
- package/dist/plugins/client/client/config.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/LoginDialog.d.ts +0 -15
- package/dist/plugins/client/client/grapesjs/LoginDialog.js +0 -195
- package/dist/plugins/client/client/grapesjs/LoginDialog.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/PublicationManager.d.ts +0 -72
- package/dist/plugins/client/client/grapesjs/PublicationManager.js +0 -356
- package/dist/plugins/client/client/grapesjs/PublicationManager.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/PublicationUi.d.ts +0 -62
- package/dist/plugins/client/client/grapesjs/PublicationUi.js +0 -336
- package/dist/plugins/client/client/grapesjs/PublicationUi.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/blocks.d.ts +0 -1
- package/dist/plugins/client/client/grapesjs/blocks.js +0 -37
- package/dist/plugins/client/client/grapesjs/blocks.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/breadcrumbs.d.ts +0 -1
- package/dist/plugins/client/client/grapesjs/breadcrumbs.js +0 -75
- package/dist/plugins/client/client/grapesjs/breadcrumbs.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/footer.d.ts +0 -2
- package/dist/plugins/client/client/grapesjs/footer.js +0 -28
- package/dist/plugins/client/client/grapesjs/footer.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/index.d.ts +0 -5
- package/dist/plugins/client/client/grapesjs/index.js +0 -313
- package/dist/plugins/client/client/grapesjs/index.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/internal-links.d.ts +0 -1
- package/dist/plugins/client/client/grapesjs/internal-links.js +0 -219
- package/dist/plugins/client/client/grapesjs/internal-links.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/new-page-dialog.d.ts +0 -2
- package/dist/plugins/client/client/grapesjs/new-page-dialog.js +0 -83
- package/dist/plugins/client/client/grapesjs/new-page-dialog.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/openImport.d.ts +0 -9
- package/dist/plugins/client/client/grapesjs/openImport.js +0 -74
- package/dist/plugins/client/client/grapesjs/openImport.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/page-panel.d.ts +0 -3
- package/dist/plugins/client/client/grapesjs/page-panel.js +0 -113
- package/dist/plugins/client/client/grapesjs/page-panel.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/project-bar.d.ts +0 -1
- package/dist/plugins/client/client/grapesjs/project-bar.js +0 -104
- package/dist/plugins/client/client/grapesjs/project-bar.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/rich-text.d.ts +0 -1
- package/dist/plugins/client/client/grapesjs/rich-text.js +0 -31
- package/dist/plugins/client/client/grapesjs/rich-text.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/semantic.d.ts +0 -1
- package/dist/plugins/client/client/grapesjs/semantic.js +0 -110
- package/dist/plugins/client/client/grapesjs/semantic.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/settings.d.ts +0 -2
- package/dist/plugins/client/client/grapesjs/settings.js +0 -198
- package/dist/plugins/client/client/grapesjs/settings.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/storage.d.ts +0 -1
- package/dist/plugins/client/client/grapesjs/storage.js +0 -126
- package/dist/plugins/client/client/grapesjs/storage.js.map +0 -1
- package/dist/plugins/client/client/grapesjs/symbolDialogs.d.ts +0 -3
- package/dist/plugins/client/client/grapesjs/symbolDialogs.js +0 -36
- package/dist/plugins/client/client/grapesjs/symbolDialogs.js.map +0 -1
- package/dist/plugins/client/client/publication-transformers.d.ts +0 -41
- package/dist/plugins/client/client/publication-transformers.js +0 -238
- package/dist/plugins/client/client/publication-transformers.js.map +0 -1
- package/dist/plugins/client/client/utils.d.ts +0 -2
- package/dist/plugins/client/client/utils.js.map +0 -1
- package/dist/plugins/client/constants.d.ts +0 -29
- package/dist/plugins/client/constants.js +0 -46
- package/dist/plugins/client/constants.js.map +0 -1
- package/dist/plugins/client/page.d.ts +0 -2
- package/dist/plugins/client/page.js.map +0 -1
- package/dist/plugins/client/plugins/client/11ty.d.ts +0 -17
- package/dist/plugins/client/plugins/client/11ty.js +0 -86
- package/dist/plugins/client/plugins/client/11ty.js.map +0 -1
- package/dist/plugins/client/plugins/client/publicationRenderer.d.ts +0 -2
- package/dist/plugins/client/plugins/client/publicationRenderer.js +0 -170
- package/dist/plugins/client/plugins/client/publicationRenderer.js.map +0 -1
- package/src/ts/plugins/client/11ty.ts +0 -104
- package/src/ts/plugins/client/publicationRenderer.ts +0 -174
|
@@ -21,6 +21,7 @@ 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 { join } from 'path'
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Gitlab connector
|
|
@@ -193,7 +194,7 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
193
194
|
// **
|
|
194
195
|
// Convenience methods for the Gitlab API
|
|
195
196
|
private getAssetPath(path: string): string {
|
|
196
|
-
return encodeURIComponent(
|
|
197
|
+
return encodeURIComponent(join(this.options.assetsFolder, path))
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
private async createFile(session: GitlabSession, websiteId: WebsiteId, path: string, content: string, isBase64 = false): Promise<void> {
|
|
@@ -220,6 +221,24 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
220
221
|
})
|
|
221
222
|
}
|
|
222
223
|
|
|
224
|
+
async readFile(session: GitlabSession, websiteId: string, fileName: string): Promise<Buffer> {
|
|
225
|
+
const safePath = fileName.replace(/^\//, '')
|
|
226
|
+
// Call the API
|
|
227
|
+
const url = `https://gitlab.com/api/v4/projects/${websiteId}/repository/files/${safePath}?ref=${this.options.branch}&access_token=${session.gitlab?.token?.access_token}`
|
|
228
|
+
const response = await fetch(url, {
|
|
229
|
+
method: 'GET',
|
|
230
|
+
headers: {
|
|
231
|
+
'Content-Type': 'application/json',
|
|
232
|
+
},
|
|
233
|
+
})
|
|
234
|
+
const json = await response.json()
|
|
235
|
+
if(!response.ok) throw new ApiError(`Error reading file "${fileName}" from Gitlab: ${json?.message ?? json?.error ?? response.statusText}`, response.status)
|
|
236
|
+
// From base64 string to buffer
|
|
237
|
+
const buf = Buffer.from(json.content, 'base64')
|
|
238
|
+
// Return the image bytes
|
|
239
|
+
return buf
|
|
240
|
+
}
|
|
241
|
+
|
|
223
242
|
/*
|
|
224
243
|
* Get the meta repo path for the current user
|
|
225
244
|
* The meta repo contains a JSON file which contains the list of websites
|
|
@@ -273,20 +292,10 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
273
292
|
let json: { message: string, error: string } | any
|
|
274
293
|
// Handle the case when the server returns an non-JSON response (e.g. 400 Bad Request)
|
|
275
294
|
const text = await response.text()
|
|
276
|
-
try {
|
|
277
|
-
json = JSON.parse(text)
|
|
278
|
-
} catch (e) {
|
|
279
|
-
if(!response.ok) {
|
|
280
|
-
// A real error
|
|
281
|
-
throw e
|
|
282
|
-
} else {
|
|
283
|
-
// Useless error linked to the fact that the response is not JSON
|
|
284
|
-
console.error('Gitlab API error (3) - could not parse response', response.status, response.statusText, {url, method, body, params, text})
|
|
285
|
-
return text
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
295
|
if(!response.ok) {
|
|
289
|
-
if (
|
|
296
|
+
if (text.includes('A file with this name doesn\'t exist')) {
|
|
297
|
+
throw new ApiError('Gitlab API error (5): Not Found', 404)
|
|
298
|
+
} else if (response.status === 401 && session?.gitlab?.token?.refresh_token) {
|
|
290
299
|
// Refresh the token
|
|
291
300
|
const token = session?.gitlab?.token
|
|
292
301
|
const body = {
|
|
@@ -320,10 +329,21 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
320
329
|
}
|
|
321
330
|
} else {
|
|
322
331
|
const message = typeof json?.message === 'object' ? Object.entries(json.message).map(entry => entry.join(' ')).join(' ') : json?.message ?? json?.error ?? response.statusText
|
|
323
|
-
console.error('Gitlab API error (1)', response.status, response.statusText, {message})
|
|
324
332
|
throw new ApiError(`Gitlab API error (1): ${message}`, response.status)
|
|
325
333
|
}
|
|
326
334
|
}
|
|
335
|
+
try {
|
|
336
|
+
json = JSON.parse(text)
|
|
337
|
+
} catch (e) {
|
|
338
|
+
if(!response.ok) {
|
|
339
|
+
// A real error
|
|
340
|
+
throw e
|
|
341
|
+
} else {
|
|
342
|
+
// Useless error linked to the fact that the response is not JSON
|
|
343
|
+
console.error('Gitlab API error (3) - could not parse response', response.status, response.statusText, {url, method, body, params, text})
|
|
344
|
+
return text
|
|
345
|
+
}
|
|
346
|
+
}
|
|
327
347
|
return json
|
|
328
348
|
}
|
|
329
349
|
|
|
@@ -501,7 +521,6 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
501
521
|
branch: this.options.branch,
|
|
502
522
|
commit_message: 'Update website data from Silex',
|
|
503
523
|
content: JSON.stringify(websiteData),
|
|
504
|
-
file_path: WEBSITE_DATA_FILE,
|
|
505
524
|
id: websiteId,
|
|
506
525
|
})
|
|
507
526
|
}
|
|
@@ -527,6 +546,37 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
527
546
|
//})
|
|
528
547
|
}
|
|
529
548
|
|
|
549
|
+
async duplicateWebsite(session: GitlabSession, websiteId: string): Promise<void> {
|
|
550
|
+
// Get the repo meta data
|
|
551
|
+
const meta = await this.getWebsiteMeta(session, websiteId)
|
|
552
|
+
// List all the repository files
|
|
553
|
+
const blobs = await this.callApi(session, `api/v4/projects/${websiteId}/repository/tree`, 'GET', null, {
|
|
554
|
+
recursive: true,
|
|
555
|
+
})
|
|
556
|
+
const files = blobs
|
|
557
|
+
.filter(item => item.type === 'blob')
|
|
558
|
+
.map(item => item.path)
|
|
559
|
+
// Create a new repo
|
|
560
|
+
const newId = await this.createWebsite(session, {
|
|
561
|
+
...meta,
|
|
562
|
+
name: meta.name + ' Copy ' + new Date().toISOString().replace(/T.*/, '') + ' ' + Math.random().toString(36).substring(2, 4),
|
|
563
|
+
})
|
|
564
|
+
// Upload all files
|
|
565
|
+
for(const file of files) {
|
|
566
|
+
const path = encodeURIComponent(file)
|
|
567
|
+
const content = await this.readFile(session, websiteId, path)
|
|
568
|
+
// From buffer to string
|
|
569
|
+
const contentStr = content.toString('base64')
|
|
570
|
+
switch(file) {
|
|
571
|
+
case WEBSITE_DATA_FILE:
|
|
572
|
+
await this.updateFile(session, newId, path, contentStr, true)
|
|
573
|
+
break
|
|
574
|
+
default:
|
|
575
|
+
await this.createFile(session, newId, path, contentStr, true)
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
530
580
|
async getWebsiteMeta(session: GitlabSession, websiteId: WebsiteId): Promise<WebsiteMeta> {
|
|
531
581
|
//const file = await this.callApi(session, `api/v4/projects/${this.getMetaRepoPath(session)}/repository/files/${this.options.metaRepoFile}`, 'GET', null, {
|
|
532
582
|
// ref: this.options.branch,
|
|
@@ -598,12 +648,14 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
598
648
|
for (const file of files) {
|
|
599
649
|
// Convert to base64
|
|
600
650
|
const content = (await contentToBuffer(file.content)).toString('base64')
|
|
651
|
+
const path = this.getAssetPath(file.path)
|
|
652
|
+
|
|
601
653
|
try {
|
|
602
|
-
await this.updateFile(session, websiteId,
|
|
654
|
+
await this.updateFile(session, websiteId, path, content, true)
|
|
603
655
|
} catch (e) {
|
|
604
656
|
// If the file does not exist, create it
|
|
605
657
|
if (e.statusCode === 404 || e.httpStatusCode === 404 || e.message.endsWith('A file with this name doesn\'t exist')) {
|
|
606
|
-
await this.createFile(session, websiteId,
|
|
658
|
+
await this.createFile(session, websiteId, path, content, true)
|
|
607
659
|
} else {
|
|
608
660
|
throw e
|
|
609
661
|
}
|
|
@@ -614,20 +666,7 @@ export default class GitlabConnector implements StorageConnector {
|
|
|
614
666
|
async readAsset(session: GitlabSession, websiteId: string, fileName: string): Promise<ConnectorFileContent> {
|
|
615
667
|
// Remove leading slash
|
|
616
668
|
const finalPath = this.getAssetPath(fileName)
|
|
617
|
-
|
|
618
|
-
const url = `https://gitlab.com/api/v4/projects/${websiteId}/repository/files/${finalPath}?ref=${this.options.branch}&access_token=${session.gitlab?.token?.access_token}`
|
|
619
|
-
const response = await fetch(url, {
|
|
620
|
-
method: 'GET',
|
|
621
|
-
headers: {
|
|
622
|
-
'Content-Type': 'application/json',
|
|
623
|
-
},
|
|
624
|
-
})
|
|
625
|
-
const json = await response.json()
|
|
626
|
-
if(!response.ok) throw new ApiError(`Gitlab API error: ${json?.message ?? json?.error ?? response.statusText}`, response.status)
|
|
627
|
-
// From base64 string to buffer
|
|
628
|
-
const buf = Buffer.from(json.content, 'base64')
|
|
629
|
-
// Return the image bytes
|
|
630
|
-
return buf
|
|
669
|
+
return this.readFile(session, websiteId, finalPath)
|
|
631
670
|
}
|
|
632
671
|
|
|
633
672
|
async deleteAssets(session: GitlabSession, websiteId: string, fileNames: string[]): Promise<void> {
|
|
@@ -124,15 +124,15 @@ export default function (config: ServerConfig): Router {
|
|
|
124
124
|
|
|
125
125
|
// Load the content if necessary
|
|
126
126
|
const filesList: ConnectorFile[] = await Promise.all(files.map(async (file: ClientSideFile) => {
|
|
127
|
-
const prefix = API_WEBSITE_ASSET_READ
|
|
128
127
|
const fileWithContent = file as ClientSideFileWithContent
|
|
129
128
|
const fileWithSrc = file as ClientSideFileWithSrc
|
|
130
129
|
if(!fileWithContent.content && !fileWithSrc.src) throw new PublicationError('Missing content or src in file', 400)
|
|
131
|
-
const src = fileWithSrc.src
|
|
130
|
+
const src = fileWithSrc.src
|
|
132
131
|
return {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
// Destination
|
|
133
|
+
path: file.permalink ?? file.path,
|
|
134
|
+
// Content
|
|
135
|
+
content: fileWithContent.content ?? await storage.readAsset(session, websiteId, fileWithSrc.src),
|
|
136
136
|
}
|
|
137
137
|
}))
|
|
138
138
|
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
import { Router } from 'express'
|
|
19
19
|
import formidable from 'formidable'
|
|
20
20
|
import PersistentFile from 'formidable/src/PersistentFile'
|
|
21
|
-
import { API_WEBSITE_ASSET_READ, API_WEBSITE_ASSETS_WRITE, API_WEBSITE_READ, API_WEBSITE_WRITE, API_WEBSITE_DELETE, API_WEBSITE_META_READ, API_WEBSITE_META_WRITE, API_WEBSITE_LIST, API_WEBSITE_CREATE, API_PATH, API_WEBSITE_PATH } from '../../constants'
|
|
21
|
+
import { API_WEBSITE_ASSET_READ, API_WEBSITE_ASSETS_WRITE, API_WEBSITE_READ, API_WEBSITE_WRITE, API_WEBSITE_DELETE, API_WEBSITE_META_READ, API_WEBSITE_META_WRITE, API_WEBSITE_LIST, API_WEBSITE_CREATE, API_PATH, API_WEBSITE_PATH, API_WEBSITE_DUPLICATE } from '../../constants'
|
|
22
22
|
import { createReadStream } from 'fs'
|
|
23
|
-
import { ApiError, ApiWebsiteAssetsReadParams, ApiWebsiteAssetsReadQuery, ApiWebsiteAssetsReadResponse, ApiWebsiteAssetsWriteQuery, ApiWebsiteAssetsWriteResponse, ApiWebsiteDeleteQuery, ApiWebsiteReadQuery, ApiWebsiteReadResponse, ApiWebsiteWriteBody, ApiWebsiteWriteQuery, ConnectorId, ConnectorType, WebsiteMeta, WebsiteData, WebsiteId, ApiWebsiteListQuery, ApiWebsiteListResponse, ApiWebsiteMetaReadQuery, ApiWebsiteMetaReadResponse, ApiWebsiteMetaWriteQuery, ApiWebsiteMetaWriteBody, WebsiteMetaFileContent, ApiWebsiteMetaWriteResponse, ApiWebsiteWriteResponse, ApiWebsiteCreateQuery, ApiWebsiteCreateBody } from '../../types'
|
|
23
|
+
import { ApiError, ApiWebsiteAssetsReadParams, ApiWebsiteAssetsReadQuery, ApiWebsiteAssetsReadResponse, ApiWebsiteAssetsWriteQuery, ApiWebsiteAssetsWriteResponse, ApiWebsiteDeleteQuery, ApiWebsiteReadQuery, ApiWebsiteReadResponse, ApiWebsiteWriteBody, ApiWebsiteWriteQuery, ConnectorId, ConnectorType, WebsiteMeta, WebsiteData, WebsiteId, ApiWebsiteListQuery, ApiWebsiteListResponse, ApiWebsiteMetaReadQuery, ApiWebsiteMetaReadResponse, ApiWebsiteMetaWriteQuery, ApiWebsiteMetaWriteBody, WebsiteMetaFileContent, ApiWebsiteMetaWriteResponse, ApiWebsiteWriteResponse, ApiWebsiteCreateQuery, ApiWebsiteCreateBody, ApiWebsiteDuplicateQuery } from '../../types'
|
|
24
24
|
import { ConnectorFile, ConnectorFileContent, ConnectorSession, StorageConnector, getConnector } from '../connectors/connectors'
|
|
25
25
|
import { Readable } from 'stream'
|
|
26
26
|
import { requiredParam } from '../utils/validation'
|
|
27
|
-
import { basename } from 'path'
|
|
27
|
+
import { basename, join } from 'path'
|
|
28
28
|
import { ServerConfig } from '../config'
|
|
29
29
|
import { ServerEvent, WebsiteStoreEndEventType, WebsiteStoreStartEventType, WebsiteAssetStoreStartEventType, WebsiteAssetStoreEndEventType } from '../events'
|
|
30
30
|
|
|
@@ -226,6 +226,23 @@ export default function (config: ServerConfig, opts = {}): Router {
|
|
|
226
226
|
}
|
|
227
227
|
})
|
|
228
228
|
|
|
229
|
+
// Duplicate website
|
|
230
|
+
router.post(API_WEBSITE_DUPLICATE, async (req, res) => {
|
|
231
|
+
try {
|
|
232
|
+
const query: ApiWebsiteDuplicateQuery = req.query as any
|
|
233
|
+
const websiteId= requiredParam<WebsiteId>(query.websiteId, 'New website id')
|
|
234
|
+
await duplicateWebsite(req['session'], websiteId, query.connectorId)
|
|
235
|
+
res.status(200).json({ message: 'Website duplicated' } as ApiError)
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.error('Error duplicating website data', e)
|
|
238
|
+
if (e.httpStatusCode) {
|
|
239
|
+
res.status(e.httpStatusCode).json({ message: e.message } as ApiError)
|
|
240
|
+
} else {
|
|
241
|
+
res.status(500).json({ message: e.message } as ApiError)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
|
|
229
246
|
// Load assets
|
|
230
247
|
router.get(API_WEBSITE_ASSET_READ + '/:path', async (req, res) => {
|
|
231
248
|
{
|
|
@@ -261,11 +278,10 @@ export default function (config: ServerConfig, opts = {}): Router {
|
|
|
261
278
|
try {
|
|
262
279
|
// Check input
|
|
263
280
|
const query: ApiWebsiteAssetsWriteQuery = req.query as any
|
|
264
|
-
const websiteId= requiredParam<WebsiteId>(query.websiteId as WebsiteId, 'Website id')
|
|
281
|
+
const websiteId = requiredParam<WebsiteId>(query.websiteId as WebsiteId, 'Website id')
|
|
265
282
|
|
|
266
283
|
// Get the file data from the request
|
|
267
284
|
const form = formidable({
|
|
268
|
-
filename: (name, ext, part, _form) => `${name}${ext}`,
|
|
269
285
|
multiples: true,
|
|
270
286
|
keepExtensions: true,
|
|
271
287
|
})
|
|
@@ -278,7 +294,7 @@ export default function (config: ServerConfig, opts = {}): Router {
|
|
|
278
294
|
console.error('Error parsing upload data', err)
|
|
279
295
|
reject(new ApiError('Error parsing upload data: ' + err.message, 400))
|
|
280
296
|
} else {
|
|
281
|
-
const files = ([].concat(_files['files[]']) as PersistentFile[])
|
|
297
|
+
const files = ([].concat(_files['files[]'] as PersistentFile) as PersistentFile[])
|
|
282
298
|
.map(file => file.toJSON())
|
|
283
299
|
.map(file => ({
|
|
284
300
|
path: `/${file.originalFilename}`,
|
|
@@ -300,13 +316,19 @@ export default function (config: ServerConfig, opts = {}): Router {
|
|
|
300
316
|
|
|
301
317
|
// Return the file URLs to insert in the website
|
|
302
318
|
// As expected by grapesjs (https://grapesjs.com/docs/modules/Assets.html#uploading-assets)
|
|
319
|
+
const data = result.map(path =>
|
|
320
|
+
join(
|
|
321
|
+
// We should return path without this line, as it is saved, not as it is displayed
|
|
322
|
+
// But this url is sent straight to grapesjs, so we need to return the url as it is displayed
|
|
323
|
+
baseUrl, API_PATH, API_WEBSITE_PATH, API_WEBSITE_ASSET_READ,
|
|
324
|
+
path,
|
|
325
|
+
)
|
|
326
|
+
+ `?websiteId=${websiteId}&connectorId=${connectorId ? connectorId : ''}` // As expected by wesite API (readAsset)
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
// Return the file URLs
|
|
303
330
|
res.json({
|
|
304
|
-
data
|
|
305
|
-
// FIXME: We should return path without this line, as it is saved, not as it is displayed
|
|
306
|
-
baseUrl + API_PATH + API_WEBSITE_PATH + API_WEBSITE_ASSET_READ
|
|
307
|
-
+ path
|
|
308
|
-
+ `?websiteId=${websiteId}&connectorId=${connectorId ? connectorId : ''}` // As expected by wesite API (readAsset)
|
|
309
|
-
),
|
|
331
|
+
data,
|
|
310
332
|
} as ApiWebsiteAssetsWriteResponse)
|
|
311
333
|
|
|
312
334
|
// Hook for plugins
|
|
@@ -385,6 +407,17 @@ export default function (config: ServerConfig, opts = {}): Router {
|
|
|
385
407
|
return storageConnector.deleteWebsite(session, websiteId)
|
|
386
408
|
}
|
|
387
409
|
|
|
410
|
+
/**
|
|
411
|
+
* Duplicate a website
|
|
412
|
+
*/
|
|
413
|
+
async function duplicateWebsite(session: any, websiteId: string, connectorId?: string): Promise<void> {
|
|
414
|
+
// Get the desired connector
|
|
415
|
+
const storageConnector = await getStorageConnector(session, connectorId)
|
|
416
|
+
|
|
417
|
+
// Duplicate the website
|
|
418
|
+
return storageConnector.duplicateWebsite(session, websiteId)
|
|
419
|
+
}
|
|
420
|
+
|
|
388
421
|
/**
|
|
389
422
|
* Read an asset
|
|
390
423
|
*/
|
|
@@ -418,11 +451,12 @@ export default function (config: ServerConfig, opts = {}): Router {
|
|
|
418
451
|
)
|
|
419
452
|
|
|
420
453
|
// Return the files URLs with the website id
|
|
421
|
-
return files
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
454
|
+
return files
|
|
455
|
+
// Use the original path or the one returned by the connector
|
|
456
|
+
.map(({ path }, idx) => result && result[idx] ? result[idx] : path)
|
|
457
|
+
// Make it an absolute path with the website id and the connector id as query params
|
|
458
|
+
//.map((path) => toAssetUrl(path, config.url, websiteId, connectorId))
|
|
426
459
|
}
|
|
460
|
+
|
|
427
461
|
return router
|
|
428
462
|
}
|
package/src/ts/server/cli.ts
CHANGED
|
@@ -25,11 +25,12 @@ const options = parse({
|
|
|
25
25
|
'host': ['', 'Sets the host param of the express module "cookie-session".', 'string'],
|
|
26
26
|
'port': [ 'p', 'Port to listen to', 'int'],
|
|
27
27
|
'protocol': ['', 'Sets the protocol param of the express module "cookie-session".', 'string'],
|
|
28
|
-
'config': [ '
|
|
29
|
-
'client-config': ['', 'Path to client config file to be served on .silex-client.js', '
|
|
30
|
-
'fs-root': ['', 'Path to the root folder where to store websites. Used by the default connector (fs).', 'string'],
|
|
28
|
+
'server-config': [ 's', 'Path for the server side config file to load at startup', 'file'],
|
|
29
|
+
'client-config': ['c', 'Path to client config file to be served on .silex-client.js', 'file'],
|
|
30
|
+
'fs-root': ['', 'Path to the root folder where to store websites. Used by the default storage connector (fs).', 'string'],
|
|
31
|
+
'fs-hosting-root': ['', 'Path to the root folder where to publish websites. Used by the default hosting connector (fs).', 'string'],
|
|
31
32
|
'session-name': ['', 'Sets the name param of the express module "cookie-session".', 'string'],
|
|
32
|
-
'session-secret': ['
|
|
33
|
+
'session-secret': ['', 'Session secret', 'string'],
|
|
33
34
|
'ssl-port': [ false, 'Port to listen to for SSL/HTTPS', 'int'],
|
|
34
35
|
'force-https': [ false, 'Force HTTPS', 'boolean'],
|
|
35
36
|
'ssl-private-key': [ false, 'Path to private key for SSL', 'file'],
|
|
@@ -46,9 +47,10 @@ if(options['url']) process.env.SILEX_URL = options['url']
|
|
|
46
47
|
if(options['host']) process.env.SILEX_HOST = options['host']
|
|
47
48
|
if(options.port) process.env.SILEX_PORT = options.port
|
|
48
49
|
if(options['protocol']) process.env.SILEX_PROTOCOL = options['protocol']
|
|
49
|
-
if(options
|
|
50
|
+
if(options['server-config']) process.env.SILEX_SERVER_CONFIG = options['server-config']
|
|
50
51
|
if(options['client-config']) process.env.SILEX_CLIENT_CONFIG = options['client-config']
|
|
51
52
|
if(options['fs-root']) process.env.SILEX_FS_ROOT = options['fs-root']
|
|
53
|
+
if(options['fs-hosting-root']) process.env.SILEX_FS_HOSTING_ROOT = options['fs-hosting-root']
|
|
52
54
|
if(options['session-name']) process.env.SILEX_SESSION_NAME = options['session-name']
|
|
53
55
|
if(options['session-secret']) process.env.SILEX_SESSION_SECRET = options['session-secret']
|
|
54
56
|
if(options['ssl-port']) process.env.SILEX_SSL_PORT = options['ssl-port']
|
package/src/ts/server/config.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { Config, Plugin } from '@silexlabs/silex-plugins'
|
|
28
|
-
import { resolve } from 'path'
|
|
28
|
+
import { join, resolve } from 'path'
|
|
29
29
|
import { CLIENT_CONFIG_FILE_NAME } from '../constants'
|
|
30
30
|
import { Application, Request, Response, Router } from 'express'
|
|
31
31
|
import { readFile } from 'fs/promises'
|
|
@@ -50,7 +50,7 @@ export class ServerConfig extends Config {
|
|
|
50
50
|
public port = process.env.SILEX_PORT
|
|
51
51
|
public debug = process.env.SILEX_DEBUG === 'true'
|
|
52
52
|
public url = process.env.SILEX_URL?.replace(/\/$/, '') ?? `${process.env.SILEX_PROTOCOL}://${process.env.SILEX_HOST}:${process.env.SILEX_PORT}`
|
|
53
|
-
public userConfigPath: Plugin | undefined = process.env.
|
|
53
|
+
public userConfigPath: Plugin | undefined = process.env.SILEX_SERVER_CONFIG
|
|
54
54
|
public configFilePath: Plugin = resolve(__dirname, '../../../.silex.js')
|
|
55
55
|
|
|
56
56
|
// All connectors or just the storage or hosting connectors
|
|
@@ -176,13 +176,13 @@ export class ServerConfig extends Config {
|
|
|
176
176
|
// Add default storage connectors
|
|
177
177
|
if (!this.storageConnectors.length) {
|
|
178
178
|
this.addStorageConnector(new FsStorage(null, {
|
|
179
|
-
path: process.env.SILEX_FS_ROOT,
|
|
179
|
+
path: process.env.SILEX_FS_ROOT || join(process.cwd(), '/silex/storage'),
|
|
180
180
|
}))
|
|
181
181
|
}
|
|
182
182
|
// Add default hosting connectors
|
|
183
183
|
if (!this.hostingConnectors.length) {
|
|
184
184
|
this.addHostingConnector(new FsHosting(null, {
|
|
185
|
-
path: process.env.
|
|
185
|
+
path: process.env.SILEX_FS_HOSTING_ROOT || join(process.cwd(), '/silex/hosting'),
|
|
186
186
|
}))
|
|
187
187
|
}
|
|
188
188
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import fs from 'fs/promises'
|
|
18
19
|
import { ConnectorFile, StorageConnector, HostingConnector, StatusCallback, ConnectorSession, contentToString, toConnectorData, ConnectorFileContent} from './connectors'
|
|
19
20
|
import { join } from 'path'
|
|
20
21
|
import { FsStorage } from './FsStorage'
|
|
@@ -23,21 +24,26 @@ import { JobManager } from '../jobs'
|
|
|
23
24
|
|
|
24
25
|
type FsSession = ConnectorSession
|
|
25
26
|
|
|
26
|
-
interface FsOptions {
|
|
27
|
-
path?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
27
|
export class FsHosting extends FsStorage implements HostingConnector<FsSession> {
|
|
31
28
|
connectorId = 'fs-hosting'
|
|
32
29
|
displayName = 'File system hosting'
|
|
33
30
|
connectorType = ConnectorType.HOSTING
|
|
34
31
|
|
|
32
|
+
protected async initFs() {
|
|
33
|
+
const stat = await fs.stat(this.options.path).catch(() => null)
|
|
34
|
+
if (!stat) {
|
|
35
|
+
await fs.mkdir(join(this.options.path, 'assets'), { recursive: true })
|
|
36
|
+
await fs.mkdir(join(this.options.path, 'css'), { recursive: true })
|
|
37
|
+
console.info(`> [FsHosting] Created folders assets/ and css/ in ${this.options.path}`)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
async publish(session: FsSession, id: WebsiteId, files: ConnectorFile[], {startJob, jobSuccess, jobError}: JobManager): Promise<JobData> {
|
|
36
42
|
const job = startJob(`Publishing to ${this.displayName}`) as PublicationJobData
|
|
37
43
|
job.logs = [[`Publishing to ${this.displayName}`]]
|
|
38
44
|
job.errors = [[]]
|
|
39
45
|
// Call write without id or folder so that it goes in / (path will be modified by publication transformers)
|
|
40
|
-
this.write(session, '', files, '', async ({status, message}) => {
|
|
46
|
+
await this.write(session, '', files, '', async ({status, message}) => {
|
|
41
47
|
// Update the job status
|
|
42
48
|
job.status = status
|
|
43
49
|
job.message = message
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import fs
|
|
18
|
+
import fs from 'fs/promises'
|
|
19
19
|
import { createWriteStream } from 'fs'
|
|
20
20
|
import { ConnectorFile, StorageConnector, StatusCallback, ConnectorSession, toConnectorData, ConnectorFileContent} from './connectors'
|
|
21
21
|
import { dirname, join } from 'path'
|
|
@@ -35,6 +35,22 @@ if(!globalThis.__dirname) {
|
|
|
35
35
|
console.info('Redefining __dirname', globalThis.__dirname)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Copy a folder recursively
|
|
39
|
+
async function copyDir(src, dest) {
|
|
40
|
+
await fs.mkdir(dest, { recursive: true })
|
|
41
|
+
const entries = await fs.readdir(src, { withFileTypes: true })
|
|
42
|
+
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
const srcPath = join(src, entry.name)
|
|
45
|
+
const destPath = join(dest, entry.name)
|
|
46
|
+
|
|
47
|
+
entry.isDirectory() ?
|
|
48
|
+
await copyDir(srcPath, destPath) :
|
|
49
|
+
await fs.copyFile(srcPath, destPath)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
38
54
|
type FsSession = ConnectorSession
|
|
39
55
|
|
|
40
56
|
const USER_ICON = 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' height=\'1em\' viewBox=\'0 0 448 512\'%3E%3Cpath d=\'M304 128a80 80 0 1 0 -160 0 80 80 0 1 0 160 0zM96 128a128 128 0 1 1 256 0A128 128 0 1 1 96 128zM49.3 464H398.7c-8.9-63.3-63.3-112-129-112H178.3c-65.7 0-120.1 48.7-129 112zM0 482.3C0 383.8 79.8 304 178.3 304h91.4C368.2 304 448 383.8 448 482.3c0 16.4-13.3 29.7-29.7 29.7H29.7C13.3 512 0 498.7 0 482.3z\'/%3E%3C/svg%3E'
|
|
@@ -64,7 +80,7 @@ export class FsStorage implements StorageConnector<FsSession> {
|
|
|
64
80
|
this.initFs()
|
|
65
81
|
}
|
|
66
82
|
|
|
67
|
-
|
|
83
|
+
protected async initFs() {
|
|
68
84
|
const stat = await fs.stat(this.options.path).catch(() => null)
|
|
69
85
|
if (!stat) {
|
|
70
86
|
// create data folder with a default website
|
|
@@ -72,6 +88,7 @@ export class FsStorage implements StorageConnector<FsSession> {
|
|
|
72
88
|
await fs.mkdir(join(this.options.path, id, this.options.assetsFolder), { recursive: true })
|
|
73
89
|
await this.setWebsiteMeta({}, id, { name: 'Default website', connectorUserSettings: {} })
|
|
74
90
|
await this.updateWebsite({}, id, defaultWebsiteData)
|
|
91
|
+
console.info(`> [FsStorage] Created ${id} in ${this.options.path}`)
|
|
75
92
|
}
|
|
76
93
|
}
|
|
77
94
|
|
|
@@ -136,7 +153,7 @@ export class FsStorage implements StorageConnector<FsSession> {
|
|
|
136
153
|
async getWebsiteMeta(session: FsSession, id: WebsiteId): Promise<WebsiteMeta> {
|
|
137
154
|
const websiteId = requiredParam<WebsiteId>(id, 'website id')
|
|
138
155
|
// Get stats for website folder
|
|
139
|
-
const fileStat = await stat(join(this.options.path, websiteId))
|
|
156
|
+
const fileStat = await fs.stat(join(this.options.path, websiteId))
|
|
140
157
|
const path = join(this.options.path, websiteId, WEBSITE_META_DATA_FILE)
|
|
141
158
|
// Get meta file
|
|
142
159
|
const content = await fs.readFile(path)
|
|
@@ -182,6 +199,18 @@ export class FsStorage implements StorageConnector<FsSession> {
|
|
|
182
199
|
return fs.rmdir(path, { recursive: true })
|
|
183
200
|
}
|
|
184
201
|
|
|
202
|
+
async duplicateWebsite(session: FsSession, websiteId: WebsiteId): Promise<void> {
|
|
203
|
+
const newWebsiteId = uuid()
|
|
204
|
+
const from = join(this.options.path, websiteId)
|
|
205
|
+
const to = join(this.options.path, newWebsiteId)
|
|
206
|
+
await copyDir(from, to)
|
|
207
|
+
const meta = await this.getWebsiteMeta(session, websiteId)
|
|
208
|
+
await this.setWebsiteMeta(session, newWebsiteId, {
|
|
209
|
+
...meta,
|
|
210
|
+
name: `${meta.name} copy`,
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
185
214
|
async listWebsites(session: any): Promise<WebsiteMeta[]> {
|
|
186
215
|
const list = await fs.readdir(this.options.path)
|
|
187
216
|
return Promise.all(list.map(async fileName => {
|
|
@@ -210,7 +239,7 @@ export class FsStorage implements StorageConnector<FsSession> {
|
|
|
210
239
|
fileStatus.message = 'Writing'
|
|
211
240
|
this.updateStatus(filesStatuses, JobStatus.IN_PROGRESS, statusCbk)
|
|
212
241
|
try {
|
|
213
|
-
await fs.writeFile(path, file.content
|
|
242
|
+
await fs.writeFile(path, file.content)
|
|
214
243
|
} catch(err) {
|
|
215
244
|
fileStatus.message = `Error (${err})`
|
|
216
245
|
this.updateStatus(filesStatuses, JobStatus.IN_PROGRESS, statusCbk)
|
|
@@ -86,6 +86,7 @@ export interface StorageConnector<Session extends ConnectorSession = ConnectorSe
|
|
|
86
86
|
createWebsite(session: Session, data: WebsiteMetaFileContent): Promise<WebsiteId>
|
|
87
87
|
updateWebsite(session: Session, websiteId: WebsiteId, data: WebsiteData): Promise<void>
|
|
88
88
|
deleteWebsite(session: Session, websiteId: WebsiteId): Promise<void>
|
|
89
|
+
duplicateWebsite(session: Session, websiteId: WebsiteId): Promise<void>
|
|
89
90
|
// CRUD on assets
|
|
90
91
|
writeAssets(session: Session, websiteId: WebsiteId, files: ConnectorFile[], status?: StatusCallback): Promise<string[] | void>
|
|
91
92
|
readAsset(session: Session, websiteId: WebsiteId, fileName: string): Promise<ConnectorFileContent>
|
|
@@ -102,7 +103,6 @@ export interface StorageConnector<Session extends ConnectorSession = ConnectorSe
|
|
|
102
103
|
export interface HostingConnector<Session extends ConnectorSession = ConnectorSession> extends Connector<Session> {
|
|
103
104
|
publish(session: Session, websiteId: WebsiteId, files: ConnectorFile[], jobManager: JobManager): Promise<JobData> // Pass the jobManager as plugins do not neccessarily share the same module instance
|
|
104
105
|
getUrl(session: Session, websiteId: WebsiteId): Promise<string>
|
|
105
|
-
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
export function toConnectorEnum(type: string | ConnectorType): ConnectorType {
|
package/src/ts/server/jobs.ts
CHANGED
|
@@ -22,6 +22,7 @@ const TIME_TO_KEEP_AFTER_STOP = 60*10*1000 // 10 min
|
|
|
22
22
|
|
|
23
23
|
// Store the jobs in memory
|
|
24
24
|
const jobs = new Array<JobData>()
|
|
25
|
+
const timers = new WeakMap<JobData, NodeJS.Timeout>()
|
|
25
26
|
|
|
26
27
|
// Key which will change on server restart
|
|
27
28
|
const processKey = Math.ceil(Math.random() * 1000000)
|
|
@@ -60,7 +61,8 @@ export function jobError(id: JobId, message = ''): boolean {
|
|
|
60
61
|
// Remove a job from memory
|
|
61
62
|
// This is also used in unit tests to avoid memory lea
|
|
62
63
|
export function killJob(job: JobData) {
|
|
63
|
-
clearTimeout(job
|
|
64
|
+
clearTimeout(timers.get(job))
|
|
65
|
+
timers.delete(job)
|
|
64
66
|
jobs.splice(jobs.findIndex(j => job.jobId === j.jobId), 1)
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -70,7 +72,7 @@ function end(id: JobId, status: JobStatus, message = ''): boolean {
|
|
|
70
72
|
if(job) {
|
|
71
73
|
job.status = status
|
|
72
74
|
if(message) job.message = message
|
|
73
|
-
job
|
|
75
|
+
timers.set(job, setTimeout(() => killJob(job), TIME_TO_KEEP_AFTER_STOP))
|
|
74
76
|
return true
|
|
75
77
|
}
|
|
76
78
|
return false
|
package/src/ts/types.ts
CHANGED
|
@@ -58,6 +58,7 @@ export type ApiWebsiteCreateQuery = { connectorId?: ConnectorId }
|
|
|
58
58
|
export type ApiWebsiteCreateBody = WebsiteMetaFileContent
|
|
59
59
|
export type ApiWebsiteCreateResponse = { message: string }
|
|
60
60
|
export type ApiWebsiteDeleteQuery = { websiteId: WebsiteId, connectorId?: ConnectorId }
|
|
61
|
+
export type ApiWebsiteDuplicateQuery = { websiteId: WebsiteId, connectorId?: ConnectorId }
|
|
61
62
|
export type ApiWebsiteMetaReadQuery = { websiteId: WebsiteId, connectorId?: ConnectorId }
|
|
62
63
|
export type ApiWebsiteMetaReadResponse = WebsiteMeta
|
|
63
64
|
export type ApiWebsiteMetaWriteQuery = { websiteId: WebsiteId, connectorId?: ConnectorId }
|
|
@@ -201,6 +202,12 @@ export enum ClientSideFileType {
|
|
|
201
202
|
HTML = 'html',
|
|
202
203
|
ASSET = 'asset',
|
|
203
204
|
CSS = 'css',
|
|
205
|
+
OTHER = 'other',
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export enum Initiator {
|
|
209
|
+
HTML = 'html',
|
|
210
|
+
CSS = 'css',
|
|
204
211
|
}
|
|
205
212
|
|
|
206
213
|
/**
|
|
@@ -208,26 +215,22 @@ export enum ClientSideFileType {
|
|
|
208
215
|
*/
|
|
209
216
|
export interface ClientSideFileWithPermalink {
|
|
210
217
|
path: string, // Path in the connector
|
|
211
|
-
permalink
|
|
218
|
+
permalink?: string, // Defaults to path, this the path where the file is served, it is used to link to the file
|
|
212
219
|
type: ClientSideFileType,
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
/**
|
|
216
223
|
* Type for a client side file when the content is available as a string
|
|
217
224
|
*/
|
|
218
|
-
export interface ClientSideFileWithContent {
|
|
219
|
-
path: string,
|
|
225
|
+
export interface ClientSideFileWithContent extends ClientSideFileWithPermalink {
|
|
220
226
|
content: string, // Not buffer because it's sent from the client in JSON
|
|
221
|
-
type: ClientSideFileType
|
|
222
227
|
}
|
|
223
228
|
|
|
224
229
|
/**
|
|
225
230
|
* Type for a client side file when the content is in the connector
|
|
226
231
|
*/
|
|
227
|
-
export interface ClientSideFileWithSrc {
|
|
228
|
-
path: string,
|
|
232
|
+
export interface ClientSideFileWithSrc extends ClientSideFileWithPermalink {
|
|
229
233
|
src: string, // Where to download the file, a path for the storage connector
|
|
230
|
-
type: ClientSideFileType
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
/**
|