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.
- package/.editorconfig +21 -0
- package/.github/workflows/auto-pr-description.yml +49 -0
- package/.github/workflows/release.yml +32 -0
- package/.github/workflows/test-coverage.yml +58 -0
- package/.husky/pre-commit +2 -0
- package/.kiro/steering/code-style.md +56 -0
- package/.kiro/steering/product.md +20 -0
- package/.kiro/steering/structure.md +77 -0
- package/.kiro/steering/tech.md +87 -0
- package/.prettierrc +10 -0
- package/.releaserc.js +134 -0
- package/AGENTS.md +84 -0
- package/CHANGELOG.md +181 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +661 -0
- package/README.md +57 -0
- package/SECURITY.md +26 -0
- package/bin/candy +10 -0
- package/bin/candypack +10 -0
- package/cli/index.js +3 -0
- package/cli/src/Cli.js +348 -0
- package/cli/src/Connector.js +93 -0
- package/cli/src/Monitor.js +416 -0
- package/core/Candy.js +87 -0
- package/core/Commands.js +239 -0
- package/core/Config.js +1094 -0
- package/core/Lang.js +52 -0
- package/core/Log.js +43 -0
- package/core/Process.js +26 -0
- package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
- package/docs/backend/01-overview/03-development-server.md +79 -0
- package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
- package/docs/backend/03-config/00-configuration-overview.md +214 -0
- package/docs/backend/03-config/01-database-connection.md +60 -0
- package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
- package/docs/backend/03-config/03-request-timeout.md +11 -0
- package/docs/backend/03-config/04-environment-variables.md +227 -0
- package/docs/backend/03-config/05-early-hints.md +352 -0
- package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
- package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
- package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
- package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
- package/docs/backend/04-routing/05-advanced-routing.md +14 -0
- package/docs/backend/04-routing/06-error-pages.md +101 -0
- package/docs/backend/04-routing/07-cron-jobs.md +149 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
- package/docs/backend/05-controllers/03-controller-classes.md +93 -0
- package/docs/backend/05-forms/01-custom-forms.md +395 -0
- package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
- package/docs/backend/07-views/01-the-view-directory.md +73 -0
- package/docs/backend/07-views/02-rendering-a-view.md +179 -0
- package/docs/backend/07-views/03-template-syntax.md +181 -0
- package/docs/backend/07-views/03-variables.md +328 -0
- package/docs/backend/07-views/04-request-data.md +231 -0
- package/docs/backend/07-views/05-conditionals.md +290 -0
- package/docs/backend/07-views/06-loops.md +353 -0
- package/docs/backend/07-views/07-translations.md +358 -0
- package/docs/backend/07-views/08-backend-javascript.md +398 -0
- package/docs/backend/07-views/09-comments.md +297 -0
- package/docs/backend/08-database/01-database-connection.md +99 -0
- package/docs/backend/08-database/02-using-mysql.md +322 -0
- package/docs/backend/09-validation/01-the-validator-service.md +424 -0
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
- package/docs/backend/10-authentication/03-register.md +134 -0
- package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
- package/docs/backend/10-authentication/05-session-management.md +159 -0
- package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
- package/docs/backend/11-mail/01-the-mail-service.md +42 -0
- package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
- package/docs/backend/13-utilities/01-candy-var.md +504 -0
- package/docs/frontend/01-overview/01-introduction.md +146 -0
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
- package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
- package/docs/frontend/03-forms/01-form-handling.md +420 -0
- package/docs/frontend/04-api-requests/01-get-post.md +443 -0
- package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
- package/docs/index.json +452 -0
- package/docs/server/01-installation/01-quick-install.md +19 -0
- package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
- package/docs/server/02-get-started/01-core-concepts.md +7 -0
- package/docs/server/02-get-started/02-basic-commands.md +57 -0
- package/docs/server/02-get-started/03-cli-reference.md +276 -0
- package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
- package/docs/server/03-service/01-start-a-new-service.md +57 -0
- package/docs/server/03-service/02-delete-a-service.md +48 -0
- package/docs/server/04-web/01-create-a-website.md +36 -0
- package/docs/server/04-web/02-list-websites.md +9 -0
- package/docs/server/04-web/03-delete-a-website.md +29 -0
- package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
- package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
- package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
- package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
- package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
- package/docs/server/07-mail/04-change-account-password.md +23 -0
- package/eslint.config.mjs +120 -0
- package/framework/index.js +4 -0
- package/framework/src/Auth.js +309 -0
- package/framework/src/Candy.js +81 -0
- package/framework/src/Config.js +79 -0
- package/framework/src/Env.js +60 -0
- package/framework/src/Lang.js +57 -0
- package/framework/src/Mail.js +83 -0
- package/framework/src/Mysql.js +575 -0
- package/framework/src/Request.js +301 -0
- package/framework/src/Route/Cron.js +128 -0
- package/framework/src/Route/Internal.js +439 -0
- package/framework/src/Route.js +455 -0
- package/framework/src/Server.js +15 -0
- package/framework/src/Stream.js +163 -0
- package/framework/src/Token.js +37 -0
- package/framework/src/Validator.js +271 -0
- package/framework/src/Var.js +211 -0
- package/framework/src/View/EarlyHints.js +190 -0
- package/framework/src/View/Form.js +600 -0
- package/framework/src/View.js +513 -0
- package/framework/web/candy.js +838 -0
- package/jest.config.js +22 -0
- package/locale/de-DE.json +80 -0
- package/locale/en-US.json +79 -0
- package/locale/es-ES.json +80 -0
- package/locale/fr-FR.json +80 -0
- package/locale/pt-BR.json +80 -0
- package/locale/ru-RU.json +80 -0
- package/locale/tr-TR.json +85 -0
- package/locale/zh-CN.json +80 -0
- package/package.json +86 -0
- package/server/index.js +5 -0
- package/server/src/Api.js +88 -0
- package/server/src/DNS.js +940 -0
- package/server/src/Hub.js +535 -0
- package/server/src/Mail.js +571 -0
- package/server/src/SSL.js +180 -0
- package/server/src/Server.js +27 -0
- package/server/src/Service.js +248 -0
- package/server/src/Subdomain.js +64 -0
- package/server/src/Web/Firewall.js +170 -0
- package/server/src/Web/Proxy.js +134 -0
- package/server/src/Web.js +451 -0
- package/server/src/mail/imap.js +1091 -0
- package/server/src/mail/server.js +32 -0
- package/server/src/mail/smtp.js +786 -0
- package/test/cli/Cli.test.js +36 -0
- package/test/core/Candy.test.js +234 -0
- package/test/core/Commands.test.js +538 -0
- package/test/core/Config.test.js +1435 -0
- package/test/core/Lang.test.js +250 -0
- package/test/core/Process.test.js +156 -0
- package/test/framework/Route.test.js +239 -0
- package/test/framework/View/EarlyHints.test.js +282 -0
- package/test/scripts/check-coverage.js +132 -0
- package/test/server/Api.test.js +647 -0
- package/test/server/Client.test.js +338 -0
- package/test/server/DNS.test.js +2050 -0
- package/test/server/DNS.test.js.bak +2084 -0
- package/test/server/Log.test.js +73 -0
- package/test/server/Mail.account.test_.js +460 -0
- package/test/server/Mail.init.test_.js +411 -0
- package/test/server/Mail.test_.js +1340 -0
- package/test/server/SSL.test_.js +1491 -0
- package/test/server/Server.test.js +765 -0
- package/test/server/Service.test_.js +1127 -0
- package/test/server/Subdomain.test.js +440 -0
- package/test/server/Web/Firewall.test.js +175 -0
- package/test/server/Web.test_.js +1562 -0
- package/test/server/__mocks__/acme-client.js +17 -0
- package/test/server/__mocks__/bcrypt.js +50 -0
- package/test/server/__mocks__/child_process.js +389 -0
- package/test/server/__mocks__/crypto.js +432 -0
- package/test/server/__mocks__/fs.js +450 -0
- package/test/server/__mocks__/globalCandy.js +227 -0
- package/test/server/__mocks__/http-proxy.js +105 -0
- package/test/server/__mocks__/http.js +575 -0
- package/test/server/__mocks__/https.js +272 -0
- package/test/server/__mocks__/index.js +249 -0
- package/test/server/__mocks__/mail/server.js +100 -0
- package/test/server/__mocks__/mail/smtp.js +31 -0
- package/test/server/__mocks__/mailparser.js +81 -0
- package/test/server/__mocks__/net.js +369 -0
- package/test/server/__mocks__/node-forge.js +328 -0
- package/test/server/__mocks__/os.js +320 -0
- package/test/server/__mocks__/path.js +291 -0
- package/test/server/__mocks__/selfsigned.js +8 -0
- package/test/server/__mocks__/server/src/mail/server.js +100 -0
- package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
- package/test/server/__mocks__/smtp-server.js +106 -0
- package/test/server/__mocks__/sqlite3.js +394 -0
- package/test/server/__mocks__/testFactories.js +299 -0
- package/test/server/__mocks__/testHelpers.js +363 -0
- package/test/server/__mocks__/tls.js +229 -0
- package/watchdog/index.js +3 -0
- package/watchdog/src/Watchdog.js +156 -0
- package/web/config.json +5 -0
- package/web/controller/page/about.js +27 -0
- package/web/controller/page/index.js +34 -0
- package/web/package.json +18 -0
- package/web/public/assets/css/style.css +1835 -0
- package/web/public/assets/js/app.js +96 -0
- package/web/route/www.js +19 -0
- package/web/skeleton/main.html +22 -0
- package/web/view/content/about.html +65 -0
- package/web/view/content/home.html +205 -0
- package/web/view/footer/main.html +11 -0
- package/web/view/head/main.html +5 -0
- 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
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
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()
|