@silexlabs/silex 3.0.1-0 → 3.0.1

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.
Files changed (250) hide show
  1. package/.env.default +3 -2
  2. package/.silex.js +39 -1
  3. package/README.md +37 -35
  4. package/dist/client/client/api.d.ts +8 -1
  5. package/dist/client/client/assetUrl.d.ts +54 -0
  6. package/dist/client/client/config.d.ts +60 -1
  7. package/dist/client/client/events.d.ts +19 -10
  8. package/dist/client/client/grapesjs/PublicationManager.d.ts +6 -8
  9. package/dist/client/client/grapesjs/PublicationUi.d.ts +8 -10
  10. package/dist/client/client/grapesjs/breadcrumbs.d.ts +1 -0
  11. package/dist/client/client/grapesjs/css-props.d.ts +6 -0
  12. package/dist/client/client/grapesjs/footer.d.ts +2 -0
  13. package/dist/client/client/grapesjs/img.d.ts +6 -0
  14. package/dist/client/client/grapesjs/index.d.ts +6 -2
  15. package/dist/client/client/grapesjs/internal-links.d.ts +1 -1
  16. package/dist/client/client/grapesjs/keymaps.d.ts +123 -0
  17. package/dist/client/client/grapesjs/new-page-dialog.d.ts +1 -1
  18. package/dist/client/client/grapesjs/page-panel.d.ts +7 -1
  19. package/dist/client/client/grapesjs/project-bar.d.ts +20 -0
  20. package/dist/client/client/grapesjs/resize-panel.d.ts +1 -0
  21. package/dist/client/client/grapesjs/rich-text.d.ts +4 -1
  22. package/dist/client/client/grapesjs/semantic.d.ts +1 -1
  23. package/dist/client/client/grapesjs/settings-sections.d.ts +16 -0
  24. package/dist/client/client/grapesjs/settings.d.ts +3 -1
  25. package/dist/client/client/grapesjs/storage.d.ts +1 -0
  26. package/dist/client/client/grapesjs/symbolDialogs.d.ts +1 -1
  27. package/dist/client/client/grapesjs/view-buttons.d.ts +1 -0
  28. package/dist/client/client/index.d.ts +5 -0
  29. package/dist/client/client/publication-transformers.d.ts +42 -0
  30. package/dist/client/client/publish-custom-code-block.d.ts +4 -0
  31. package/dist/client/client/publish-fonts.d.ts +4 -0
  32. package/dist/client/client/utils.d.ts +22 -1
  33. package/dist/client/constants.d.ts +3 -0
  34. package/dist/client/css/admin.css +1107 -411
  35. package/dist/client/css/admin.css.map +1 -0
  36. package/dist/client/grapesjs-filter-styles/dist/index.d.ts +4 -0
  37. package/dist/client/grapesjs-fonts/dist/index.d.ts +4 -0
  38. package/dist/client/grapesjs-fonts/src/commands.d.ts +3 -0
  39. package/dist/client/grapesjs-fonts/src/fonts.d.ts +4 -0
  40. package/dist/client/grapesjs-storage-rate-limit/dist/index.d.ts +4 -0
  41. package/dist/client/grapesjs-ui-suggest-classes/dist/index.d.ts +4 -0
  42. package/dist/client/index.html +3 -2
  43. package/dist/client/js/main.js +305 -170
  44. package/dist/client/js/main.js.LICENSE.txt +21 -1
  45. package/dist/client/page.d.ts +1 -1
  46. package/dist/client/silex-lib/src/ts/client/api.d.ts +90 -0
  47. package/dist/client/silex-lib/src/ts/client/assetUrl.d.ts +54 -0
  48. package/dist/client/silex-lib/src/ts/client/config.d.ts +102 -0
  49. package/dist/client/silex-lib/src/ts/client/events.d.ts +23 -0
  50. package/dist/client/silex-lib/src/ts/client/expose.d.ts +8 -0
  51. package/dist/client/silex-lib/src/ts/client/grapesjs/LoginDialog.d.ts +15 -0
  52. package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationManager.d.ts +73 -0
  53. package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationUi.d.ts +59 -0
  54. package/dist/client/silex-lib/src/ts/client/grapesjs/blocks.d.ts +1 -0
  55. package/dist/client/silex-lib/src/ts/client/grapesjs/breadcrumbs.d.ts +1 -0
  56. package/dist/client/silex-lib/src/ts/client/grapesjs/css-props.d.ts +6 -0
  57. package/dist/client/silex-lib/src/ts/client/grapesjs/footer.d.ts +2 -0
  58. package/dist/client/silex-lib/src/ts/client/grapesjs/img.d.ts +6 -0
  59. package/dist/client/silex-lib/src/ts/client/grapesjs/index.d.ts +9 -0
  60. package/dist/client/silex-lib/src/ts/client/grapesjs/internal-links.d.ts +1 -0
  61. package/dist/client/silex-lib/src/ts/client/grapesjs/keymaps.d.ts +123 -0
  62. package/dist/client/silex-lib/src/ts/client/grapesjs/new-page-dialog.d.ts +2 -0
  63. package/dist/client/silex-lib/src/ts/client/grapesjs/openImport.d.ts +9 -0
  64. package/dist/client/silex-lib/src/ts/client/grapesjs/page-panel.d.ts +9 -0
  65. package/dist/client/silex-lib/src/ts/client/grapesjs/project-bar.d.ts +21 -0
  66. package/dist/client/silex-lib/src/ts/client/grapesjs/resize-panel.d.ts +1 -0
  67. package/dist/client/silex-lib/src/ts/client/grapesjs/rich-text.d.ts +4 -0
  68. package/dist/client/silex-lib/src/ts/client/grapesjs/semantic.d.ts +1 -0
  69. package/dist/client/silex-lib/src/ts/client/grapesjs/settings-sections.d.ts +16 -0
  70. package/dist/client/silex-lib/src/ts/client/grapesjs/settings.d.ts +4 -0
  71. package/dist/client/silex-lib/src/ts/client/grapesjs/storage.d.ts +2 -0
  72. package/dist/client/silex-lib/src/ts/client/grapesjs/symbolDialogs.d.ts +3 -0
  73. package/dist/client/silex-lib/src/ts/client/grapesjs/view-buttons.d.ts +1 -0
  74. package/dist/client/silex-lib/src/ts/client/index.d.ts +10 -0
  75. package/dist/client/silex-lib/src/ts/client/publication-transformers.d.ts +42 -0
  76. package/dist/client/silex-lib/src/ts/client/publish-custom-code-block.d.ts +4 -0
  77. package/dist/client/silex-lib/src/ts/client/publish-fonts.d.ts +4 -0
  78. package/dist/client/silex-lib/src/ts/client/utils.d.ts +22 -0
  79. package/dist/client/silex-lib/src/ts/constants.d.ts +32 -0
  80. package/dist/client/silex-lib/src/ts/page.d.ts +2 -0
  81. package/dist/client/silex-lib/src/ts/types.d.ts +339 -0
  82. package/dist/client/types.d.ts +59 -35
  83. package/dist/plugins/client/client/events.d.ts +19 -10
  84. package/dist/plugins/client/client/events.js +11 -0
  85. package/dist/plugins/client/client/events.js.map +1 -1
  86. package/dist/plugins/client/plugins/client/template.js +119 -71
  87. package/dist/plugins/client/plugins/client/template.js.map +1 -1
  88. package/dist/plugins/client/plugins/client/website-info.d.ts +1 -0
  89. package/dist/plugins/client/plugins/client/website-info.js +72 -0
  90. package/dist/plugins/client/plugins/client/website-info.js.map +1 -0
  91. package/dist/plugins/client/types.d.ts +59 -35
  92. package/dist/plugins/client/types.js +23 -0
  93. package/dist/plugins/client/types.js.map +1 -1
  94. package/dist/plugins/server/constants.d.ts +3 -0
  95. package/dist/plugins/server/constants.js +28 -1
  96. package/dist/plugins/server/constants.js.map +1 -1
  97. package/dist/plugins/server/plugins/server/Cache.js +9 -5
  98. package/dist/plugins/server/plugins/server/Cache.js.map +1 -1
  99. package/dist/plugins/server/plugins/server/DownloadConnector.d.ts +29 -0
  100. package/dist/plugins/server/plugins/server/DownloadConnector.js +127 -0
  101. package/dist/plugins/server/plugins/server/DownloadConnector.js.map +1 -0
  102. package/dist/plugins/server/plugins/server/FtpConnector.d.ts +5 -2
  103. package/dist/plugins/server/plugins/server/FtpConnector.js +38 -23
  104. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
  105. package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +63 -9
  106. package/dist/plugins/server/plugins/server/GitlabConnector.js +367 -91
  107. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
  108. package/dist/plugins/server/plugins/server/GitlabHostingConnector.d.ts +16 -0
  109. package/dist/plugins/server/plugins/server/GitlabHostingConnector.js +209 -0
  110. package/dist/plugins/server/plugins/server/GitlabHostingConnector.js.map +1 -0
  111. package/dist/plugins/server/plugins/server/SslPlugin.js +6 -6
  112. package/dist/plugins/server/plugins/server/SslPlugin.js.map +1 -1
  113. package/dist/plugins/server/plugins/server/StaticPlugin.d.ts +8 -1
  114. package/dist/plugins/server/plugins/server/StaticPlugin.js +5 -31
  115. package/dist/plugins/server/plugins/server/StaticPlugin.js.map +1 -1
  116. package/dist/plugins/server/server/config.js +8 -9
  117. package/dist/plugins/server/server/config.js.map +1 -1
  118. package/dist/plugins/server/server/connectors/FsHosting.d.ts +3 -2
  119. package/dist/plugins/server/server/connectors/FsHosting.js +18 -7
  120. package/dist/plugins/server/server/connectors/FsHosting.js.map +1 -1
  121. package/dist/plugins/server/server/connectors/FsStorage.d.ts +3 -1
  122. package/dist/plugins/server/server/connectors/FsStorage.js +35 -28
  123. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
  124. package/dist/plugins/server/server/connectors/connectors.d.ts +7 -7
  125. package/dist/plugins/server/server/connectors/connectors.js +6 -7
  126. package/dist/plugins/server/server/connectors/connectors.js.map +1 -1
  127. package/dist/plugins/server/server/events.js +16 -0
  128. package/dist/plugins/server/server/events.js.map +1 -1
  129. package/dist/plugins/server/server/jobs.d.ts +6 -0
  130. package/dist/plugins/server/server/jobs.js +12 -8
  131. package/dist/plugins/server/server/jobs.js.map +1 -1
  132. package/dist/plugins/server/server/utils/validation.js +1 -2
  133. package/dist/plugins/server/server/utils/validation.js.map +1 -1
  134. package/dist/plugins/server/types.d.ts +59 -35
  135. package/dist/plugins/server/types.js +24 -1
  136. package/dist/plugins/server/types.js.map +1 -1
  137. package/dist/server/constants.js +27 -1
  138. package/dist/server/plugins/server/Cache.js +9 -5
  139. package/dist/server/server/api/api.js +1 -1
  140. package/dist/server/server/api/connectorApi.js +154 -25
  141. package/dist/server/server/api/publicationApi.js +17 -25
  142. package/dist/server/server/api/websiteApi.js +30 -8
  143. package/dist/server/server/cli.js +9 -6
  144. package/dist/server/server/config.js +8 -9
  145. package/dist/server/server/connectors/FsHosting.js +17 -7
  146. package/dist/server/server/connectors/FsStorage.js +34 -28
  147. package/dist/server/server/connectors/connectors.js +6 -7
  148. package/dist/server/server/express.js +4 -5
  149. package/dist/server/server/index.js +5 -1
  150. package/dist/server/server/jobs.js +11 -8
  151. package/dist/server/server/utils/validation.js +1 -2
  152. package/dist/server/types.js +13 -1
  153. package/package.json +56 -42
  154. package/public/assets/download.png +0 -0
  155. package/public/assets/favicon.ico +0 -0
  156. package/public/assets/favicon.png +0 -0
  157. package/public/assets/ftp.png +0 -0
  158. package/public/assets/gitlab.png +0 -0
  159. package/public/assets/gitlab2.png +0 -0
  160. package/public/assets/laptop.png +0 -0
  161. package/public/assets/logo-silex-small.png +0 -0
  162. package/public/silex.js +5 -23
  163. package/src/css/_variables.scss +23 -20
  164. package/src/css/dialog.scss +27 -1
  165. package/src/css/fonts.scss +31 -0
  166. package/src/css/footer.scss +12 -0
  167. package/src/css/grapesjs-light.scss +180 -0
  168. package/src/css/grapesjs-plugins.scss +227 -0
  169. package/src/css/grapesjs.scss +84 -10
  170. package/src/css/index.scss +12 -1
  171. package/src/css/light.scss +7 -0
  172. package/src/css/project-bar.scss +32 -16
  173. package/src/css/silex.scss +79 -5
  174. package/src/css/website-info.scss +14 -0
  175. package/src/html/index.pug +3 -3
  176. package/src/ts/client/api.ts +38 -14
  177. package/src/ts/client/assetUrl.test.ts +164 -0
  178. package/src/ts/client/assetUrl.ts +254 -0
  179. package/src/ts/client/config.ts +133 -5
  180. package/src/ts/client/events.ts +21 -9
  181. package/src/ts/client/grapesjs/LoginDialog.ts +4 -2
  182. package/src/ts/client/grapesjs/PublicationManager.ts +180 -54
  183. package/src/ts/client/grapesjs/PublicationUi.ts +122 -86
  184. package/src/ts/client/grapesjs/breadcrumbs.ts +79 -0
  185. package/src/ts/client/grapesjs/css-props.ts +910 -0
  186. package/src/ts/client/grapesjs/eleventy.ts +11 -8
  187. package/{dist/plugins/client/page.js → src/ts/client/grapesjs/footer.ts} +24 -13
  188. package/src/ts/client/grapesjs/img.ts +147 -0
  189. package/src/ts/client/grapesjs/index.ts +234 -52
  190. package/src/ts/client/grapesjs/internal-links.test.ts +2 -1
  191. package/src/ts/client/grapesjs/internal-links.ts +14 -7
  192. package/src/ts/client/grapesjs/keymaps.ts +207 -0
  193. package/src/ts/client/grapesjs/new-page-dialog.ts +2 -3
  194. package/src/ts/client/grapesjs/page-panel.ts +92 -7
  195. package/src/ts/client/grapesjs/project-bar.ts +116 -72
  196. package/src/ts/client/grapesjs/resize-panel.ts +20 -0
  197. package/src/ts/client/grapesjs/rich-text.ts +21 -13
  198. package/src/ts/client/grapesjs/semantic.ts +3 -3
  199. package/src/ts/client/grapesjs/settings-sections.ts +117 -0
  200. package/src/ts/client/grapesjs/settings.ts +174 -96
  201. package/src/ts/client/grapesjs/storage.ts +98 -33
  202. package/src/ts/client/grapesjs/symbolDialogs.ts +3 -4
  203. package/src/ts/{plugins/server/AuthPlugin.ts → client/grapesjs/view-buttons.ts} +23 -19
  204. package/src/ts/client/index.ts +35 -16
  205. package/src/ts/client/publication-transformers.test.ts +207 -0
  206. package/src/ts/client/publication-transformers.ts +280 -0
  207. package/{dist/plugins/client/client/utils.js → src/ts/client/publish-custom-code-block.ts} +14 -8
  208. package/src/ts/client/publish-fonts.ts +36 -0
  209. package/src/ts/client/utils.ts +53 -1
  210. package/src/ts/constants.ts +31 -0
  211. package/src/ts/page.ts +2 -2
  212. package/src/ts/plugins/client/template.ts +125 -75
  213. package/src/ts/plugins/client/website-info.ts +84 -0
  214. package/src/ts/plugins/server/Cache.ts +6 -2
  215. package/src/ts/plugins/server/DownloadConnector.ts +146 -0
  216. package/src/ts/plugins/server/FtpConnector.ts +47 -21
  217. package/src/ts/plugins/server/GitlabConnector.ts +423 -108
  218. package/src/ts/plugins/server/GitlabHostingConnector.ts +215 -0
  219. package/src/ts/plugins/server/SslPlugin.ts +5 -5
  220. package/src/ts/plugins/server/StaticPlugin.ts +3 -30
  221. package/src/ts/server/api/connectorApi.ts +165 -28
  222. package/src/ts/server/api/publicationApi.ts +17 -24
  223. package/src/ts/server/api/websiteApi.ts +57 -14
  224. package/src/ts/server/cli.ts +7 -5
  225. package/src/ts/server/config.ts +9 -10
  226. package/src/ts/server/connectors/FsHosting.ts +14 -8
  227. package/src/ts/server/connectors/FsStorage.ts +40 -7
  228. package/src/ts/server/connectors/connectors.ts +8 -6
  229. package/src/ts/server/events.ts +17 -0
  230. package/src/ts/server/express.ts +2 -2
  231. package/src/ts/server/index.ts +6 -0
  232. package/src/ts/server/jobs.ts +14 -2
  233. package/src/ts/server/utils/BackwardCompat.ts +530 -529
  234. package/src/ts/types.ts +78 -34
  235. package/dist/plugins/client/client/utils.d.ts +0 -1
  236. package/dist/plugins/client/client/utils.js.map +0 -1
  237. package/dist/plugins/client/page.d.ts +0 -2
  238. package/dist/plugins/client/page.js.map +0 -1
  239. package/dist/plugins/client/plugins/client/publicationRenderer.d.ts +0 -2
  240. package/dist/plugins/client/plugins/client/publicationRenderer.js +0 -128
  241. package/dist/plugins/client/plugins/client/publicationRenderer.js.map +0 -1
  242. package/dist/plugins/server/plugins/server/AuthPlugin.d.ts +0 -1
  243. package/dist/plugins/server/plugins/server/AuthPlugin.js +0 -38
  244. package/dist/plugins/server/plugins/server/AuthPlugin.js.map +0 -1
  245. package/dist/plugins/server/plugins/server/HooksPlugin.d.ts +0 -7
  246. package/dist/plugins/server/plugins/server/HooksPlugin.js +0 -74
  247. package/dist/plugins/server/plugins/server/HooksPlugin.js.map +0 -1
  248. package/src/example-client-config.js +0 -64
  249. package/src/ts/plugins/client/publicationRenderer.ts +0 -128
  250. package/src/ts/plugins/server/HooksPlugin.ts +0 -76
@@ -17,13 +17,12 @@
17
17
 
18
18
  import { Router } from 'express'
19
19
  import { API_PUBLICATION_PUBLISH, API_PUBLICATION_STATUS, API_WEBSITE_ASSET_READ } from '../../constants'
20
- import { getJob } from '../jobs'
21
- import { ApiPublicationPublishBody, ApiPublicationPublishQuery, ApiPublicationPublishResponse, ApiPublicationStatusQuery, ApiPublicationStatusResponse, JobId, PublicationSettings, PublicationData as PublicationData, ConnectorType, ConnectorId, WebsiteId} from '../../types'
20
+ import { ApiPublicationPublishBody, ApiPublicationPublishQuery, ApiPublicationPublishResponse, ApiPublicationStatusQuery, ApiPublicationStatusResponse, JobId, PublicationData, ConnectorType, ConnectorId, WebsiteId, ClientSideFile, ClientSideFileWithContent, ClientSideFileWithSrc} from '../../types'
22
21
  import { ConnectorFile, HostingConnector, StorageConnector, getConnector } from '../connectors/connectors'
23
22
  import { ServerConfig } from '../config'
24
23
  import { requiredParam } from '../utils/validation'
25
- import { join } from 'path'
26
24
  import { PublishEndEventType, PublishStartEventType, ServerEvent } from '../events'
25
+ import { getJob, jobManager } from '../jobs'
27
26
 
28
27
  /**
29
28
  * @fileoverview Publication plugin for Silex
@@ -109,9 +108,9 @@ export default function (config: ServerConfig): Router {
109
108
 
110
109
  // Check params
111
110
  const data = body as PublicationData
112
- const { files, publication, assets } = data
113
- if(!files || !publication || !assets) {
114
- throw new PublicationError('Missing data from request: files, publication or assets', 400)
111
+ const { files } = data
112
+ if(!files) {
113
+ throw new PublicationError('Missing files in body', 400)
115
114
  }
116
115
 
117
116
  // Hook for plugins
@@ -123,29 +122,23 @@ export default function (config: ServerConfig): Router {
123
122
  // Get storage connector which holds the assets
124
123
  const storage = await getStorageConnector(session, config, storageId)
125
124
 
126
- // Publication
127
- const assetsFiles: ConnectorFile[] = await Promise.all(assets.map(async asset => {
128
- if(!asset.path) throw new PublicationError('Missing path in asset', 400)
129
- if(!asset.src) throw new PublicationError('Missing src in asset', 400)
130
- // Remove the / from the asset root if it starts with one
131
- const prefix = API_WEBSITE_ASSET_READ.replace(/^\//, '')
132
- // Get the asset url relative to the asset root
133
- const src = asset.src.startsWith(prefix) ? asset.src.substring(prefix.length) : asset.src
125
+ // Load the content if necessary
126
+ const filesList: ConnectorFile[] = await Promise.all(files.map(async (file: ClientSideFile) => {
127
+ const fileWithContent = file as ClientSideFileWithContent
128
+ const fileWithSrc = file as ClientSideFileWithSrc
129
+ if(!fileWithContent.content && !fileWithSrc.src) throw new PublicationError('Missing content or src in file', 400)
130
+ const src = fileWithSrc.src
134
131
  return {
135
- path: asset.path,
136
- content: await storage.readAsset(session, websiteId, src),
132
+ // Destination
133
+ path: file.permalink ?? file.path,
134
+ // Content
135
+ content: fileWithContent.content ?? await storage.readAsset(session, websiteId, fileWithSrc.src),
137
136
  }
138
137
  }))
139
- const filesList: ConnectorFile[] = files.flatMap<ConnectorFile>(file => ([{
140
- path: file.htmlPath,
141
- content: file.html,
142
- }, {
143
- path: file.cssPath,
144
- content: file.css,
145
- }])).concat(assetsFiles)
138
+
146
139
  res.json({
147
140
  url: await hostingConnector.getUrl(session, websiteId),
148
- job: await hostingConnector.publish(session, websiteId, filesList),
141
+ job: await hostingConnector.publish(session, websiteId, filesList, jobManager),
149
142
  } as ApiPublicationPublishResponse)
150
143
  } catch (err) {
151
144
  console.error('Error publishing the website', err)
@@ -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}`,
@@ -293,16 +309,26 @@ export default function (config: ServerConfig, opts = {}): Router {
293
309
  config.emit(ServerEvent.WEBSITE_ASSET_STORE_START, { files, websiteId, connectorId } as WebsiteAssetStoreStartEventType)
294
310
 
295
311
  // Write the files
296
- await writeAssets(req['session'], websiteId, files, connectorId)
312
+ const result = await writeAssets(req['session'], websiteId, files, connectorId)
313
+
314
+ // Base URL of silex serve
315
+ const baseUrl = new URL(config.url).pathname.replace(/\/$/, '')
297
316
 
298
317
  // Return the file URLs to insert in the website
299
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
300
330
  res.json({
301
- data: files.map(file =>
302
- API_PATH + API_WEBSITE_PATH + API_WEBSITE_ASSET_READ
303
- + file.path
304
- + `?websiteId=${websiteId}&connectorId=${connectorId ? connectorId : ''}` // As expected by wesite API (readAsset)
305
- ),
331
+ data,
306
332
  } as ApiWebsiteAssetsWriteResponse)
307
333
 
308
334
  // Hook for plugins
@@ -381,6 +407,17 @@ export default function (config: ServerConfig, opts = {}): Router {
381
407
  return storageConnector.deleteWebsite(session, websiteId)
382
408
  }
383
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
+
384
421
  /**
385
422
  * Read an asset
386
423
  */
@@ -394,6 +431,7 @@ export default function (config: ServerConfig, opts = {}): Router {
394
431
 
395
432
  /**
396
433
  * Write an asset to the connector
434
+ * @returns File names on the storage connector, always starting with a slash
397
435
  */
398
436
  async function writeAssets(session: any, websiteId: string, files: ConnectorFile[], connectorId?: string): Promise<string[]> {
399
437
  // Get the desired connector
@@ -406,14 +444,19 @@ export default function (config: ServerConfig, opts = {}): Router {
406
444
  }))
407
445
 
408
446
  // Write the asset to the connector
409
- await storageConnector.writeAssets(
447
+ const result = await storageConnector.writeAssets(
410
448
  session,
411
449
  websiteId,
412
450
  cleanPathFiles,
413
451
  )
414
452
 
415
453
  // Return the files URLs with the website id
416
- return files.map(({ path }) => `${path}?websiteId=${websiteId}`)
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))
417
459
  }
460
+
418
461
  return router
419
462
  }
@@ -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': [ 'c', 'Path for the server side config file to load at startup', 'file'],
29
- 'client-config': ['', 'Path to client config file to be served on .silex-client.js', 'string'],
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': ['s', 'Session secret', 'string'],
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.config) process.env.SILEX_CONFIG = options.config
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']
@@ -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'
@@ -49,8 +49,8 @@ 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_URL ?? `${process.env.SILEX_PROTOCOL}://${process.env.SILEX_HOST}:${process.env.SILEX_PORT}`
53
- public userConfigPath: Plugin | undefined = process.env.SILEX_CONFIG
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.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
@@ -103,7 +103,7 @@ export class ServerConfig extends Config {
103
103
  async addRoutes(app: Application) {
104
104
  const path = process.env.SILEX_CLIENT_CONFIG
105
105
  if (path) {
106
- console.log(`> Serving client side config ${path} at ${CLIENT_CONFIG_FILE_NAME}`)
106
+ console.info(`> Serving client side config ${path} at ${CLIENT_CONFIG_FILE_NAME}`)
107
107
  try {
108
108
  // Load the client config file
109
109
  let clientConfig = (await readFile(path as string)).toString()
@@ -111,7 +111,7 @@ export class ServerConfig extends Config {
111
111
  app.get(`/${CLIENT_CONFIG_FILE_NAME}`, async (req: Request, res: Response) => {
112
112
  // Reload each time in debug mode
113
113
  if (this.debug) {
114
- console.log('[Debug mode] Reloading config file', path)
114
+ console.info('[Debug mode] Reloading config file', path)
115
115
  clientConfig = (await readFile(path)).toString()
116
116
  }
117
117
  // Send the config file
@@ -143,7 +143,7 @@ export class ServerConfig extends Config {
143
143
  */
144
144
  async loadUserConfig() {
145
145
  if (this.userConfigPath) {
146
- console.log('> Loading user config', this.userConfigPath)
146
+ console.info('> Loading user config', this.userConfigPath)
147
147
  try {
148
148
  // Initiate the process with the config file which is just another plugin
149
149
  await this.addPlugin(this.userConfigPath, {})
@@ -158,12 +158,11 @@ export class ServerConfig extends Config {
158
158
  * This is the main config file
159
159
  */
160
160
  async loadSilexConfig() {
161
- console.log('> Loading config', this.configFilePath)
161
+ console.info('> Loading config', this.configFilePath)
162
162
  try {
163
163
  // Initiate the process with the config file which is just another plugin
164
164
  await this.addPlugin(this.configFilePath, {})
165
165
  } catch(e) {
166
- console.log(e.requireStack[0])
167
166
  // Check if the error is about the config file not found and not another module not found in the config file
168
167
  if(e.code === 'MODULE_NOT_FOUND' && (!e.requireStack || !e.requireStack.find(path => path === this.configFilePath))) {
169
168
  console.info('> /!\\ Config file not found', this.configFilePath)
@@ -177,13 +176,13 @@ export class ServerConfig extends Config {
177
176
  // Add default storage connectors
178
177
  if (!this.storageConnectors.length) {
179
178
  this.addStorageConnector(new FsStorage(null, {
180
- path: process.env.SILEX_FS_ROOT,
179
+ path: process.env.SILEX_FS_ROOT || join(process.cwd(), '/silex/storage'),
181
180
  }))
182
181
  }
183
182
  // Add default hosting connectors
184
183
  if (!this.hostingConnectors.length) {
185
184
  this.addHostingConnector(new FsHosting(null, {
186
- path: process.env.SILEX_FS_ROOT,
185
+ path: process.env.SILEX_FS_HOSTING_ROOT || join(process.cwd(), '/silex/hosting'),
187
186
  }))
188
187
  }
189
188
  }
@@ -15,29 +15,35 @@
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
- import { jobError, jobSuccess, startJob } from '../jobs'
21
21
  import { FsStorage } from './FsStorage'
22
22
  import { ConnectorType, JobData, JobStatus, PublicationJobData, WebsiteId } from '../../types'
23
+ 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
- icon = 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M6%202L6%2022%2018%2022%2018%207%2012%202%206%202Z%22%3E%3C%2Fpath%3E%3Cpath%20d%3D%22M18%202L12%202%2012%208%2018%208%2018%202Z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E'
34
30
  connectorType = ConnectorType.HOSTING
35
31
 
36
- async publish(session: FsSession, id: WebsiteId, files: ConnectorFile[]): Promise<JobData> {
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
+
41
+ async publish(session: FsSession, id: WebsiteId, files: ConnectorFile[], {startJob, jobSuccess, jobError}: JobManager): Promise<JobData> {
37
42
  const job = startJob(`Publishing to ${this.displayName}`) as PublicationJobData
38
43
  job.logs = [[`Publishing to ${this.displayName}`]]
39
44
  job.errors = [[]]
40
- this.writeAssets(session, id, files, async ({status, message}) => {
45
+ // Call write without id or folder so that it goes in / (path will be modified by publication transformers)
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, { stat } from 'fs/promises'
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'
@@ -32,13 +32,29 @@ import { fileURLToPath } from 'url'
32
32
  if(!globalThis.__dirname) {
33
33
  // @ts-ignore
34
34
  globalThis.__dirname = dirname(process.cwd() + '/src/ts/server/connectors/FsStorage.ts')
35
- console.log('Redefining __dirname', globalThis.__dirname)
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'
41
- const FILE_ICON = 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M6%202L6%2022%2018%2022%2018%207%2012%202%206%202Z%22%3E%3C%2Fpath%3E%3Cpath%20d%3D%22M18%202L12%202%2012%208%2018%208%2018%202Z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E'
57
+ const FILE_ICON = '/assets/laptop.png'
42
58
 
43
59
  interface FsOptions {
44
60
  path: string
@@ -64,7 +80,7 @@ export class FsStorage implements StorageConnector<FsSession> {
64
80
  this.initFs()
65
81
  }
66
82
 
67
- private async initFs() {
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 => {
@@ -197,16 +226,20 @@ export class FsStorage implements StorageConnector<FsSession> {
197
226
  }
198
227
 
199
228
  async writeAssets(session: FsSession, id: WebsiteId, files: ConnectorFile[], statusCbk?: StatusCallback): Promise<void> {
229
+ return this.write(session, id, files, this.options.assetsFolder, statusCbk)
230
+ }
231
+
232
+ async write(session: FsSession, id: WebsiteId, files: ConnectorFile[], assetsFolder: string, statusCbk?: StatusCallback): Promise<void> {
200
233
  const filesStatuses = this.initStatus(files)
201
234
  let error: Error | null = null
202
235
  for (const fileStatus of filesStatuses) {
203
236
  const {file} = fileStatus
204
- const path = join(this.options.path, id, this.options.assetsFolder, file.path)
237
+ const path = join(this.options.path, id, assetsFolder, file.path)
205
238
  if (typeof file.content === 'string' || Buffer.isBuffer(file.content)) {
206
239
  fileStatus.message = 'Writing'
207
240
  this.updateStatus(filesStatuses, JobStatus.IN_PROGRESS, statusCbk)
208
241
  try {
209
- await fs.writeFile(path, file.content, 'binary')
242
+ await fs.writeFile(path, file.content)
210
243
  } catch(err) {
211
244
  fileStatus.message = `Error (${err})`
212
245
  this.updateStatus(filesStatuses, JobStatus.IN_PROGRESS, statusCbk)
@@ -18,6 +18,7 @@
18
18
  import { Readable } from 'stream'
19
19
  import { ServerConfig } from '../config'
20
20
  import { JobStatus, JobData, ConnectorData, ConnectorId, WebsiteId, ConnectorType, ConnectorUser, WebsiteMeta, WebsiteMetaFileContent, WebsiteData, ConnectorOptions } from '../../types'
21
+ import { JobManager } from '../jobs'
21
22
 
22
23
  /**
23
24
  * @fileoverview define types for Silex connectors
@@ -66,10 +67,7 @@ export interface Connector<Session extends ConnectorSession = ConnectorSession>
66
67
  isLoggedIn(session: Session): Promise<boolean>
67
68
  setToken(session: Session, token: object): Promise<void>
68
69
  logout(session: Session): Promise<void>
69
- getUser(session: Session): Promise<ConnectorUser>
70
- // Handle website meta file
71
- getWebsiteMeta(session: Session, websiteId: WebsiteId): Promise<WebsiteMeta>
72
- setWebsiteMeta(session: Session, websiteId: WebsiteId, data: WebsiteMetaFileContent): Promise<void>
70
+ getUser(session: Session): Promise<ConnectorUser | null>
73
71
  // Get the connector options from login form
74
72
  // They are stored in the website meta file for hosting connectors
75
73
  // And in the user session for storage connectors
@@ -88,18 +86,22 @@ export interface StorageConnector<Session extends ConnectorSession = ConnectorSe
88
86
  createWebsite(session: Session, data: WebsiteMetaFileContent): Promise<WebsiteId>
89
87
  updateWebsite(session: Session, websiteId: WebsiteId, data: WebsiteData): Promise<void>
90
88
  deleteWebsite(session: Session, websiteId: WebsiteId): Promise<void>
89
+ duplicateWebsite(session: Session, websiteId: WebsiteId): Promise<void>
91
90
  // CRUD on assets
92
- writeAssets(session: Session, websiteId: WebsiteId, files: ConnectorFile[], status?: StatusCallback): Promise<void>
91
+ writeAssets(session: Session, websiteId: WebsiteId, files: ConnectorFile[], status?: StatusCallback): Promise<string[] | void>
93
92
  readAsset(session: Session, websiteId: WebsiteId, fileName: string): Promise<ConnectorFileContent>
94
93
  deleteAssets(session: Session, websiteId: WebsiteId, fileNames: string[]): Promise<void>
95
94
  //getFileUrl(session: Session, websiteId: WebsiteId, path: string): Promise<string>
95
+ // Handle website meta file
96
+ getWebsiteMeta(session: Session, websiteId: WebsiteId): Promise<WebsiteMeta>
97
+ setWebsiteMeta(session: Session, websiteId: WebsiteId, data: WebsiteMetaFileContent): Promise<void>
96
98
  }
97
99
 
98
100
  /**
99
101
  * Hosting connectors are used to publish the website
100
102
  */
101
103
  export interface HostingConnector<Session extends ConnectorSession = ConnectorSession> extends Connector<Session> {
102
- publish(session: Session, websiteId: WebsiteId, files: ConnectorFile[]): Promise<JobData>
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
103
105
  getUrl(session: Session, websiteId: WebsiteId): Promise<string>
104
106
  }
105
107
 
@@ -1,3 +1,20 @@
1
+ /*
2
+ * Silex website builder, free/libre no-code tool for makers.
3
+ * Copyright (c) 2023 lexoyo and Silex Labs foundation
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
1
18
  import { ConnectorId, PublicationData, WebsiteData, WebsiteId } from '../types'
2
19
  import { ConnectorFile } from './connectors/connectors'
3
20
 
@@ -33,7 +33,7 @@ export function create(config: ServerConfig): Application {
33
33
  // CORS
34
34
  const options = config.expressOptions
35
35
  if (options.cors) {
36
- console.log('> CORS are ENABLED:', options.cors)
36
+ console.info('> CORS are ENABLED:', options.cors)
37
37
  app.use(cors({
38
38
  origin: options.cors,
39
39
  }))
@@ -45,7 +45,7 @@ export function create(config: ServerConfig): Application {
45
45
  app.use(bodyParser.json({ limit: options.jsonLimit }))
46
46
  app.use(bodyParser.text({ limit: options.textLimit }))
47
47
  app.use(bodyParser.urlencoded({ limit: options.urlencodedLimit }))
48
- console.log('> Session name:', options.sessionName)
48
+ console.info('> Session name:', options.sessionName)
49
49
  app.use(cookieParser() as any)
50
50
  app.use(session({
51
51
  name: options.sessionName,
@@ -20,12 +20,18 @@ import api from './api/api'
20
20
  import { ServerConfig } from './config'
21
21
  import dotenv from 'dotenv'
22
22
  import { join } from 'path'
23
+ import { DEV_MESSAGE } from '../constants'
23
24
 
24
25
  // Expose API to calling app as function silex()
25
26
  export * from './expose'
26
27
 
27
28
  // Main app
28
29
  export default async function silex() {
30
+ // Log Silex version
31
+ console.info(DEV_MESSAGE)
32
+ console.info(`Node version: ${process.version}`)
33
+ console.info(`CWD: ${process.cwd()}`)
34
+
29
35
  // Load env vars from .env file if any
30
36
  const curDirEnv = dotenv.config()
31
37
  if (curDirEnv.error) {
@@ -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._timeout)
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,8 +72,18 @@ 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._timeout = setTimeout(() => killJob(job), TIME_TO_KEEP_AFTER_STOP)
75
+ timers.set(job, setTimeout(() => killJob(job), TIME_TO_KEEP_AFTER_STOP))
74
76
  return true
75
77
  }
76
78
  return false
77
79
  }
80
+
81
+ // Type definition of the job manager
82
+ export interface JobManager {
83
+ startJob: typeof startJob
84
+ jobSuccess: typeof jobSuccess
85
+ jobError: typeof jobError
86
+ }
87
+
88
+ // Export the job manager
89
+ export const jobManager: JobManager = { startJob, jobSuccess, jobError }