@simplens/onboard 1.0.5 → 1.0.7
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 +27 -1
- package/dist/__tests__/infra-prompts.test.js +3 -1
- package/dist/__tests__/infra-prompts.test.js.map +1 -1
- package/dist/__tests__/infra.test.js +13 -0
- package/dist/__tests__/infra.test.js.map +1 -1
- package/dist/__tests__/validators.test.js +21 -1
- package/dist/__tests__/validators.test.js.map +1 -1
- package/dist/index.js +138 -20
- package/dist/index.js.map +1 -1
- package/dist/infra.d.ts +12 -3
- package/dist/infra.d.ts.map +1 -1
- package/dist/infra.js +94 -22
- package/dist/infra.js.map +1 -1
- package/dist/services.d.ts +12 -0
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +52 -2
- package/dist/services.js.map +1 -1
- package/dist/templates.d.ts +5 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +66 -0
- package/dist/templates.js.map +1 -1
- package/dist/types/domain.d.ts +6 -0
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/validators.d.ts +9 -0
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +38 -0
- package/dist/validators.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/infra-prompts.test.ts +3 -1
- package/src/__tests__/infra.test.ts +15 -0
- package/src/__tests__/validators.test.ts +27 -1
- package/src/index.ts +147 -19
- package/src/infra.ts +119 -23
- package/src/services.ts +74 -2
- package/src/templates.ts +70 -0
- package/src/types/domain.ts +6 -0
- package/src/validators.ts +51 -0
package/dist/validators.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -28,12 +28,14 @@ describe('promptInfraServicesWithBasePath', () => {
|
|
|
28
28
|
const mockMultiselect = vi.mocked(multiselect);
|
|
29
29
|
mockMultiselect.mockResolvedValue(['mongo', 'redis', 'nginx']);
|
|
30
30
|
|
|
31
|
-
const result = await promptInfraServicesWithBasePath({ allowNginx: true });
|
|
31
|
+
const result = await promptInfraServicesWithBasePath({ allowNginx: true, defaultNginx: true });
|
|
32
32
|
|
|
33
33
|
// Should include nginx in options
|
|
34
34
|
const callArgs = mockMultiselect.mock.calls[0][0] as any;
|
|
35
35
|
const values = callArgs.options.map((o: any) => o.value);
|
|
36
36
|
expect(values).toContain('nginx');
|
|
37
|
+
expect(callArgs.initialValues).toContain('nginx');
|
|
38
|
+
expect(callArgs.initialValues).not.toContain('kafka-ui');
|
|
37
39
|
|
|
38
40
|
expect(result).toContain('nginx');
|
|
39
41
|
});
|
|
@@ -12,4 +12,19 @@ describe('infra app compose generation', () => {
|
|
|
12
12
|
expect(compose).toContain(' nginx:');
|
|
13
13
|
expect(compose).toContain('./nginx.conf:/etc/nginx/conf.d/default.conf:ro');
|
|
14
14
|
});
|
|
15
|
+
|
|
16
|
+
it('does not include certbot services when ssl is disabled', () => {
|
|
17
|
+
const compose = buildAppComposeContent(true, { includeSsl: false });
|
|
18
|
+
expect(compose).not.toContain(' certbot:');
|
|
19
|
+
expect(compose).not.toContain(' certbot-renew:');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('includes certbot services and volumes when ssl is enabled', () => {
|
|
23
|
+
const compose = buildAppComposeContent(false, { includeSsl: true });
|
|
24
|
+
expect(compose).toContain(' nginx:');
|
|
25
|
+
expect(compose).toContain(' certbot:');
|
|
26
|
+
expect(compose).toContain(' certbot-renew:');
|
|
27
|
+
expect(compose).toContain('certbot-etc:');
|
|
28
|
+
expect(compose).toContain('certbot-www:');
|
|
29
|
+
});
|
|
15
30
|
});
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
checkDockerRunning,
|
|
5
5
|
detectOS,
|
|
6
6
|
validatePrerequisites,
|
|
7
|
-
validateEnvValue
|
|
7
|
+
validateEnvValue,
|
|
8
|
+
validatePublicDomain,
|
|
9
|
+
validateEmailAddress
|
|
8
10
|
} from '../validators.js';
|
|
9
11
|
import {
|
|
10
12
|
DockerNotInstalledError,
|
|
@@ -192,4 +194,28 @@ describe('validators', () => {
|
|
|
192
194
|
});
|
|
193
195
|
});
|
|
194
196
|
});
|
|
197
|
+
|
|
198
|
+
describe('validatePublicDomain', () => {
|
|
199
|
+
it('accepts valid public domains', () => {
|
|
200
|
+
expect(validatePublicDomain('example.com')).toBe(true);
|
|
201
|
+
expect(validatePublicDomain('app.example.com')).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('rejects urls or malformed values', () => {
|
|
205
|
+
expect(validatePublicDomain('https://example.com')).not.toBe(true);
|
|
206
|
+
expect(validatePublicDomain('example')).not.toBe(true);
|
|
207
|
+
expect(validatePublicDomain('example.com/path')).not.toBe(true);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('validateEmailAddress', () => {
|
|
212
|
+
it('accepts valid email addresses', () => {
|
|
213
|
+
expect(validateEmailAddress('admin@example.com')).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('rejects invalid email values', () => {
|
|
217
|
+
expect(validateEmailAddress('admin@')).not.toBe(true);
|
|
218
|
+
expect(validateEmailAddress('not-an-email')).not.toBe(true);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
195
221
|
});
|
package/src/index.ts
CHANGED
|
@@ -15,7 +15,11 @@ import {
|
|
|
15
15
|
} from './utils.js';
|
|
16
16
|
import { text, confirm, select } from '@clack/prompts';
|
|
17
17
|
import { intro, outro, handleCancel, log, note } from './ui.js';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
validatePrerequisites,
|
|
20
|
+
validatePublicDomain,
|
|
21
|
+
validateEmailAddress,
|
|
22
|
+
} from './validators.js';
|
|
19
23
|
import {
|
|
20
24
|
promptInfraServicesWithBasePath,
|
|
21
25
|
generateInfraCompose,
|
|
@@ -45,6 +49,8 @@ import {
|
|
|
45
49
|
waitForInfraHealth,
|
|
46
50
|
startAppServices,
|
|
47
51
|
displayServiceStatus,
|
|
52
|
+
setupSslCertificates,
|
|
53
|
+
getSslManualCommands,
|
|
48
54
|
} from './services.js';
|
|
49
55
|
|
|
50
56
|
const program = new Command();
|
|
@@ -61,6 +67,9 @@ program
|
|
|
61
67
|
.option('--core-version <version>', 'Override CORE_VERSION in generated .env (primarily for --full mode)')
|
|
62
68
|
.option('--dashboard-version <version>', 'Override DASHBOARD_VERSION in generated .env (primarily for --full mode)')
|
|
63
69
|
.option('--plugin [plugins...]', 'Plugins to install (e.g., @simplens/mock @simplens/nodemailer-gmail)')
|
|
70
|
+
.option('--ssl', 'Enable optional SSL certificate setup using Dockerized Certbot')
|
|
71
|
+
.option('--ssl-domain <domain>', 'Public domain for SSL certificate (required with --ssl in --full mode)')
|
|
72
|
+
.option('--ssl-email <email>', 'Email for Let\'s Encrypt registration (required with --ssl in --full mode)')
|
|
64
73
|
.option('--no-output', 'Suppress all console output (silent mode)');
|
|
65
74
|
|
|
66
75
|
interface OnboardSetupOptions {
|
|
@@ -70,6 +79,9 @@ interface OnboardSetupOptions {
|
|
|
70
79
|
targetDir: string;
|
|
71
80
|
basePath: string;
|
|
72
81
|
plugins: string[];
|
|
82
|
+
enableSsl: boolean;
|
|
83
|
+
sslDomain?: string;
|
|
84
|
+
sslEmail?: string;
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
function printStep(step: number, total: number, title: string): void {
|
|
@@ -112,6 +124,9 @@ function showSetupSummary(setupOptions: OnboardSetupOptions, targetDir: string,
|
|
|
112
124
|
const pluginsLabel = setupOptions.plugins.length > 0
|
|
113
125
|
? setupOptions.plugins.join(', ')
|
|
114
126
|
: 'none';
|
|
127
|
+
const sslLabel = setupOptions.enableSsl
|
|
128
|
+
? `enabled (${setupOptions.sslDomain})`
|
|
129
|
+
: 'disabled';
|
|
115
130
|
|
|
116
131
|
const summaryLines = [
|
|
117
132
|
`Target directory : ${targetDir}`,
|
|
@@ -119,6 +134,7 @@ function showSetupSummary(setupOptions: OnboardSetupOptions, targetDir: string,
|
|
|
119
134
|
`Environment mode : ${setupOptions.envMode}`,
|
|
120
135
|
`BASE_PATH : ${basePathLabel}`,
|
|
121
136
|
`Plugins : ${pluginsLabel}`,
|
|
137
|
+
`SSL (Certbot) : ${sslLabel}`,
|
|
122
138
|
`Nginx auto-include : ${autoNginx ? 'enabled (BASE_PATH is non-default)' : 'disabled'}`,
|
|
123
139
|
].join('\n');
|
|
124
140
|
|
|
@@ -170,6 +186,26 @@ async function promptSetupOptions(options: any): Promise<OnboardSetupOptions> {
|
|
|
170
186
|
}
|
|
171
187
|
}
|
|
172
188
|
|
|
189
|
+
if (options.ssl === true) {
|
|
190
|
+
if (!options.sslDomain) {
|
|
191
|
+
errors.push('--ssl-domain <domain> is required in --full mode when --ssl is enabled');
|
|
192
|
+
} else {
|
|
193
|
+
const domainValidation = validatePublicDomain(options.sslDomain);
|
|
194
|
+
if (domainValidation !== true) {
|
|
195
|
+
errors.push(`Invalid --ssl-domain: ${domainValidation}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!options.sslEmail) {
|
|
200
|
+
errors.push('--ssl-email <email> is required in --full mode when --ssl is enabled');
|
|
201
|
+
} else {
|
|
202
|
+
const emailValidation = validateEmailAddress(options.sslEmail);
|
|
203
|
+
if (emailValidation !== true) {
|
|
204
|
+
errors.push(`Invalid --ssl-email: ${emailValidation}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
173
209
|
if (errors.length > 0) {
|
|
174
210
|
console.error('\\n❌ Validation errors in --full mode:\\n');
|
|
175
211
|
errors.forEach(err => console.error(` • ${err}`));
|
|
@@ -274,6 +310,65 @@ async function promptSetupOptions(options: any): Promise<OnboardSetupOptions> {
|
|
|
274
310
|
}
|
|
275
311
|
// If not provided and not in full mode, will prompt later in the main workflow
|
|
276
312
|
|
|
313
|
+
// --- SSL ---
|
|
314
|
+
let enableSslValue = false;
|
|
315
|
+
let sslDomainValue: string | undefined;
|
|
316
|
+
let sslEmailValue: string | undefined;
|
|
317
|
+
|
|
318
|
+
if (options.ssl === true) {
|
|
319
|
+
enableSslValue = true;
|
|
320
|
+
} else if (!isFullMode) {
|
|
321
|
+
const sslConfirm = await confirm({
|
|
322
|
+
message: 'Do you want to automatically setup SSL certificate using Certbot?',
|
|
323
|
+
initialValue: false,
|
|
324
|
+
withGuide: true,
|
|
325
|
+
});
|
|
326
|
+
handleCancel(sslConfirm);
|
|
327
|
+
enableSslValue = sslConfirm as boolean;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (enableSslValue) {
|
|
331
|
+
if (typeof options.sslDomain === 'string') {
|
|
332
|
+
const normalizedDomain = options.sslDomain.trim().toLowerCase();
|
|
333
|
+
const domainValidation = validatePublicDomain(normalizedDomain);
|
|
334
|
+
if (domainValidation !== true) {
|
|
335
|
+
throw new Error(`Invalid --ssl-domain value: ${domainValidation}`);
|
|
336
|
+
}
|
|
337
|
+
sslDomainValue = normalizedDomain;
|
|
338
|
+
} else if (!isFullMode) {
|
|
339
|
+
const domainAnswer = await text({
|
|
340
|
+
message: 'Public domain to secure (example: app.example.com):',
|
|
341
|
+
validate: (value: string | undefined) => {
|
|
342
|
+
const validation = validatePublicDomain(value ?? '');
|
|
343
|
+
return validation === true ? undefined : validation;
|
|
344
|
+
},
|
|
345
|
+
withGuide: true,
|
|
346
|
+
});
|
|
347
|
+
handleCancel(domainAnswer);
|
|
348
|
+
sslDomainValue = (domainAnswer as string).trim().toLowerCase();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (typeof options.sslEmail === 'string') {
|
|
352
|
+
const normalizedEmail = options.sslEmail.trim();
|
|
353
|
+
const emailValidation = validateEmailAddress(normalizedEmail);
|
|
354
|
+
if (emailValidation !== true) {
|
|
355
|
+
throw new Error(`Invalid --ssl-email value: ${emailValidation}`);
|
|
356
|
+
}
|
|
357
|
+
sslEmailValue = normalizedEmail;
|
|
358
|
+
} else if (!isFullMode) {
|
|
359
|
+
const emailAnswer = await text({
|
|
360
|
+
message: 'Email for Let\'s Encrypt registration:',
|
|
361
|
+
validate: (value: string | undefined) => {
|
|
362
|
+
const validation = validateEmailAddress(value ?? '');
|
|
363
|
+
return validation === true ? undefined : validation;
|
|
364
|
+
},
|
|
365
|
+
withGuide: true,
|
|
366
|
+
});
|
|
367
|
+
handleCancel(emailAnswer);
|
|
368
|
+
sslEmailValue = (emailAnswer as string).trim();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
277
372
|
return {
|
|
278
373
|
infra: infraValue,
|
|
279
374
|
infraServices: infraServices,
|
|
@@ -281,6 +376,9 @@ async function promptSetupOptions(options: any): Promise<OnboardSetupOptions> {
|
|
|
281
376
|
targetDir: targetDirValue || process.cwd(),
|
|
282
377
|
basePath: basePathValue,
|
|
283
378
|
plugins: pluginsValue,
|
|
379
|
+
enableSsl: enableSslValue,
|
|
380
|
+
sslDomain: sslDomainValue,
|
|
381
|
+
sslEmail: sslEmailValue,
|
|
284
382
|
};
|
|
285
383
|
}
|
|
286
384
|
|
|
@@ -318,6 +416,7 @@ async function main() {
|
|
|
318
416
|
// Get target directory
|
|
319
417
|
const targetDir = path.resolve(setupOptions.targetDir);
|
|
320
418
|
const autoEnableNginx = shouldAutoEnableNginx(setupOptions.basePath);
|
|
419
|
+
const nginxRequired = autoEnableNginx || setupOptions.enableSsl;
|
|
321
420
|
|
|
322
421
|
logDebug(`Resolved target directory: ${targetDir}`);
|
|
323
422
|
showSetupSummary(setupOptions, targetDir, autoEnableNginx);
|
|
@@ -329,39 +428,50 @@ async function main() {
|
|
|
329
428
|
// Step 2: Infrastructure setup (if --infra flag is provided)
|
|
330
429
|
log.step('Step 2/6 — Infrastructure Setup');
|
|
331
430
|
let selectedInfraServices: string[] = [];
|
|
431
|
+
const shouldSetupInfra = setupOptions.infra || nginxRequired;
|
|
332
432
|
|
|
333
|
-
if (
|
|
433
|
+
if (shouldSetupInfra) {
|
|
334
434
|
// Use pre-provided services from CLI, or prompt for them
|
|
335
|
-
if (setupOptions.infraServices.length > 0) {
|
|
435
|
+
if (setupOptions.infra && setupOptions.infraServices.length > 0) {
|
|
336
436
|
selectedInfraServices = setupOptions.infraServices;
|
|
337
437
|
log.info(`Using infrastructure services: ${selectedInfraServices.join(', ')}`);
|
|
438
|
+
} else if (!setupOptions.infra && nginxRequired) {
|
|
439
|
+
selectedInfraServices = ['nginx'];
|
|
440
|
+
log.info('Nginx is required (BASE_PATH/SSL), so infrastructure compose will be generated with nginx.');
|
|
338
441
|
} else {
|
|
339
442
|
// Prompt for services (interactive mode)
|
|
340
443
|
if (!autoEnableNginx) {
|
|
341
444
|
log.info('BASE_PATH is empty, nginx reverse proxy is disabled.');
|
|
342
|
-
selectedInfraServices = await promptInfraServicesWithBasePath({
|
|
445
|
+
selectedInfraServices = await promptInfraServicesWithBasePath({
|
|
446
|
+
allowNginx: false,
|
|
447
|
+
});
|
|
343
448
|
} else {
|
|
344
|
-
selectedInfraServices = await promptInfraServicesWithBasePath({
|
|
449
|
+
selectedInfraServices = await promptInfraServicesWithBasePath({
|
|
450
|
+
allowNginx: true,
|
|
451
|
+
defaultNginx: true,
|
|
452
|
+
});
|
|
345
453
|
}
|
|
346
454
|
}
|
|
347
455
|
|
|
348
|
-
if (
|
|
456
|
+
if (setupOptions.enableSsl && !selectedInfraServices.includes('nginx')) {
|
|
349
457
|
selectedInfraServices.push('nginx');
|
|
350
|
-
log.info('
|
|
458
|
+
log.info('SSL is enabled, so nginx was added automatically.');
|
|
351
459
|
}
|
|
352
460
|
|
|
353
|
-
|
|
461
|
+
const infraHasNginx = selectedInfraServices.includes('nginx');
|
|
462
|
+
await generateInfraCompose(targetDir, selectedInfraServices, {
|
|
463
|
+
includeSsl: setupOptions.enableSsl && infraHasNginx,
|
|
464
|
+
});
|
|
354
465
|
} else {
|
|
355
466
|
log.info('Skipping infrastructure setup (use --infra to enable).');
|
|
356
467
|
}
|
|
357
468
|
|
|
358
469
|
// Step 3: Always write app docker-compose
|
|
359
470
|
log.step('Step 3/6 — Application Compose Setup');
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
await writeAppCompose(targetDir, { includeNginx: includeNginxInAppCompose });
|
|
471
|
+
await writeAppCompose(targetDir, {
|
|
472
|
+
includeNginx: false,
|
|
473
|
+
includeSsl: false,
|
|
474
|
+
});
|
|
365
475
|
|
|
366
476
|
// Step 4: Environment configuration
|
|
367
477
|
log.step('Step 4/6 — Environment Configuration');
|
|
@@ -390,9 +500,12 @@ async function main() {
|
|
|
390
500
|
}
|
|
391
501
|
|
|
392
502
|
// Generate nginx.conf whenever nginx is active in either compose file
|
|
393
|
-
const nginxEnabled = selectedInfraServices.includes('nginx')
|
|
503
|
+
const nginxEnabled = selectedInfraServices.includes('nginx');
|
|
394
504
|
if (nginxEnabled) {
|
|
395
|
-
await generateNginxConfig(targetDir, setupOptions.basePath
|
|
505
|
+
await generateNginxConfig(targetDir, setupOptions.basePath, {
|
|
506
|
+
enableSsl: setupOptions.enableSsl,
|
|
507
|
+
domain: setupOptions.sslDomain,
|
|
508
|
+
});
|
|
396
509
|
}
|
|
397
510
|
|
|
398
511
|
// Step 5: Plugin installation
|
|
@@ -447,7 +560,7 @@ async function main() {
|
|
|
447
560
|
|
|
448
561
|
if (shouldStart) {
|
|
449
562
|
// Start infra services first (if --infra was used)
|
|
450
|
-
if (
|
|
563
|
+
if (selectedInfraServices.length > 0) {
|
|
451
564
|
await startInfraServices(targetDir);
|
|
452
565
|
await waitForInfraHealth(targetDir);
|
|
453
566
|
}
|
|
@@ -455,15 +568,30 @@ async function main() {
|
|
|
455
568
|
// Start app services
|
|
456
569
|
await startAppServices(targetDir);
|
|
457
570
|
|
|
571
|
+
if (setupOptions.enableSsl && setupOptions.sslDomain && setupOptions.sslEmail) {
|
|
572
|
+
await setupSslCertificates(targetDir, {
|
|
573
|
+
composeFile: 'docker-compose.infra.yaml',
|
|
574
|
+
domain: setupOptions.sslDomain,
|
|
575
|
+
email: setupOptions.sslEmail,
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
458
579
|
// Display service status
|
|
459
580
|
await displayServiceStatus();
|
|
460
581
|
} else {
|
|
461
582
|
log.info('Services not started. You can start them later with:');
|
|
462
583
|
const commands: string[] = [];
|
|
463
|
-
if (
|
|
464
|
-
commands.push('docker
|
|
584
|
+
if (selectedInfraServices.length > 0) {
|
|
585
|
+
commands.push('docker compose -f docker-compose.infra.yaml up -d');
|
|
586
|
+
}
|
|
587
|
+
commands.push('docker compose up -d');
|
|
588
|
+
if (setupOptions.enableSsl && setupOptions.sslDomain && setupOptions.sslEmail) {
|
|
589
|
+
commands.push(...getSslManualCommands({
|
|
590
|
+
composeFile: 'docker-compose.infra.yaml',
|
|
591
|
+
domain: setupOptions.sslDomain,
|
|
592
|
+
email: setupOptions.sslEmail,
|
|
593
|
+
}));
|
|
465
594
|
}
|
|
466
|
-
commands.push('docker-compose up -d');
|
|
467
595
|
printCommandHints('Manual startup commands', commands);
|
|
468
596
|
}
|
|
469
597
|
|
package/src/infra.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
APP_COMPOSE_TEMPLATE,
|
|
3
|
+
APP_NGINX_SERVICE_TEMPLATE,
|
|
4
|
+
APP_NGINX_SSL_SERVICE_TEMPLATE,
|
|
5
|
+
APP_CERTBOT_SERVICES_TEMPLATE,
|
|
6
|
+
INFRA_CERTBOT_SERVICES_TEMPLATE,
|
|
7
|
+
INFRA_CERTBOT_VOLUMES,
|
|
8
|
+
} from './templates.js';
|
|
2
9
|
import { writeFile, logInfo, logSuccess } from './utils.js';
|
|
3
10
|
import { multiselect } from '@clack/prompts';
|
|
4
11
|
import { handleCancel, spinner } from './ui.js';
|
|
@@ -8,7 +15,7 @@ import type { InfraService } from './types/domain.js';
|
|
|
8
15
|
const INFRA_SERVICES: InfraService[] = [
|
|
9
16
|
{ name: 'MongoDB (Database)', value: 'mongo', checked: true },
|
|
10
17
|
{ name: 'Kafka (Message Queue)', value: 'kafka', checked: true },
|
|
11
|
-
{ name: 'Kafka UI (Dashboard)', value: 'kafka-ui', checked:
|
|
18
|
+
{ name: 'Kafka UI (Dashboard)', value: 'kafka-ui', checked: false },
|
|
12
19
|
{ name: 'Redis (Cache)', value: 'redis', checked: true },
|
|
13
20
|
{ name: 'Nginx (Reverse Proxy)', value: 'nginx', checked: false },
|
|
14
21
|
{ name: 'Loki (Log Aggregation)', value: 'loki', checked: false },
|
|
@@ -31,9 +38,15 @@ export async function promptInfraServices(): Promise<string[]> {
|
|
|
31
38
|
*/
|
|
32
39
|
export async function promptInfraServicesWithBasePath(options: {
|
|
33
40
|
allowNginx: boolean;
|
|
41
|
+
defaultNginx?: boolean;
|
|
34
42
|
}): Promise<string[]> {
|
|
35
43
|
const choices = options.allowNginx
|
|
36
|
-
? INFRA_SERVICES
|
|
44
|
+
? INFRA_SERVICES.map(service => {
|
|
45
|
+
if (service.value === 'nginx') {
|
|
46
|
+
return { ...service, checked: options.defaultNginx === true };
|
|
47
|
+
}
|
|
48
|
+
return service;
|
|
49
|
+
})
|
|
37
50
|
: INFRA_SERVICES.filter(service => service.value !== 'nginx');
|
|
38
51
|
|
|
39
52
|
const message = options.allowNginx
|
|
@@ -179,6 +192,18 @@ const SERVICE_CHUNKS: Record<string, string> = {
|
|
|
179
192
|
restart: unless-stopped`,
|
|
180
193
|
};
|
|
181
194
|
|
|
195
|
+
const NGINX_INFRA_SSL_SERVICE_CHUNK = ` nginx:
|
|
196
|
+
image: nginx:alpine
|
|
197
|
+
container_name: nginx
|
|
198
|
+
ports:
|
|
199
|
+
- "80:80"
|
|
200
|
+
- "443:443"
|
|
201
|
+
volumes:
|
|
202
|
+
- "./nginx.conf:/etc/nginx/conf.d/default.conf:ro"
|
|
203
|
+
- certbot-etc:/etc/letsencrypt
|
|
204
|
+
- certbot-www:/var/www/certbot
|
|
205
|
+
restart: unless-stopped`;
|
|
206
|
+
|
|
182
207
|
/**
|
|
183
208
|
* Service-to-volumes mapping
|
|
184
209
|
*/
|
|
@@ -195,7 +220,10 @@ const SERVICE_VOLUMES: Record<string, string[]> = {
|
|
|
195
220
|
/**
|
|
196
221
|
* Build docker-compose content from selected services
|
|
197
222
|
*/
|
|
198
|
-
function buildInfraCompose(
|
|
223
|
+
function buildInfraCompose(
|
|
224
|
+
selectedServices: string[],
|
|
225
|
+
options: { includeSsl?: boolean } = {}
|
|
226
|
+
): string {
|
|
199
227
|
// Header
|
|
200
228
|
const header = `# ============================================
|
|
201
229
|
# SimpleNS Infrastructure Services
|
|
@@ -212,9 +240,17 @@ services:
|
|
|
212
240
|
const serviceBlocks: string[] = [];
|
|
213
241
|
for (const service of selectedServices) {
|
|
214
242
|
if (SERVICE_CHUNKS[service]) {
|
|
215
|
-
|
|
243
|
+
if (service === 'nginx' && options.includeSsl === true) {
|
|
244
|
+
serviceBlocks.push(NGINX_INFRA_SSL_SERVICE_CHUNK);
|
|
245
|
+
} else {
|
|
246
|
+
serviceBlocks.push(SERVICE_CHUNKS[service]);
|
|
247
|
+
}
|
|
216
248
|
}
|
|
217
249
|
}
|
|
250
|
+
|
|
251
|
+
if (options.includeSsl === true) {
|
|
252
|
+
serviceBlocks.push(INFRA_CERTBOT_SERVICES_TEMPLATE);
|
|
253
|
+
}
|
|
218
254
|
|
|
219
255
|
// Collect volumes for selected services
|
|
220
256
|
const volumeSet = new Set<string>();
|
|
@@ -222,6 +258,12 @@ services:
|
|
|
222
258
|
const volumes = SERVICE_VOLUMES[service] || [];
|
|
223
259
|
volumes.forEach(v => volumeSet.add(v));
|
|
224
260
|
}
|
|
261
|
+
|
|
262
|
+
if (options.includeSsl === true) {
|
|
263
|
+
for (const volumeName of INFRA_CERTBOT_VOLUMES) {
|
|
264
|
+
volumeSet.add(volumeName);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
225
267
|
|
|
226
268
|
// Build volumes section
|
|
227
269
|
const volumeLines: string[] = ['', 'volumes:'];
|
|
@@ -248,13 +290,14 @@ services:
|
|
|
248
290
|
*/
|
|
249
291
|
export async function generateInfraCompose(
|
|
250
292
|
targetDir: string,
|
|
251
|
-
selectedServices: string[]
|
|
293
|
+
selectedServices: string[],
|
|
294
|
+
options: { includeSsl?: boolean } = {}
|
|
252
295
|
): Promise<void> {
|
|
253
296
|
const s = spinner();
|
|
254
297
|
s.start('Generating docker-compose.infra.yaml...');
|
|
255
298
|
|
|
256
299
|
// Build compose content from service chunks
|
|
257
|
-
const infraContent = buildInfraCompose(selectedServices);
|
|
300
|
+
const infraContent = buildInfraCompose(selectedServices, options);
|
|
258
301
|
|
|
259
302
|
// Write infrastructure compose file
|
|
260
303
|
const infraPath = path.join(targetDir, 'docker-compose.infra.yaml');
|
|
@@ -266,17 +309,35 @@ export async function generateInfraCompose(
|
|
|
266
309
|
* Build app docker-compose content.
|
|
267
310
|
* Optionally inject nginx reverse-proxy service before the volumes section.
|
|
268
311
|
*/
|
|
269
|
-
export function buildAppComposeContent(
|
|
270
|
-
|
|
271
|
-
|
|
312
|
+
export function buildAppComposeContent(
|
|
313
|
+
includeNginx: boolean,
|
|
314
|
+
options: { includeSsl?: boolean } = {}
|
|
315
|
+
): string {
|
|
316
|
+
let content = APP_COMPOSE_TEMPLATE;
|
|
317
|
+
const includeSsl = options.includeSsl === true;
|
|
318
|
+
const shouldIncludeNginx = includeNginx || includeSsl;
|
|
319
|
+
const marker = '\nvolumes:';
|
|
320
|
+
|
|
321
|
+
if (!content.includes(marker)) {
|
|
322
|
+
return content;
|
|
272
323
|
}
|
|
273
324
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
325
|
+
if (shouldIncludeNginx) {
|
|
326
|
+
const nginxBlock = includeSsl
|
|
327
|
+
? APP_NGINX_SSL_SERVICE_TEMPLATE
|
|
328
|
+
: APP_NGINX_SERVICE_TEMPLATE;
|
|
329
|
+
content = content.replace(marker, `\n${nginxBlock}\n${marker}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (includeSsl) {
|
|
333
|
+
content = content.replace(marker, `\n${APP_CERTBOT_SERVICES_TEMPLATE}\n${marker}`);
|
|
334
|
+
content = content.replace(
|
|
335
|
+
'\nvolumes:\n plugin-data:',
|
|
336
|
+
'\nvolumes:\n certbot-etc:\n certbot-www:\n plugin-data:'
|
|
337
|
+
);
|
|
277
338
|
}
|
|
278
339
|
|
|
279
|
-
return
|
|
340
|
+
return content;
|
|
280
341
|
}
|
|
281
342
|
|
|
282
343
|
/**
|
|
@@ -284,12 +345,14 @@ export function buildAppComposeContent(includeNginx: boolean): string {
|
|
|
284
345
|
*/
|
|
285
346
|
export async function writeAppCompose(
|
|
286
347
|
targetDir: string,
|
|
287
|
-
options: { includeNginx?: boolean } = {}
|
|
348
|
+
options: { includeNginx?: boolean; includeSsl?: boolean } = {}
|
|
288
349
|
): Promise<void> {
|
|
289
350
|
const s = spinner();
|
|
290
351
|
s.start('Generating docker-compose.yaml...');
|
|
291
352
|
const appPath = path.join(targetDir, 'docker-compose.yaml');
|
|
292
|
-
const appContent = buildAppComposeContent(options.includeNginx === true
|
|
353
|
+
const appContent = buildAppComposeContent(options.includeNginx === true, {
|
|
354
|
+
includeSsl: options.includeSsl === true,
|
|
355
|
+
});
|
|
293
356
|
await writeFile(appPath, appContent);
|
|
294
357
|
s.stop('Generated docker-compose.yaml');
|
|
295
358
|
}
|
|
@@ -299,7 +362,8 @@ export async function writeAppCompose(
|
|
|
299
362
|
*/
|
|
300
363
|
export async function generateNginxConfig(
|
|
301
364
|
targetDir: string,
|
|
302
|
-
basePath: string
|
|
365
|
+
basePath: string,
|
|
366
|
+
options: { enableSsl?: boolean; domain?: string } = {}
|
|
303
367
|
): Promise<void> {
|
|
304
368
|
const s = spinner();
|
|
305
369
|
s.start('Generating nginx.conf...');
|
|
@@ -307,12 +371,10 @@ export async function generateNginxConfig(
|
|
|
307
371
|
// Normalize basePath (remove leading/trailing slashes for template)
|
|
308
372
|
const normalizedPath = basePath.trim().replace(/^\/|\/$/g, '');
|
|
309
373
|
const hasBasePath = normalizedPath.length > 0;
|
|
374
|
+
const enableSsl = options.enableSsl === true;
|
|
375
|
+
const domain = options.domain?.trim() || 'localhost';
|
|
310
376
|
|
|
311
|
-
|
|
312
|
-
const nginxTemplate = `server {
|
|
313
|
-
listen 80;
|
|
314
|
-
server_name localhost;
|
|
315
|
-
|
|
377
|
+
const proxyRoutes = `
|
|
316
378
|
location /api {
|
|
317
379
|
proxy_pass http://api:3000;
|
|
318
380
|
proxy_http_version 1.1;
|
|
@@ -395,10 +457,44 @@ ${hasBasePath ? `
|
|
|
395
457
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
396
458
|
}
|
|
397
459
|
`}
|
|
460
|
+
`;
|
|
461
|
+
|
|
462
|
+
const nginxTemplate = enableSsl
|
|
463
|
+
? `server {
|
|
464
|
+
listen 80;
|
|
465
|
+
server_name ${domain};
|
|
466
|
+
|
|
467
|
+
location /.well-known/acme-challenge/ {
|
|
468
|
+
root /var/www/certbot;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
location / {
|
|
472
|
+
return 301 https://$host$request_uri;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
server {
|
|
477
|
+
listen 443 ssl;
|
|
478
|
+
server_name ${domain};
|
|
479
|
+
|
|
480
|
+
ssl_certificate /etc/letsencrypt/live/${domain}/fullchain.pem;
|
|
481
|
+
ssl_certificate_key /etc/letsencrypt/live/${domain}/privkey.pem;
|
|
482
|
+
ssl_protocols TLSv1.2 TLSv1.3;
|
|
483
|
+
ssl_prefer_server_ciphers on;
|
|
484
|
+
location = / {
|
|
485
|
+
return 302 /dashboard;
|
|
486
|
+
}
|
|
487
|
+
${proxyRoutes}
|
|
488
|
+
}
|
|
489
|
+
`
|
|
490
|
+
: `server {
|
|
491
|
+
listen 80;
|
|
492
|
+
server_name localhost;${proxyRoutes}
|
|
398
493
|
}
|
|
399
494
|
`;
|
|
400
495
|
|
|
401
496
|
const nginxPath = path.join(targetDir, 'nginx.conf');
|
|
402
497
|
await writeFile(nginxPath, nginxTemplate);
|
|
403
|
-
|
|
498
|
+
const sslLabel = enableSsl ? `, SSL domain: ${domain}` : '';
|
|
499
|
+
s.stop(`Generated nginx.conf${hasBasePath ? ` with base path: /${normalizedPath}` : ' (root path)'}${sslLabel}`);
|
|
404
500
|
}
|