suthep 0.1.0-beta.1 → 0.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +167 -69
  2. package/dist/commands/down.js +179 -0
  3. package/dist/commands/down.js.map +1 -0
  4. package/dist/commands/redeploy.js +59 -0
  5. package/dist/commands/redeploy.js.map +1 -0
  6. package/dist/commands/up.js +213 -0
  7. package/dist/commands/up.js.map +1 -0
  8. package/dist/index.js +28 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/utils/deployment.js +10 -1
  11. package/dist/utils/deployment.js.map +1 -1
  12. package/dist/utils/docker.js +35 -0
  13. package/dist/utils/docker.js.map +1 -1
  14. package/dist/utils/nginx.js +10 -0
  15. package/dist/utils/nginx.js.map +1 -1
  16. package/docs/README.md +25 -82
  17. package/docs/english/01-introduction.md +84 -0
  18. package/docs/english/02-installation.md +200 -0
  19. package/docs/english/03-quick-start.md +256 -0
  20. package/docs/english/04-configuration.md +358 -0
  21. package/docs/english/05-commands.md +363 -0
  22. package/docs/english/06-examples.md +456 -0
  23. package/docs/english/07-troubleshooting.md +417 -0
  24. package/docs/english/08-advanced.md +411 -0
  25. package/docs/english/README.md +48 -0
  26. package/docs/thai/01-introduction.md +84 -0
  27. package/docs/thai/02-installation.md +200 -0
  28. package/docs/thai/03-quick-start.md +256 -0
  29. package/docs/thai/04-configuration.md +358 -0
  30. package/docs/thai/05-commands.md +363 -0
  31. package/docs/thai/06-examples.md +456 -0
  32. package/docs/thai/07-troubleshooting.md +417 -0
  33. package/docs/thai/08-advanced.md +411 -0
  34. package/docs/thai/README.md +48 -0
  35. package/example/README.md +282 -53
  36. package/example/suthep-complete.yml +103 -0
  37. package/example/suthep-docker-only.yml +71 -0
  38. package/example/suthep-no-docker.yml +51 -0
  39. package/example/{muacle.yml → suthep-path-routing.yml} +20 -5
  40. package/example/suthep.example.yml +89 -0
  41. package/package.json +1 -1
  42. package/src/commands/down.ts +240 -0
  43. package/src/commands/redeploy.ts +78 -0
  44. package/src/commands/up.ts +271 -0
  45. package/src/index.ts +53 -0
  46. package/suthep-0.1.0-beta.1.tgz +0 -0
  47. package/suthep.example.yml +5 -0
  48. package/suthep.yml +39 -0
  49. package/docs/TRANSLATIONS.md +0 -211
  50. package/docs/en/README.md +0 -76
  51. package/docs/en/api-reference.md +0 -545
  52. package/docs/en/architecture.md +0 -369
  53. package/docs/en/commands.md +0 -273
  54. package/docs/en/configuration.md +0 -347
  55. package/docs/en/developer-guide.md +0 -588
  56. package/docs/en/docker-ports-config.md +0 -333
  57. package/docs/en/examples.md +0 -537
  58. package/docs/en/getting-started.md +0 -202
  59. package/docs/en/port-binding.md +0 -268
  60. package/docs/en/troubleshooting.md +0 -441
  61. package/docs/th/README.md +0 -64
  62. package/docs/th/commands.md +0 -202
  63. package/docs/th/configuration.md +0 -325
  64. package/docs/th/getting-started.md +0 -203
  65. package/example/docker-compose.yml +0 -76
  66. package/example/docker-ports-example.yml +0 -81
  67. package/example/port-binding-example.yml +0 -45
  68. package/example/suthep.yml +0 -46
  69. package/example/suthep=1.yml +0 -46
package/README.md CHANGED
@@ -2,18 +2,32 @@
2
2
 
3
3
  A powerful CLI tool for deploying projects with automatic Nginx reverse proxy setup, HTTPS with Certbot, and zero-downtime deployments.
4
4
 
5
+ ## Table of Contents
6
+
7
+ - [Features](#features)
8
+ - [Installation](#installation)
9
+ - [Quick Start](#quick-start)
10
+ - [Configuration](#configuration)
11
+ - [Commands](#commands)
12
+ - [Examples](#examples)
13
+ - [Requirements](#requirements)
14
+ - [Benefits](#benefits)
15
+ - [License](#license)
16
+
5
17
  ## Features
6
18
 
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
19
+ - ✅ **Automatic Nginx Reverse Proxy** - Configures Nginx automatically for your services
20
+ - ✅ **Automatic HTTPS** - Sets up SSL/TLS certificates with Let's Encrypt via Certbot
21
+ - ✅ **Zero-Downtime Deployment** - Rolling deployment strategy ensures continuous availability
22
+ - ✅ **Docker Support** - Deploy Docker containers or connect to existing containers
23
+ - ✅ **Multiple Domains** - Support for multiple domains and subdomains per service
24
+ - ✅ **Health Checks** - Built-in health check monitoring for service reliability
25
+ - ✅ **YAML Configuration** - Simple, declarative configuration file format
14
26
 
15
27
  ## Installation
16
28
 
29
+ Install Suthep globally using your preferred package manager:
30
+
17
31
  ```bash
18
32
  npm install -g suthep
19
33
  # or
@@ -22,39 +36,49 @@ yarn global add suthep
22
36
  pnpm add -g suthep
23
37
  ```
24
38
 
25
- The tool uses `tsx` to run TypeScript directly, so no build step is required for development. For production, you can still build:
39
+ > **Note:** The tool uses `tsx` to run TypeScript directly, so no build step is required for development. For production builds, you can optionally compile to JavaScript using `npm run build`.
40
+
41
+ ## Quick Start
42
+
43
+ ### 1. Initialize Configuration
44
+
45
+ Create a new configuration file interactively:
26
46
 
27
47
  ```bash
28
- npm run build # Optional: compile to JavaScript
48
+ suthep init
29
49
  ```
30
50
 
31
- ## Quick Start
51
+ This will create a `suthep.yml` file with interactive prompts, or you can use an example configuration:
32
52
 
33
- 1. **Initialize a configuration file:**
34
- ```bash
35
- deploy init
36
- ```
37
- This will create a `deploy.yml` file with interactive prompts.
53
+ ```bash
54
+ cp example/suthep.yml suthep.yml
55
+ # Edit suthep.yml with your settings
56
+ ```
57
+
58
+ ### 2. Setup Prerequisites
38
59
 
39
- 2. **Or use the example configuration:**
40
- ```bash
41
- cp example.yml deploy.yml
42
- # Edit deploy.yml with your settings
43
- ```
60
+ Install and configure Nginx and Certbot on your system:
44
61
 
45
- 3. **Setup prerequisites (Nginx and Certbot):**
46
- ```bash
47
- deploy setup
48
- ```
62
+ ```bash
63
+ suthep setup
64
+ ```
49
65
 
50
- 4. **Deploy your project:**
51
- ```bash
52
- deploy deploy
53
- ```
66
+ This command will:
67
+ - Install Nginx (if not already installed)
68
+ - Install Certbot (if not already installed)
69
+ - Configure system dependencies
70
+
71
+ ### 3. Deploy Your Project
72
+
73
+ Deploy your project using the configuration file:
74
+
75
+ ```bash
76
+ suthep deploy
77
+ ```
54
78
 
55
79
  ## Configuration
56
80
 
57
- The configuration file (`deploy.yml`) supports the following structure:
81
+ The configuration file (`suthep.yml`) uses a YAML format to define your deployment. Here's the complete structure:
58
82
 
59
83
  ```yaml
60
84
  project:
@@ -98,56 +122,84 @@ deployment:
98
122
 
99
123
  ### Service Configuration
100
124
 
101
- Each service can have:
125
+ Each service in the `services` array can have the following properties:
126
+
127
+ | Property | Required | Description |
128
+ |----------|----------|-------------|
129
+ | `name` | ✅ Yes | Unique identifier for the service |
130
+ | `port` | ✅ Yes | Port number the service runs on (host port) |
131
+ | `domains` | ✅ Yes | Array of domain names or subdomains to route to this service |
132
+ | `docker` | ❌ No | Docker configuration (see below) |
133
+ | `healthCheck` | ❌ No | Health check configuration (see below) |
134
+ | `environment` | ❌ No | Environment variables as key-value pairs |
135
+
136
+ #### Docker Configuration
137
+
138
+ When deploying Docker containers, use the `docker` object:
139
+
140
+ - **`image`**: Docker image to pull and run (e.g., `nginx:latest`)
141
+ - **`container`**: Name for the Docker container
142
+ - **`port`**: Internal port the container listens on (mapped to the service `port`)
143
+
144
+ #### Health Check Configuration
102
145
 
103
- - **name**: Unique service name
104
- - **port**: Port the service runs on
105
- - **domains**: Array of domain names or subdomains
106
- - **docker** (optional):
107
- - **image**: Docker image to use
108
- - **container**: Container name
109
- - **port**: Container's internal port
110
- - **healthCheck** (optional):
111
- - **path**: Health check endpoint path
112
- - **interval**: Check interval in seconds
113
- - **environment** (optional): Environment variables
146
+ Configure health monitoring for your service:
147
+
148
+ - **`path`**: HTTP endpoint path for health checks (e.g., `/health`)
149
+ - **`interval`**: Time between health checks in seconds (default: 30)
114
150
 
115
151
  ## Commands
116
152
 
117
- ### `deploy init`
153
+ ### `suthep init`
118
154
 
119
- Initialize a new deployment configuration file.
155
+ Initialize a new deployment configuration file with interactive prompts.
120
156
 
121
157
  ```bash
122
- deploy init [-f deploy.yml]
158
+ suthep init [-f suthep.yml]
123
159
  ```
124
160
 
125
- ### `deploy setup`
161
+ **Options:**
162
+ - `-f, --file`: Configuration file path (default: `suthep.yml`)
163
+
164
+ ### `suthep setup`
126
165
 
127
- Setup Nginx and Certbot on the system.
166
+ Install and configure Nginx and Certbot on your system. This command checks for existing installations and sets up any missing components.
128
167
 
129
168
  ```bash
130
- deploy setup [--nginx-only] [--certbot-only]
169
+ suthep setup [--nginx-only] [--certbot-only]
131
170
  ```
132
171
 
133
- ### `deploy deploy`
172
+ **Options:**
173
+ - `--nginx-only`: Only install and configure Nginx
174
+ - `--certbot-only`: Only install and configure Certbot
134
175
 
135
- Deploy a project using the configuration file.
176
+ ### `suthep deploy`
177
+
178
+ Deploy your project using the configuration file. This command will:
179
+ 1. Configure Nginx reverse proxy for all services
180
+ 2. Obtain SSL certificates via Certbot (if enabled)
181
+ 3. Deploy services with zero-downtime strategy
136
182
 
137
183
  ```bash
138
- deploy deploy [-f deploy.yml] [--no-https] [--no-nginx]
184
+ suthep deploy [-f suthep.yml] [--no-https] [--no-nginx]
139
185
  ```
140
186
 
141
- Options:
142
- - `-f, --file`: Configuration file path (default: `deploy.yml`)
143
- - `--no-https`: Skip HTTPS setup
144
- - `--no-nginx`: Skip Nginx configuration
187
+ **Options:**
188
+ - `-f, --file`: Configuration file path (default: `suthep.yml`)
189
+ - `--no-https`: Skip HTTPS/SSL certificate setup
190
+ - `--no-nginx`: Skip Nginx configuration (useful for testing)
145
191
 
146
192
  ## Examples
147
193
 
148
- ### Simple Node.js Service
194
+ ### Example 1: Simple Node.js API Service
195
+
196
+ Deploy a Node.js API service with health checks:
149
197
 
150
198
  ```yaml
199
+ project:
200
+ name: my-api
201
+ version: 1.0.0
202
+
151
203
  services:
152
204
  - name: api
153
205
  port: 3000
@@ -155,9 +207,26 @@ services:
155
207
  - api.example.com
156
208
  healthCheck:
157
209
  path: /health
210
+ interval: 30
211
+ environment:
212
+ NODE_ENV: production
213
+
214
+ nginx:
215
+ configPath: /etc/nginx/sites-available
216
+ reloadCommand: sudo nginx -t && sudo systemctl reload nginx
217
+
218
+ certbot:
219
+ email: admin@example.com
220
+ staging: false
221
+
222
+ deployment:
223
+ strategy: rolling
224
+ healthCheckTimeout: 30000
158
225
  ```
159
226
 
160
- ### Docker Container Service
227
+ ### Example 2: Docker Container Service
228
+
229
+ Deploy a web application using a Docker container:
161
230
 
162
231
  ```yaml
163
232
  services:
@@ -169,9 +238,12 @@ services:
169
238
  port: 80
170
239
  domains:
171
240
  - example.com
241
+ - www.example.com
172
242
  ```
173
243
 
174
- ### Multiple Domains
244
+ ### Example 3: Multiple Domains for Single Service
245
+
246
+ Route multiple domains to the same service:
175
247
 
176
248
  ```yaml
177
249
  services:
@@ -181,9 +253,13 @@ services:
181
253
  - dashboard.example.com
182
254
  - admin.example.com
183
255
  - app.example.com
256
+ healthCheck:
257
+ path: /api/health
184
258
  ```
185
259
 
186
- ### Connect to Existing Docker Container
260
+ ### Example 4: Connect to Existing Docker Container
261
+
262
+ Connect to an already running Docker container:
187
263
 
188
264
  ```yaml
189
265
  services:
@@ -196,21 +272,43 @@ services:
196
272
  - db.example.com
197
273
  ```
198
274
 
275
+ > **Note:** When connecting to an existing container, omit the `image` field. The tool will use the existing container.
276
+
199
277
  ## Requirements
200
278
 
201
- - Node.js 16+
202
- - Nginx (installed via `deploy setup`)
203
- - Certbot (installed via `deploy setup`)
204
- - Docker (optional, for Docker-based services)
205
- - sudo access (for Nginx and Certbot operations)
279
+ ### System Requirements
280
+
281
+ - **Node.js** 16 or higher
282
+ - **Nginx** (installed automatically via `suthep setup`)
283
+ - **Certbot** (installed automatically via `suthep setup`)
284
+ - **Docker** (optional, required only for Docker-based services)
285
+ - **sudo access** (required for Nginx and Certbot operations)
286
+
287
+ ### Permissions
288
+
289
+ Suthep requires sudo privileges to:
290
+ - Install system packages (Nginx, Certbot)
291
+ - Modify Nginx configuration files
292
+ - Reload Nginx service
293
+ - Obtain SSL certificates from Let's Encrypt
294
+
295
+ ## Benefits
296
+
297
+ ### Cost Optimization
298
+
299
+ Suthep helps optimize infrastructure costs by:
300
+
301
+ - **Multi-Service Management** - Run multiple services on a single server efficiently
302
+ - **Automated Configuration** - Eliminates manual Nginx and SSL setup, saving time and reducing errors
303
+ - **Zero-Downtime Deployments** - Rolling deployment strategy ensures continuous service availability
304
+ - **Health Monitoring** - Built-in health checks help maintain service reliability and catch issues early
206
305
 
207
- ## Cost Optimization
306
+ ### Developer Experience
208
307
 
209
- This tool helps save costs on VMs by:
210
- - Efficiently managing multiple services on a single server
211
- - Automatic reverse proxy setup reduces manual configuration
212
- - Zero-downtime deployments reduce service interruptions
213
- - Health checks ensure service reliability
308
+ - **Simple Configuration** - YAML-based configuration is easy to understand and maintain
309
+ - **One Command Deployment** - Deploy entire infrastructure with a single command
310
+ - **Automatic SSL** - HTTPS certificates are obtained and renewed automatically
311
+ - **Docker Integration** - Seamless support for containerized applications
214
312
 
215
313
  ## License
216
314
 
@@ -0,0 +1,179 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs-extra";
3
+ import { loadConfig } from "../utils/config-loader.js";
4
+ import { stopDockerContainer, removeDockerContainer, isContainerRunning } from "../utils/docker.js";
5
+ import { disableSite, generateNginxConfig, generateMultiServiceNginxConfig, writeNginxConfig, enableSite, reloadNginx } from "../utils/nginx.js";
6
+ async function downCommand(options) {
7
+ console.log(chalk.blue.bold("\n🛑 Bringing Down Services\n"));
8
+ try {
9
+ if (!await fs.pathExists(options.file)) {
10
+ throw new Error(`Configuration file not found: ${options.file}`);
11
+ }
12
+ console.log(chalk.cyan(`📄 Loading configuration from ${options.file}...`));
13
+ const config = await loadConfig(options.file);
14
+ console.log(chalk.green(`✅ Configuration loaded for project: ${config.project.name}`));
15
+ let servicesToDown = [];
16
+ if (options.all) {
17
+ servicesToDown = config.services;
18
+ console.log(
19
+ chalk.cyan(
20
+ `📋 Bringing down all services: ${servicesToDown.map((s) => s.name).join(", ")}
21
+ `
22
+ )
23
+ );
24
+ } else if (options.serviceName) {
25
+ const service = config.services.find((s) => s.name === options.serviceName);
26
+ if (!service) {
27
+ throw new Error(
28
+ `Service "${options.serviceName}" not found in configuration. Available services: ${config.services.map((s) => s.name).join(", ")}`
29
+ );
30
+ }
31
+ servicesToDown = [service];
32
+ console.log(chalk.cyan(`📋 Bringing down service: ${options.serviceName}
33
+ `));
34
+ } else {
35
+ throw new Error("Either specify a service name or use --all flag");
36
+ }
37
+ const domainToServices = /* @__PURE__ */ new Map();
38
+ const allDomains = /* @__PURE__ */ new Set();
39
+ for (const service of servicesToDown) {
40
+ for (const domain of service.domains) {
41
+ allDomains.add(domain);
42
+ if (!domainToServices.has(domain)) {
43
+ domainToServices.set(domain, []);
44
+ }
45
+ domainToServices.get(domain).push(service);
46
+ }
47
+ }
48
+ for (const service of servicesToDown) {
49
+ if (service.docker) {
50
+ console.log(chalk.cyan(`
51
+ 🐳 Stopping Docker container for service: ${service.name}`));
52
+ try {
53
+ const containerName = service.docker.container;
54
+ try {
55
+ await stopDockerContainer(containerName);
56
+ console.log(chalk.green(` ✅ Stopped container: ${containerName}`));
57
+ } catch (error) {
58
+ const errorMessage = error?.message || String(error) || "Unknown error";
59
+ if (errorMessage.toLowerCase().includes("no such container") || errorMessage.toLowerCase().includes("container not found")) {
60
+ console.log(
61
+ chalk.yellow(` ⚠️ Container ${containerName} not found (already stopped)`)
62
+ );
63
+ } else {
64
+ throw error;
65
+ }
66
+ }
67
+ try {
68
+ await removeDockerContainer(containerName);
69
+ console.log(chalk.green(` ✅ Removed container: ${containerName}`));
70
+ } catch (error) {
71
+ const errorMessage = error?.message || String(error) || "Unknown error";
72
+ if (errorMessage.toLowerCase().includes("no such container") || errorMessage.toLowerCase().includes("container not found")) {
73
+ console.log(
74
+ chalk.yellow(` ⚠️ Container ${containerName} not found (already removed)`)
75
+ );
76
+ } else {
77
+ throw error;
78
+ }
79
+ }
80
+ } catch (error) {
81
+ console.error(
82
+ chalk.red(` ❌ Failed to stop/remove container for service ${service.name}:`),
83
+ error instanceof Error ? error.message : error
84
+ );
85
+ throw error;
86
+ }
87
+ }
88
+ }
89
+ if (allDomains.size > 0) {
90
+ console.log(chalk.cyan(`
91
+ ⚙️ Updating Nginx configurations...`));
92
+ const servicesBeingDowned = new Set(servicesToDown.map((s) => s.name));
93
+ for (const domain of allDomains) {
94
+ const configName = domain.replace(/\./g, "_");
95
+ const allServicesForDomain = config.services.filter((s) => s.domains.includes(domain));
96
+ const remainingServices = allServicesForDomain.filter(
97
+ (s) => !servicesBeingDowned.has(s.name)
98
+ );
99
+ const activeServices = [];
100
+ for (const service of remainingServices) {
101
+ if (service.docker) {
102
+ const isRunning = await isContainerRunning(service.docker.container);
103
+ if (isRunning) {
104
+ activeServices.push(service);
105
+ }
106
+ } else {
107
+ activeServices.push(service);
108
+ }
109
+ }
110
+ try {
111
+ if (activeServices.length === 0) {
112
+ console.log(
113
+ chalk.cyan(` 📋 No active services on ${domain}, disabling Nginx config...`)
114
+ );
115
+ await disableSite(configName, config.nginx.configPath);
116
+ console.log(chalk.green(` ✅ Disabled Nginx config for ${domain}`));
117
+ } else {
118
+ console.log(
119
+ chalk.cyan(
120
+ ` 📋 Regenerating Nginx config for ${domain} with ${activeServices.length} active service(s): ${activeServices.map((s) => s.name).join(", ")}`
121
+ )
122
+ );
123
+ let nginxConfigContent;
124
+ if (activeServices.length === 1) {
125
+ nginxConfigContent = generateNginxConfig(activeServices[0], false);
126
+ } else {
127
+ nginxConfigContent = generateMultiServiceNginxConfig(activeServices, domain, false);
128
+ }
129
+ const { certificateExists } = await import("../utils/certbot.js");
130
+ const hasHttps = await certificateExists(domain);
131
+ if (hasHttps) {
132
+ if (activeServices.length === 1) {
133
+ nginxConfigContent = generateNginxConfig(activeServices[0], true);
134
+ } else {
135
+ nginxConfigContent = generateMultiServiceNginxConfig(activeServices, domain, true);
136
+ }
137
+ }
138
+ await writeNginxConfig(configName, config.nginx.configPath, nginxConfigContent);
139
+ await enableSite(configName, config.nginx.configPath);
140
+ console.log(
141
+ chalk.green(
142
+ ` ✅ Updated Nginx config for ${domain} (${activeServices.length} service(s) active)`
143
+ )
144
+ );
145
+ }
146
+ } catch (error) {
147
+ console.error(
148
+ chalk.red(` ❌ Failed to update Nginx config for ${domain}:`),
149
+ error instanceof Error ? error.message : error
150
+ );
151
+ throw error;
152
+ }
153
+ }
154
+ console.log(chalk.cyan(`
155
+ 🔄 Reloading Nginx...`));
156
+ try {
157
+ await reloadNginx(config.nginx.reloadCommand);
158
+ console.log(chalk.green(` ✅ Nginx reloaded`));
159
+ } catch (error) {
160
+ console.error(
161
+ chalk.red(` ❌ Failed to reload Nginx:`),
162
+ error instanceof Error ? error.message : error
163
+ );
164
+ throw error;
165
+ }
166
+ }
167
+ console.log(chalk.green.bold("\n✅ Services brought down successfully!\n"));
168
+ } catch (error) {
169
+ console.error(
170
+ chalk.red("\n❌ Failed to bring down services:"),
171
+ error instanceof Error ? error.message : error
172
+ );
173
+ process.exit(1);
174
+ }
175
+ }
176
+ export {
177
+ downCommand
178
+ };
179
+ //# sourceMappingURL=down.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"down.js","sources":["../../src/commands/down.ts"],"sourcesContent":["import chalk from 'chalk'\nimport fs from 'fs-extra'\nimport type { ServiceConfig } from '../types/config'\nimport { loadConfig } from '../utils/config-loader'\nimport { isContainerRunning, removeDockerContainer, stopDockerContainer } from '../utils/docker'\nimport {\n disableSite,\n enableSite,\n generateMultiServiceNginxConfig,\n generateNginxConfig,\n reloadNginx,\n writeNginxConfig,\n} from '../utils/nginx'\n\ninterface DownOptions {\n file: string\n all: boolean\n serviceName?: string\n}\n\nexport async function downCommand(options: DownOptions): Promise<void> {\n console.log(chalk.blue.bold('\\n🛑 Bringing Down 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\n // Determine which services to bring down\n let servicesToDown: ServiceConfig[] = []\n\n if (options.all) {\n servicesToDown = config.services\n console.log(\n chalk.cyan(\n `📋 Bringing down all services: ${servicesToDown.map((s) => s.name).join(', ')}\\n`\n )\n )\n } else if (options.serviceName) {\n const service = config.services.find((s) => s.name === options.serviceName)\n if (!service) {\n throw new Error(\n `Service \"${\n options.serviceName\n }\" not found in configuration. Available services: ${config.services\n .map((s) => s.name)\n .join(', ')}`\n )\n }\n servicesToDown = [service]\n console.log(chalk.cyan(`📋 Bringing down service: ${options.serviceName}\\n`))\n } else {\n throw new Error('Either specify a service name or use --all flag')\n }\n\n // Group services by domain for nginx config management\n const domainToServices = new Map<string, ServiceConfig[]>()\n const allDomains = new Set<string>()\n\n for (const service of servicesToDown) {\n for (const domain of service.domains) {\n allDomains.add(domain)\n if (!domainToServices.has(domain)) {\n domainToServices.set(domain, [])\n }\n domainToServices.get(domain)!.push(service)\n }\n }\n\n // Stop and remove Docker containers\n for (const service of servicesToDown) {\n if (service.docker) {\n console.log(chalk.cyan(`\\n🐳 Stopping Docker container for service: ${service.name}`))\n try {\n const containerName = service.docker.container\n\n // Stop container\n try {\n await stopDockerContainer(containerName)\n console.log(chalk.green(` ✅ Stopped container: ${containerName}`))\n } catch (error: any) {\n const errorMessage = error?.message || String(error) || 'Unknown error'\n if (\n errorMessage.toLowerCase().includes('no such container') ||\n errorMessage.toLowerCase().includes('container not found')\n ) {\n console.log(\n chalk.yellow(` ⚠️ Container ${containerName} not found (already stopped)`)\n )\n } else {\n throw error\n }\n }\n\n // Remove container\n try {\n await removeDockerContainer(containerName)\n console.log(chalk.green(` ✅ Removed container: ${containerName}`))\n } catch (error: any) {\n const errorMessage = error?.message || String(error) || 'Unknown error'\n if (\n errorMessage.toLowerCase().includes('no such container') ||\n errorMessage.toLowerCase().includes('container not found')\n ) {\n console.log(\n chalk.yellow(` ⚠️ Container ${containerName} not found (already removed)`)\n )\n } else {\n throw error\n }\n }\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to stop/remove container for service ${service.name}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n }\n\n // Handle Nginx configs by domain\n // Check which services remain active for each domain\n if (allDomains.size > 0) {\n console.log(chalk.cyan(`\\n⚙️ Updating Nginx configurations...`))\n\n // Create a set of service names being brought down for quick lookup\n const servicesBeingDowned = new Set(servicesToDown.map((s) => s.name))\n\n // For each domain, find all services that use it (from all config services)\n // and determine which ones remain active\n for (const domain of allDomains) {\n const configName = domain.replace(/\\./g, '_')\n\n // Find all services that use this domain (from entire config)\n const allServicesForDomain = config.services.filter((s) => s.domains.includes(domain))\n\n // Filter out services being brought down\n const remainingServices = allServicesForDomain.filter(\n (s) => !servicesBeingDowned.has(s.name)\n )\n\n // Check if remaining services are actually running (have active containers)\n const activeServices: ServiceConfig[] = []\n for (const service of remainingServices) {\n if (service.docker) {\n const isRunning = await isContainerRunning(service.docker.container)\n if (isRunning) {\n activeServices.push(service)\n }\n } else {\n // Service without docker - assume it's running if not being brought down\n activeServices.push(service)\n }\n }\n\n try {\n if (activeServices.length === 0) {\n // No active services on this domain, disable the config\n console.log(\n chalk.cyan(` 📋 No active services on ${domain}, disabling Nginx config...`)\n )\n await disableSite(configName, config.nginx.configPath)\n console.log(chalk.green(` ✅ Disabled Nginx config for ${domain}`))\n } else {\n // Regenerate config with remaining active services\n console.log(\n chalk.cyan(\n ` 📋 Regenerating Nginx config for ${domain} with ${\n activeServices.length\n } active service(s): ${activeServices.map((s) => s.name).join(', ')}`\n )\n )\n\n // Generate config for remaining services\n let nginxConfigContent: string\n if (activeServices.length === 1) {\n nginxConfigContent = generateNginxConfig(activeServices[0], false)\n } else {\n nginxConfigContent = generateMultiServiceNginxConfig(activeServices, domain, false)\n }\n\n // Check if HTTPS is enabled (check if certificate exists)\n const { certificateExists } = await import('../utils/certbot')\n const hasHttps = await certificateExists(domain)\n if (hasHttps) {\n // Regenerate with HTTPS\n if (activeServices.length === 1) {\n nginxConfigContent = generateNginxConfig(activeServices[0], true)\n } else {\n nginxConfigContent = generateMultiServiceNginxConfig(activeServices, domain, true)\n }\n }\n\n await writeNginxConfig(configName, config.nginx.configPath, nginxConfigContent)\n await enableSite(configName, config.nginx.configPath)\n console.log(\n chalk.green(\n ` ✅ Updated Nginx config for ${domain} (${activeServices.length} service(s) active)`\n )\n )\n }\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to update Nginx config for ${domain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n // Reload Nginx to apply changes\n console.log(chalk.cyan(`\\n🔄 Reloading Nginx...`))\n try {\n await reloadNginx(config.nginx.reloadCommand)\n console.log(chalk.green(` ✅ Nginx reloaded`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to reload Nginx:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n console.log(chalk.green.bold('\\n✅ Services brought down successfully!\\n'))\n } catch (error) {\n console.error(\n chalk.red('\\n❌ Failed to bring down services:'),\n error instanceof Error ? error.message : error\n )\n process.exit(1)\n }\n}\n"],"names":[],"mappings":";;;;;AAoBA,eAAsB,YAAY,SAAqC;AACrE,UAAQ,IAAI,MAAM,KAAK,KAAK,+BAA+B,CAAC;AAE5D,MAAI;AAEF,QAAI,CAAE,MAAM,GAAG,WAAW,QAAQ,IAAI,GAAI;AACxC,YAAM,IAAI,MAAM,iCAAiC,QAAQ,IAAI,EAAE;AAAA,IACjE;AAEA,YAAQ,IAAI,MAAM,KAAK,iCAAiC,QAAQ,IAAI,KAAK,CAAC;AAC1E,UAAM,SAAS,MAAM,WAAW,QAAQ,IAAI;AAE5C,YAAQ,IAAI,MAAM,MAAM,uCAAuC,OAAO,QAAQ,IAAI,EAAE,CAAC;AAGrF,QAAI,iBAAkC,CAAA;AAEtC,QAAI,QAAQ,KAAK;AACf,uBAAiB,OAAO;AACxB,cAAQ;AAAA,QACN,MAAM;AAAA,UACJ,kCAAkC,eAAe,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA,QAAA;AAAA,MAChF;AAAA,IAEJ,WAAW,QAAQ,aAAa;AAC9B,YAAM,UAAU,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,WAAW;AAC1E,UAAI,CAAC,SAAS;AACZ,cAAM,IAAI;AAAA,UACR,YACE,QAAQ,WACV,qDAAqD,OAAO,SACzD,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK,IAAI,CAAC;AAAA,QAAA;AAAA,MAEjB;AACA,uBAAiB,CAAC,OAAO;AACzB,cAAQ,IAAI,MAAM,KAAK,6BAA6B,QAAQ,WAAW;AAAA,CAAI,CAAC;AAAA,IAC9E,OAAO;AACL,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AAGA,UAAM,uCAAuB,IAAA;AAC7B,UAAM,iCAAiB,IAAA;AAEvB,eAAW,WAAW,gBAAgB;AACpC,iBAAW,UAAU,QAAQ,SAAS;AACpC,mBAAW,IAAI,MAAM;AACrB,YAAI,CAAC,iBAAiB,IAAI,MAAM,GAAG;AACjC,2BAAiB,IAAI,QAAQ,EAAE;AAAA,QACjC;AACA,yBAAiB,IAAI,MAAM,EAAG,KAAK,OAAO;AAAA,MAC5C;AAAA,IACF;AAGA,eAAW,WAAW,gBAAgB;AACpC,UAAI,QAAQ,QAAQ;AAClB,gBAAQ,IAAI,MAAM,KAAK;AAAA,4CAA+C,QAAQ,IAAI,EAAE,CAAC;AACrF,YAAI;AACF,gBAAM,gBAAgB,QAAQ,OAAO;AAGrC,cAAI;AACF,kBAAM,oBAAoB,aAAa;AACvC,oBAAQ,IAAI,MAAM,MAAM,0BAA0B,aAAa,EAAE,CAAC;AAAA,UACpE,SAAS,OAAY;AACnB,kBAAM,eAAe,OAAO,WAAW,OAAO,KAAK,KAAK;AACxD,gBACE,aAAa,cAAc,SAAS,mBAAmB,KACvD,aAAa,YAAA,EAAc,SAAS,qBAAqB,GACzD;AACA,sBAAQ;AAAA,gBACN,MAAM,OAAO,mBAAmB,aAAa,8BAA8B;AAAA,cAAA;AAAA,YAE/E,OAAO;AACL,oBAAM;AAAA,YACR;AAAA,UACF;AAGA,cAAI;AACF,kBAAM,sBAAsB,aAAa;AACzC,oBAAQ,IAAI,MAAM,MAAM,0BAA0B,aAAa,EAAE,CAAC;AAAA,UACpE,SAAS,OAAY;AACnB,kBAAM,eAAe,OAAO,WAAW,OAAO,KAAK,KAAK;AACxD,gBACE,aAAa,cAAc,SAAS,mBAAmB,KACvD,aAAa,YAAA,EAAc,SAAS,qBAAqB,GACzD;AACA,sBAAQ;AAAA,gBACN,MAAM,OAAO,mBAAmB,aAAa,8BAA8B;AAAA,cAAA;AAAA,YAE/E,OAAO;AACL,oBAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF,SAAS,OAAO;AACd,kBAAQ;AAAA,YACN,MAAM,IAAI,mDAAmD,QAAQ,IAAI,GAAG;AAAA,YAC5E,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UAAA;AAE3C,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAIA,QAAI,WAAW,OAAO,GAAG;AACvB,cAAQ,IAAI,MAAM,KAAK;AAAA,qCAAwC,CAAC;AAGhE,YAAM,sBAAsB,IAAI,IAAI,eAAe,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAIrE,iBAAW,UAAU,YAAY;AAC/B,cAAM,aAAa,OAAO,QAAQ,OAAO,GAAG;AAG5C,cAAM,uBAAuB,OAAO,SAAS,OAAO,CAAC,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC;AAGrF,cAAM,oBAAoB,qBAAqB;AAAA,UAC7C,CAAC,MAAM,CAAC,oBAAoB,IAAI,EAAE,IAAI;AAAA,QAAA;AAIxC,cAAM,iBAAkC,CAAA;AACxC,mBAAW,WAAW,mBAAmB;AACvC,cAAI,QAAQ,QAAQ;AAClB,kBAAM,YAAY,MAAM,mBAAmB,QAAQ,OAAO,SAAS;AACnE,gBAAI,WAAW;AACb,6BAAe,KAAK,OAAO;AAAA,YAC7B;AAAA,UACF,OAAO;AAEL,2BAAe,KAAK,OAAO;AAAA,UAC7B;AAAA,QACF;AAEA,YAAI;AACF,cAAI,eAAe,WAAW,GAAG;AAE/B,oBAAQ;AAAA,cACN,MAAM,KAAK,8BAA8B,MAAM,6BAA6B;AAAA,YAAA;AAE9E,kBAAM,YAAY,YAAY,OAAO,MAAM,UAAU;AACrD,oBAAQ,IAAI,MAAM,MAAM,iCAAiC,MAAM,EAAE,CAAC;AAAA,UACpE,OAAO;AAEL,oBAAQ;AAAA,cACN,MAAM;AAAA,gBACJ,sCAAsC,MAAM,SAC1C,eAAe,MACjB,uBAAuB,eAAe,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,cAAA;AAAA,YACrE;AAIF,gBAAI;AACJ,gBAAI,eAAe,WAAW,GAAG;AAC/B,mCAAqB,oBAAoB,eAAe,CAAC,GAAG,KAAK;AAAA,YACnE,OAAO;AACL,mCAAqB,gCAAgC,gBAAgB,QAAQ,KAAK;AAAA,YACpF;AAGA,kBAAM,EAAE,kBAAA,IAAsB,MAAM,OAAO,qBAAkB;AAC7D,kBAAM,WAAW,MAAM,kBAAkB,MAAM;AAC/C,gBAAI,UAAU;AAEZ,kBAAI,eAAe,WAAW,GAAG;AAC/B,qCAAqB,oBAAoB,eAAe,CAAC,GAAG,IAAI;AAAA,cAClE,OAAO;AACL,qCAAqB,gCAAgC,gBAAgB,QAAQ,IAAI;AAAA,cACnF;AAAA,YACF;AAEA,kBAAM,iBAAiB,YAAY,OAAO,MAAM,YAAY,kBAAkB;AAC9E,kBAAM,WAAW,YAAY,OAAO,MAAM,UAAU;AACpD,oBAAQ;AAAA,cACN,MAAM;AAAA,gBACJ,gCAAgC,MAAM,KAAK,eAAe,MAAM;AAAA,cAAA;AAAA,YAClE;AAAA,UAEJ;AAAA,QACF,SAAS,OAAO;AACd,kBAAQ;AAAA,YACN,MAAM,IAAI,yCAAyC,MAAM,GAAG;AAAA,YAC5D,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UAAA;AAE3C,gBAAM;AAAA,QACR;AAAA,MACF;AAGA,cAAQ,IAAI,MAAM,KAAK;AAAA,sBAAyB,CAAC;AACjD,UAAI;AACF,cAAM,YAAY,OAAO,MAAM,aAAa;AAC5C,gBAAQ,IAAI,MAAM,MAAM,oBAAoB,CAAC;AAAA,MAC/C,SAAS,OAAO;AACd,gBAAQ;AAAA,UACN,MAAM,IAAI,6BAA6B;AAAA,UACvC,iBAAiB,QAAQ,MAAM,UAAU;AAAA,QAAA;AAE3C,cAAM;AAAA,MACR;AAAA,IACF;AAEA,YAAQ,IAAI,MAAM,MAAM,KAAK,2CAA2C,CAAC;AAAA,EAC3E,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,MAAM,IAAI,oCAAoC;AAAA,MAC9C,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAAA;AAE3C,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;"}
@@ -0,0 +1,59 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs-extra";
3
+ import { loadConfig } from "../utils/config-loader.js";
4
+ import { deployCommand } from "./deploy.js";
5
+ import { downCommand } from "./down.js";
6
+ async function redeployCommand(options) {
7
+ console.log(chalk.blue.bold("\n🔄 Redeploying Services\n"));
8
+ try {
9
+ if (!await fs.pathExists(options.file)) {
10
+ throw new Error(`Configuration file not found: ${options.file}`);
11
+ }
12
+ const config = await loadConfig(options.file);
13
+ let servicesToRedeploy = [];
14
+ if (options.all) {
15
+ servicesToRedeploy = config.services;
16
+ console.log(
17
+ chalk.cyan(
18
+ `📋 Redeploying all services: ${servicesToRedeploy.map((s) => s.name).join(", ")}
19
+ `
20
+ )
21
+ );
22
+ } else if (options.serviceName) {
23
+ const service = config.services.find((s) => s.name === options.serviceName);
24
+ if (!service) {
25
+ throw new Error(
26
+ `Service "${options.serviceName}" not found in configuration. Available services: ${config.services.map((s) => s.name).join(", ")}`
27
+ );
28
+ }
29
+ servicesToRedeploy = [service];
30
+ console.log(chalk.cyan(`📋 Redeploying service: ${options.serviceName}
31
+ `));
32
+ } else {
33
+ throw new Error("Either specify a service name or use --all flag");
34
+ }
35
+ console.log(chalk.yellow("Step 1: Bringing down services...\n"));
36
+ await downCommand({
37
+ file: options.file,
38
+ all: options.all,
39
+ serviceName: options.serviceName
40
+ });
41
+ console.log(chalk.yellow("\nStep 2: Deploying services...\n"));
42
+ await deployCommand({
43
+ file: options.file,
44
+ https: options.https,
45
+ nginx: options.nginx
46
+ });
47
+ console.log(chalk.green.bold("\n✅ Services redeployed successfully!\n"));
48
+ } catch (error) {
49
+ console.error(
50
+ chalk.red("\n❌ Failed to redeploy services:"),
51
+ error instanceof Error ? error.message : error
52
+ );
53
+ process.exit(1);
54
+ }
55
+ }
56
+ export {
57
+ redeployCommand
58
+ };
59
+ //# sourceMappingURL=redeploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redeploy.js","sources":["../../src/commands/redeploy.ts"],"sourcesContent":["import chalk from 'chalk'\nimport fs from 'fs-extra'\nimport type { ServiceConfig } from '../types/config'\nimport { loadConfig } from '../utils/config-loader'\nimport { deployCommand } from './deploy'\nimport { downCommand } from './down'\n\ninterface RedeployOptions {\n file: string\n all: boolean\n serviceName?: string\n https: boolean\n nginx: boolean\n}\n\nexport async function redeployCommand(options: RedeployOptions): Promise<void> {\n console.log(chalk.blue.bold('\\n🔄 Redeploying Services\\n'))\n\n try {\n // Load configuration to validate service names\n if (!(await fs.pathExists(options.file))) {\n throw new Error(`Configuration file not found: ${options.file}`)\n }\n\n const config = await loadConfig(options.file)\n\n // Determine which services to redeploy\n let servicesToRedeploy: ServiceConfig[] = []\n\n if (options.all) {\n servicesToRedeploy = config.services\n console.log(\n chalk.cyan(\n `📋 Redeploying all services: ${servicesToRedeploy.map((s) => s.name).join(', ')}\\n`\n )\n )\n } else if (options.serviceName) {\n const service = config.services.find((s) => s.name === options.serviceName)\n if (!service) {\n throw new Error(\n `Service \"${\n options.serviceName\n }\" not found in configuration. Available services: ${config.services\n .map((s) => s.name)\n .join(', ')}`\n )\n }\n servicesToRedeploy = [service]\n console.log(chalk.cyan(`📋 Redeploying service: ${options.serviceName}\\n`))\n } else {\n throw new Error('Either specify a service name or use --all flag')\n }\n\n // Step 1: Bring down the services\n console.log(chalk.yellow('Step 1: Bringing down services...\\n'))\n await downCommand({\n file: options.file,\n all: options.all,\n serviceName: options.serviceName,\n })\n\n // Step 2: Deploy the services (which will bring them back up)\n console.log(chalk.yellow('\\nStep 2: Deploying services...\\n'))\n await deployCommand({\n file: options.file,\n https: options.https,\n nginx: options.nginx,\n })\n\n console.log(chalk.green.bold('\\n✅ Services redeployed successfully!\\n'))\n } catch (error) {\n console.error(\n chalk.red('\\n❌ Failed to redeploy services:'),\n error instanceof Error ? error.message : error\n )\n process.exit(1)\n }\n}\n"],"names":[],"mappings":";;;;;AAeA,eAAsB,gBAAgB,SAAyC;AAC7E,UAAQ,IAAI,MAAM,KAAK,KAAK,6BAA6B,CAAC;AAE1D,MAAI;AAEF,QAAI,CAAE,MAAM,GAAG,WAAW,QAAQ,IAAI,GAAI;AACxC,YAAM,IAAI,MAAM,iCAAiC,QAAQ,IAAI,EAAE;AAAA,IACjE;AAEA,UAAM,SAAS,MAAM,WAAW,QAAQ,IAAI;AAG5C,QAAI,qBAAsC,CAAA;AAE1C,QAAI,QAAQ,KAAK;AACf,2BAAqB,OAAO;AAC5B,cAAQ;AAAA,QACN,MAAM;AAAA,UACJ,gCAAgC,mBAAmB,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA,QAAA;AAAA,MAClF;AAAA,IAEJ,WAAW,QAAQ,aAAa;AAC9B,YAAM,UAAU,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,WAAW;AAC1E,UAAI,CAAC,SAAS;AACZ,cAAM,IAAI;AAAA,UACR,YACE,QAAQ,WACV,qDAAqD,OAAO,SACzD,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK,IAAI,CAAC;AAAA,QAAA;AAAA,MAEjB;AACA,2BAAqB,CAAC,OAAO;AAC7B,cAAQ,IAAI,MAAM,KAAK,2BAA2B,QAAQ,WAAW;AAAA,CAAI,CAAC;AAAA,IAC5E,OAAO;AACL,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AAGA,YAAQ,IAAI,MAAM,OAAO,qCAAqC,CAAC;AAC/D,UAAM,YAAY;AAAA,MAChB,MAAM,QAAQ;AAAA,MACd,KAAK,QAAQ;AAAA,MACb,aAAa,QAAQ;AAAA,IAAA,CACtB;AAGD,YAAQ,IAAI,MAAM,OAAO,mCAAmC,CAAC;AAC7D,UAAM,cAAc;AAAA,MAClB,MAAM,QAAQ;AAAA,MACd,OAAO,QAAQ;AAAA,MACf,OAAO,QAAQ;AAAA,IAAA,CAChB;AAED,YAAQ,IAAI,MAAM,MAAM,KAAK,yCAAyC,CAAC;AAAA,EACzE,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,MAAM,IAAI,kCAAkC;AAAA,MAC5C,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAAA;AAE3C,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;"}