@simplens/onboard 1.0.0 → 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 +258 -141
- package/dist/env-config.js.map +1 -1
- package/dist/index.js +341 -71
- package/dist/index.js.map +1 -1
- package/dist/infra.d.ts +17 -14
- package/dist/infra.d.ts.map +1 -1
- package/dist/infra.js +265 -176
- 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 +3 -2
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +203 -198
- 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 -320
- package/src/index.ts +534 -203
- package/src/infra.ts +404 -300
- package/src/plugins.ts +221 -190
- package/src/services.ts +175 -190
- package/src/templates.ts +209 -203
- 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 -20
package/src/env-config.ts
CHANGED
|
@@ -1,320 +1,455 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
1
|
+
import { writeFile, appendFile, logInfo, logSuccess, logWarning } from './utils.js';
|
|
2
|
+
import { text, password } from '@clack/prompts';
|
|
3
|
+
import { handleCancel } from './ui.js';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
import { CRITICAL_ENV_KEYS } from './config/constants.js';
|
|
7
|
+
import type { EnvVariable } from './types/domain.js';
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_BASE_PATH = '';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate a secure random string for credentials
|
|
13
|
+
*/
|
|
14
|
+
export function generateSecureRandom(length: number = 32): string {
|
|
15
|
+
return crypto.randomBytes(length).toString('base64').slice(0, length);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate default value for a critical environment variable
|
|
20
|
+
*/
|
|
21
|
+
export function generateDefaultValue(key: string): string {
|
|
22
|
+
if (key === 'NS_API_KEY') {
|
|
23
|
+
return `sk_${generateSecureRandom(48)}`;
|
|
24
|
+
}
|
|
25
|
+
if (key === 'AUTH_SECRET') {
|
|
26
|
+
return generateSecureRandom(64);
|
|
27
|
+
}
|
|
28
|
+
if (key === 'ADMIN_PASSWORD') {
|
|
29
|
+
return `Admin${generateSecureRandom(16)}`;
|
|
30
|
+
}
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validate BASE_PATH value.
|
|
36
|
+
* Accepts:
|
|
37
|
+
* - Empty value for root path
|
|
38
|
+
* - Slash-prefixed lowercase segments (e.g. /dashboard, /admin/v1)
|
|
39
|
+
*/
|
|
40
|
+
export function validateBasePath(input: string): true | string {
|
|
41
|
+
const value = input.trim();
|
|
42
|
+
|
|
43
|
+
if (!value) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!value.startsWith('/')) {
|
|
48
|
+
return 'Base path must start with / (example: /dashboard)';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (value.endsWith('/')) {
|
|
52
|
+
return 'Base path must not end with /';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!/^\/[a-z0-9-]+(?:\/[a-z0-9-]+)*$/.test(value)) {
|
|
56
|
+
return 'Use lowercase letters, numbers, hyphens, and "/" separators only';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Normalize BASE_PATH for consistent downstream use.
|
|
64
|
+
*/
|
|
65
|
+
export function normalizeBasePath(input: string): string {
|
|
66
|
+
return input.trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Prompt BASE_PATH once at the beginning of onboarding.
|
|
71
|
+
*/
|
|
72
|
+
export async function promptBasePath(defaultValue: string = DEFAULT_BASE_PATH): Promise<string> {
|
|
73
|
+
const result = await text({
|
|
74
|
+
message: 'BASE_PATH for dashboard (leave empty for root, example: /dashboard):',
|
|
75
|
+
placeholder: defaultValue || 'leave empty for root',
|
|
76
|
+
defaultValue,
|
|
77
|
+
validate: (value: string | undefined) => {
|
|
78
|
+
const v = validateBasePath(value ?? '');
|
|
79
|
+
return v === true ? undefined : v;
|
|
80
|
+
},
|
|
81
|
+
withGuide: true,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
handleCancel(result);
|
|
85
|
+
return normalizeBasePath(result as string);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Load and parse .env.example from embedded template
|
|
90
|
+
*/
|
|
91
|
+
export async function loadEnvExample(): Promise<EnvVariable[]> {
|
|
92
|
+
// Embedded .env template - always available regardless of installation
|
|
93
|
+
const envTemplate = `
|
|
94
|
+
NODE_ENV=production
|
|
95
|
+
# ============================================
|
|
96
|
+
# API SERVER
|
|
97
|
+
# ============================================
|
|
98
|
+
NS_API_KEY=
|
|
99
|
+
PORT=3000
|
|
100
|
+
MAX_BATCH_REQ_LIMIT=1000
|
|
101
|
+
|
|
102
|
+
# ============================================
|
|
103
|
+
# DATABASE
|
|
104
|
+
# ============================================
|
|
105
|
+
MONGO_URI=
|
|
106
|
+
|
|
107
|
+
# ============================================
|
|
108
|
+
# KAFKA
|
|
109
|
+
# ============================================
|
|
110
|
+
BROKERS=
|
|
111
|
+
|
|
112
|
+
# Kafka Topic Partitions (Core Topics)
|
|
113
|
+
DELAYED_PARTITION=1
|
|
114
|
+
NOTIFICATION_STATUS_PARTITION=1
|
|
115
|
+
|
|
116
|
+
# ============================================
|
|
117
|
+
# REDIS
|
|
118
|
+
# ============================================
|
|
119
|
+
REDIS_URL=
|
|
120
|
+
|
|
121
|
+
# ============================================
|
|
122
|
+
# PLUGIN SYSTEM
|
|
123
|
+
# ============================================
|
|
124
|
+
SIMPLENS_CONFIG_PATH=./simplens.config.yaml
|
|
125
|
+
PROCESSOR_CHANNEL=all
|
|
126
|
+
|
|
127
|
+
# ============================================
|
|
128
|
+
# BACKGROUND WORKER
|
|
129
|
+
# ============================================
|
|
130
|
+
OUTBOX_POLL_INTERVAL_MS=5000
|
|
131
|
+
OUTBOX_CLEANUP_INTERVAL_MS=60000
|
|
132
|
+
OUTBOX_BATCH_SIZE=100
|
|
133
|
+
OUTBOX_RETENTION_MS=300000
|
|
134
|
+
OUTBOX_CLAIM_TIMEOUT_MS=30000
|
|
135
|
+
|
|
136
|
+
# ============================================
|
|
137
|
+
# RETRY & IDEMPOTENCY
|
|
138
|
+
# ============================================
|
|
139
|
+
IDEMPOTENCY_TTL_SECONDS=86400
|
|
140
|
+
MAX_RETRY_COUNT=5
|
|
141
|
+
PROCESSING_TTL_SECONDS=120
|
|
142
|
+
|
|
143
|
+
# ============================================
|
|
144
|
+
# DELAYED NOTIFICATIONS
|
|
145
|
+
# ============================================
|
|
146
|
+
DELAYED_POLL_INTERVAL_MS=1000
|
|
147
|
+
DELAYED_BATCH_SIZE=10
|
|
148
|
+
MAX_POLLER_RETRIES=3
|
|
149
|
+
|
|
150
|
+
# ============================================
|
|
151
|
+
# RECOVERY SERVICE
|
|
152
|
+
# ============================================
|
|
153
|
+
RECOVERY_POLL_INTERVAL_MS=60000
|
|
154
|
+
PROCESSING_STUCK_THRESHOLD_MS=300000
|
|
155
|
+
PENDING_STUCK_THRESHOLD_MS=300000
|
|
156
|
+
RECOVERY_BATCH_SIZE=50
|
|
157
|
+
RECOVERY_CLAIM_TIMEOUT_MS=60000
|
|
158
|
+
|
|
159
|
+
# ============================================
|
|
160
|
+
# CLEANUP
|
|
161
|
+
# ============================================
|
|
162
|
+
CLEANUP_RESOLVED_ALERTS_RETENTION_MS=86400000
|
|
163
|
+
CLEANUP_PROCESSED_STATUS_OUTBOX_RETENTION_MS=86400000
|
|
164
|
+
|
|
165
|
+
# ============================================
|
|
166
|
+
# LOGGING
|
|
167
|
+
# ============================================
|
|
168
|
+
LOKI_URL=
|
|
169
|
+
LOG_LEVEL=info
|
|
170
|
+
LOG_TO_FILE=true
|
|
171
|
+
|
|
172
|
+
# ============================================
|
|
173
|
+
# DOCKER IMAGE VERSIONS
|
|
174
|
+
# ============================================
|
|
175
|
+
CORE_VERSION=latest
|
|
176
|
+
DASHBOARD_VERSION=latest
|
|
177
|
+
|
|
178
|
+
# ============================================
|
|
179
|
+
# ADMIN DASHBOARD
|
|
180
|
+
# ============================================
|
|
181
|
+
AUTH_SECRET=
|
|
182
|
+
ADMIN_USERNAME=admin
|
|
183
|
+
ADMIN_PASSWORD=
|
|
184
|
+
AUTH_TRUST_HOST=true
|
|
185
|
+
API_BASE_URL=http://api:3000
|
|
186
|
+
WEBHOOK_HOST=dashboard
|
|
187
|
+
WEBHOOK_PORT=3002
|
|
188
|
+
BASE_PATH=
|
|
189
|
+
DASHBOARD_PORT=3002
|
|
190
|
+
`;
|
|
191
|
+
|
|
192
|
+
return parseEnvContent(envTemplate);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Parse .env content into structured format
|
|
197
|
+
*/
|
|
198
|
+
function parseEnvContent(content: string): EnvVariable[] {
|
|
199
|
+
const lines = content.split('\n');
|
|
200
|
+
const variables: EnvVariable[] = [];
|
|
201
|
+
let currentComment = '';
|
|
202
|
+
|
|
203
|
+
for (const line of lines) {
|
|
204
|
+
const trimmed = line.trim();
|
|
205
|
+
|
|
206
|
+
// Capture comments as descriptions
|
|
207
|
+
if (trimmed.startsWith('#') && !trimmed.includes('====')) {
|
|
208
|
+
currentComment = trimmed.replace(/^#\s*/, '');
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Skip empty lines and section dividers
|
|
213
|
+
if (!trimmed || trimmed.includes('====')) {
|
|
214
|
+
currentComment = '';
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Parse key=value pairs
|
|
219
|
+
const match = trimmed.match(/^([A-Z_]+)=(.*)$/);
|
|
220
|
+
if (match) {
|
|
221
|
+
const [, key, value] = match;
|
|
222
|
+
variables.push({
|
|
223
|
+
key,
|
|
224
|
+
value: value || '',
|
|
225
|
+
description: currentComment || undefined,
|
|
226
|
+
required: CRITICAL_ENV_KEYS.includes(key) || !value,
|
|
227
|
+
});
|
|
228
|
+
currentComment = '';
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return variables;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Prompts user for environment variable values based on the selected mode.
|
|
237
|
+
*
|
|
238
|
+
* @param mode - 'default' prompts only for critical vars, 'interactive' prompts for all
|
|
239
|
+
* @param infraServices - List of selected infrastructure service IDs
|
|
240
|
+
* @param basePath - BASE_PATH value already collected
|
|
241
|
+
* @param fullMode - If true, auto-generate critical values without prompting
|
|
242
|
+
* @returns Map of environment variable keys to values
|
|
243
|
+
*/
|
|
244
|
+
export async function promptEnvVariables(
|
|
245
|
+
mode: 'default' | 'interactive',
|
|
246
|
+
infraServices: string[],
|
|
247
|
+
basePath: string = DEFAULT_BASE_PATH,
|
|
248
|
+
fullMode: boolean = false
|
|
249
|
+
): Promise<Map<string, string>> {
|
|
250
|
+
logInfo('Configuring environment variables...');
|
|
251
|
+
|
|
252
|
+
const envVars = await loadEnvExample();
|
|
253
|
+
const result = new Map<string, string>();
|
|
254
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
255
|
+
const basePathLabel = normalizedBasePath || '(root)';
|
|
256
|
+
logInfo(`BASE_PATH selected: ${basePathLabel}`);
|
|
257
|
+
|
|
258
|
+
// Auto-fill infra connection URLs based on selected services using Docker service names
|
|
259
|
+
const autoInfraUrls: Record<string, string> = {
|
|
260
|
+
MONGO_URI: infraServices.includes('mongo')
|
|
261
|
+
? `mongodb://mongo:27017/simplens?replicaSet=rs0`
|
|
262
|
+
: '',
|
|
263
|
+
BROKERS: infraServices.includes('kafka')
|
|
264
|
+
? 'kafka:9093'
|
|
265
|
+
: '',
|
|
266
|
+
REDIS_URL: infraServices.includes('redis')
|
|
267
|
+
? 'redis://redis:6379'
|
|
268
|
+
: '',
|
|
269
|
+
LOKI_URL: infraServices.includes('loki')
|
|
270
|
+
? 'http://loki:3100'
|
|
271
|
+
: '',
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
if (mode === 'default') {
|
|
275
|
+
// Use defaults, only prompt for critical values
|
|
276
|
+
for (const envVar of envVars) {
|
|
277
|
+
// Use auto-filled infra URLs
|
|
278
|
+
if (autoInfraUrls[envVar.key]) {
|
|
279
|
+
result.set(envVar.key, autoInfraUrls[envVar.key]);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// BASE_PATH is collected upfront in onboarding flow
|
|
284
|
+
if (envVar.key === 'BASE_PATH') {
|
|
285
|
+
result.set(envVar.key, normalizedBasePath);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Use default value if available
|
|
290
|
+
if (envVar.value && !CRITICAL_ENV_KEYS.includes(envVar.key)) {
|
|
291
|
+
result.set(envVar.key, envVar.value);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Prompt for critical values (only if not auto-filled)
|
|
296
|
+
if (CRITICAL_ENV_KEYS.includes(envVar.key)) {
|
|
297
|
+
if (fullMode) {
|
|
298
|
+
// In full mode, auto-generate critical values
|
|
299
|
+
const defaultValue = generateDefaultValue(envVar.key);
|
|
300
|
+
if (defaultValue) {
|
|
301
|
+
result.set(envVar.key, defaultValue);
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
// Show changelog info for version variables
|
|
305
|
+
if (envVar.key === 'CORE_VERSION' || envVar.key === 'DASHBOARD_VERSION') {
|
|
306
|
+
logInfo('ℹ️ Visit https://simplens.in/changelog for version information');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const promptMessage = `${envVar.key}${envVar.description ? ` (${envVar.description})` : ''}:`;
|
|
310
|
+
const isPasswordField = envVar.key.includes('PASSWORD');
|
|
311
|
+
|
|
312
|
+
let answer: string | symbol;
|
|
313
|
+
if (isPasswordField) {
|
|
314
|
+
answer = await password({
|
|
315
|
+
message: promptMessage,
|
|
316
|
+
validate: (input: string | undefined) => {
|
|
317
|
+
if (!input && envVar.required) {
|
|
318
|
+
return `${envVar.key} is required`;
|
|
319
|
+
}
|
|
320
|
+
return undefined;
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
answer = await text({
|
|
325
|
+
message: promptMessage,
|
|
326
|
+
placeholder: getSuggestedValue(envVar.key) || undefined,
|
|
327
|
+
defaultValue: getSuggestedValue(envVar.key) || undefined,
|
|
328
|
+
validate: (input: string | undefined) => {
|
|
329
|
+
if (!input && envVar.required) {
|
|
330
|
+
return `${envVar.key} is required`;
|
|
331
|
+
}
|
|
332
|
+
return undefined;
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
handleCancel(answer);
|
|
338
|
+
result.set(envVar.key, answer as string);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
// Interactive mode: prompt for everything
|
|
344
|
+
logInfo('Interactive mode: You will be prompted for each environment variable.');
|
|
345
|
+
|
|
346
|
+
for (const envVar of envVars) {
|
|
347
|
+
const defaultValue = autoInfraUrls[envVar.key] || envVar.value || getSuggestedValue(envVar.key);
|
|
348
|
+
|
|
349
|
+
// BASE_PATH is collected upfront in onboarding flow
|
|
350
|
+
if (envVar.key === 'BASE_PATH') {
|
|
351
|
+
result.set(envVar.key, normalizedBasePath);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Show changelog info for version variables
|
|
356
|
+
if (envVar.key === 'CORE_VERSION' || envVar.key === 'DASHBOARD_VERSION') {
|
|
357
|
+
logInfo('ℹ️ Visit https://simplens.in/changelog for version information');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const promptMessage = `${envVar.key}${envVar.description ? ` (${envVar.description})` : ''}:`;
|
|
361
|
+
const isPasswordField = envVar.key.includes('PASSWORD');
|
|
362
|
+
|
|
363
|
+
let answer: string | symbol;
|
|
364
|
+
if (isPasswordField) {
|
|
365
|
+
answer = await password({
|
|
366
|
+
message: promptMessage,
|
|
367
|
+
validate: (input: string | undefined) => {
|
|
368
|
+
if (!input && envVar.required) {
|
|
369
|
+
return `${envVar.key} is required`;
|
|
370
|
+
}
|
|
371
|
+
return undefined;
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
} else {
|
|
375
|
+
answer = await text({
|
|
376
|
+
message: promptMessage,
|
|
377
|
+
placeholder: defaultValue || undefined,
|
|
378
|
+
defaultValue: defaultValue || undefined,
|
|
379
|
+
validate: (input: string | undefined) => {
|
|
380
|
+
if (!input && envVar.required) {
|
|
381
|
+
return `${envVar.key} is required`;
|
|
382
|
+
}
|
|
383
|
+
return undefined;
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
handleCancel(answer);
|
|
389
|
+
result.set(envVar.key, answer as string);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
logSuccess('Environment variables configured');
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get suggested value for specific keys
|
|
399
|
+
*/
|
|
400
|
+
function getSuggestedValue(key: string): string {
|
|
401
|
+
if (key === 'NS_API_KEY' || key === 'AUTH_SECRET') {
|
|
402
|
+
return `Replace with: openssl rand -base64 32`;
|
|
403
|
+
}
|
|
404
|
+
if (key === 'NODE_ENV') {
|
|
405
|
+
return 'production';
|
|
406
|
+
}
|
|
407
|
+
if (key === 'ADMIN_USERNAME') {
|
|
408
|
+
return 'admin';
|
|
409
|
+
}
|
|
410
|
+
return '';
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Generate .env file from variables
|
|
415
|
+
*/
|
|
416
|
+
export async function generateEnvFile(
|
|
417
|
+
targetDir: string,
|
|
418
|
+
envVars: Map<string, string>
|
|
419
|
+
): Promise<void> {
|
|
420
|
+
const envPath = path.join(targetDir, '.env');
|
|
421
|
+
|
|
422
|
+
let content = '# SimpleNS Environment Configuration\n';
|
|
423
|
+
content += '# Generated by @simplens/onboard\n\n';
|
|
424
|
+
|
|
425
|
+
for (const [key, value] of envVars.entries()) {
|
|
426
|
+
content += `${key}=${value}\n`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
await writeFile(envPath, content);
|
|
430
|
+
logSuccess('Generated .env file');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Append plugin credentials to .env file
|
|
435
|
+
*/
|
|
436
|
+
export async function appendPluginEnv(
|
|
437
|
+
targetDir: string,
|
|
438
|
+
pluginEnvVars: Map<string, string>
|
|
439
|
+
): Promise<void> {
|
|
440
|
+
// Only append if there are actually credentials to add
|
|
441
|
+
if (pluginEnvVars.size === 0) {
|
|
442
|
+
logInfo('No plugin credentials to add');
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const envPath = path.join(targetDir, '.env');
|
|
447
|
+
|
|
448
|
+
let content = '\n# Plugin Credentials\n';
|
|
449
|
+
for (const [key, value] of pluginEnvVars.entries()) {
|
|
450
|
+
content += `${key}=${value}\n`;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
await appendFile(envPath, content);
|
|
454
|
+
logSuccess('Added plugin credentials to .env');
|
|
455
|
+
}
|