odac 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/.editorconfig +21 -0
  2. package/.github/workflows/auto-pr-description.yml +49 -0
  3. package/.github/workflows/release.yml +32 -0
  4. package/.github/workflows/test-coverage.yml +58 -0
  5. package/.husky/pre-commit +2 -0
  6. package/.kiro/steering/code-style.md +56 -0
  7. package/.kiro/steering/product.md +20 -0
  8. package/.kiro/steering/structure.md +77 -0
  9. package/.kiro/steering/tech.md +87 -0
  10. package/.prettierrc +10 -0
  11. package/.releaserc.js +134 -0
  12. package/AGENTS.md +84 -0
  13. package/CHANGELOG.md +181 -0
  14. package/CODE_OF_CONDUCT.md +83 -0
  15. package/CONTRIBUTING.md +63 -0
  16. package/LICENSE +661 -0
  17. package/README.md +57 -0
  18. package/SECURITY.md +26 -0
  19. package/bin/candy +10 -0
  20. package/bin/candypack +10 -0
  21. package/cli/index.js +3 -0
  22. package/cli/src/Cli.js +348 -0
  23. package/cli/src/Connector.js +93 -0
  24. package/cli/src/Monitor.js +416 -0
  25. package/core/Candy.js +87 -0
  26. package/core/Commands.js +239 -0
  27. package/core/Config.js +1094 -0
  28. package/core/Lang.js +52 -0
  29. package/core/Log.js +43 -0
  30. package/core/Process.js +26 -0
  31. package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
  32. package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
  33. package/docs/backend/01-overview/03-development-server.md +79 -0
  34. package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
  35. package/docs/backend/03-config/00-configuration-overview.md +214 -0
  36. package/docs/backend/03-config/01-database-connection.md +60 -0
  37. package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
  38. package/docs/backend/03-config/03-request-timeout.md +11 -0
  39. package/docs/backend/03-config/04-environment-variables.md +227 -0
  40. package/docs/backend/03-config/05-early-hints.md +352 -0
  41. package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
  42. package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
  43. package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
  44. package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
  45. package/docs/backend/04-routing/05-advanced-routing.md +14 -0
  46. package/docs/backend/04-routing/06-error-pages.md +101 -0
  47. package/docs/backend/04-routing/07-cron-jobs.md +149 -0
  48. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
  49. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
  50. package/docs/backend/05-controllers/03-controller-classes.md +93 -0
  51. package/docs/backend/05-forms/01-custom-forms.md +395 -0
  52. package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
  53. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
  54. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
  55. package/docs/backend/07-views/01-the-view-directory.md +73 -0
  56. package/docs/backend/07-views/02-rendering-a-view.md +179 -0
  57. package/docs/backend/07-views/03-template-syntax.md +181 -0
  58. package/docs/backend/07-views/03-variables.md +328 -0
  59. package/docs/backend/07-views/04-request-data.md +231 -0
  60. package/docs/backend/07-views/05-conditionals.md +290 -0
  61. package/docs/backend/07-views/06-loops.md +353 -0
  62. package/docs/backend/07-views/07-translations.md +358 -0
  63. package/docs/backend/07-views/08-backend-javascript.md +398 -0
  64. package/docs/backend/07-views/09-comments.md +297 -0
  65. package/docs/backend/08-database/01-database-connection.md +99 -0
  66. package/docs/backend/08-database/02-using-mysql.md +322 -0
  67. package/docs/backend/09-validation/01-the-validator-service.md +424 -0
  68. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
  69. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
  70. package/docs/backend/10-authentication/03-register.md +134 -0
  71. package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
  72. package/docs/backend/10-authentication/05-session-management.md +159 -0
  73. package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
  74. package/docs/backend/11-mail/01-the-mail-service.md +42 -0
  75. package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
  76. package/docs/backend/13-utilities/01-candy-var.md +504 -0
  77. package/docs/frontend/01-overview/01-introduction.md +146 -0
  78. package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
  79. package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
  80. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
  81. package/docs/frontend/03-forms/01-form-handling.md +420 -0
  82. package/docs/frontend/04-api-requests/01-get-post.md +443 -0
  83. package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
  84. package/docs/index.json +452 -0
  85. package/docs/server/01-installation/01-quick-install.md +19 -0
  86. package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
  87. package/docs/server/02-get-started/01-core-concepts.md +7 -0
  88. package/docs/server/02-get-started/02-basic-commands.md +57 -0
  89. package/docs/server/02-get-started/03-cli-reference.md +276 -0
  90. package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
  91. package/docs/server/03-service/01-start-a-new-service.md +57 -0
  92. package/docs/server/03-service/02-delete-a-service.md +48 -0
  93. package/docs/server/04-web/01-create-a-website.md +36 -0
  94. package/docs/server/04-web/02-list-websites.md +9 -0
  95. package/docs/server/04-web/03-delete-a-website.md +29 -0
  96. package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
  97. package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
  98. package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
  99. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
  100. package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
  101. package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
  102. package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
  103. package/docs/server/07-mail/04-change-account-password.md +23 -0
  104. package/eslint.config.mjs +120 -0
  105. package/framework/index.js +4 -0
  106. package/framework/src/Auth.js +309 -0
  107. package/framework/src/Candy.js +81 -0
  108. package/framework/src/Config.js +79 -0
  109. package/framework/src/Env.js +60 -0
  110. package/framework/src/Lang.js +57 -0
  111. package/framework/src/Mail.js +83 -0
  112. package/framework/src/Mysql.js +575 -0
  113. package/framework/src/Request.js +301 -0
  114. package/framework/src/Route/Cron.js +128 -0
  115. package/framework/src/Route/Internal.js +439 -0
  116. package/framework/src/Route.js +455 -0
  117. package/framework/src/Server.js +15 -0
  118. package/framework/src/Stream.js +163 -0
  119. package/framework/src/Token.js +37 -0
  120. package/framework/src/Validator.js +271 -0
  121. package/framework/src/Var.js +211 -0
  122. package/framework/src/View/EarlyHints.js +190 -0
  123. package/framework/src/View/Form.js +600 -0
  124. package/framework/src/View.js +513 -0
  125. package/framework/web/candy.js +838 -0
  126. package/jest.config.js +22 -0
  127. package/locale/de-DE.json +80 -0
  128. package/locale/en-US.json +79 -0
  129. package/locale/es-ES.json +80 -0
  130. package/locale/fr-FR.json +80 -0
  131. package/locale/pt-BR.json +80 -0
  132. package/locale/ru-RU.json +80 -0
  133. package/locale/tr-TR.json +85 -0
  134. package/locale/zh-CN.json +80 -0
  135. package/package.json +86 -0
  136. package/server/index.js +5 -0
  137. package/server/src/Api.js +88 -0
  138. package/server/src/DNS.js +940 -0
  139. package/server/src/Hub.js +535 -0
  140. package/server/src/Mail.js +571 -0
  141. package/server/src/SSL.js +180 -0
  142. package/server/src/Server.js +27 -0
  143. package/server/src/Service.js +248 -0
  144. package/server/src/Subdomain.js +64 -0
  145. package/server/src/Web/Firewall.js +170 -0
  146. package/server/src/Web/Proxy.js +134 -0
  147. package/server/src/Web.js +451 -0
  148. package/server/src/mail/imap.js +1091 -0
  149. package/server/src/mail/server.js +32 -0
  150. package/server/src/mail/smtp.js +786 -0
  151. package/test/cli/Cli.test.js +36 -0
  152. package/test/core/Candy.test.js +234 -0
  153. package/test/core/Commands.test.js +538 -0
  154. package/test/core/Config.test.js +1435 -0
  155. package/test/core/Lang.test.js +250 -0
  156. package/test/core/Process.test.js +156 -0
  157. package/test/framework/Route.test.js +239 -0
  158. package/test/framework/View/EarlyHints.test.js +282 -0
  159. package/test/scripts/check-coverage.js +132 -0
  160. package/test/server/Api.test.js +647 -0
  161. package/test/server/Client.test.js +338 -0
  162. package/test/server/DNS.test.js +2050 -0
  163. package/test/server/DNS.test.js.bak +2084 -0
  164. package/test/server/Log.test.js +73 -0
  165. package/test/server/Mail.account.test_.js +460 -0
  166. package/test/server/Mail.init.test_.js +411 -0
  167. package/test/server/Mail.test_.js +1340 -0
  168. package/test/server/SSL.test_.js +1491 -0
  169. package/test/server/Server.test.js +765 -0
  170. package/test/server/Service.test_.js +1127 -0
  171. package/test/server/Subdomain.test.js +440 -0
  172. package/test/server/Web/Firewall.test.js +175 -0
  173. package/test/server/Web.test_.js +1562 -0
  174. package/test/server/__mocks__/acme-client.js +17 -0
  175. package/test/server/__mocks__/bcrypt.js +50 -0
  176. package/test/server/__mocks__/child_process.js +389 -0
  177. package/test/server/__mocks__/crypto.js +432 -0
  178. package/test/server/__mocks__/fs.js +450 -0
  179. package/test/server/__mocks__/globalCandy.js +227 -0
  180. package/test/server/__mocks__/http-proxy.js +105 -0
  181. package/test/server/__mocks__/http.js +575 -0
  182. package/test/server/__mocks__/https.js +272 -0
  183. package/test/server/__mocks__/index.js +249 -0
  184. package/test/server/__mocks__/mail/server.js +100 -0
  185. package/test/server/__mocks__/mail/smtp.js +31 -0
  186. package/test/server/__mocks__/mailparser.js +81 -0
  187. package/test/server/__mocks__/net.js +369 -0
  188. package/test/server/__mocks__/node-forge.js +328 -0
  189. package/test/server/__mocks__/os.js +320 -0
  190. package/test/server/__mocks__/path.js +291 -0
  191. package/test/server/__mocks__/selfsigned.js +8 -0
  192. package/test/server/__mocks__/server/src/mail/server.js +100 -0
  193. package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
  194. package/test/server/__mocks__/smtp-server.js +106 -0
  195. package/test/server/__mocks__/sqlite3.js +394 -0
  196. package/test/server/__mocks__/testFactories.js +299 -0
  197. package/test/server/__mocks__/testHelpers.js +363 -0
  198. package/test/server/__mocks__/tls.js +229 -0
  199. package/watchdog/index.js +3 -0
  200. package/watchdog/src/Watchdog.js +156 -0
  201. package/web/config.json +5 -0
  202. package/web/controller/page/about.js +27 -0
  203. package/web/controller/page/index.js +34 -0
  204. package/web/package.json +18 -0
  205. package/web/public/assets/css/style.css +1835 -0
  206. package/web/public/assets/js/app.js +96 -0
  207. package/web/route/www.js +19 -0
  208. package/web/skeleton/main.html +22 -0
  209. package/web/view/content/about.html +65 -0
  210. package/web/view/content/home.html +205 -0
  211. package/web/view/footer/main.html +11 -0
  212. package/web/view/head/main.html +5 -0
  213. package/web/view/header/main.html +14 -0
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ <p align="center">
2
+ <img src="https://candypack.dev/assets/img/github/header.png?v=1" alt="CandyPack Header">
3
+ </p>
4
+
5
+ # 🍭 CandyPack
6
+
7
+ **CandyPack** is a lightweight yet powerful server + framework toolkit for building and deploying modern web apps with ease — with built-in automation and a developer-first philosophy.
8
+
9
+ ## ✨ Key Features
10
+
11
+ ### Core Server Features
12
+
13
+ * ⚡ **Blazing Fast & Ultra Light:** Optimized for performance, CandyPack is significantly lighter and faster than traditional server solutions, ensuring maximum performance with minimal resource usage.
14
+ * 🚀 **Zero-Config Hosting:** Leave the complex server configurations to CandyPack and focus solely on your code. Get your web applications up and running in minutes.
15
+ * 🌐 **One Server, Many Domains:** Easily host and manage multiple websites on a single CandyPack instance, each with its own domain and resources.
16
+ * 🔒 **SSL in Seconds:** Secure all your websites in seconds with free, auto-renewing SSL certificates.
17
+ * 📬 **Native Mail Server:** A full-featured, built-in mail server (IMAP/SMTP) that allows you to create and manage email accounts for your domains without needing an external service.
18
+ * ⚙️ **Process & CLI Monitor:** Keep your applications running smoothly with the integrated process manager and monitor your server from anywhere with the powerful command-line tool.
19
+
20
+ ### Integrated Candy Framework
21
+
22
+ * 🔗 **Custom URLs & Infinite Pages:** Easily create clean, custom URLs and an unlimited number of pages thanks to the powerful routing and skeleton system.
23
+ * ✨ **No-Code AJAX:** Automatically enable AJAX for form submissions and page transitions without writing any custom JavaScript, providing your users with a seamless single-page application (SPA) experience.
24
+ * 🛡️ **Safe Requests:** Automatically secure all your endpoints against common vulnerabilities like CSRF with built-in token verification for POST and GET requests.
25
+ * 🔐 **Auth Made Easy:** Implement user authentication in minutes with built-in session management, password hashing, and ready-to-use login/register forms.
26
+ * 🌍 **Global Ready:** Reach a worldwide audience with built-in, automatic multi-language support. The Candy Framework simplifies internationalization (i18n).
27
+ * ⏰ **Built-in Cron Jobs:** Schedule and automate recurring tasks with the integrated cron system, perfect for background jobs, data cleanup, and scheduled operations.
28
+
29
+ ## 🚀 Quick Start
30
+
31
+ > 🔥 **Install with a single command. Works on Linux, macOS, and Windows.**
32
+
33
+ #### Linux & macOS
34
+
35
+ ```bash
36
+ curl -sL https://candypack.dev/install | sudo bash
37
+ ```
38
+
39
+ #### Windows (PowerShell)
40
+
41
+ ```powershell
42
+ irm https://candypack.dev/install | iex
43
+ ```
44
+
45
+ This command:
46
+
47
+ - Installs Node.js (v18+) if missing
48
+ - Installs CandyPack globally via npm
49
+ - Prepares your system for development or deployment
50
+
51
+ ## 📚 Documentation
52
+
53
+ For more detailed information and API reference, please check out our [official documentation website](https://docs.candypack.dev).
54
+
55
+ ## 📄 License
56
+
57
+ This project is licensed under the AGPL-3.0 License. See the [LICENSE](LICENSE) file for details.
package/SECURITY.md ADDED
@@ -0,0 +1,26 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ We are committed to ensuring the security of CandyPack. Below is a table of our currently supported versions and their security patch status.
6
+
7
+ | Version | Supported |
8
+ | ------- | ------------------ |
9
+ | 0.9.x | :white_check_mark: |
10
+
11
+ ## Reporting a Vulnerability
12
+
13
+ We take all security vulnerabilities seriously. Thank you for improving the security of our project. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
14
+
15
+ To report a security vulnerability, please send an email to **support@candypack.dev**. **Do not create a public GitHub issue.**
16
+
17
+ Please include the following details with your report:
18
+
19
+ - A clear description of the vulnerability.
20
+ - The version of CandyPack affected.
21
+ - Steps to reproduce the vulnerability.
22
+ - Any proof-of-concept code or screenshots.
23
+
24
+ Once the vulnerability is confirmed, we will work on a patch and release it as quickly as possible.
25
+
26
+ We look forward to working with you to make CandyPack safer for everyone.
package/bin/candy ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ let args = process.argv
4
+ if(process.argv[0].includes('node')) args = process.argv.slice(1)
5
+ if (args[1] === 'framework') {
6
+ require('../framework/index.js')
7
+ return
8
+ }
9
+
10
+ require('../cli/index.js')
package/bin/candypack ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ let args = process.argv
4
+ if(process.argv[0].includes('node')) args = process.argv.slice(1)
5
+ if (args[1] === 'framework') {
6
+ require('../framework/index.js')
7
+ return
8
+ }
9
+
10
+ require(args.includes('watchdog') ? '../watchdog/index.js' : '../cli/index.js')
package/cli/index.js ADDED
@@ -0,0 +1,3 @@
1
+ require('../core/Candy.js')
2
+
3
+ Candy.cli('Cli')
package/cli/src/Cli.js ADDED
@@ -0,0 +1,348 @@
1
+ require('../../core/Candy.js')
2
+
3
+ const childProcess = require('child_process')
4
+ const readline = require('readline')
5
+
6
+ class Cli {
7
+ #backgrounds = {red: 41, green: 42, yellow: 43, blue: 44, magenta: 45, white: 47, gray: 100}
8
+ colors = {red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, white: 37, gray: 90}
9
+ rl
10
+ boot() {
11
+ if (!this.booting) this.booting = true
12
+ else return
13
+ return new Promise(resolve => {
14
+ console.log(__('Starting CandyPack Server...'))
15
+ const child = childProcess.spawn('node', [__dirname + '/../../watchdog/index.js'], {
16
+ detached: true,
17
+ stdio: 'ignore'
18
+ })
19
+ child.unref()
20
+ setTimeout(() => {
21
+ Candy.core('Config').reload()
22
+ resolve()
23
+ }, 1000)
24
+ })
25
+ }
26
+
27
+ close() {
28
+ if (this.rl) this.rl.close()
29
+ this.rl = null
30
+ }
31
+
32
+ color(text, color, ...args) {
33
+ let output = text
34
+ if (this.colors[color]) output = '\x1b[' + this.colors[color] + 'm' + output + '\x1b[0m'
35
+ for (const arg of args) {
36
+ if (this.#backgrounds[arg]) output = '\x1b[' + this.#backgrounds[arg] + 'm' + output + '\x1b[0m'
37
+ if (arg == 'bold') output = '\x1b[1m' + output + '\x1b[0m'
38
+ }
39
+ return output
40
+ }
41
+
42
+ #format(text, raw) {
43
+ if (typeof text !== 'string') return text
44
+ let output = text.toString()
45
+ if (output.toString().length > 1) {
46
+ let begin = ''
47
+ let end = ''
48
+ while (output.substr(output.length - 1) == ' ') {
49
+ end += ' '
50
+ output = output.substr(0, output.length - 1)
51
+ }
52
+ while (output.substr(0, 1) == ' ') {
53
+ begin += ' '
54
+ output = output.substr(1)
55
+ }
56
+ if (output.substr(output.length - 1) == ':') {
57
+ end = ':'
58
+ output = output.substr(0, output.length - 1)
59
+ }
60
+ output = begin + output + end
61
+ }
62
+ if (!raw) {
63
+ if (text == 'CandyPack') output = this.color(output, 'magenta')
64
+ if (text == __('Running')) output = this.color(output, 'green')
65
+ if (text == '\u2713') output = this.color(output, 'green')
66
+ if (text == '\u2717') output = this.color(output, 'red')
67
+ }
68
+ return output
69
+ }
70
+
71
+ async #detail(command, obj) {
72
+ let result = ''
73
+ let space = 0
74
+ if (obj.title) result += '\n\x1b[90m' + (await obj.title) + '\x1b\n'
75
+ if (obj.description) {
76
+ let args = ''
77
+ if (obj.args) {
78
+ // Only show positional arguments (not prefix arguments starting with -)
79
+ let positionalArgs = obj.args.filter(arg => !arg.startsWith('-'))
80
+
81
+ if (positionalArgs.length > 0) {
82
+ args += ' <' + positionalArgs.join('> <') + '>'
83
+ }
84
+ }
85
+ let line = '\x1b[91mcandy ' + command + '\x1b[0m\x1b[90m' + args + '\x1b[0m : ' + __(obj.description)
86
+ result += line
87
+ line = line.split(':')[0]
88
+ if (line.length > space) space = line.length
89
+ }
90
+ if (obj.sub) {
91
+ let lines = []
92
+ for (const sub in obj.sub) {
93
+ let detail = await this.#detail(command + ' ' + sub, obj.sub[sub])
94
+ lines.push(detail.result)
95
+ if (detail.space > space) space = detail.space
96
+ }
97
+ result += lines.join('\n')
98
+ }
99
+
100
+ return {result: result, space: space}
101
+ }
102
+
103
+ async help(commands) {
104
+ let result = []
105
+ let space = 0
106
+ if (typeof commands == 'string') {
107
+ let obj = Candy.core('Commands')
108
+ let command = commands.shift()
109
+ if (!obj[command]) return console.log(__(`'%s' is not a valid command.`, this.color(`candy ${commands.join(' ')}`, 'yellow')))
110
+ obj = obj[command]
111
+ while (commands.length > 0 && commands.length && obj.sub[commands[0]]) {
112
+ command = commands.shift()
113
+ if (!obj.sub[command]) return console.log(__(`'%s' is not a valid command.`, this.color(`candy ${commands.join(' ')}`, 'yellow')))
114
+ obj = obj.sub[command]
115
+ }
116
+ let detail = await this.#detail(command, obj)
117
+ if (detail.space > space) space = detail.space
118
+ let lines = detail.result.split('\n')
119
+ for (let line of lines) result.push(line)
120
+ } else {
121
+ const isAuthenticated = !!(Candy.core('Config').config.hub && Candy.core('Config').config.hub.token)
122
+ for (const command in Candy.core('Commands')) {
123
+ if (commands && commands !== true && commands[0] !== command) continue
124
+ if (isAuthenticated && command === 'auth') continue
125
+ let obj = Candy.core('Commands')[command]
126
+ if (commands === true && !obj.action) continue
127
+ let detail = await this.#detail(command, obj)
128
+ if (detail.space > space) space = detail.space
129
+ let lines = detail.result.split('\n')
130
+ for (let line of lines) result.push(line)
131
+ }
132
+ result = result.map(line => {
133
+ if (line.includes(':')) {
134
+ let parts = line.split(':')
135
+ parts[0] = parts[0] + ' '.repeat(space - parts[0].length)
136
+ line = parts.join(':')
137
+ }
138
+ return line
139
+ })
140
+ }
141
+ result.push('')
142
+ for (let line of result) console.log(line)
143
+ }
144
+
145
+ icon(status, selected) {
146
+ if (status == 'errored') return this.color(' ! ', 'red', selected ? 'white' : null)
147
+ if (status == 'progress') return this.color(' - ', 'gray', selected ? 'white' : null)
148
+ if (status == 'running') return this.color(' \u25B6 ', 'green', selected ? 'white' : null)
149
+ if (status == 'stopped') return this.color(' \u23F8 ', 'yellow', selected ? 'white' : null)
150
+ if (status == 'success') return this.color(' \u2713 ', 'green', selected ? 'white' : null)
151
+ return ' '
152
+ }
153
+
154
+ async init() {
155
+ console.log('\n', this.#format('CandyPack'), '\n')
156
+ if (!(await Candy.cli('Connector').check())) await this.boot()
157
+ let args = process.argv.slice(2)
158
+ let cmds = process.argv.slice(2)
159
+ if (args.length == 0) return this.#status()
160
+ let command = args.shift()
161
+ if (!Candy.core('Commands')[command])
162
+ return console.log(__(`'%s' is not a valid command.`, this.color(`candy ${cmds.join(' ')}`, 'yellow')))
163
+ let action = Candy.core('Commands')[command]
164
+ while (args.length > 0 && !action.args) {
165
+ command = args.shift()
166
+ if (!action.sub || !action.sub[command]) return this.help(cmds)
167
+ action = action.sub[command]
168
+ }
169
+ if (action.action) return action.action(args)
170
+ else return this.help(cmds)
171
+ }
172
+
173
+ #length(text) {
174
+ return (
175
+ this.#value(text, true)
176
+ .toString()
177
+ // eslint-disable-next-line no-control-regex
178
+ .replace(/\x1b\[[0-9;]*m/g, '').length
179
+ )
180
+ }
181
+
182
+ formatDate(date) {
183
+ const YYYY = date.getFullYear()
184
+ const MM = String(date.getMonth() + 1).padStart(2, '0')
185
+ const DD = String(date.getDate()).padStart(2, '0')
186
+ const HH = String(date.getHours()).padStart(2, '0')
187
+ const mm = String(date.getMinutes()).padStart(2, '0')
188
+ const ss = String(date.getSeconds()).padStart(2, '0')
189
+ return `${YYYY}-${MM}-${DD} ${HH}:${mm}:${ss}`
190
+ }
191
+
192
+ async log(...args) {
193
+ let output = []
194
+ for (let i = 0; i < args.length; i++) {
195
+ if (typeof args[i] != 'string') output.push(args[i])
196
+ else output.push(this.#format(args[i]))
197
+ }
198
+ console.log(...output)
199
+ }
200
+
201
+ spacing(text, len, direction) {
202
+ if (direction == 'right') return ' '.repeat(len - this.#length(text)) + text
203
+ if (direction == 'center')
204
+ return ' '.repeat(Math.floor((len - this.#length(text)) / 2)) + text + ' '.repeat(Math.ceil((len - this.#length(text)) / 2))
205
+ if (this.#length(text) > len) return text.substr(0, text.length - this.#length(text) + len)
206
+ return text + ' '.repeat(len - this.#length(text))
207
+ }
208
+
209
+ async #status() {
210
+ let status = {
211
+ online: false,
212
+ services: 0,
213
+ auth: false,
214
+ uptime: 0
215
+ }
216
+ status.online = await Candy.cli('Connector').check()
217
+ var uptime = Date.now() - Candy.core('Config').config.server.started
218
+ let seconds = Math.floor(uptime / 1000)
219
+ let minutes = Math.floor(seconds / 60)
220
+ let hours = Math.floor(minutes / 60)
221
+ let days = Math.floor(hours / 24)
222
+ seconds %= 60
223
+ minutes %= 60
224
+ hours %= 24
225
+ let uptimeString = ''
226
+ if (days) uptimeString += days + 'd '
227
+ if (hours) uptimeString += hours + 'h '
228
+ if (minutes && !days) uptimeString += minutes + 'm '
229
+ if (seconds && !hours) uptimeString += seconds + 's'
230
+ status.uptime = uptimeString
231
+ status.services = Candy.core('Config').config.services ? Object.keys(Candy.core('Config').config.services).length : 0
232
+ status.websites = Candy.core('Config').config.websites ? Object.keys(Candy.core('Config').config.websites).length : 0
233
+ status.auth = !!(Candy.core('Config').config.hub && Candy.core('Config').config.hub.token)
234
+ var args = process.argv.slice(2)
235
+ if (args.length == 0) {
236
+ let length = 0
237
+ for (let i = 0; i < 2; i++) {
238
+ for (let iterator of ['Status', 'Uptime', 'Websites', 'Services', 'Auth']) {
239
+ let title = __(iterator)
240
+ if (title.length > length) length = title.length
241
+ if (i) {
242
+ let space = ''
243
+ for (let j = 0; j < length - title.length; j++) space += ' '
244
+ switch (iterator) {
245
+ case 'Status':
246
+ console.log(title + space + ' : ' + (status.online ? '\x1b[32m ' + __('Online') : '\x1b[33m ' + __('Offline')) + '\x1b[0m')
247
+ break
248
+ case 'Uptime':
249
+ if (status.online) console.log(title + space + ' : ' + '\x1b[32m ' + status.uptime + '\x1b[0m')
250
+ break
251
+ case 'Websites':
252
+ if (status.online) console.log(title + space + ' : ' + '\x1b[32m ' + status.websites + '\x1b[0m')
253
+ break
254
+ case 'Services':
255
+ if (status.online) console.log(title + space + ' : ' + '\x1b[32m ' + status.services + '\x1b[0m')
256
+ break
257
+ case 'Auth':
258
+ console.log(
259
+ title + space + ' : ' + (status.auth ? '\x1b[32m ' + __('Logged in') : '\x1b[33m ' + __('Not logged in')) + '\x1b[0m'
260
+ )
261
+ break
262
+ }
263
+ }
264
+ }
265
+ }
266
+ if (!status.auth) console.log(__('Login on %s to manage all your server operations.', '\x1b[95mhttps://candypack.dev\x1b[0m'))
267
+ console.log()
268
+ console.log(__('Commands:'))
269
+ length = 0
270
+ this.help(true)
271
+ console.log('')
272
+ }
273
+ }
274
+
275
+ table(input) {
276
+ let result = ''
277
+ let width = []
278
+ for (const row of input) {
279
+ for (const key of Object.keys(row)) {
280
+ if (input.indexOf(row) == 0) width[key] = this.#length(key)
281
+ if (this.#length(row[key]) > width[key]) width[key] = this.#length(row[key])
282
+ }
283
+ }
284
+ for (const row of input) {
285
+ let insert = ''
286
+ if (input.indexOf(row) == 0) {
287
+ result += '┌─'
288
+ for (const key of Object.keys(row)) result += '─'.repeat(width[key]) + '─┬─'
289
+ result = this.color(result.substr(0, result.length - 3) + '─┐\n', 'gray')
290
+ result += this.color('│ ', 'gray')
291
+ for (const key of Object.keys(row)) {
292
+ result += this.color(this.#value(key), 'blue') + ' '.repeat(width[key] - this.#length(key)) + this.color(' │ ', 'gray')
293
+ }
294
+ result += '\n'
295
+ }
296
+ insert += '├─'
297
+ for (const key of Object.keys(row)) insert += '─'.repeat(width[key]) + '─┼─'
298
+ insert = insert.substr(0, insert.length - 3) + '─┤\n'
299
+ insert += '│ '
300
+ result += this.color(insert, 'gray')
301
+ for (const key of Object.keys(row)) {
302
+ result += this.#value(row[key]) + ' '.repeat(width[key] - this.#length(row[key])) + this.color(' │ ', 'gray')
303
+ }
304
+ result += '\n'
305
+ }
306
+ let insert = '└─'
307
+ for (const key of Object.keys(input[0])) insert += '─'.repeat(width[key]) + '─┴─'
308
+ insert = insert.substr(0, insert.length - 3) + '─┘'
309
+ result += this.color(insert, 'gray')
310
+ console.log(result)
311
+ }
312
+
313
+ #value(text, raw) {
314
+ if (!text) return ''
315
+ let result = ''
316
+ if (typeof text == 'object') result = this.#format(text.content)
317
+ else result = this.#format(text, raw)
318
+ return result
319
+ }
320
+
321
+ parseArg(args, prefixes) {
322
+ if (!args || !prefixes) return null
323
+
324
+ for (let i = 0; i < args.length; i++) {
325
+ if (prefixes.includes(args[i]) && i + 1 < args.length) {
326
+ return args[i + 1]
327
+ }
328
+ }
329
+ return null
330
+ }
331
+
332
+ question(question) {
333
+ return new Promise(resolve => {
334
+ if (!this.rl) {
335
+ this.rl = readline.createInterface({
336
+ input: process.stdin,
337
+ output: process.stdout
338
+ })
339
+ }
340
+ this.rl.question(question, answer => {
341
+ this.close()
342
+ return resolve(answer.trim())
343
+ })
344
+ })
345
+ }
346
+ }
347
+
348
+ module.exports = new Cli()
@@ -0,0 +1,93 @@
1
+ const findProcess = require('find-process').default
2
+ const net = require('net')
3
+
4
+ class Connector {
5
+ constructor() {
6
+ this.socket = null
7
+ this.connected = false
8
+ this.connecting = false
9
+ this.manualClose = false
10
+ }
11
+
12
+ #connect() {
13
+ if (this.connected || this.connecting) return
14
+ this.connecting = true
15
+ this.socket = net.createConnection({port: 1453, host: '127.0.0.1'}, () => {
16
+ this.connected = true
17
+ this.connecting = false
18
+ })
19
+
20
+ this.socket.on('data', raw => {
21
+ raw = raw.toString()
22
+ if (raw.includes('\r\n')) {
23
+ raw = raw.split('\r\n')
24
+ } else {
25
+ raw = [raw]
26
+ }
27
+ for (let payload of raw) {
28
+ try {
29
+ payload = JSON.parse(payload)
30
+ } catch {
31
+ continue
32
+ }
33
+ if (payload.message) {
34
+ if (payload.status) {
35
+ if (this.lastProcess == payload.process) {
36
+ process.stdout.clearLine(0)
37
+ process.stdout.cursorTo(0)
38
+ } else {
39
+ this.lastProcess = payload.process
40
+ process.stdout.write('\n')
41
+ }
42
+ process.stdout.write(Candy.cli('Cli').icon(payload.status) + payload.message + '\r')
43
+ } else {
44
+ if (this.lastProcess) process.stdout.write('\n')
45
+ if (payload.result) {
46
+ console.log(payload.message)
47
+ } else {
48
+ console.error(payload.message)
49
+ }
50
+ if (!this.manualClose) this.socket.end()
51
+ this.connected = false
52
+ this.connecting = false
53
+ this.lastProcess = null
54
+ }
55
+ }
56
+ }
57
+ })
58
+
59
+ this.socket.on('error', err => {
60
+ console.error('Socket error:', err.message)
61
+ })
62
+ }
63
+
64
+ call(command) {
65
+ if (!command) return
66
+ this.manualClose = false
67
+ this.#connect()
68
+ this.socket.write(
69
+ JSON.stringify({
70
+ auth: Candy.core('Config').config.api.auth,
71
+ action: command.action,
72
+ data: command.data
73
+ })
74
+ )
75
+ }
76
+
77
+ check() {
78
+ return new Promise(resolve => {
79
+ if (!Candy.core('Config').config.server.watchdog) return resolve(false)
80
+ findProcess('pid', Candy.core('Config').config.server.watchdog)
81
+ .then(list => {
82
+ if (list.length > 0 && list[0].name == 'node') return resolve(true)
83
+ return resolve(false)
84
+ })
85
+ .catch(err => {
86
+ console.error('Error checking process:', err)
87
+ return resolve(false)
88
+ })
89
+ })
90
+ }
91
+ }
92
+
93
+ module.exports = new Connector()