@silexlabs/silex 3.0.1-alpha.0 → 3.0.2

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 +92 -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 +80 -19
  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,621 @@
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 { Client } from 'basic-ftp'
19
+ import { Readable } from 'stream'
20
+ import { ConnectorFile, ConnectorFileContent, HostingConnector, StatusCallback, StorageConnector, contentToReadable, contentToString, toConnectorData, toConnectorEnum} from '../../server/connectors/connectors'
21
+ import { requiredParam } from '../../server/utils/validation'
22
+ import { WEBSITE_DATA_FILE, WEBSITE_META_DATA_FILE } from '../../constants'
23
+ import { ConnectorType, ConnectorUser, WebsiteMeta, FileMeta, JobData, JobStatus, WebsiteId, PublicationJobData, WebsiteMetaFileContent, defaultWebsiteData, WebsiteData, ConnectorOptions } from '../../types'
24
+ import { ServerConfig } from '../../server/config'
25
+ import { join } from 'path'
26
+ import { tmpdir } from 'os'
27
+ import { v4 as uuid } from 'uuid'
28
+ import { JobManager } from '../../server/jobs'
29
+ import { mkdtemp, rm, rmdir } from 'fs/promises'
30
+ import { createReadStream } from 'fs'
31
+
32
+ /**
33
+ * @fileoverview FTP connector for Silex
34
+ * This is a connector for Silex, which allows to store the website on a FTP server
35
+ * and to publish the website on a FTP server
36
+ * FIXME: As a hosting connector, this connector should only store the password in the session and the rest in the website data
37
+ * to prevent publishing a site on the wrong server + multiple sites on 1 server
38
+ * FIXME: Allow reuse of FTP session between calls
39
+ */
40
+
41
+ export interface FtpSessionData {
42
+ host: string
43
+ user: string
44
+ pass: string
45
+ port: number
46
+ secure: boolean
47
+ // Storage options
48
+ storageRootPath?: string
49
+ // Hosting options
50
+ publicationPath?: string
51
+ websiteUrl?: string
52
+ }
53
+
54
+ export interface FtpSession {
55
+ [ConnectorType.STORAGE]?: FtpSessionData
56
+ [ConnectorType.HOSTING]?: FtpSessionData
57
+ }
58
+
59
+ interface FileStatus {
60
+ file: ConnectorFile
61
+ message: string
62
+ status: JobStatus
63
+ }
64
+
65
+ export interface FtpOptions {
66
+ type: ConnectorType
67
+ path: string
68
+ assetsFolder: string
69
+ cssFolder: string // For publication only
70
+ authorizeUrl: string
71
+ authorizePath: string
72
+ }
73
+
74
+ // **
75
+ // Utils methos
76
+ function formHtml(redirectTo: string, type: ConnectorType, { host, user, pass, port, secure, storageRootPath, publicationPath, websiteUrl}: FtpSessionData, err = '') {
77
+ return `
78
+ ${ err && `<div class="error">${err || ''}</div>` }
79
+ <form method="POST" action="${redirectTo}">
80
+ <label for="host">Host</label>
81
+ <input placeholder="ftp.example.com" type="text" name="host" value="${host || ''}" />
82
+ <label for="user">User</label>
83
+ <input placeholder="user" type="text" name="user" value="${user || ''}" />
84
+ <label for="pass">Pass</label>
85
+ <input placeholder="****" type="password" name="pass" value="${pass || ''}" />
86
+ <label for="port">Port</label>
87
+ <input placeholder="21" type="number" name="port" value="${port || '21'}" />
88
+ <div class="checkbox-container">
89
+ <input class="checkbox" type="checkbox" name="secure" value="true" ${secure ? 'checked' : ''} />
90
+ <label for="secure">Secure</label>
91
+ </div>
92
+ ${ type === ConnectorType.STORAGE ? `
93
+ <details>
94
+ <summary>Storage options</summary>
95
+ <p>If you are not sure, don't change this</p>
96
+ <label for="storageRootPath">Root path where to store the website files</label>
97
+ <input placeholder="/silex/" type="text" name="storageRootPath" value="${storageRootPath || '/silex/'}" />
98
+ </details>
99
+ ` : ''}
100
+ ${ type === ConnectorType.HOSTING ? `
101
+ <fieldset>
102
+ <legend>Publication options</legend>
103
+ <label for="publicationPath">Path where to publish</label>
104
+ <input placeholder="/public_html/" type="text" name="publicationPath" value="${publicationPath || ''}" />
105
+ <label for="websiteUrl">URL where to the site will be accessible</label>
106
+ <input placeholder="https://mysite.com/" type="text" name="websiteUrl" value="${websiteUrl || ''}" />
107
+ </fieldset>
108
+ ` : ''}
109
+ <div class="button-container">
110
+ <button type="submit" class="primary-button">Login</button>
111
+ <button type="button" class="secondary-button">Cancel</button>
112
+ </div>
113
+ </form>
114
+ `
115
+ }
116
+
117
+ const formCss = `
118
+ /* Reset default form styles */
119
+ form {
120
+ margin: 0;
121
+ padding: 0;
122
+ }
123
+
124
+ /* Center the form */
125
+ body {
126
+ display: flex;
127
+ justify-content: center;
128
+ align-items: center;
129
+ height: 100vh;
130
+ background-color: #f5f5f5;
131
+ }
132
+
133
+ /* Style form container */
134
+ form {
135
+ width: 400px;
136
+ padding: 20px;
137
+ border-radius: 4px;
138
+ background-color: white;
139
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
140
+ }
141
+
142
+ /* Style form labels */
143
+ label {
144
+ font-weight: bold;
145
+ margin-bottom: 5px;
146
+ color: #333;
147
+ }
148
+
149
+ /* Style form inputs */
150
+ input {
151
+ width: 100%;
152
+ padding: 8px;
153
+ margin-bottom: 10px;
154
+ border: 1px solid #ccc;
155
+ border-radius: 4px;
156
+ }
157
+ input.checkbox {
158
+ width: 20px;
159
+ height: 20px;
160
+ }
161
+ fieldset {
162
+ padding: 20px;
163
+ }
164
+
165
+ /* Style form buttons */
166
+ .button-container {
167
+ display: flex;
168
+ justify-content: space-between;
169
+ margin-top: 20px;
170
+ }
171
+
172
+ .primary-button,
173
+ .secondary-button {
174
+ width: 48%;
175
+ padding: 10px;
176
+ border: none;
177
+ border-radius: 4px;
178
+ cursor: pointer;
179
+ transition: background-color 0.3s ease;
180
+ }
181
+
182
+ .primary-button {
183
+ background-color: #4caf50;
184
+ color: white;
185
+ }
186
+
187
+ .secondary-button {
188
+ background-color: #e0e0e0;
189
+ color: #333;
190
+ }
191
+
192
+ .primary-button:hover,
193
+ .secondary-button:hover {
194
+ background-color: #388e3c;
195
+ }
196
+
197
+ /* Style form checkbox */
198
+ .checkbox-container {
199
+ display: flex;
200
+ align-items: center;
201
+ margin-bottom: 10px;
202
+ margin: 20px 0;
203
+ }
204
+
205
+ .checkbox-container label {
206
+ margin-left: 5px;
207
+ color: #333;
208
+ }
209
+
210
+ /* Style details and summary elements */
211
+ details {
212
+ margin-top: 20px;
213
+ color: #333;
214
+ }
215
+
216
+ summary {
217
+ font-weight: bold;
218
+ cursor: pointer;
219
+ }
220
+
221
+ details > p {
222
+ margin-top: 10px;
223
+ }
224
+ `
225
+
226
+ /**
227
+ * @class FtpConnector
228
+ * @implements {HostingConnector}
229
+ * @implements {StorageConnector}
230
+ */
231
+ export default class FtpConnector implements StorageConnector<FtpSession> {
232
+ connectorId = 'ftp'
233
+ displayName = 'Ftp'
234
+ icon = '/assets/ftp.png'
235
+ options: FtpOptions
236
+ connectorType: ConnectorType
237
+ color = '#ffffff'
238
+ background = '#0066CC'
239
+
240
+ constructor(config: ServerConfig, opts: Partial<FtpOptions>) {
241
+ this.options = {
242
+ path: '',
243
+ assetsFolder: 'assets',
244
+ cssFolder: 'css',
245
+ authorizeUrl: './api/authorize/ftp/',
246
+ authorizePath: './api/authorize/ftp/',
247
+ ...opts,
248
+ } as FtpOptions
249
+ if(!this.options.type) throw new Error('missing type in option of FtpConnector')
250
+ this.connectorType = toConnectorEnum(this.options.type)
251
+ }
252
+
253
+ // **
254
+ // Utils
255
+ sessionData(session: FtpSession): FtpSessionData {
256
+ return session[`ftp-${this.options.type}`] ?? {} as FtpSessionData
257
+ }
258
+
259
+ rootPath(session: FtpSession): string {
260
+ return this.connectorType === ConnectorType.STORAGE ?
261
+ requiredParam<string>(this.sessionData(session).storageRootPath, 'storage root path') :
262
+ requiredParam<string>(this.sessionData(session).publicationPath, 'publication path')
263
+ }
264
+
265
+ // **
266
+ // FTP methods
267
+ private async write(ftp: Client, path: string, content: ConnectorFileContent, progress?: (message: string) => void): Promise<void> {
268
+ ftp.trackProgress(info => {
269
+ progress && progress(`Uploading ${info.bytes / 1000} KB}`)
270
+ })
271
+ progress && progress('Upload started')
272
+ await ftp.uploadFrom(contentToReadable(content), path)
273
+ progress && progress('Upload complete')
274
+ }
275
+
276
+ private async read(ftp: Client, path: string): Promise<Readable> {
277
+ const tempDir = await mkdtemp(join(tmpdir(), 'silex-tmp'))
278
+ const tempPath = join(tempDir, 'silex-ftp.tmp')
279
+ await ftp.downloadTo(tempPath, path)
280
+ const rStream = createReadStream(tempPath)
281
+ await rm(tempPath)
282
+ await rmdir(tempDir)
283
+ return rStream
284
+ }
285
+
286
+ private async readdir(ftp: Client, path: string): Promise<FileMeta[]> {
287
+ const list = await ftp.list(path)
288
+ return list.map((file) => ({
289
+ name: file.name,
290
+ isDir: file.isDirectory,
291
+ size: file.size,
292
+ createdAt: file.modifiedAt,
293
+ updatedAt: file.modifiedAt,
294
+ metaData: file,
295
+ } as FileMeta))
296
+ }
297
+
298
+ private async mkdir(ftp: Client, path: string) {
299
+ return ftp.ensureDir(path)
300
+ }
301
+
302
+ private async rmdir(ftp: Client, path: string) {
303
+ return ftp.removeDir(path)
304
+ }
305
+
306
+ private async unlink(ftp: Client, path: string) {
307
+ return ftp.remove(path)
308
+ }
309
+
310
+ private async getClient({host, user, pass, port, secure}): Promise<Client> {
311
+ const ftp = new Client()
312
+ await ftp.access({
313
+ host,
314
+ port,
315
+ user,
316
+ password: pass,
317
+ secure,
318
+ })
319
+ return ftp
320
+ }
321
+
322
+ private closeClient(ftp: Client) {
323
+ if(ftp && !ftp.closed) {
324
+ ftp.close()
325
+ } else {
326
+ console.warn('ftp already closed')
327
+ }
328
+ }
329
+
330
+ // **
331
+ // Connector interface
332
+ getOptions(formData: object): ConnectorOptions {
333
+ return {
334
+ // Storage
335
+ storageRootPath: formData['storageRootPath'],
336
+ // Hosting
337
+ publicationPath: formData['publicationPath'],
338
+ websiteUrl: formData['websiteUrl'],
339
+ }
340
+ }
341
+
342
+ async getOAuthUrl(session: FtpSession): Promise<null> { return null }
343
+
344
+ async getLoginForm(session: FtpSession, redirectTo: string): Promise<string | null> {
345
+ const { host, user, pass, port, secure, publicationPath, storageRootPath, websiteUrl } = this.sessionData(session)
346
+ return `
347
+ <style>
348
+ ${formCss}
349
+ </style>
350
+ ${formHtml(redirectTo, this.connectorType, { host, user, pass, port, secure, publicationPath, storageRootPath, websiteUrl })}
351
+ `
352
+ }
353
+
354
+ async getSettingsForm(session: FtpSession, redirectTo: string): Promise<string | null> { return null }
355
+
356
+ async setToken(session: FtpSession, token: object): Promise<void> {
357
+ // Check all required params are present
358
+ const { host, user, pass, port, secure = false, publicationPath, storageRootPath, websiteUrl } = token as FtpSessionData
359
+ requiredParam(session, 'session')
360
+ requiredParam(host, 'host')
361
+ requiredParam(user, 'user')
362
+ requiredParam(pass, 'pass')
363
+ requiredParam(port, 'port')
364
+
365
+ // Check if the connection is valid
366
+ const ftp = await this.getClient({ host, user, pass, port, secure })
367
+ // Save the token
368
+ session[`ftp-${this.connectorType}`] = { host, user, pass, port, secure, publicationPath, storageRootPath, websiteUrl }
369
+ // Clean up
370
+ this.closeClient(ftp)
371
+ }
372
+
373
+ async logout(session: FtpSession) {
374
+ delete session[`ftp-${this.options.type}`]
375
+ }
376
+
377
+ async getUser(session: FtpSession): Promise<ConnectorUser> {
378
+ return {
379
+ name: this.sessionData(session).user,
380
+ picture: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' height=\'1em\' viewBox=\'0 0 448 512\'%3E%3C!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --%3E%3Cpath d=\'M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z\'/%3E%3C/svg%3E',
381
+ storage: await toConnectorData(session, this)
382
+ }
383
+ }
384
+
385
+ async isLoggedIn(session: FtpSession) {
386
+ try {
387
+ if (!session[`ftp-${this.options.type}`]) {
388
+ return false
389
+ }
390
+ const ftp = await this.getClient(this.sessionData(session))
391
+ this.closeClient(ftp)
392
+ return true
393
+ } catch(err) {
394
+ return false
395
+ }
396
+ }
397
+
398
+ async setWebsiteMeta(session: FtpSession, id: string, data: WebsiteMetaFileContent): Promise<void> {
399
+ const websiteId = requiredParam<WebsiteId>(id, 'website id')
400
+ const path = join(this.rootPath(session), websiteId, WEBSITE_META_DATA_FILE)
401
+ const ftp = await this.getClient(this.sessionData(session))
402
+ await this.write(ftp, path, JSON.stringify(data))
403
+ this.closeClient(ftp)
404
+ }
405
+
406
+ async getWebsiteMeta(session: FtpSession, id: WebsiteId): Promise<WebsiteMeta> {
407
+ try {
408
+ const websiteId = requiredParam<WebsiteId>(id, 'website id')
409
+ const ftp = await this.getClient(this.sessionData(session))
410
+ // Get stats for the website folder
411
+ const folder = join(this.rootPath(session), websiteId)
412
+ const path = join(this.rootPath(session), websiteId, WEBSITE_META_DATA_FILE)
413
+ const readable = await this.read(ftp, path)
414
+ const meta = JSON.parse(await contentToString(readable)) as WebsiteMetaFileContent
415
+ this.closeClient(ftp)
416
+ // Return all meta
417
+ return {
418
+ websiteId,
419
+ ...meta,
420
+ }
421
+ } catch(err) {
422
+ console.error(err)
423
+ throw err
424
+ }
425
+ }
426
+
427
+ // **
428
+ // Storage interface
429
+ /**
430
+ * Create necessary folders
431
+ * Assets and root folders
432
+ */
433
+ async createWebsite(session: FtpSession): Promise<WebsiteId> {
434
+ const ftp = await this.getClient(this.sessionData(session))
435
+ // Generate a random id
436
+ const id = uuid()
437
+ // // create root folder
438
+ // const rootPath = join(this.storageRootPath(session), id)
439
+ // await this.mkdir(ftp, rootPath)
440
+ // create assets folder
441
+ const assetsPath = join(this.rootPath(session), id, '/assets')
442
+ await this.mkdir(ftp, assetsPath)
443
+ // create website data file
444
+ const websiteDataPath = join(this.rootPath(session), id, WEBSITE_DATA_FILE)
445
+ await this.write(ftp, websiteDataPath, JSON.stringify(defaultWebsiteData))
446
+ // create website meta data file
447
+ const websiteMetaDataPath = join(this.rootPath(session), id, WEBSITE_META_DATA_FILE)
448
+ await this.write(ftp, websiteMetaDataPath, JSON.stringify({}))
449
+ // Clean up
450
+ this.closeClient(ftp)
451
+ // All good
452
+ return id
453
+ }
454
+
455
+ async listWebsites(session: FtpSession): Promise<WebsiteMeta[]> {
456
+ const storageRootPath = this.rootPath(session)
457
+ const ftp = await this.getClient(this.sessionData(session))
458
+ const files = await this.readdir(ftp, storageRootPath)
459
+ const list = await Promise.all(files.map(async file => {
460
+ const websiteId = file.name
461
+ const websiteMeta = await this.getWebsiteMeta(session, websiteId)
462
+ return websiteMeta
463
+ }))
464
+ this.closeClient(ftp)
465
+ return list
466
+ }
467
+
468
+ async readWebsite(session: FtpSession, websiteId: string): Promise<WebsiteData | Readable> {
469
+ const storageRootPath = this.rootPath(session)
470
+ const ftp = await this.getClient(this.sessionData(session))
471
+ const websiteDataPath = join(storageRootPath, websiteId, WEBSITE_DATA_FILE)
472
+ const data = await this.read(ftp, websiteDataPath)
473
+ this.closeClient(ftp)
474
+ return data
475
+ }
476
+
477
+ async updateWebsite(session: FtpSession, websiteId: string, data: WebsiteData): Promise<void> {
478
+ const storageRootPath = this.rootPath(session)
479
+ const ftp = await this.getClient(this.sessionData(session))
480
+ const websiteDataPath = join(storageRootPath, websiteId, WEBSITE_DATA_FILE)
481
+ await this.write(ftp, websiteDataPath, JSON.stringify(data))
482
+ this.closeClient(ftp)
483
+ }
484
+
485
+ async deleteWebsite(session: FtpSession, websiteId: string): Promise<void> {
486
+ const storageRootPath = this.rootPath(session)
487
+ const ftp = await this.getClient(this.sessionData(session))
488
+ const websitePath = join(storageRootPath, websiteId)
489
+ await this.rmdir(ftp, websitePath)
490
+ this.closeClient(ftp)
491
+ }
492
+
493
+ async duplicateWebsite(session: FtpSession, websiteId: string): Promise<void> {
494
+ const newWebsiteId = uuid()
495
+ const storageRootPath = this.rootPath(session)
496
+ const ftp = await this.getClient(this.sessionData(session))
497
+ const websitePath = join(storageRootPath, websiteId)
498
+ const newWebsitePath = join(storageRootPath, newWebsiteId)
499
+ const tempDir = await mkdtemp(tmpdir())
500
+ await ftp.downloadToDir(websitePath, tempDir)
501
+ await ftp.uploadFromDir(tempDir, newWebsitePath)
502
+ this.closeClient(ftp)
503
+ }
504
+
505
+ async writeAssets(
506
+ session: any,
507
+ id: WebsiteId,
508
+ files: ConnectorFile[],
509
+ statusCbk?: StatusCallback,
510
+ ): Promise<void> {
511
+ return this.writeFile(session, id, files, this.options.assetsFolder, statusCbk)
512
+ }
513
+
514
+ async writeFile(
515
+ session: any,
516
+ id: WebsiteId,
517
+ files: ConnectorFile[],
518
+ relativePath: string,
519
+ statusCbk?: StatusCallback,
520
+ ): Promise<void> {
521
+ // Connect to FTP server
522
+ statusCbk && statusCbk({
523
+ message: 'Connecting to FTP server',
524
+ status: JobStatus.IN_PROGRESS,
525
+ })
526
+ const ftp = await this.getClient(this.sessionData(session))
527
+ const rootPath = this.rootPath(session)
528
+ // Make sure that root folder exists
529
+ statusCbk && statusCbk({
530
+ message: 'Making sure that root folder exists',
531
+ status: JobStatus.IN_PROGRESS,
532
+ })
533
+ // Useless as ftp write will create the folder
534
+ // await this.mkdir(ftp, rootPath)
535
+ // Write files
536
+ let lastFile: ConnectorFile | undefined
537
+ try {
538
+ // Sequentially write files
539
+ for(const file of files) {
540
+ statusCbk && statusCbk({
541
+ message: `Writing file ${file.path}`,
542
+ status: JobStatus.IN_PROGRESS,
543
+ })
544
+ const dstPath = join(this.options.path, rootPath, id, relativePath, file.path)
545
+ lastFile = file
546
+ const result = await this.write(ftp, dstPath, file.content, message => {
547
+ statusCbk && statusCbk({
548
+ message: `Writing file ${file.path.split('/').pop()} to ${dstPath.split('/').slice(0, -1).join('/')}: ${message}`,
549
+ status: JobStatus.IN_PROGRESS,
550
+ })
551
+ })
552
+ }
553
+ this.closeClient(ftp)
554
+ statusCbk && statusCbk({
555
+ message: `Finished writing ${files.length} files to ${rootPath}`,
556
+ status: JobStatus.SUCCESS,
557
+ })
558
+ } catch(err) {
559
+ // Not sure why it never gets here
560
+ statusCbk && statusCbk({
561
+ message: `Error writing file ${lastFile?.path}: ${err.message}`,
562
+ status: JobStatus.ERROR,
563
+ })
564
+ this.closeClient(ftp)
565
+ }
566
+ }
567
+
568
+ async readAsset(session: FtpSession, id: string, path: string): Promise<ConnectorFileContent> {
569
+ if (!this.sessionData(session)) throw new Error('Not logged in')
570
+ const storageRootPath = this.rootPath(session)
571
+ const ftp = await this.getClient(this.sessionData(session))
572
+ const dirPath = join(this.options.path, storageRootPath, id, this.options.assetsFolder, path)
573
+ const asset = await this.read(ftp, dirPath)
574
+ this.closeClient(ftp)
575
+ return asset
576
+ }
577
+
578
+ async deleteAssets(session: FtpSession, id: WebsiteId, paths: string[]): Promise<void> {
579
+ const storageRootPath = this.rootPath(session)
580
+ const ftp = await this.getClient(this.sessionData(session))
581
+ await Promise.all(
582
+ paths.map((path) => this.unlink(ftp, join(this.options.path, storageRootPath, id, this.options.assetsFolder, path)))
583
+ )
584
+ this.closeClient(ftp)
585
+ }
586
+
587
+ // **
588
+ // Hosting interface
589
+ async getUrl(session: FtpSession, id: WebsiteId): Promise<string> {
590
+ // FIXME: do not store websiteUrl in the session, but in the website data
591
+ console.warn('FIXME: do not store websiteUrl in the session, but in the website data')
592
+ return this.sessionData(session).websiteUrl ?? ''
593
+ }
594
+
595
+ async publish(session: FtpSession, id: WebsiteId, files: ConnectorFile[], {startJob, jobSuccess, jobError}: JobManager): Promise<JobData> {
596
+ const job = startJob(`Publishing to ${this.displayName}`) as PublicationJobData
597
+ job.logs = [[`Publishing to ${this.displayName}`]]
598
+ job.errors = [[]]
599
+ // Create folders
600
+ const rootPath = this.rootPath(session)
601
+ const ftp = await this.getClient(this.sessionData(session))
602
+ await this.mkdir(ftp, rootPath)
603
+ await this.mkdir(ftp, join(rootPath, this.options.assetsFolder))
604
+ await this.mkdir(ftp, join(rootPath, this.options.cssFolder))
605
+ // Write files
606
+ // Do not await for the result, return the job and continue the publication in the background
607
+ this.writeFile(session, '', files, '', async ({status, message}) => {
608
+ // Update the job status
609
+ job.status = status
610
+ job.message = message
611
+ job.logs[0].push(message)
612
+ if(status === JobStatus.SUCCESS) {
613
+ jobSuccess(job.jobId, message)
614
+ } else if(status === JobStatus.ERROR) {
615
+ job.errors[0].push(message)
616
+ jobError(job.jobId, message)
617
+ }
618
+ })
619
+ return job
620
+ }
621
+ }