@silexlabs/silex 3.0.1-alpha.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 (323) hide show
  1. package/.env.default +20 -0
  2. package/.silex.js +67 -8
  3. package/LICENSE +661 -203
  4. package/README.md +37 -35
  5. package/dist/client/client/api.d.ts +90 -0
  6. package/dist/client/client/assetUrl.d.ts +54 -0
  7. package/dist/client/client/config.d.ts +102 -0
  8. package/dist/client/client/events.d.ts +23 -0
  9. package/dist/client/client/expose.d.ts +8 -0
  10. package/dist/client/client/grapesjs/LoginDialog.d.ts +15 -0
  11. package/dist/client/client/grapesjs/PublicationManager.d.ts +73 -0
  12. package/dist/client/client/grapesjs/PublicationUi.d.ts +59 -0
  13. package/dist/client/client/grapesjs/blocks.d.ts +1 -0
  14. package/dist/client/client/grapesjs/breadcrumbs.d.ts +1 -0
  15. package/dist/client/client/grapesjs/css-props.d.ts +6 -0
  16. package/dist/client/client/grapesjs/footer.d.ts +2 -0
  17. package/dist/client/client/grapesjs/img.d.ts +6 -0
  18. package/dist/client/client/grapesjs/index.d.ts +9 -0
  19. package/dist/client/client/grapesjs/internal-links.d.ts +1 -0
  20. package/dist/client/client/grapesjs/keymaps.d.ts +123 -0
  21. package/dist/client/client/grapesjs/new-page-dialog.d.ts +2 -0
  22. package/dist/client/client/grapesjs/openImport.d.ts +9 -0
  23. package/dist/client/client/grapesjs/page-panel.d.ts +9 -0
  24. package/dist/client/client/grapesjs/project-bar.d.ts +21 -0
  25. package/dist/client/client/grapesjs/resize-panel.d.ts +1 -0
  26. package/dist/client/client/grapesjs/rich-text.d.ts +4 -0
  27. package/dist/client/client/grapesjs/semantic.d.ts +1 -0
  28. package/dist/client/client/grapesjs/settings-sections.d.ts +16 -0
  29. package/dist/client/client/grapesjs/settings.d.ts +4 -0
  30. package/dist/client/client/grapesjs/storage.d.ts +2 -0
  31. package/dist/client/client/grapesjs/symbolDialogs.d.ts +3 -0
  32. package/dist/client/client/grapesjs/view-buttons.d.ts +1 -0
  33. package/dist/client/client/index.d.ts +10 -0
  34. package/dist/client/client/publication-transformers.d.ts +42 -0
  35. package/dist/client/client/publish-custom-code-block.d.ts +4 -0
  36. package/dist/client/client/publish-fonts.d.ts +4 -0
  37. package/dist/client/client/utils.d.ts +22 -0
  38. package/dist/client/constants.d.ts +32 -0
  39. package/dist/client/css/admin.css +1114 -328
  40. package/dist/client/css/admin.css.map +1 -0
  41. package/dist/client/grapesjs-filter-styles/dist/index.d.ts +4 -0
  42. package/dist/client/grapesjs-fonts/dist/index.d.ts +4 -0
  43. package/dist/client/grapesjs-fonts/src/commands.d.ts +3 -0
  44. package/dist/client/grapesjs-fonts/src/fonts.d.ts +4 -0
  45. package/dist/client/grapesjs-storage-rate-limit/dist/index.d.ts +4 -0
  46. package/dist/client/grapesjs-ui-suggest-classes/dist/index.d.ts +4 -0
  47. package/dist/client/index.html +3 -2
  48. package/dist/client/js/main.js +356 -131
  49. package/dist/client/js/main.js.LICENSE.txt +15 -1
  50. package/dist/client/page.d.ts +2 -0
  51. package/dist/client/silex-lib/src/ts/client/api.d.ts +90 -0
  52. package/dist/client/silex-lib/src/ts/client/assetUrl.d.ts +54 -0
  53. package/dist/client/silex-lib/src/ts/client/config.d.ts +102 -0
  54. package/dist/client/silex-lib/src/ts/client/events.d.ts +23 -0
  55. package/dist/client/silex-lib/src/ts/client/expose.d.ts +8 -0
  56. package/dist/client/silex-lib/src/ts/client/grapesjs/LoginDialog.d.ts +15 -0
  57. package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationManager.d.ts +73 -0
  58. package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationUi.d.ts +59 -0
  59. package/dist/client/silex-lib/src/ts/client/grapesjs/blocks.d.ts +1 -0
  60. package/dist/client/silex-lib/src/ts/client/grapesjs/breadcrumbs.d.ts +1 -0
  61. package/dist/client/silex-lib/src/ts/client/grapesjs/css-props.d.ts +6 -0
  62. package/dist/client/silex-lib/src/ts/client/grapesjs/footer.d.ts +2 -0
  63. package/dist/client/silex-lib/src/ts/client/grapesjs/img.d.ts +6 -0
  64. package/dist/client/silex-lib/src/ts/client/grapesjs/index.d.ts +9 -0
  65. package/dist/client/silex-lib/src/ts/client/grapesjs/internal-links.d.ts +1 -0
  66. package/dist/client/silex-lib/src/ts/client/grapesjs/keymaps.d.ts +123 -0
  67. package/dist/client/silex-lib/src/ts/client/grapesjs/new-page-dialog.d.ts +2 -0
  68. package/dist/client/silex-lib/src/ts/client/grapesjs/openImport.d.ts +9 -0
  69. package/dist/client/silex-lib/src/ts/client/grapesjs/page-panel.d.ts +9 -0
  70. package/dist/client/silex-lib/src/ts/client/grapesjs/project-bar.d.ts +21 -0
  71. package/dist/client/silex-lib/src/ts/client/grapesjs/resize-panel.d.ts +1 -0
  72. package/dist/client/silex-lib/src/ts/client/grapesjs/rich-text.d.ts +4 -0
  73. package/dist/client/silex-lib/src/ts/client/grapesjs/semantic.d.ts +1 -0
  74. package/dist/client/silex-lib/src/ts/client/grapesjs/settings-sections.d.ts +16 -0
  75. package/dist/client/silex-lib/src/ts/client/grapesjs/settings.d.ts +4 -0
  76. package/dist/client/silex-lib/src/ts/client/grapesjs/storage.d.ts +2 -0
  77. package/dist/client/silex-lib/src/ts/client/grapesjs/symbolDialogs.d.ts +3 -0
  78. package/dist/client/silex-lib/src/ts/client/grapesjs/view-buttons.d.ts +1 -0
  79. package/dist/client/silex-lib/src/ts/client/index.d.ts +10 -0
  80. package/dist/client/silex-lib/src/ts/client/publication-transformers.d.ts +42 -0
  81. package/dist/client/silex-lib/src/ts/client/publish-custom-code-block.d.ts +4 -0
  82. package/dist/client/silex-lib/src/ts/client/publish-fonts.d.ts +4 -0
  83. package/dist/client/silex-lib/src/ts/client/utils.d.ts +22 -0
  84. package/dist/client/silex-lib/src/ts/constants.d.ts +32 -0
  85. package/dist/client/silex-lib/src/ts/page.d.ts +2 -0
  86. package/dist/client/silex-lib/src/ts/types.d.ts +339 -0
  87. package/dist/client/types.d.ts +339 -0
  88. package/dist/plugins/client/client/events.d.ts +23 -0
  89. package/dist/plugins/client/client/events.js +43 -0
  90. package/dist/plugins/client/client/events.js.map +1 -0
  91. package/dist/plugins/client/plugins/client/template.d.ts +2 -0
  92. package/dist/plugins/client/plugins/client/template.js +234 -0
  93. package/dist/plugins/client/plugins/client/template.js.map +1 -0
  94. package/dist/plugins/client/plugins/client/website-info.d.ts +1 -0
  95. package/dist/plugins/client/plugins/client/website-info.js +72 -0
  96. package/dist/plugins/client/plugins/client/website-info.js.map +1 -0
  97. package/dist/plugins/client/types.d.ts +339 -0
  98. package/dist/plugins/client/types.js +80 -0
  99. package/dist/plugins/client/types.js.map +1 -0
  100. package/dist/plugins/server/constants.d.ts +32 -0
  101. package/dist/plugins/server/constants.js +76 -0
  102. package/dist/plugins/server/constants.js.map +1 -0
  103. package/dist/plugins/server/plugins/server/Cache.d.ts +2 -0
  104. package/dist/plugins/server/plugins/server/Cache.js +36 -0
  105. package/dist/plugins/server/plugins/server/Cache.js.map +1 -0
  106. package/dist/plugins/server/plugins/server/DownloadConnector.d.ts +29 -0
  107. package/dist/plugins/server/plugins/server/DownloadConnector.js +127 -0
  108. package/dist/plugins/server/plugins/server/DownloadConnector.js.map +1 -0
  109. package/dist/plugins/server/plugins/server/FtpConnector.d.ts +86 -0
  110. package/dist/plugins/server/plugins/server/FtpConnector.js +536 -0
  111. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -0
  112. package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +135 -0
  113. package/dist/plugins/server/plugins/server/GitlabConnector.js +649 -0
  114. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -0
  115. package/dist/plugins/server/plugins/server/GitlabHostingConnector.d.ts +16 -0
  116. package/dist/plugins/server/plugins/server/GitlabHostingConnector.js +209 -0
  117. package/dist/plugins/server/plugins/server/GitlabHostingConnector.js.map +1 -0
  118. package/dist/plugins/server/plugins/server/PublicationOptimizer.d.ts +0 -0
  119. package/dist/plugins/server/plugins/server/PublicationOptimizer.js +14 -0
  120. package/dist/plugins/server/plugins/server/PublicationOptimizer.js.map +1 -0
  121. package/dist/plugins/server/plugins/server/SslPlugin.d.ts +1 -0
  122. package/dist/plugins/server/plugins/server/SslPlugin.js +86 -0
  123. package/dist/plugins/server/plugins/server/SslPlugin.js.map +1 -0
  124. package/dist/plugins/server/plugins/server/StaticPlugin.d.ts +8 -0
  125. package/dist/plugins/server/plugins/server/StaticPlugin.js +45 -0
  126. package/dist/plugins/server/plugins/server/StaticPlugin.js.map +1 -0
  127. package/dist/plugins/server/server/config.d.ts +59 -0
  128. package/dist/plugins/server/server/config.js +181 -0
  129. package/dist/plugins/server/server/config.js.map +1 -0
  130. package/dist/plugins/server/server/connectors/FsHosting.d.ts +14 -0
  131. package/dist/plugins/server/server/connectors/FsHosting.js +66 -0
  132. package/dist/plugins/server/server/connectors/FsHosting.js.map +1 -0
  133. package/dist/plugins/server/server/connectors/FsStorage.d.ts +44 -0
  134. package/dist/plugins/server/server/connectors/FsStorage.js +261 -0
  135. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -0
  136. package/dist/plugins/server/server/connectors/connectors.d.ts +86 -0
  137. package/dist/plugins/server/server/connectors/connectors.js +126 -0
  138. package/dist/plugins/server/server/connectors/connectors.js.map +1 -0
  139. package/dist/plugins/server/server/events.d.ts +26 -0
  140. package/dist/plugins/server/server/events.js +31 -0
  141. package/dist/plugins/server/server/events.js.map +1 -0
  142. package/dist/plugins/server/server/jobs.d.ts +12 -0
  143. package/dist/plugins/server/server/jobs.js +79 -0
  144. package/dist/plugins/server/server/jobs.js.map +1 -0
  145. package/dist/plugins/server/server/utils/validation.d.ts +7 -0
  146. package/dist/plugins/server/server/utils/validation.js +37 -0
  147. package/dist/plugins/server/server/utils/validation.js.map +1 -0
  148. package/dist/plugins/server/types.d.ts +339 -0
  149. package/dist/plugins/server/types.js +84 -0
  150. package/dist/plugins/server/types.js.map +1 -0
  151. package/dist/server/constants.js +55 -0
  152. package/dist/server/plugins/server/Cache.js +19 -0
  153. package/dist/server/server/api/api.js +20 -0
  154. package/dist/server/server/api/connectorApi.js +334 -0
  155. package/dist/server/server/api/publicationApi.js +106 -0
  156. package/dist/server/server/api/websiteApi.js +315 -0
  157. package/dist/server/server/cli.js +71 -0
  158. package/dist/server/server/config.js +120 -0
  159. package/dist/server/server/connectors/FsHosting.js +47 -0
  160. package/dist/server/server/connectors/FsStorage.js +228 -0
  161. package/dist/server/server/connectors/connectors.js +88 -0
  162. package/dist/server/server/events.js +14 -0
  163. package/dist/server/server/expose.js +27 -0
  164. package/dist/server/server/express.js +46 -0
  165. package/dist/server/server/index.js +94 -0
  166. package/dist/server/server/jobs.js +50 -0
  167. package/dist/server/server/utils/validation.js +14 -0
  168. package/dist/server/types.js +40 -0
  169. package/package.json +99 -49
  170. package/public/assets/download.png +0 -0
  171. package/public/assets/favicon.ico +0 -0
  172. package/public/assets/favicon.png +0 -0
  173. package/public/assets/ftp.png +0 -0
  174. package/public/assets/gitlab.png +0 -0
  175. package/public/assets/gitlab2.png +0 -0
  176. package/public/assets/laptop.png +0 -0
  177. package/public/assets/logo-silex-small.png +0 -0
  178. package/public/silex.js +22 -23
  179. package/src/css/_variables.scss +24 -20
  180. package/src/css/dialog.scss +35 -2
  181. package/src/css/fonts.scss +31 -0
  182. package/src/css/footer.scss +12 -0
  183. package/src/css/grapesjs-light.scss +180 -0
  184. package/src/css/grapesjs-plugins.scss +227 -0
  185. package/src/css/grapesjs.scss +159 -1
  186. package/src/css/index.scss +12 -1
  187. package/src/css/light.scss +7 -0
  188. package/src/css/loader.scss +2 -0
  189. package/src/css/project-bar.scss +32 -16
  190. package/src/css/silex.scss +79 -5
  191. package/src/css/website-info.scss +14 -0
  192. package/src/html/index.pug +3 -3
  193. package/src/html/templates/includes.pug +0 -1
  194. package/src/ts/client/api.ts +156 -0
  195. package/src/ts/client/assetUrl.test.ts +164 -0
  196. package/src/ts/client/assetUrl.ts +254 -0
  197. package/src/ts/client/config.ts +175 -13
  198. package/src/ts/client/events.ts +43 -0
  199. package/src/ts/client/expose.ts +27 -1
  200. package/src/ts/client/grapesjs/LoginDialog.ts +204 -0
  201. package/src/ts/client/grapesjs/PublicationManager.ts +433 -0
  202. package/src/ts/client/grapesjs/PublicationUi.ts +366 -0
  203. package/src/ts/client/grapesjs/blocks.ts +17 -0
  204. package/src/ts/client/grapesjs/breadcrumbs.ts +79 -0
  205. package/src/ts/client/grapesjs/css-props.ts +910 -0
  206. package/src/ts/client/grapesjs/eleventy.ts +30 -7
  207. package/src/ts/client/grapesjs/footer.ts +42 -0
  208. package/src/ts/client/grapesjs/img.ts +147 -0
  209. package/src/ts/client/grapesjs/index.ts +311 -122
  210. package/src/ts/client/grapesjs/internal-links.test.ts +19 -1
  211. package/src/ts/client/grapesjs/internal-links.ts +31 -7
  212. package/src/ts/client/grapesjs/keymaps.ts +207 -0
  213. package/src/ts/client/grapesjs/new-page-dialog.ts +19 -3
  214. package/src/ts/client/grapesjs/openImport.ts +22 -0
  215. package/src/ts/client/grapesjs/page-panel.ts +109 -7
  216. package/src/ts/client/grapesjs/project-bar.ts +133 -72
  217. package/src/ts/client/grapesjs/resize-panel.ts +20 -0
  218. package/src/ts/client/grapesjs/rich-text.ts +38 -13
  219. package/src/ts/client/grapesjs/semantic.ts +20 -3
  220. package/src/ts/client/grapesjs/settings-sections.ts +117 -0
  221. package/src/ts/client/grapesjs/settings.ts +191 -96
  222. package/src/ts/client/grapesjs/storage.ts +187 -0
  223. package/src/ts/client/grapesjs/symbolDialogs.ts +21 -5
  224. package/src/ts/client/grapesjs/view-buttons.ts +42 -0
  225. package/src/ts/client/index.ts +80 -21
  226. package/src/ts/client/publication-transformers.test.ts +207 -0
  227. package/src/ts/client/publication-transformers.ts +280 -0
  228. package/src/ts/client/publish-custom-code-block.ts +31 -0
  229. package/src/ts/client/publish-fonts.ts +36 -0
  230. package/src/ts/client/utils.ts +70 -1
  231. package/src/ts/constants.ts +78 -0
  232. package/src/ts/externs.ts +17 -0
  233. package/src/ts/page.ts +20 -3
  234. package/src/ts/plugins/client/template.ts +246 -0
  235. package/src/ts/plugins/client/website-info.ts +84 -0
  236. package/src/ts/plugins/server/Cache.ts +32 -0
  237. package/src/ts/plugins/server/DownloadConnector.ts +146 -0
  238. package/src/ts/plugins/server/FtpConnector.test.ts +82 -0
  239. package/src/ts/plugins/server/FtpConnector.ts +621 -0
  240. package/src/ts/plugins/server/GitlabConnector.ts +749 -0
  241. package/src/ts/plugins/server/GitlabHostingConnector.ts +215 -0
  242. package/src/ts/plugins/server/PublicationOptimizer.ts +14 -0
  243. package/src/ts/plugins/server/SslPlugin.ts +84 -0
  244. package/src/ts/plugins/server/StaticPlugin.ts +45 -0
  245. package/src/ts/server/api/api.ts +19 -0
  246. package/src/ts/server/api/connectorApi.ts +464 -0
  247. package/src/ts/server/api/publicationApi.ts +159 -0
  248. package/src/ts/server/api/websiteApi.ts +462 -0
  249. package/src/ts/server/cli.ts +48 -14
  250. package/src/ts/server/config.ts +158 -60
  251. package/src/ts/server/connectors/FsHosting.ts +66 -0
  252. package/src/ts/server/connectors/FsStorage.test.ts +51 -0
  253. package/src/ts/server/connectors/FsStorage.ts +290 -0
  254. package/src/ts/server/connectors/connectors.ts +203 -0
  255. package/src/ts/server/events.ts +37 -0
  256. package/src/ts/server/expose.ts +19 -0
  257. package/src/ts/server/express.ts +57 -5
  258. package/src/ts/server/index.ts +79 -16
  259. package/src/ts/server/jobs.test.ts +75 -0
  260. package/src/ts/server/jobs.ts +89 -0
  261. package/src/ts/server/utils/BackwardCompat.ts +546 -528
  262. package/src/ts/server/utils/validation.ts +34 -0
  263. package/src/ts/types.ts +295 -85
  264. package/.dockerignore +0 -5
  265. package/.editorconfig +0 -14
  266. package/.eslintrc.json +0 -43
  267. package/.github/FUNDING.yml +0 -13
  268. package/.github/workflows/docker-hub-publish.yml +0 -36
  269. package/.github/workflows/npm-publish.yml +0 -19
  270. package/.github/workflows/test.yml +0 -26
  271. package/.nvmrc +0 -1
  272. package/.silex/default/.silex.data.json +0 -1
  273. package/CODE_OF_CONDUCT.md +0 -128
  274. package/CONTRIBUTING.md +0 -131
  275. package/Dockerfile +0 -15
  276. package/directus.silex.js +0 -7
  277. package/dist/constants.js +0 -4
  278. package/dist/events.js +0 -5
  279. package/dist/page.js +0 -15
  280. package/dist/server/cli.js +0 -43
  281. package/dist/server/config.js +0 -70
  282. package/dist/server/express.js +0 -18
  283. package/dist/server/index.js +0 -58
  284. package/dist/types.js +0 -34
  285. package/jest.config.js +0 -10
  286. package/logos/logo_by_ceubri/silex-icon-2018@200px.png +0 -0
  287. package/logos/logo_by_ceubri/silex-icon-2018@4x.png +0 -0
  288. package/logos/logo_by_ceubri/silex-logo-2018-white-transp.pdf +0 -0
  289. package/logos/logo_by_ceubri/silex-logo-2018-white.jpg +0 -0
  290. package/logos/logo_by_ceubri/silex-logo-2018-white.pdf +0 -0
  291. package/logos/logo_by_ceubri/silex-logo-2018-white.png +0 -0
  292. package/logos/logo_by_ceubri/silex-logo-2018-white@0.5x.png +0 -0
  293. package/logos/logo_by_ceubri/silex-logo-2018-white@0.75x.png +0 -0
  294. package/logos/logo_by_ceubri/silex-logo-2018-white@4x.jpg +0 -0
  295. package/logos/logo_by_ceubri/silex-logo-2018-white@4x.png +0 -0
  296. package/logos/logo_by_ceubri/silex-logo-2018.ai +5 -2521
  297. package/logos/logo_by_ceubri/silex-logo-2018.jpg +0 -0
  298. package/logos/logo_by_ceubri/silex-logo-2018.pdf +0 -0
  299. package/logos/logo_by_ceubri/silex-logo-2018.png +0 -0
  300. package/logos/logo_by_ceubri/silex-logo-2018@0.5x.png +0 -0
  301. package/logos/logo_by_ceubri/silex-logo-2018@0.75x.png +0 -0
  302. package/logos/logo_by_ceubri/silex-logo-2018@4x.jpg +0 -0
  303. package/logos/logo_by_ceubri/silex-logo-2018@4x.png +0 -0
  304. package/logos/silexlabs.png +0 -0
  305. package/src/example-client-config.js +0 -47
  306. package/src/plugins/AuthPlugin.js +0 -21
  307. package/src/plugins/Cache.js +0 -11
  308. package/src/plugins/DefaultBackend.js +0 -149
  309. package/src/plugins/ExpressPlugin.js +0 -39
  310. package/src/plugins/HooksPlugin.js +0 -58
  311. package/src/plugins/PublishPlugin.js +0 -69
  312. package/src/plugins/SslPlugin.js +0 -66
  313. package/src/plugins/StaticPlugin.js +0 -48
  314. package/src/plugins/WebsitePlugin.js +0 -207
  315. package/src/ts/client/grapesjs/publish.ts +0 -386
  316. package/src/ts/client/grapesjs/template.ts +0 -166
  317. package/src/ts/events.ts +0 -2
  318. package/tsconfig-server-debug.json +0 -19
  319. package/tsconfig-server-release.json +0 -19
  320. package/tsconfig.jest.json +0 -9
  321. package/tsconfig.json +0 -26
  322. package/webpack.config.js +0 -28
  323. package/webpack.debug.config.js +0 -8
@@ -0,0 +1,203 @@
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
+
18
+ import { Readable } from 'stream'
19
+ import { ServerConfig } from '../config'
20
+ import { JobStatus, JobData, ConnectorData, ConnectorId, WebsiteId, ConnectorType, ConnectorUser, WebsiteMeta, WebsiteMetaFileContent, WebsiteData, ConnectorOptions } from '../../types'
21
+ import { JobManager } from '../jobs'
22
+
23
+ /**
24
+ * @fileoverview define types for Silex connectors
25
+ * Bakends can provide storage for website data and assets, and/or hosting to publish the website online
26
+ */
27
+
28
+ /**
29
+ * Connector data stored in the website meta file
30
+ */
31
+ export type ConnectorFileContent = string | Buffer | Readable
32
+
33
+ /**
34
+ * Files are stored in connector as a File object
35
+ * @see types/ClientSideFile
36
+ * @see contentToString()
37
+ */
38
+ export interface ConnectorFile {
39
+ path: string,
40
+ content: ConnectorFileContent,
41
+ }
42
+
43
+ /**
44
+ * Callback to update the publication status
45
+ */
46
+ export type StatusCallback = ({message, status}: {message: string, status: JobStatus}) => Promise<void>
47
+
48
+ export type ConnectorSession = object
49
+ /**
50
+ * Connectors are the base interface for Storage and Hosting connectors
51
+ */
52
+ export interface Connector<Session extends ConnectorSession = ConnectorSession> {
53
+ connectorId: ConnectorId
54
+ connectorType: ConnectorType
55
+ displayName: string
56
+ icon: string
57
+ disableLogout?: boolean
58
+ color: string
59
+ background: string
60
+ // Get the URL to start the authentication process with OAuth (not used for basic auth)
61
+ getOAuthUrl(session: Session): Promise<string | null>
62
+ // Get the form to display to the user to authenticate (not used for OAuth)
63
+ getLoginForm(session: Session, redirectTo: string): Promise<string | null>
64
+ // Get the form to display to the user to set the connector settings for a given website
65
+ getSettingsForm(session: Session, redirectTo: string): Promise<string | null>
66
+ // Auth and user management
67
+ isLoggedIn(session: Session): Promise<boolean>
68
+ setToken(session: Session, token: object): Promise<void>
69
+ logout(session: Session): Promise<void>
70
+ getUser(session: Session): Promise<ConnectorUser | null>
71
+ // Get the connector options from login form
72
+ // They are stored in the website meta file for hosting connectors
73
+ // And in the user session for storage connectors
74
+ getOptions(formData: object): ConnectorOptions
75
+ }
76
+
77
+ /**
78
+ * Storage are used to store the website data and assets
79
+ * And possibly rename files and directories, and get the URL of a file
80
+ *
81
+ */
82
+ export interface StorageConnector<Session extends ConnectorSession = ConnectorSession> extends Connector<Session> {
83
+ // CRUD on websites
84
+ listWebsites(session: Session): Promise<WebsiteMeta[]>
85
+ readWebsite(session: Session, websiteId: WebsiteId): Promise<WebsiteData | Readable>
86
+ createWebsite(session: Session, data: WebsiteMetaFileContent): Promise<WebsiteId>
87
+ updateWebsite(session: Session, websiteId: WebsiteId, data: WebsiteData): Promise<void>
88
+ deleteWebsite(session: Session, websiteId: WebsiteId): Promise<void>
89
+ duplicateWebsite(session: Session, websiteId: WebsiteId): Promise<void>
90
+ // CRUD on assets
91
+ writeAssets(session: Session, websiteId: WebsiteId, files: ConnectorFile[], status?: StatusCallback): Promise<string[] | void>
92
+ readAsset(session: Session, websiteId: WebsiteId, fileName: string): Promise<ConnectorFileContent>
93
+ deleteAssets(session: Session, websiteId: WebsiteId, fileNames: string[]): Promise<void>
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>
98
+ }
99
+
100
+ /**
101
+ * Hosting connectors are used to publish the website
102
+ */
103
+ export interface HostingConnector<Session extends ConnectorSession = ConnectorSession> extends Connector<Session> {
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
105
+ getUrl(session: Session, websiteId: WebsiteId): Promise<string>
106
+ }
107
+
108
+ export function toConnectorEnum(type: string | ConnectorType): ConnectorType {
109
+ const result = ConnectorType[type.toString().toUpperCase() as keyof typeof ConnectorType]
110
+ if(!result) throw new Error(`Unknown connector type ${type}. It should be one of ${Object.keys(ConnectorType).join(', ')}`)
111
+ return result
112
+ }
113
+
114
+ /**
115
+ * Get a connector by id or by type
116
+ */
117
+ export async function getConnector<T extends Connector>(config: ServerConfig, session: any, type: ConnectorType, connectorId?: ConnectorId): Promise<T | undefined> {
118
+ // Get the connectors for this type
119
+ const connectors = config.getConnectors(type) as T[]
120
+
121
+ // Find the connector by id
122
+ if (connectorId) return connectors.find(s => s.connectorId === connectorId && s.connectorType === type)
123
+ // Find the first logged in connector
124
+ for (const connector of connectors) {
125
+ if (await connector.isLoggedIn(session)) {
126
+ return connector
127
+ }
128
+ }
129
+ // Defaults to the first connector
130
+ return connectors[0]
131
+ }
132
+
133
+ /**
134
+ * Convert a connector to a ConnectorData object to be sent to the frontend
135
+ */
136
+ export async function toConnectorData(session: any, connector: Connector): Promise<ConnectorData> {
137
+ return {
138
+ connectorId: connector.connectorId,
139
+ type: connector.connectorType,
140
+ displayName: connector.displayName,
141
+ icon: connector.icon,
142
+ disableLogout: !!connector.disableLogout,
143
+ isLoggedIn: await connector.isLoggedIn(session),
144
+ oauthUrl: await connector.getOAuthUrl(session),
145
+ color: connector.color,
146
+ background: connector.background,
147
+ }
148
+ }
149
+
150
+ export async function contentToString(content: ConnectorFileContent): Promise<string> {
151
+ // String
152
+ if (typeof content === 'string') return content
153
+ // Buffer
154
+ if (Buffer.isBuffer(content)) return content.toString('utf8')
155
+ // Stream
156
+ return new Promise((resolve, reject) => {
157
+ let result = ''
158
+ content.on('data', (chunk: Buffer) => {
159
+ result += chunk.toString('utf8')
160
+ })
161
+ content.on('error', (err: Error) => {
162
+ reject(err)
163
+ })
164
+ content.on('end', () => {
165
+ resolve(result)
166
+ })
167
+ })
168
+ }
169
+
170
+ export async function contentToBuffer(content: ConnectorFileContent): Promise<Buffer> {
171
+ // String
172
+ if (typeof content === 'string') return Buffer.from(content, 'utf8')
173
+ // Buffer
174
+ if (Buffer.isBuffer(content)) return content
175
+ // Stream
176
+ if(content instanceof Readable) {
177
+ return new Promise((resolve, reject) => {
178
+ const chunks: Buffer[] = []
179
+ content.on('data', (chunk: Buffer) => {
180
+ chunks.push(chunk)
181
+ })
182
+ content.on('error', (err: Error) => {
183
+ reject(err)
184
+ })
185
+ content.on('end', () => {
186
+ resolve(Buffer.concat(chunks))
187
+ })
188
+ })
189
+ }
190
+ // Unknown
191
+ throw new Error('Unknown content type')
192
+ }
193
+
194
+ export function contentToReadable(content: ConnectorFileContent): Readable {
195
+ // String
196
+ if (typeof content === 'string') return Readable.from([content])
197
+ // Buffer
198
+ if (Buffer.isBuffer(content)) return Readable.from([content])
199
+ // Stream
200
+ if (content instanceof Readable) return content
201
+ // Unknown
202
+ throw new Error('Unknown content type')
203
+ }
@@ -0,0 +1,37 @@
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
+
18
+ import { ConnectorId, PublicationData, WebsiteData, WebsiteId } from '../types'
19
+ import { ConnectorFile } from './connectors/connectors'
20
+
21
+ export enum ServerEvent {
22
+ STARTUP_START = 'startup-start',
23
+ STARTUP_END = 'startup-end',
24
+ PUBLISH_START = 'publish-start',
25
+ PUBLISH_END = 'publish-end',
26
+ WEBSITE_STORE_START = 'store-start',
27
+ WEBSITE_STORE_END = 'store-end',
28
+ WEBSITE_ASSET_STORE_START = 'asset-store-start',
29
+ WEBSITE_ASSET_STORE_END = 'asset-store-end',
30
+ }
31
+
32
+ export type PublishStartEventType = PublicationData
33
+ export type PublishEndEventType = Error | null
34
+ export type WebsiteStoreStartEventType = { websiteId: WebsiteId, websiteData: WebsiteData, connectorId: ConnectorId}
35
+ export type WebsiteStoreEndEventType = Error | null
36
+ export type WebsiteAssetStoreStartEventType = { files: ConnectorFile[], websiteId: WebsiteId, connectorId: ConnectorId}
37
+ export type WebsiteAssetStoreEndEventType = Error | null
@@ -0,0 +1,19 @@
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
+
18
+
19
+ export * as events from './events'
@@ -1,17 +1,69 @@
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 express, { Application } from 'express'
19
+ import bodyParser from 'body-parser'
20
+ import compression from 'compression'
21
+ import cookieParser from 'cookie-parser'
22
+ import session from 'cookie-session'
23
+ import cors from 'cors'
2
24
 
3
25
  import { ServerConfig } from './config'
4
- import { EVENT_STARTUP_START, EVENT_STARTUP_END } from '../events'
26
+ import { ServerEvent } from './events'
5
27
 
6
- export default async function(config: ServerConfig): Promise<Application> {
7
- // Plugins hook to create API routes
28
+ export function create(config: ServerConfig): Application {
29
+ // Express app
8
30
  const app = express()
9
- await config.emit(EVENT_STARTUP_START, { app })
31
+ app.set('config', config)
32
+
33
+ // CORS
34
+ const options = config.expressOptions
35
+ if (options.cors) {
36
+ console.info('> CORS are ENABLED:', options.cors)
37
+ app.use(cors({
38
+ origin: options.cors,
39
+ }))
40
+ }
41
+ // compress gzip when possible
42
+ app.use(compression() as any)
43
+
44
+ // cookie & session
45
+ app.use(bodyParser.json({ limit: options.jsonLimit }))
46
+ app.use(bodyParser.text({ limit: options.textLimit }))
47
+ app.use(bodyParser.urlencoded({ limit: options.urlencodedLimit }))
48
+ console.info('> Session name:', options.sessionName)
49
+ app.use(cookieParser() as any)
50
+ app.use(session({
51
+ name: options.sessionName,
52
+ secret: options.sessionSecret,
53
+ }) as any)
54
+ return app
55
+ }
56
+
57
+ export async function start(app: Application): Promise<Application> {
58
+ const config = app.get('config') as ServerConfig
59
+
60
+ // Plugins hook to create API routes
61
+ config.emit(ServerEvent.STARTUP_START, { app })
10
62
 
11
63
  // Start server
12
64
  return new Promise((resolve, reject) => {
13
65
  app.listen(config.port, () => {
14
- config.emit(EVENT_STARTUP_END, { app })
66
+ config.emit(ServerEvent.STARTUP_END, { app })
15
67
  resolve(app)
16
68
  })
17
69
  })
@@ -1,24 +1,84 @@
1
- import getConfig from './config'
2
- import start from './express'
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
+ */
3
17
 
4
- import * as config from './config'
5
- import * as types from '../types'
6
- import * as events from '../events'
7
- import * as constants from '../constants'
8
- import * as page from '../page'
9
- export default { config, types, events, constants, page }
18
+ import * as silexApp from './express'
19
+ import api from './api/api'
20
+ import { ServerConfig } from './config'
21
+ import dotenv from 'dotenv'
22
+ import { join } from 'path'
23
+ import { DEV_MESSAGE } from '../constants'
24
+
25
+ // Expose API to calling app as function silex()
26
+ export * from './expose'
10
27
 
11
28
  // Main app
12
- async function main() {
13
- // Get the default config object
14
- const config = await getConfig()
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
+
35
+ // Load env vars from .env file if any
36
+ const curDirEnv = dotenv.config()
37
+ if (curDirEnv.error) {
38
+ if(curDirEnv.error['code'] === 'ENOENT') {
39
+ console.info('> No .env file found in current directory')
40
+ } else {
41
+ throw curDirEnv.error
42
+ }
43
+ } else {
44
+ console.info('> Env vars loaded from .env file')
45
+ }
15
46
 
16
- // Here one can mutate the config object
17
- // Check the docs, look for the "add silex to your project as an npm dependency"
47
+ // Load default env vars
48
+ // This will not override existing env vars
49
+ const DEFAULT_ENV_FILE = join(__dirname, '../../../.env.default')
50
+ const rootDirEnv = dotenv.config({ path: DEFAULT_ENV_FILE })
51
+ if (rootDirEnv.error) {
52
+ if(rootDirEnv.error['code'] === 'ENOENT') {
53
+ throw new Error(`\n\nFailed to load default env vars. File not found ${DEFAULT_ENV_FILE}\n\n`)
54
+ } else {
55
+ throw new Error(`\n\nFailed to load default env vars. Error in ${DEFAULT_ENV_FILE}: ${ rootDirEnv.error.message }\n\n`)
56
+ }
57
+ }
58
+ console.info('> Default env vars loaded')
59
+
60
+ // Get the default config object
61
+ const config = new ServerConfig()
18
62
 
19
63
  // start silex
20
- const app = await start(config)
64
+ const app = silexApp.create(config)
21
65
 
66
+ // Serve the client config file
67
+ await config.addRoutes(app)
68
+
69
+ // Load the config files
70
+ await config.loadConfigFiles()
71
+
72
+ // Init the connectors in case no plugin adds them
73
+ await config.initDefaultConnectors()
74
+
75
+ // APIs
76
+ app.use('/api', api(config))
77
+
78
+ // Start the server
79
+ await silexApp.start(app)
80
+
81
+ // All good, server is ready
22
82
  console.info(`
23
83
  I'm ready, listening to port ${config.port}
24
84
  `)
@@ -26,14 +86,13 @@ I'm ready, listening to port ${config.port}
26
86
  startLiverReload()
27
87
  }
28
88
  }
29
- main()
30
89
 
31
90
  // livereload
32
91
  async function startLiverReload() {
33
92
  // Load modules only when needed (they may not even be installed)
34
93
  const { createServer } = await import('livereload')
35
94
  const { resolve } = await import('path')
36
- const dist = resolve(__dirname, '../client')
95
+ const dist = resolve(__dirname, '../../client')
37
96
  const server = createServer({
38
97
  delay: 0,
39
98
  }, () => {
@@ -41,3 +100,7 @@ async function startLiverReload() {
41
100
  })
42
101
  server.watch(dist)
43
102
  }
103
+
104
+ if (require.main === module) {
105
+ silex()
106
+ }
@@ -0,0 +1,75 @@
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
+
18
+ import { JobData, JobStatus } from '../types'
19
+ import { startJob, jobSuccess, jobError, getJob, killJob } from './jobs'
20
+ import { expect, afterEach, it, describe } from '@jest/globals'
21
+
22
+ let job: JobData | undefined
23
+ afterEach(() => {
24
+ // Avoid memory leak
25
+ if(job) killJob(job)
26
+ })
27
+
28
+ describe('Job Module', () => {
29
+ it('should start a job', () => {
30
+ const {jobId} = startJob('Test job')
31
+ job = getJob(jobId)
32
+ expect(job).not.toBeNull()
33
+ if (job) {
34
+ expect(job.jobId).toEqual(jobId)
35
+ expect(job.status).toEqual(JobStatus.IN_PROGRESS)
36
+ expect(job.message).toEqual('Test job')
37
+ killJob(job)
38
+ expect(getJob(jobId)).toBeUndefined()
39
+ }
40
+ })
41
+
42
+ it('should end a job with success', () => {
43
+ const {jobId} = startJob('Test job')
44
+ const result = jobSuccess(jobId, 'Job completed successfully')
45
+ expect(result).toBeTruthy()
46
+ job = getJob(jobId)
47
+ expect(job).not.toBeNull()
48
+ if (job) {
49
+ expect(job.status).toEqual(JobStatus.SUCCESS)
50
+ expect(job.message).toEqual('Job completed successfully')
51
+ }
52
+ })
53
+
54
+ it('should end a job with error', () => {
55
+ const {jobId} = startJob('Test job')
56
+ const result = jobError(jobId, 'Job ended with error')
57
+ expect(result).toBeTruthy()
58
+ job = getJob(jobId)
59
+ expect(job).not.toBeNull()
60
+ if (job) {
61
+ expect(job.status).toEqual(JobStatus.ERROR)
62
+ expect(job.message).toEqual('Job ended with error')
63
+ }
64
+ })
65
+
66
+ it('should not find a job after ending', done => {
67
+ const {jobId} = startJob('Test job')
68
+ jobSuccess(jobId, 'Job completed successfully')
69
+ setTimeout(() => {
70
+ job = getJob(jobId)
71
+ expect(job).not.toBeUndefined()
72
+ done()
73
+ }, 1000)
74
+ })
75
+ })
@@ -0,0 +1,89 @@
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
+
18
+ import { JobData, JobStatus, JobId } from '../types'
19
+
20
+ // Delay after which the job is deleted from memory
21
+ const TIME_TO_KEEP_AFTER_STOP = 60*10*1000 // 10 min
22
+
23
+ // Store the jobs in memory
24
+ const jobs = new Array<JobData>()
25
+ const timers = new WeakMap<JobData, NodeJS.Timeout>()
26
+
27
+ // Key which will change on server restart
28
+ const processKey = Math.ceil(Math.random() * 1000000)
29
+
30
+ // Next id for the next job
31
+ let nextJobId = 0
32
+
33
+ // Create a new job
34
+ export function startJob(message = ''): JobData {
35
+ const jobId = `${processKey}_${++nextJobId}`
36
+ const job: JobData = {
37
+ jobId,
38
+ status: JobStatus.IN_PROGRESS,
39
+ message: message
40
+ }
41
+ jobs.push(job)
42
+ return job
43
+ }
44
+
45
+ // Retriev a job if it exists
46
+ export function getJob(id: JobId): JobData | undefined {
47
+ return jobs.find(job => job.jobId === id)
48
+ }
49
+
50
+
51
+ // End the job with success
52
+ export function jobSuccess(id: JobId, message = ''): boolean {
53
+ return end(id, JobStatus.SUCCESS, message)
54
+ }
55
+
56
+ // End the job with error
57
+ export function jobError(id: JobId, message = ''): boolean {
58
+ return end(id, JobStatus.ERROR, message)
59
+ }
60
+
61
+ // Remove a job from memory
62
+ // This is also used in unit tests to avoid memory lea
63
+ export function killJob(job: JobData) {
64
+ clearTimeout(timers.get(job))
65
+ timers.delete(job)
66
+ jobs.splice(jobs.findIndex(j => job.jobId === j.jobId), 1)
67
+ }
68
+
69
+ // End the job
70
+ function end(id: JobId, status: JobStatus, message = ''): boolean {
71
+ const job = getJob(id)
72
+ if(job) {
73
+ job.status = status
74
+ if(message) job.message = message
75
+ timers.set(job, setTimeout(() => killJob(job), TIME_TO_KEEP_AFTER_STOP))
76
+ return true
77
+ }
78
+ return false
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 }