@simplens/onboard 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +331 -214
- package/dist/__tests__/env-config.test.d.ts +2 -0
- package/dist/__tests__/env-config.test.d.ts.map +1 -0
- package/dist/__tests__/env-config.test.js +23 -0
- package/dist/__tests__/env-config.test.js.map +1 -0
- package/dist/__tests__/infra-prompts.test.d.ts +2 -0
- package/dist/__tests__/infra-prompts.test.d.ts.map +1 -0
- package/dist/__tests__/infra-prompts.test.js +43 -0
- package/dist/__tests__/infra-prompts.test.js.map +1 -0
- package/dist/__tests__/infra.test.d.ts +2 -0
- package/dist/__tests__/infra.test.d.ts.map +1 -0
- package/dist/__tests__/infra.test.js +14 -0
- package/dist/__tests__/infra.test.js.map +1 -0
- package/dist/__tests__/nginx.test.d.ts +2 -0
- package/dist/__tests__/nginx.test.d.ts.map +1 -0
- package/dist/__tests__/nginx.test.js +16 -0
- package/dist/__tests__/nginx.test.js.map +1 -0
- package/dist/env-config.d.ts +27 -12
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +253 -128
- package/dist/env-config.js.map +1 -1
- package/dist/index.js +340 -69
- package/dist/index.js.map +1 -1
- package/dist/infra.d.ts +19 -8
- package/dist/infra.d.ts.map +1 -1
- package/dist/infra.js +267 -128
- package/dist/infra.js.map +1 -1
- package/dist/plugins.d.ts +5 -10
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +75 -44
- package/dist/plugins.js.map +1 -1
- package/dist/services.d.ts +1 -23
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +47 -62
- package/dist/services.js.map +1 -1
- package/dist/templates.d.ts +2 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +203 -191
- package/dist/templates.js.map +1 -1
- package/dist/types/domain.d.ts +2 -0
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/ui.d.ts +45 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +93 -0
- package/dist/ui.js.map +1 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +32 -7
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +66 -2
- package/dist/utils.js.map +1 -1
- package/dist/validators.d.ts +1 -52
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +10 -57
- package/dist/validators.js.map +1 -1
- package/package.json +3 -5
- package/src/__tests__/env-config.test.ts +28 -0
- package/src/__tests__/errors.test.ts +187 -187
- package/src/__tests__/infra-prompts.test.ts +54 -0
- package/src/__tests__/infra.test.ts +15 -0
- package/src/__tests__/utils.test.ts +142 -142
- package/src/__tests__/validators.test.ts +195 -195
- package/src/config/constants.ts +86 -86
- package/src/config/index.ts +1 -1
- package/src/env-config.ts +455 -311
- package/src/index.ts +534 -202
- package/src/infra.ts +404 -245
- package/src/plugins.ts +221 -190
- package/src/services.ts +175 -190
- package/src/templates.ts +209 -196
- package/src/types/domain.ts +129 -127
- package/src/types/errors.ts +173 -173
- package/src/types/index.ts +2 -2
- package/src/ui.ts +91 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/logger.ts +144 -118
- package/src/utils.ts +183 -105
- package/src/validators.ts +145 -192
- package/tsconfig.json +18 -18
- package/vitest.config.ts +22 -22
package/src/infra.ts
CHANGED
|
@@ -1,245 +1,404 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import type { InfraService } from './types/domain.js';
|
|
7
|
-
|
|
8
|
-
const INFRA_SERVICES: InfraService[] = [
|
|
9
|
-
{ name: 'MongoDB (Database)', value: 'mongo', checked: true },
|
|
10
|
-
{ name: 'Kafka (Message Queue)', value: 'kafka', checked: true },
|
|
11
|
-
{ name: 'Kafka UI (Dashboard)', value: 'kafka-ui', checked: true },
|
|
12
|
-
{ name: 'Redis (Cache)', value: 'redis', checked: true },
|
|
13
|
-
{ name: '
|
|
14
|
-
{ name: '
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @returns Array of selected service IDs (e.g., ['mongo', 'kafka', 'redis'])
|
|
22
|
-
* @throws Error if no services are selected
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
ports:
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
- "
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
1
|
+
import { APP_COMPOSE_TEMPLATE, APP_NGINX_SERVICE_TEMPLATE } from './templates.js';
|
|
2
|
+
import { writeFile, logInfo, logSuccess } from './utils.js';
|
|
3
|
+
import { multiselect } from '@clack/prompts';
|
|
4
|
+
import { handleCancel, spinner } from './ui.js';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import type { InfraService } from './types/domain.js';
|
|
7
|
+
|
|
8
|
+
const INFRA_SERVICES: InfraService[] = [
|
|
9
|
+
{ name: 'MongoDB (Database)', value: 'mongo', checked: true },
|
|
10
|
+
{ name: 'Kafka (Message Queue)', value: 'kafka', checked: true },
|
|
11
|
+
{ name: 'Kafka UI (Dashboard)', value: 'kafka-ui', checked: true },
|
|
12
|
+
{ name: 'Redis (Cache)', value: 'redis', checked: true },
|
|
13
|
+
{ name: 'Nginx (Reverse Proxy)', value: 'nginx', checked: false },
|
|
14
|
+
{ name: 'Loki (Log Aggregation)', value: 'loki', checked: false },
|
|
15
|
+
{ name: 'Grafana (Observability Dashboard)', value: 'grafana', checked: false },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Prompts user to select which infrastructure services to deploy.
|
|
20
|
+
*
|
|
21
|
+
* @returns Array of selected service IDs (e.g., ['mongo', 'kafka', 'redis'])
|
|
22
|
+
* @throws Error if no services are selected
|
|
23
|
+
*/
|
|
24
|
+
export async function promptInfraServices(): Promise<string[]> {
|
|
25
|
+
return promptInfraServicesWithBasePath({ allowNginx: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Prompts infrastructure services with optional nginx availability.
|
|
30
|
+
* If nginx is disabled, it is removed from choices and from result safety-check.
|
|
31
|
+
*/
|
|
32
|
+
export async function promptInfraServicesWithBasePath(options: {
|
|
33
|
+
allowNginx: boolean;
|
|
34
|
+
}): Promise<string[]> {
|
|
35
|
+
const choices = options.allowNginx
|
|
36
|
+
? INFRA_SERVICES
|
|
37
|
+
: INFRA_SERVICES.filter(service => service.value !== 'nginx');
|
|
38
|
+
|
|
39
|
+
const message = options.allowNginx
|
|
40
|
+
? 'Select infrastructure services to run (Space to select, Enter to confirm):'
|
|
41
|
+
: 'Select infrastructure services to run (Space to select, Enter to confirm) — nginx disabled:';
|
|
42
|
+
|
|
43
|
+
const selected = await multiselect({
|
|
44
|
+
message,
|
|
45
|
+
options: choices.map(s => ({
|
|
46
|
+
value: s.value,
|
|
47
|
+
label: s.name,
|
|
48
|
+
hint: s.checked ? 'recommended' : undefined,
|
|
49
|
+
})),
|
|
50
|
+
initialValues: choices.filter(s => s.checked).map(s => s.value),
|
|
51
|
+
required: true,
|
|
52
|
+
withGuide: true,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
handleCancel(selected);
|
|
56
|
+
const result = selected as string[];
|
|
57
|
+
|
|
58
|
+
if (options.allowNginx) {
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result.filter(service => service !== 'nginx');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Service chunk definitions - each service as a complete block
|
|
69
|
+
*/
|
|
70
|
+
const SERVICE_CHUNKS: Record<string, string> = {
|
|
71
|
+
'mongo': ` mongo:
|
|
72
|
+
image: mongo:7.0
|
|
73
|
+
container_name: mongo
|
|
74
|
+
command: [ "--replSet", "rs0", "--bind_ip_all", "--port", "27017" ]
|
|
75
|
+
ports:
|
|
76
|
+
- 27017:27017
|
|
77
|
+
healthcheck:
|
|
78
|
+
test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongo:27017'}]}) }" | mongosh --port 27017 --quiet
|
|
79
|
+
interval: 5s
|
|
80
|
+
timeout: 30s
|
|
81
|
+
start_period: 0s
|
|
82
|
+
start_interval: 1s
|
|
83
|
+
retries: 30
|
|
84
|
+
volumes:
|
|
85
|
+
- "mongo_data:/data/db"
|
|
86
|
+
- "mongo_config:/data/configdb"`,
|
|
87
|
+
|
|
88
|
+
'kafka': ` kafka:
|
|
89
|
+
image: apache/kafka-native
|
|
90
|
+
container_name: kafka
|
|
91
|
+
ports:
|
|
92
|
+
- "9092:9092"
|
|
93
|
+
environment:
|
|
94
|
+
# Configure listeners for both docker and host communication
|
|
95
|
+
KAFKA_LISTENERS: CONTROLLER://localhost:9091,HOST://0.0.0.0:9092,DOCKER://0.0.0.0:9093
|
|
96
|
+
KAFKA_ADVERTISED_LISTENERS: HOST://kafka:9092,DOCKER://kafka:9093
|
|
97
|
+
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,DOCKER:PLAINTEXT,HOST:PLAINTEXT
|
|
98
|
+
|
|
99
|
+
# Settings required for KRaft mode
|
|
100
|
+
KAFKA_NODE_ID: 1
|
|
101
|
+
KAFKA_PROCESS_ROLES: broker,controller
|
|
102
|
+
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
|
103
|
+
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9091
|
|
104
|
+
|
|
105
|
+
# Listener to use for broker-to-broker communication
|
|
106
|
+
KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER
|
|
107
|
+
|
|
108
|
+
# Required for a single node cluster
|
|
109
|
+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
|
110
|
+
|
|
111
|
+
# Disable auto-topic creation - API server will create topics with correct partitions
|
|
112
|
+
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
|
|
113
|
+
volumes:
|
|
114
|
+
- "kafka_data:/var/lib/kafka/data"`,
|
|
115
|
+
|
|
116
|
+
'kafka-ui': ` kafka-ui:
|
|
117
|
+
image: kafbat/kafka-ui:main
|
|
118
|
+
container_name: kafka-ui
|
|
119
|
+
ports:
|
|
120
|
+
- 8080:8080
|
|
121
|
+
environment:
|
|
122
|
+
DYNAMIC_CONFIG_ENABLED: "true"
|
|
123
|
+
KAFKA_CLUSTERS_0_NAME: local
|
|
124
|
+
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9093
|
|
125
|
+
depends_on:
|
|
126
|
+
- kafka`,
|
|
127
|
+
|
|
128
|
+
'redis': ` redis:
|
|
129
|
+
image: redis:7-alpine
|
|
130
|
+
container_name: redis
|
|
131
|
+
ports:
|
|
132
|
+
- "6379:6379"
|
|
133
|
+
command: redis-server --appendonly yes
|
|
134
|
+
volumes:
|
|
135
|
+
- "redis_data:/data"
|
|
136
|
+
healthcheck:
|
|
137
|
+
test: [ "CMD", "redis-cli", "ping" ]
|
|
138
|
+
interval: 5s
|
|
139
|
+
timeout: 3s
|
|
140
|
+
retries: 5`,
|
|
141
|
+
|
|
142
|
+
'loki': ` loki:
|
|
143
|
+
image: grafana/loki:2.9.0
|
|
144
|
+
container_name: loki
|
|
145
|
+
ports:
|
|
146
|
+
- "3100:3100"
|
|
147
|
+
command: -config.file=/etc/loki/local-config.yaml
|
|
148
|
+
volumes:
|
|
149
|
+
- "loki_data:/loki"
|
|
150
|
+
healthcheck:
|
|
151
|
+
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3100/ready || exit 1" ]
|
|
152
|
+
interval: 10s
|
|
153
|
+
timeout: 5s
|
|
154
|
+
retries: 5`,
|
|
155
|
+
|
|
156
|
+
'grafana': ` grafana:
|
|
157
|
+
image: grafana/grafana:10.2.0
|
|
158
|
+
container_name: grafana
|
|
159
|
+
ports:
|
|
160
|
+
- "3001:3000"
|
|
161
|
+
environment:
|
|
162
|
+
- GF_PATHS_PROVISIONING=/etc/grafana/provisioning
|
|
163
|
+
- GF_AUTH_ANONYMOUS_ENABLED=true
|
|
164
|
+
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
|
|
165
|
+
- GF_SECURITY_ADMIN_PASSWORD=admin
|
|
166
|
+
volumes:
|
|
167
|
+
- "grafana_data:/var/lib/grafana"
|
|
168
|
+
depends_on:
|
|
169
|
+
loki:
|
|
170
|
+
condition: service_healthy`,
|
|
171
|
+
|
|
172
|
+
'nginx': ` nginx:
|
|
173
|
+
image: nginx:alpine
|
|
174
|
+
container_name: nginx
|
|
175
|
+
ports:
|
|
176
|
+
- "80:80"
|
|
177
|
+
volumes:
|
|
178
|
+
- "./nginx.conf:/etc/nginx/conf.d/default.conf:ro"
|
|
179
|
+
restart: unless-stopped`,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Service-to-volumes mapping
|
|
184
|
+
*/
|
|
185
|
+
const SERVICE_VOLUMES: Record<string, string[]> = {
|
|
186
|
+
'mongo': ['mongo_data', 'mongo_config'],
|
|
187
|
+
'kafka': ['kafka_data'],
|
|
188
|
+
'kafka-ui': [],
|
|
189
|
+
'redis': ['redis_data'],
|
|
190
|
+
'nginx': [],
|
|
191
|
+
'loki': ['loki_data'],
|
|
192
|
+
'grafana': ['grafana_data'],
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Build docker-compose content from selected services
|
|
197
|
+
*/
|
|
198
|
+
function buildInfraCompose(selectedServices: string[]): string {
|
|
199
|
+
// Header
|
|
200
|
+
const header = `# ============================================
|
|
201
|
+
# SimpleNS Infrastructure Services
|
|
202
|
+
# All services use Docker service names for container-to-container communication.
|
|
203
|
+
# This ensures cross-platform compatibility (Windows, Linux, macOS).
|
|
204
|
+
# ============================================
|
|
205
|
+
|
|
206
|
+
services:
|
|
207
|
+
# ============================================
|
|
208
|
+
# Infrastructure Services
|
|
209
|
+
# ============================================`;
|
|
210
|
+
|
|
211
|
+
// Assemble selected service chunks
|
|
212
|
+
const serviceBlocks: string[] = [];
|
|
213
|
+
for (const service of selectedServices) {
|
|
214
|
+
if (SERVICE_CHUNKS[service]) {
|
|
215
|
+
serviceBlocks.push(SERVICE_CHUNKS[service]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Collect volumes for selected services
|
|
220
|
+
const volumeSet = new Set<string>();
|
|
221
|
+
for (const service of selectedServices) {
|
|
222
|
+
const volumes = SERVICE_VOLUMES[service] || [];
|
|
223
|
+
volumes.forEach(v => volumeSet.add(v));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Build volumes section
|
|
227
|
+
const volumeLines: string[] = ['', 'volumes:'];
|
|
228
|
+
for (const volume of Array.from(volumeSet).sort()) {
|
|
229
|
+
volumeLines.push(` ${volume}:`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Build networks section with custom default network name
|
|
233
|
+
const networkLines: string[] = ['', 'networks:', ' default:', ' name: simplens'];
|
|
234
|
+
|
|
235
|
+
// Combine all parts
|
|
236
|
+
return [
|
|
237
|
+
header,
|
|
238
|
+
serviceBlocks.join('\n\n'),
|
|
239
|
+
volumeLines.join('\n'),
|
|
240
|
+
networkLines.join('\n'),
|
|
241
|
+
].join('\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Generate and write docker-compose.infra.yaml
|
|
248
|
+
*/
|
|
249
|
+
export async function generateInfraCompose(
|
|
250
|
+
targetDir: string,
|
|
251
|
+
selectedServices: string[]
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
const s = spinner();
|
|
254
|
+
s.start('Generating docker-compose.infra.yaml...');
|
|
255
|
+
|
|
256
|
+
// Build compose content from service chunks
|
|
257
|
+
const infraContent = buildInfraCompose(selectedServices);
|
|
258
|
+
|
|
259
|
+
// Write infrastructure compose file
|
|
260
|
+
const infraPath = path.join(targetDir, 'docker-compose.infra.yaml');
|
|
261
|
+
await writeFile(infraPath, infraContent);
|
|
262
|
+
s.stop('Generated docker-compose.infra.yaml');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Build app docker-compose content.
|
|
267
|
+
* Optionally inject nginx reverse-proxy service before the volumes section.
|
|
268
|
+
*/
|
|
269
|
+
export function buildAppComposeContent(includeNginx: boolean): string {
|
|
270
|
+
if (!includeNginx) {
|
|
271
|
+
return APP_COMPOSE_TEMPLATE;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const marker = '\nvolumes:';
|
|
275
|
+
if (!APP_COMPOSE_TEMPLATE.includes(marker)) {
|
|
276
|
+
return APP_COMPOSE_TEMPLATE;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return APP_COMPOSE_TEMPLATE.replace(marker, `\n${APP_NGINX_SERVICE_TEMPLATE}\n${marker}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Write app docker-compose.yaml
|
|
284
|
+
*/
|
|
285
|
+
export async function writeAppCompose(
|
|
286
|
+
targetDir: string,
|
|
287
|
+
options: { includeNginx?: boolean } = {}
|
|
288
|
+
): Promise<void> {
|
|
289
|
+
const s = spinner();
|
|
290
|
+
s.start('Generating docker-compose.yaml...');
|
|
291
|
+
const appPath = path.join(targetDir, 'docker-compose.yaml');
|
|
292
|
+
const appContent = buildAppComposeContent(options.includeNginx === true);
|
|
293
|
+
await writeFile(appPath, appContent);
|
|
294
|
+
s.stop('Generated docker-compose.yaml');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Generate nginx.conf based on basePath configuration
|
|
299
|
+
*/
|
|
300
|
+
export async function generateNginxConfig(
|
|
301
|
+
targetDir: string,
|
|
302
|
+
basePath: string
|
|
303
|
+
): Promise<void> {
|
|
304
|
+
const s = spinner();
|
|
305
|
+
s.start('Generating nginx.conf...');
|
|
306
|
+
|
|
307
|
+
// Normalize basePath (remove leading/trailing slashes for template)
|
|
308
|
+
const normalizedPath = basePath.trim().replace(/^\/|\/$/g, '');
|
|
309
|
+
const hasBasePath = normalizedPath.length > 0;
|
|
310
|
+
|
|
311
|
+
// Template for nginx.conf
|
|
312
|
+
const nginxTemplate = `server {
|
|
313
|
+
listen 80;
|
|
314
|
+
server_name localhost;
|
|
315
|
+
|
|
316
|
+
location /api/notification/ {
|
|
317
|
+
proxy_pass http://api:3000;
|
|
318
|
+
proxy_http_version 1.1;
|
|
319
|
+
|
|
320
|
+
proxy_set_header Host $host;
|
|
321
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
322
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
323
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
location = /runtime-config.js {
|
|
327
|
+
proxy_pass http://dashboard:3002/runtime-config.js;
|
|
328
|
+
proxy_http_version 1.1;
|
|
329
|
+
|
|
330
|
+
proxy_set_header Host $host;
|
|
331
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
332
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
333
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
334
|
+
|
|
335
|
+
# optional: prevent caching if config is dynamic
|
|
336
|
+
add_header Cache-Control "no-store";
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
location ^~ /_next/ {
|
|
340
|
+
proxy_pass http://dashboard:3002/_next/;
|
|
341
|
+
proxy_http_version 1.1;
|
|
342
|
+
|
|
343
|
+
proxy_set_header Host $host;
|
|
344
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
345
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
346
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
347
|
+
|
|
348
|
+
expires 1y;
|
|
349
|
+
add_header Cache-Control "public, immutable";
|
|
350
|
+
}
|
|
351
|
+
${hasBasePath ? `
|
|
352
|
+
location ^~ /${normalizedPath}/_next/ {
|
|
353
|
+
proxy_pass http://dashboard:3002/_next/;
|
|
354
|
+
proxy_http_version 1.1;
|
|
355
|
+
|
|
356
|
+
proxy_set_header Host $host;
|
|
357
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
358
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
359
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
360
|
+
|
|
361
|
+
expires 1y;
|
|
362
|
+
add_header Cache-Control "public, immutable";
|
|
363
|
+
}
|
|
364
|
+
` : ''}
|
|
365
|
+
location ~* \\.(png|jpg|jpeg|gif|ico|svg|webp|woff|woff2|ttf|eot)$ {
|
|
366
|
+
proxy_pass http://dashboard:3002;
|
|
367
|
+
proxy_http_version 1.1;
|
|
368
|
+
|
|
369
|
+
proxy_set_header Host $host;
|
|
370
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
371
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
372
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
373
|
+
|
|
374
|
+
expires 1y;
|
|
375
|
+
add_header Cache-Control "public, max-age=31536000";
|
|
376
|
+
}
|
|
377
|
+
${hasBasePath ? `
|
|
378
|
+
location ^~ /${normalizedPath} {
|
|
379
|
+
proxy_pass http://dashboard:3002;
|
|
380
|
+
proxy_http_version 1.1;
|
|
381
|
+
|
|
382
|
+
proxy_set_header Host $host;
|
|
383
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
384
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
385
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
386
|
+
}
|
|
387
|
+
` : `
|
|
388
|
+
location / {
|
|
389
|
+
proxy_pass http://dashboard:3002;
|
|
390
|
+
proxy_http_version 1.1;
|
|
391
|
+
|
|
392
|
+
proxy_set_header Host $host;
|
|
393
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
394
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
395
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
396
|
+
}
|
|
397
|
+
`}
|
|
398
|
+
}
|
|
399
|
+
`;
|
|
400
|
+
|
|
401
|
+
const nginxPath = path.join(targetDir, 'nginx.conf');
|
|
402
|
+
await writeFile(nginxPath, nginxTemplate);
|
|
403
|
+
s.stop(`Generated nginx.conf${hasBasePath ? ` with base path: /${normalizedPath}` : ' (root path)'}`);
|
|
404
|
+
}
|