suthep 0.1.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 (50) hide show
  1. package/.editorconfig +17 -0
  2. package/.prettierignore +6 -0
  3. package/.prettierrc +7 -0
  4. package/.vscode/settings.json +19 -0
  5. package/LICENSE +21 -0
  6. package/README.md +214 -0
  7. package/dist/commands/deploy.js +104 -0
  8. package/dist/commands/deploy.js.map +1 -0
  9. package/dist/commands/init.js +188 -0
  10. package/dist/commands/init.js.map +1 -0
  11. package/dist/commands/setup.js +90 -0
  12. package/dist/commands/setup.js.map +1 -0
  13. package/dist/index.js +12 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/utils/certbot.js +27 -0
  16. package/dist/utils/certbot.js.map +1 -0
  17. package/dist/utils/config-loader.js +65 -0
  18. package/dist/utils/config-loader.js.map +1 -0
  19. package/dist/utils/deployment.js +52 -0
  20. package/dist/utils/deployment.js.map +1 -0
  21. package/dist/utils/docker.js +57 -0
  22. package/dist/utils/docker.js.map +1 -0
  23. package/dist/utils/nginx.js +154 -0
  24. package/dist/utils/nginx.js.map +1 -0
  25. package/docs/README.md +62 -0
  26. package/docs/api-reference.md +545 -0
  27. package/docs/architecture.md +367 -0
  28. package/docs/commands.md +273 -0
  29. package/docs/configuration.md +347 -0
  30. package/docs/examples.md +537 -0
  31. package/docs/getting-started.md +197 -0
  32. package/docs/troubleshooting.md +441 -0
  33. package/example/README.md +81 -0
  34. package/example/docker-compose.yml +72 -0
  35. package/example/suthep.yml +31 -0
  36. package/package.json +45 -0
  37. package/src/commands/deploy.ts +133 -0
  38. package/src/commands/init.ts +214 -0
  39. package/src/commands/setup.ts +112 -0
  40. package/src/index.ts +34 -0
  41. package/src/types/config.ts +51 -0
  42. package/src/utils/certbot.ts +82 -0
  43. package/src/utils/config-loader.ts +81 -0
  44. package/src/utils/deployment.ts +132 -0
  45. package/src/utils/docker.ts +151 -0
  46. package/src/utils/nginx.ts +143 -0
  47. package/suthep.example.yml +69 -0
  48. package/todo.md +6 -0
  49. package/tsconfig.json +26 -0
  50. package/vite.config.ts +46 -0
package/.editorconfig ADDED
@@ -0,0 +1,17 @@
1
+ # Editor configuration, see http://editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ charset = utf-8
6
+ indent_style = space
7
+ indent_size = 2
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.md]
12
+ max_line_length = off
13
+ trim_trailing_whitespace = false
14
+
15
+ [{Makefile,**.mk}]
16
+ indent_style = tab
17
+ indent_size = 2
@@ -0,0 +1,6 @@
1
+ **/dist
2
+ **/coverage
3
+ **/.turbo
4
+ **/node_modules
5
+
6
+ package.json
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "tabWidth": 2,
3
+ "trailingComma": "es5",
4
+ "printWidth": 100,
5
+ "singleQuote": true,
6
+ "semi": false
7
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "eslint.workingDirectories": [
3
+ {
4
+ "mode": "auto"
5
+ }
6
+ ],
7
+ "[typescript]": {
8
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
9
+ },
10
+ "editor.codeActionsOnSave": {
11
+ "source.organizeImports": "explicit"
12
+ },
13
+ "editor.formatOnSave": true,
14
+ "editor.insertSpaces": false,
15
+ "files.eol": "\n",
16
+ "files.insertFinalNewline": true,
17
+ "files.trimTrailingWhitespace": true
18
+ }
19
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Montol Saklor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # Suthep
2
+
3
+ A powerful CLI tool for deploying projects with automatic Nginx reverse proxy setup, HTTPS with Certbot, and zero-downtime deployments.
4
+
5
+ ## Features
6
+
7
+ - āœ… Automatic Nginx reverse proxy setup
8
+ - āœ… Automatic HTTPS with Certbot
9
+ - āœ… Zero-downtime deployment
10
+ - āœ… Docker container support
11
+ - āœ… Multiple domain/subdomain support
12
+ - āœ… Health check integration
13
+ - āœ… YAML-based configuration
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install
19
+ npm link # Makes 'deploy' command available globally
20
+ ```
21
+
22
+ The tool uses `tsx` to run TypeScript directly, so no build step is required for development. For production, you can still build:
23
+
24
+ ```bash
25
+ npm run build # Optional: compile to JavaScript
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ 1. **Initialize a configuration file:**
31
+ ```bash
32
+ deploy init
33
+ ```
34
+ This will create a `deploy.yml` file with interactive prompts.
35
+
36
+ 2. **Or use the example configuration:**
37
+ ```bash
38
+ cp example.yml deploy.yml
39
+ # Edit deploy.yml with your settings
40
+ ```
41
+
42
+ 3. **Setup prerequisites (Nginx and Certbot):**
43
+ ```bash
44
+ deploy setup
45
+ ```
46
+
47
+ 4. **Deploy your project:**
48
+ ```bash
49
+ deploy deploy
50
+ ```
51
+
52
+ ## Configuration
53
+
54
+ The configuration file (`deploy.yml`) supports the following structure:
55
+
56
+ ```yaml
57
+ project:
58
+ name: my-app
59
+ version: 1.0.0
60
+
61
+ services:
62
+ - name: api
63
+ port: 3000
64
+ domains:
65
+ - api.example.com
66
+ - www.api.example.com
67
+ healthCheck:
68
+ path: /health
69
+ interval: 30
70
+ environment:
71
+ NODE_ENV: production
72
+
73
+ - name: webapp
74
+ port: 8080
75
+ docker:
76
+ image: nginx:latest
77
+ container: webapp-container
78
+ port: 80
79
+ domains:
80
+ - example.com
81
+ - www.example.com
82
+
83
+ nginx:
84
+ configPath: /etc/nginx/sites-available
85
+ reloadCommand: sudo nginx -t && sudo systemctl reload nginx
86
+
87
+ certbot:
88
+ email: admin@example.com
89
+ staging: false
90
+
91
+ deployment:
92
+ strategy: rolling
93
+ healthCheckTimeout: 30000
94
+ ```
95
+
96
+ ### Service Configuration
97
+
98
+ Each service can have:
99
+
100
+ - **name**: Unique service name
101
+ - **port**: Port the service runs on
102
+ - **domains**: Array of domain names or subdomains
103
+ - **docker** (optional):
104
+ - **image**: Docker image to use
105
+ - **container**: Container name
106
+ - **port**: Container's internal port
107
+ - **healthCheck** (optional):
108
+ - **path**: Health check endpoint path
109
+ - **interval**: Check interval in seconds
110
+ - **environment** (optional): Environment variables
111
+
112
+ ## Commands
113
+
114
+ ### `deploy init`
115
+
116
+ Initialize a new deployment configuration file.
117
+
118
+ ```bash
119
+ deploy init [-f deploy.yml]
120
+ ```
121
+
122
+ ### `deploy setup`
123
+
124
+ Setup Nginx and Certbot on the system.
125
+
126
+ ```bash
127
+ deploy setup [--nginx-only] [--certbot-only]
128
+ ```
129
+
130
+ ### `deploy deploy`
131
+
132
+ Deploy a project using the configuration file.
133
+
134
+ ```bash
135
+ deploy deploy [-f deploy.yml] [--no-https] [--no-nginx]
136
+ ```
137
+
138
+ Options:
139
+ - `-f, --file`: Configuration file path (default: `deploy.yml`)
140
+ - `--no-https`: Skip HTTPS setup
141
+ - `--no-nginx`: Skip Nginx configuration
142
+
143
+ ## Examples
144
+
145
+ ### Simple Node.js Service
146
+
147
+ ```yaml
148
+ services:
149
+ - name: api
150
+ port: 3000
151
+ domains:
152
+ - api.example.com
153
+ healthCheck:
154
+ path: /health
155
+ ```
156
+
157
+ ### Docker Container Service
158
+
159
+ ```yaml
160
+ services:
161
+ - name: webapp
162
+ port: 8080
163
+ docker:
164
+ image: myapp/webapp:latest
165
+ container: webapp-container
166
+ port: 80
167
+ domains:
168
+ - example.com
169
+ ```
170
+
171
+ ### Multiple Domains
172
+
173
+ ```yaml
174
+ services:
175
+ - name: dashboard
176
+ port: 5000
177
+ domains:
178
+ - dashboard.example.com
179
+ - admin.example.com
180
+ - app.example.com
181
+ ```
182
+
183
+ ### Connect to Existing Docker Container
184
+
185
+ ```yaml
186
+ services:
187
+ - name: database-proxy
188
+ port: 5432
189
+ docker:
190
+ container: postgres-container
191
+ port: 5432
192
+ domains:
193
+ - db.example.com
194
+ ```
195
+
196
+ ## Requirements
197
+
198
+ - Node.js 16+
199
+ - Nginx (installed via `deploy setup`)
200
+ - Certbot (installed via `deploy setup`)
201
+ - Docker (optional, for Docker-based services)
202
+ - sudo access (for Nginx and Certbot operations)
203
+
204
+ ## Cost Optimization
205
+
206
+ This tool helps save costs on VMs by:
207
+ - Efficiently managing multiple services on a single server
208
+ - Automatic reverse proxy setup reduces manual configuration
209
+ - Zero-downtime deployments reduce service interruptions
210
+ - Health checks ensure service reliability
211
+
212
+ ## License
213
+
214
+ [MIT](LICENSE)
@@ -0,0 +1,104 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import { requestCertificate } from "../utils/certbot.js";
5
+ import { loadConfig } from "../utils/config-loader.js";
6
+ import { deployService, performHealthCheck } from "../utils/deployment.js";
7
+ import { startDockerContainer } from "../utils/docker.js";
8
+ import { generateNginxConfig, enableSite, reloadNginx } from "../utils/nginx.js";
9
+ async function deployCommand(options) {
10
+ console.log(chalk.blue.bold("\nšŸš€ Deploying Services\n"));
11
+ try {
12
+ if (!await fs.pathExists(options.file)) {
13
+ throw new Error(`Configuration file not found: ${options.file}`);
14
+ }
15
+ console.log(chalk.cyan(`šŸ“„ Loading configuration from ${options.file}...`));
16
+ const config = await loadConfig(options.file);
17
+ console.log(chalk.green(`āœ… Configuration loaded for project: ${config.project.name}`));
18
+ console.log(chalk.dim(` Services: ${config.services.map((s) => s.name).join(", ")}
19
+ `));
20
+ for (const service of config.services) {
21
+ console.log(chalk.cyan(`
22
+ šŸ“¦ Deploying service: ${service.name}`));
23
+ try {
24
+ if (service.docker) {
25
+ console.log(chalk.dim(" 🐳 Managing Docker container..."));
26
+ await startDockerContainer(service);
27
+ }
28
+ await deployService(service, config.deployment);
29
+ if (options.nginx) {
30
+ console.log(chalk.dim(" āš™ļø Configuring Nginx reverse proxy..."));
31
+ const nginxConfigContent = generateNginxConfig(service, false);
32
+ const nginxConfigPath = path.join(config.nginx.configPath, `${service.name}.conf`);
33
+ await fs.writeFile(nginxConfigPath, nginxConfigContent);
34
+ await enableSite(service.name, config.nginx.configPath);
35
+ console.log(chalk.green(` āœ… Nginx configured for ${service.domains.join(", ")}`));
36
+ }
37
+ if (options.https && service.domains.length > 0) {
38
+ console.log(chalk.dim(" šŸ” Setting up HTTPS certificates..."));
39
+ for (const domain of service.domains) {
40
+ try {
41
+ await requestCertificate(domain, config.certbot.email, config.certbot.staging);
42
+ console.log(chalk.green(` āœ… SSL certificate obtained for ${domain}`));
43
+ } catch (error) {
44
+ console.log(
45
+ chalk.yellow(
46
+ ` āš ļø Failed to obtain SSL for ${domain}: ${error instanceof Error ? error.message : error}`
47
+ )
48
+ );
49
+ }
50
+ }
51
+ if (options.nginx) {
52
+ const nginxConfigContent = generateNginxConfig(service, true);
53
+ const nginxConfigPath = path.join(config.nginx.configPath, `${service.name}.conf`);
54
+ await fs.writeFile(nginxConfigPath, nginxConfigContent);
55
+ }
56
+ }
57
+ if (options.nginx) {
58
+ console.log(chalk.dim(" šŸ”„ Reloading Nginx..."));
59
+ await reloadNginx(config.nginx.reloadCommand);
60
+ }
61
+ if (service.healthCheck) {
62
+ console.log(chalk.dim(` šŸ„ Performing health check...`));
63
+ const isHealthy = await performHealthCheck(
64
+ `http://localhost:${service.port}${service.healthCheck.path}`,
65
+ config.deployment.healthCheckTimeout
66
+ );
67
+ if (isHealthy) {
68
+ console.log(chalk.green(` āœ… Service ${service.name} is healthy`));
69
+ } else {
70
+ throw new Error(`Health check failed for service ${service.name}`);
71
+ }
72
+ }
73
+ console.log(chalk.green.bold(`
74
+ ✨ Service ${service.name} deployed successfully!`));
75
+ } catch (error) {
76
+ console.error(
77
+ chalk.red(`
78
+ āŒ Failed to deploy service ${service.name}:`),
79
+ error instanceof Error ? error.message : error
80
+ );
81
+ throw error;
82
+ }
83
+ }
84
+ console.log(chalk.green.bold("\nšŸŽ‰ All services deployed successfully!\n"));
85
+ console.log(chalk.cyan("šŸ“‹ Service URLs:"));
86
+ for (const service of config.services) {
87
+ for (const domain of service.domains) {
88
+ const protocol = options.https ? "https" : "http";
89
+ console.log(chalk.dim(` ${service.name}: ${protocol}://${domain}`));
90
+ }
91
+ }
92
+ console.log();
93
+ } catch (error) {
94
+ console.error(
95
+ chalk.red("\nāŒ Deployment failed:"),
96
+ error instanceof Error ? error.message : error
97
+ );
98
+ process.exit(1);
99
+ }
100
+ }
101
+ export {
102
+ deployCommand
103
+ };
104
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.js","sources":["../../src/commands/deploy.ts"],"sourcesContent":["import chalk from 'chalk'\nimport fs from 'fs-extra'\nimport path from 'path'\nimport { requestCertificate } from '../utils/certbot'\nimport { loadConfig } from '../utils/config-loader'\nimport { deployService, performHealthCheck } from '../utils/deployment'\nimport { startDockerContainer } from '../utils/docker'\nimport { enableSite, generateNginxConfig, reloadNginx } from '../utils/nginx'\n\ninterface DeployOptions {\n file: string\n https: boolean\n nginx: boolean\n}\n\nexport async function deployCommand(options: DeployOptions): Promise<void> {\n console.log(chalk.blue.bold('\\nšŸš€ Deploying Services\\n'))\n\n try {\n // Load configuration\n if (!(await fs.pathExists(options.file))) {\n throw new Error(`Configuration file not found: ${options.file}`)\n }\n\n console.log(chalk.cyan(`šŸ“„ Loading configuration from ${options.file}...`))\n const config = await loadConfig(options.file)\n\n console.log(chalk.green(`āœ… Configuration loaded for project: ${config.project.name}`))\n console.log(chalk.dim(` Services: ${config.services.map((s) => s.name).join(', ')}\\n`))\n\n // Deploy each service\n for (const service of config.services) {\n console.log(chalk.cyan(`\\nšŸ“¦ Deploying service: ${service.name}`))\n\n try {\n // Start Docker container if configured\n if (service.docker) {\n console.log(chalk.dim(' 🐳 Managing Docker container...'))\n await startDockerContainer(service)\n }\n\n // Deploy the service\n await deployService(service, config.deployment)\n\n // Generate and configure Nginx\n if (options.nginx) {\n console.log(chalk.dim(' āš™ļø Configuring Nginx reverse proxy...'))\n const nginxConfigContent = generateNginxConfig(service, false)\n const nginxConfigPath = path.join(config.nginx.configPath, `${service.name}.conf`)\n\n await fs.writeFile(nginxConfigPath, nginxConfigContent)\n await enableSite(service.name, config.nginx.configPath)\n\n console.log(chalk.green(` āœ… Nginx configured for ${service.domains.join(', ')}`))\n }\n\n // Setup HTTPS with Certbot\n if (options.https && service.domains.length > 0) {\n console.log(chalk.dim(' šŸ” Setting up HTTPS certificates...'))\n\n for (const domain of service.domains) {\n try {\n await requestCertificate(domain, config.certbot.email, config.certbot.staging)\n console.log(chalk.green(` āœ… SSL certificate obtained for ${domain}`))\n } catch (error) {\n console.log(\n chalk.yellow(\n ` āš ļø Failed to obtain SSL for ${domain}: ${\n error instanceof Error ? error.message : error\n }`\n )\n )\n }\n }\n\n // Update Nginx config with HTTPS\n if (options.nginx) {\n const nginxConfigContent = generateNginxConfig(service, true)\n const nginxConfigPath = path.join(config.nginx.configPath, `${service.name}.conf`)\n await fs.writeFile(nginxConfigPath, nginxConfigContent)\n }\n }\n\n // Reload Nginx after all configurations\n if (options.nginx) {\n console.log(chalk.dim(' šŸ”„ Reloading Nginx...'))\n await reloadNginx(config.nginx.reloadCommand)\n }\n\n // Perform health check\n if (service.healthCheck) {\n console.log(chalk.dim(` šŸ„ Performing health check...`))\n const isHealthy = await performHealthCheck(\n `http://localhost:${service.port}${service.healthCheck.path}`,\n config.deployment.healthCheckTimeout\n )\n\n if (isHealthy) {\n console.log(chalk.green(` āœ… Service ${service.name} is healthy`))\n } else {\n throw new Error(`Health check failed for service ${service.name}`)\n }\n }\n\n console.log(chalk.green.bold(`\\n✨ Service ${service.name} deployed successfully!`))\n } catch (error) {\n console.error(\n chalk.red(`\\nāŒ Failed to deploy service ${service.name}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n console.log(chalk.green.bold('\\nšŸŽ‰ All services deployed successfully!\\n'))\n\n // Print service URLs\n console.log(chalk.cyan('šŸ“‹ Service URLs:'))\n for (const service of config.services) {\n for (const domain of service.domains) {\n const protocol = options.https ? 'https' : 'http'\n console.log(chalk.dim(` ${service.name}: ${protocol}://${domain}`))\n }\n }\n console.log()\n } catch (error) {\n console.error(\n chalk.red('\\nāŒ Deployment failed:'),\n error instanceof Error ? error.message : error\n )\n process.exit(1)\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,188 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs-extra";
3
+ import inquirer from "inquirer";
4
+ import { saveConfig } from "../utils/config-loader.js";
5
+ async function initCommand(options) {
6
+ console.log(chalk.blue.bold("\nšŸš€ Suthep Deployment Configuration\n"));
7
+ if (await fs.pathExists(options.file)) {
8
+ const { overwrite } = await inquirer.prompt([
9
+ {
10
+ type: "confirm",
11
+ name: "overwrite",
12
+ message: `File ${options.file} already exists. Overwrite?`,
13
+ default: false
14
+ }
15
+ ]);
16
+ if (!overwrite) {
17
+ console.log(chalk.yellow("Aborted."));
18
+ return;
19
+ }
20
+ }
21
+ const projectAnswers = await inquirer.prompt([
22
+ {
23
+ type: "input",
24
+ name: "projectName",
25
+ message: "Project name:",
26
+ default: "my-app"
27
+ },
28
+ {
29
+ type: "input",
30
+ name: "projectVersion",
31
+ message: "Project version:",
32
+ default: "1.0.0"
33
+ }
34
+ ]);
35
+ const services = [];
36
+ let addMoreServices = true;
37
+ while (addMoreServices) {
38
+ console.log(chalk.cyan(`
39
+ šŸ“¦ Service ${services.length + 1} Configuration`));
40
+ const serviceAnswers = await inquirer.prompt([
41
+ {
42
+ type: "input",
43
+ name: "name",
44
+ message: "Service name:",
45
+ validate: (input) => input.trim() !== "" || "Service name is required"
46
+ },
47
+ {
48
+ type: "number",
49
+ name: "port",
50
+ message: "Service port:",
51
+ default: 3e3,
52
+ validate: (input) => {
53
+ if (input === void 0) return "Port is required";
54
+ return input > 0 && input < 65536 || "Port must be between 1 and 65535";
55
+ }
56
+ },
57
+ {
58
+ type: "input",
59
+ name: "domains",
60
+ message: "Domain names (comma-separated):",
61
+ validate: (input) => input.trim() !== "" || "At least one domain is required",
62
+ filter: (input) => input.split(",").map((d) => d.trim())
63
+ },
64
+ {
65
+ type: "confirm",
66
+ name: "useDocker",
67
+ message: "Use Docker?",
68
+ default: false
69
+ }
70
+ ]);
71
+ let dockerConfig = void 0;
72
+ if (serviceAnswers.useDocker) {
73
+ const dockerAnswers = await inquirer.prompt([
74
+ {
75
+ type: "input",
76
+ name: "image",
77
+ message: "Docker image (leave empty to connect to existing container):"
78
+ },
79
+ {
80
+ type: "input",
81
+ name: "container",
82
+ message: "Container name:",
83
+ validate: (input) => input.trim() !== "" || "Container name is required"
84
+ },
85
+ {
86
+ type: "number",
87
+ name: "port",
88
+ message: "Container port:",
89
+ default: serviceAnswers.port
90
+ }
91
+ ]);
92
+ dockerConfig = {
93
+ image: dockerAnswers.image || void 0,
94
+ container: dockerAnswers.container,
95
+ port: dockerAnswers.port
96
+ };
97
+ }
98
+ const { addHealthCheck } = await inquirer.prompt([
99
+ {
100
+ type: "confirm",
101
+ name: "addHealthCheck",
102
+ message: "Add health check?",
103
+ default: true
104
+ }
105
+ ]);
106
+ let healthCheck = void 0;
107
+ if (addHealthCheck) {
108
+ const healthCheckAnswers = await inquirer.prompt([
109
+ {
110
+ type: "input",
111
+ name: "path",
112
+ message: "Health check path:",
113
+ default: "/health"
114
+ },
115
+ {
116
+ type: "number",
117
+ name: "interval",
118
+ message: "Health check interval (seconds):",
119
+ default: 30
120
+ }
121
+ ]);
122
+ healthCheck = healthCheckAnswers;
123
+ }
124
+ services.push({
125
+ name: serviceAnswers.name,
126
+ port: serviceAnswers.port,
127
+ domains: serviceAnswers.domains,
128
+ docker: dockerConfig,
129
+ healthCheck
130
+ });
131
+ const { addMore } = await inquirer.prompt([
132
+ {
133
+ type: "confirm",
134
+ name: "addMore",
135
+ message: "Add another service?",
136
+ default: false
137
+ }
138
+ ]);
139
+ addMoreServices = addMore;
140
+ }
141
+ const certbotAnswers = await inquirer.prompt([
142
+ {
143
+ type: "input",
144
+ name: "email",
145
+ message: "Email for SSL certificates:",
146
+ validate: (input) => {
147
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
148
+ return emailRegex.test(input) || "Please enter a valid email address";
149
+ }
150
+ },
151
+ {
152
+ type: "confirm",
153
+ name: "staging",
154
+ message: "Use Certbot staging environment? (for testing)",
155
+ default: false
156
+ }
157
+ ]);
158
+ const config = {
159
+ project: {
160
+ name: projectAnswers.projectName,
161
+ version: projectAnswers.projectVersion
162
+ },
163
+ services,
164
+ nginx: {
165
+ configPath: "/etc/nginx/sites-available",
166
+ reloadCommand: "sudo nginx -t && sudo systemctl reload nginx"
167
+ },
168
+ certbot: {
169
+ email: certbotAnswers.email,
170
+ staging: certbotAnswers.staging
171
+ },
172
+ deployment: {
173
+ strategy: "rolling",
174
+ healthCheckTimeout: 3e4
175
+ }
176
+ };
177
+ await saveConfig(options.file, config);
178
+ console.log(chalk.green(`
179
+ āœ… Configuration saved to ${options.file}`));
180
+ console.log(chalk.dim("\nNext steps:"));
181
+ console.log(chalk.dim(` 1. Review and edit ${options.file} if needed`));
182
+ console.log(chalk.dim(" 2. Run: suthep setup"));
183
+ console.log(chalk.dim(" 3. Run: suthep deploy\n"));
184
+ }
185
+ export {
186
+ initCommand
187
+ };
188
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sources":["../../src/commands/init.ts"],"sourcesContent":["import chalk from 'chalk'\nimport fs from 'fs-extra'\nimport inquirer from 'inquirer'\nimport type { DeployConfig } from '../types/config'\nimport { saveConfig } from '../utils/config-loader'\n\ninterface InitOptions {\n file: string\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n console.log(chalk.blue.bold('\\nšŸš€ Suthep Deployment Configuration\\n'))\n\n // Check if file already exists\n if (await fs.pathExists(options.file)) {\n const { overwrite } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'overwrite',\n message: `File ${options.file} already exists. Overwrite?`,\n default: false,\n },\n ])\n\n if (!overwrite) {\n console.log(chalk.yellow('Aborted.'))\n return\n }\n }\n\n // Gather project information\n const projectAnswers = await inquirer.prompt([\n {\n type: 'input',\n name: 'projectName',\n message: 'Project name:',\n default: 'my-app',\n },\n {\n type: 'input',\n name: 'projectVersion',\n message: 'Project version:',\n default: '1.0.0',\n },\n ])\n\n // Gather service information\n const services = []\n let addMoreServices = true\n\n while (addMoreServices) {\n console.log(chalk.cyan(`\\nšŸ“¦ Service ${services.length + 1} Configuration`))\n\n const serviceAnswers = await inquirer.prompt([\n {\n type: 'input',\n name: 'name',\n message: 'Service name:',\n validate: (input) => input.trim() !== '' || 'Service name is required',\n },\n {\n type: 'number',\n name: 'port',\n message: 'Service port:',\n default: 3000,\n validate: (input: number | undefined) => {\n if (input === undefined) return 'Port is required'\n return (input > 0 && input < 65536) || 'Port must be between 1 and 65535'\n },\n },\n {\n type: 'input',\n name: 'domains',\n message: 'Domain names (comma-separated):',\n validate: (input) => input.trim() !== '' || 'At least one domain is required',\n filter: (input: string) => input.split(',').map((d: string) => d.trim()),\n },\n {\n type: 'confirm',\n name: 'useDocker',\n message: 'Use Docker?',\n default: false,\n },\n ])\n\n // Docker configuration\n let dockerConfig = undefined\n if (serviceAnswers.useDocker) {\n const dockerAnswers = await inquirer.prompt([\n {\n type: 'input',\n name: 'image',\n message: 'Docker image (leave empty to connect to existing container):',\n },\n {\n type: 'input',\n name: 'container',\n message: 'Container name:',\n validate: (input) => input.trim() !== '' || 'Container name is required',\n },\n {\n type: 'number',\n name: 'port',\n message: 'Container port:',\n default: serviceAnswers.port,\n },\n ])\n\n dockerConfig = {\n image: dockerAnswers.image || undefined,\n container: dockerAnswers.container,\n port: dockerAnswers.port,\n }\n }\n\n // Health check configuration\n const { addHealthCheck } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'addHealthCheck',\n message: 'Add health check?',\n default: true,\n },\n ])\n\n let healthCheck = undefined\n if (addHealthCheck) {\n const healthCheckAnswers = await inquirer.prompt([\n {\n type: 'input',\n name: 'path',\n message: 'Health check path:',\n default: '/health',\n },\n {\n type: 'number',\n name: 'interval',\n message: 'Health check interval (seconds):',\n default: 30,\n },\n ])\n\n healthCheck = healthCheckAnswers\n }\n\n services.push({\n name: serviceAnswers.name,\n port: serviceAnswers.port,\n domains: serviceAnswers.domains,\n docker: dockerConfig,\n healthCheck,\n })\n\n const { addMore } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'addMore',\n message: 'Add another service?',\n default: false,\n },\n ])\n\n addMoreServices = addMore\n }\n\n // Certbot configuration\n const certbotAnswers = await inquirer.prompt([\n {\n type: 'input',\n name: 'email',\n message: 'Email for SSL certificates:',\n validate: (input) => {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(input) || 'Please enter a valid email address'\n },\n },\n {\n type: 'confirm',\n name: 'staging',\n message: 'Use Certbot staging environment? (for testing)',\n default: false,\n },\n ])\n\n // Build configuration object\n const config: DeployConfig = {\n project: {\n name: projectAnswers.projectName,\n version: projectAnswers.projectVersion,\n },\n services,\n nginx: {\n configPath: '/etc/nginx/sites-available',\n reloadCommand: 'sudo nginx -t && sudo systemctl reload nginx',\n },\n certbot: {\n email: certbotAnswers.email,\n staging: certbotAnswers.staging,\n },\n deployment: {\n strategy: 'rolling',\n healthCheckTimeout: 30000,\n },\n }\n\n // Save configuration\n await saveConfig(options.file, config)\n\n console.log(chalk.green(`\\nāœ… Configuration saved to ${options.file}`))\n console.log(chalk.dim('\\nNext steps:'))\n console.log(chalk.dim(` 1. Review and edit ${options.file} if needed`))\n console.log(chalk.dim(' 2. Run: suthep setup'))\n console.log(chalk.dim(' 3. Run: suthep deploy\\n'))\n}\n"],"names":[],"mappings":";;;;AAUA,eAAsB,YAAY,SAAqC;AACrE,UAAQ,IAAI,MAAM,KAAK,KAAK,wCAAwC,CAAC;AAGrE,MAAI,MAAM,GAAG,WAAW,QAAQ,IAAI,GAAG;AACrC,UAAM,EAAE,UAAA,IAAc,MAAM,SAAS,OAAO;AAAA,MAC1C;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS,QAAQ,QAAQ,IAAI;AAAA,QAC7B,SAAS;AAAA,MAAA;AAAA,IACX,CACD;AAED,QAAI,CAAC,WAAW;AACd,cAAQ,IAAI,MAAM,OAAO,UAAU,CAAC;AACpC;AAAA,IACF;AAAA,EACF;AAGA,QAAM,iBAAiB,MAAM,SAAS,OAAO;AAAA,IAC3C;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,IAAA;AAAA,IAEX;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,IAAA;AAAA,EACX,CACD;AAGD,QAAM,WAAW,CAAA;AACjB,MAAI,kBAAkB;AAEtB,SAAO,iBAAiB;AACtB,YAAQ,IAAI,MAAM,KAAK;AAAA,aAAgB,SAAS,SAAS,CAAC,gBAAgB,CAAC;AAE3E,UAAM,iBAAiB,MAAM,SAAS,OAAO;AAAA,MAC3C;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,UAAU,CAAC,UAAU,MAAM,KAAA,MAAW,MAAM;AAAA,MAAA;AAAA,MAE9C;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,QACT,UAAU,CAAC,UAA8B;AACvC,cAAI,UAAU,OAAW,QAAO;AAChC,iBAAQ,QAAQ,KAAK,QAAQ,SAAU;AAAA,QACzC;AAAA,MAAA;AAAA,MAEF;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,UAAU,CAAC,UAAU,MAAM,KAAA,MAAW,MAAM;AAAA,QAC5C,QAAQ,CAAC,UAAkB,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,MAAc,EAAE,KAAA,CAAM;AAAA,MAAA;AAAA,MAEzE;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,MAAA;AAAA,IACX,CACD;AAGD,QAAI,eAAe;AACnB,QAAI,eAAe,WAAW;AAC5B,YAAM,gBAAgB,MAAM,SAAS,OAAO;AAAA,QAC1C;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,QAAA;AAAA,QAEX;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,UAAU,CAAC,UAAU,MAAM,KAAA,MAAW,MAAM;AAAA,QAAA;AAAA,QAE9C;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,SAAS,eAAe;AAAA,QAAA;AAAA,MAC1B,CACD;AAED,qBAAe;AAAA,QACb,OAAO,cAAc,SAAS;AAAA,QAC9B,WAAW,cAAc;AAAA,QACzB,MAAM,cAAc;AAAA,MAAA;AAAA,IAExB;AAGA,UAAM,EAAE,eAAA,IAAmB,MAAM,SAAS,OAAO;AAAA,MAC/C;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,MAAA;AAAA,IACX,CACD;AAED,QAAI,cAAc;AAClB,QAAI,gBAAgB;AAClB,YAAM,qBAAqB,MAAM,SAAS,OAAO;AAAA,QAC/C;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,SAAS;AAAA,QAAA;AAAA,QAEX;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,SAAS;AAAA,QAAA;AAAA,MACX,CACD;AAED,oBAAc;AAAA,IAChB;AAEA,aAAS,KAAK;AAAA,MACZ,MAAM,eAAe;AAAA,MACrB,MAAM,eAAe;AAAA,MACrB,SAAS,eAAe;AAAA,MACxB,QAAQ;AAAA,MACR;AAAA,IAAA,CACD;AAED,UAAM,EAAE,QAAA,IAAY,MAAM,SAAS,OAAO;AAAA,MACxC;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,MAAA;AAAA,IACX,CACD;AAED,sBAAkB;AAAA,EACpB;AAGA,QAAM,iBAAiB,MAAM,SAAS,OAAO;AAAA,IAC3C;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,UAAU,CAAC,UAAU;AACnB,cAAM,aAAa;AACnB,eAAO,WAAW,KAAK,KAAK,KAAK;AAAA,MACnC;AAAA,IAAA;AAAA,IAEF;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,IAAA;AAAA,EACX,CACD;AAGD,QAAM,SAAuB;AAAA,IAC3B,SAAS;AAAA,MACP,MAAM,eAAe;AAAA,MACrB,SAAS,eAAe;AAAA,IAAA;AAAA,IAE1B;AAAA,IACA,OAAO;AAAA,MACL,YAAY;AAAA,MACZ,eAAe;AAAA,IAAA;AAAA,IAEjB,SAAS;AAAA,MACP,OAAO,eAAe;AAAA,MACtB,SAAS,eAAe;AAAA,IAAA;AAAA,IAE1B,YAAY;AAAA,MACV,UAAU;AAAA,MACV,oBAAoB;AAAA,IAAA;AAAA,EACtB;AAIF,QAAM,WAAW,QAAQ,MAAM,MAAM;AAErC,UAAQ,IAAI,MAAM,MAAM;AAAA,2BAA8B,QAAQ,IAAI,EAAE,CAAC;AACrE,UAAQ,IAAI,MAAM,IAAI,eAAe,CAAC;AACtC,UAAQ,IAAI,MAAM,IAAI,wBAAwB,QAAQ,IAAI,YAAY,CAAC;AACvE,UAAQ,IAAI,MAAM,IAAI,wBAAwB,CAAC;AAC/C,UAAQ,IAAI,MAAM,IAAI,2BAA2B,CAAC;AACpD;"}