@geekmidas/cli 1.10.10 → 1.10.12
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/CHANGELOG.md +12 -0
- package/dist/{bundler-Bm3Az_sv.cjs → bundler-DVJkwNMQ.cjs} +2 -2
- package/dist/{bundler-Bm3Az_sv.cjs.map → bundler-DVJkwNMQ.cjs.map} +1 -1
- package/dist/{bundler-kk_XJTRp.mjs → bundler-Di5Gz9Ou.mjs} +2 -2
- package/dist/{bundler-kk_XJTRp.mjs.map → bundler-Di5Gz9Ou.mjs.map} +1 -1
- package/dist/config.d.cts +2 -2
- package/dist/config.d.mts +2 -2
- package/dist/{fullstack-secrets-DmUOfLeX.mjs → fullstack-secrets-BIFFv4UZ.mjs} +18 -3
- package/dist/fullstack-secrets-BIFFv4UZ.mjs.map +1 -0
- package/dist/{fullstack-secrets-BC9t9wWB.cjs → fullstack-secrets-D9rjTNyx.cjs} +18 -3
- package/dist/fullstack-secrets-D9rjTNyx.cjs.map +1 -0
- package/dist/{index-BdJZKXCJ.d.cts → index-UCsZ_Vkw.d.cts} +2 -2
- package/dist/{index-BdJZKXCJ.d.cts.map → index-UCsZ_Vkw.d.cts.map} +1 -1
- package/dist/{index-DB9VbcCD.d.mts → index-gXAGDSGu.d.mts} +2 -2
- package/dist/{index-DB9VbcCD.d.mts.map → index-gXAGDSGu.d.mts.map} +1 -1
- package/dist/index.cjs +120 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +120 -54
- package/dist/index.mjs.map +1 -1
- package/dist/openapi-BYxAWwok.cjs.map +1 -1
- package/dist/openapi-DenF-okj.mjs.map +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/{reconcile-C0dsg-Gq.mjs → reconcile-DxTEausy.mjs} +2 -2
- package/dist/{reconcile-C0dsg-Gq.mjs.map → reconcile-DxTEausy.mjs.map} +1 -1
- package/dist/{reconcile-BZ8j_-0z.cjs → reconcile-LaaJkFlO.cjs} +2 -2
- package/dist/{reconcile-BZ8j_-0z.cjs.map → reconcile-LaaJkFlO.cjs.map} +1 -1
- package/dist/{storage-Cs13jkJ9.cjs → storage-6GBoLCYF.cjs} +12 -6
- package/dist/{storage-B7H2PPCS.mjs.map → storage-6GBoLCYF.cjs.map} +1 -1
- package/dist/{storage-C1FNm2EP.mjs → storage-BFqrVsip.mjs} +1 -1
- package/dist/{storage-D6BGLgWf.cjs → storage-DCqjCiDn.cjs} +1 -1
- package/dist/{storage-B7H2PPCS.mjs → storage-DMf420PP.mjs} +12 -6
- package/dist/{storage-Cs13jkJ9.cjs.map → storage-DMf420PP.mjs.map} +1 -1
- package/dist/sync-BVNso6AA.cjs +4 -0
- package/dist/{sync-oCqELfeA.cjs → sync-DIGGOxCw.cjs} +2 -2
- package/dist/{sync-oCqELfeA.cjs.map → sync-DIGGOxCw.cjs.map} +1 -1
- package/dist/{sync-CyGe5f1I.mjs → sync-DjD_TeNX.mjs} +1 -1
- package/dist/{sync-CzXruMzP.mjs → sync-Do9O7QZ8.mjs} +2 -2
- package/dist/{sync-CzXruMzP.mjs.map → sync-Do9O7QZ8.mjs.map} +1 -1
- package/dist/{types-DwpLq_fp.d.mts → types-DiV9Mbvc.d.mts} +2 -2
- package/dist/{types-D4MLWXSL.d.cts.map → types-DiV9Mbvc.d.mts.map} +1 -1
- package/dist/{types-D4MLWXSL.d.cts → types-JvWj5Ckc.d.cts} +2 -2
- package/dist/{types-DwpLq_fp.d.mts.map → types-JvWj5Ckc.d.cts.map} +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +2 -2
- package/src/dev/index.ts +41 -1
- package/src/docker/__tests__/compose.spec.ts +48 -13
- package/src/docker/compose.ts +77 -23
- package/src/init/__tests__/generators.spec.ts +1 -0
- package/src/init/generators/docker.ts +1 -2
- package/src/init/index.ts +1 -0
- package/src/init/versions.ts +1 -1
- package/src/secrets/__tests__/generator.spec.ts +5 -5
- package/src/secrets/__tests__/storage.spec.ts +7 -7
- package/src/secrets/generator.ts +20 -4
- package/src/secrets/index.ts +4 -4
- package/src/secrets/storage.ts +11 -5
- package/src/secrets/types.ts +4 -1
- package/src/setup/__tests__/reconcile-secrets.spec.ts +3 -3
- package/src/setup/index.ts +5 -0
- package/src/test/index.ts +8 -10
- package/src/types.ts +6 -1
- package/dist/fullstack-secrets-BC9t9wWB.cjs.map +0 -1
- package/dist/fullstack-secrets-DmUOfLeX.mjs.map +0 -1
- package/dist/sync-DLlwsrBs.cjs +0 -4
|
@@ -352,12 +352,18 @@ describe('generateDockerCompose', () => {
|
|
|
352
352
|
services: { minio: true },
|
|
353
353
|
});
|
|
354
354
|
|
|
355
|
-
expect(yaml).toContain(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
expect(yaml).toContain(
|
|
359
|
-
|
|
360
|
-
|
|
355
|
+
expect(yaml).toContain(
|
|
356
|
+
'- STORAGE_ENDPOINT=${STORAGE_ENDPOINT:-http://minio:9000}',
|
|
357
|
+
);
|
|
358
|
+
expect(yaml).toContain(
|
|
359
|
+
'- STORAGE_ACCESS_KEY_ID=${STORAGE_ACCESS_KEY_ID:-my-api}',
|
|
360
|
+
);
|
|
361
|
+
expect(yaml).toContain(
|
|
362
|
+
'- STORAGE_SECRET_ACCESS_KEY=${STORAGE_SECRET_ACCESS_KEY:-my-api}',
|
|
363
|
+
);
|
|
364
|
+
expect(yaml).toContain('- STORAGE_BUCKET=${STORAGE_BUCKET:-my-api}');
|
|
365
|
+
expect(yaml).toContain('- STORAGE_REGION=${STORAGE_REGION:-eu-west-1}');
|
|
366
|
+
expect(yaml).toContain('- STORAGE_FORCE_PATH_STYLE=true');
|
|
361
367
|
});
|
|
362
368
|
|
|
363
369
|
it('should add minio service definition with default image', () => {
|
|
@@ -386,8 +392,12 @@ describe('generateDockerCompose', () => {
|
|
|
386
392
|
services: { minio: true },
|
|
387
393
|
});
|
|
388
394
|
|
|
389
|
-
expect(yaml).toContain(
|
|
390
|
-
|
|
395
|
+
expect(yaml).toContain(
|
|
396
|
+
'MINIO_ROOT_USER: ${STORAGE_ACCESS_KEY_ID:-my-api}',
|
|
397
|
+
);
|
|
398
|
+
expect(yaml).toContain(
|
|
399
|
+
'MINIO_ROOT_PASSWORD: ${STORAGE_SECRET_ACCESS_KEY:-my-api}',
|
|
400
|
+
);
|
|
391
401
|
});
|
|
392
402
|
|
|
393
403
|
it('should expose console UI port', () => {
|
|
@@ -396,7 +406,8 @@ describe('generateDockerCompose', () => {
|
|
|
396
406
|
services: { minio: true },
|
|
397
407
|
});
|
|
398
408
|
|
|
399
|
-
expect(yaml).toContain('- "
|
|
409
|
+
expect(yaml).toContain('- "${MINIO_API_PORT:-9000}:9000"');
|
|
410
|
+
expect(yaml).toContain('- "${MINIO_CONSOLE_PORT:-9001}:9001"');
|
|
400
411
|
});
|
|
401
412
|
|
|
402
413
|
it('should add minio volume', () => {
|
|
@@ -934,8 +945,30 @@ describe('generateWorkspaceCompose', () => {
|
|
|
934
945
|
|
|
935
946
|
expect(yaml).toContain('mailpit:');
|
|
936
947
|
expect(yaml).toContain('image: axllent/mailpit:latest');
|
|
937
|
-
expect(yaml).toContain('
|
|
938
|
-
expect(yaml).toContain('
|
|
948
|
+
expect(yaml).toContain('MP_SMTP_AUTH:');
|
|
949
|
+
expect(yaml).toContain('${MAILPIT_UI_PORT:-8025}:8025'); // Web UI
|
|
950
|
+
expect(yaml).toContain('${MAILPIT_SMTP_PORT:-1025}:1025'); // SMTP
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
it('should add SMTP env vars for backend apps when mail is enabled', () => {
|
|
954
|
+
const workspace = createWorkspace({
|
|
955
|
+
services: { mail: true },
|
|
956
|
+
});
|
|
957
|
+
const yaml = generateWorkspaceCompose(workspace);
|
|
958
|
+
|
|
959
|
+
expect(yaml).toContain('SMTP_HOST=${SMTP_HOST:-mailpit}');
|
|
960
|
+
expect(yaml).toContain('SMTP_PORT=${SMTP_PORT:-1025}');
|
|
961
|
+
expect(yaml).toContain('SMTP_USER=');
|
|
962
|
+
expect(yaml).toContain('SMTP_PASS=');
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
it('should add mailpit to depends_on for backend apps', () => {
|
|
966
|
+
const workspace = createWorkspace({
|
|
967
|
+
services: { mail: true },
|
|
968
|
+
});
|
|
969
|
+
const yaml = generateWorkspaceCompose(workspace);
|
|
970
|
+
|
|
971
|
+
expect(yaml).toMatch(/mailpit:\s+condition: service_healthy/);
|
|
939
972
|
});
|
|
940
973
|
|
|
941
974
|
it('should add postgres_data volume when postgres is enabled', () => {
|
|
@@ -1044,8 +1077,10 @@ describe('generateWorkspaceCompose', () => {
|
|
|
1044
1077
|
});
|
|
1045
1078
|
const yaml = generateWorkspaceCompose(workspace);
|
|
1046
1079
|
|
|
1047
|
-
expect(yaml).toContain(
|
|
1048
|
-
|
|
1080
|
+
expect(yaml).toContain(
|
|
1081
|
+
'STORAGE_ENDPOINT=${STORAGE_ENDPOINT:-http://minio:9000}',
|
|
1082
|
+
);
|
|
1083
|
+
expect(yaml).toContain('STORAGE_FORCE_PATH_STYLE=true');
|
|
1049
1084
|
});
|
|
1050
1085
|
|
|
1051
1086
|
it('should add minio_data volume when minio is enabled', () => {
|
package/src/docker/compose.ts
CHANGED
|
@@ -14,6 +14,7 @@ export const DEFAULT_SERVICE_IMAGES: Record<ComposeServiceName, string> = {
|
|
|
14
14
|
redis: 'redis',
|
|
15
15
|
rabbitmq: 'rabbitmq',
|
|
16
16
|
minio: 'minio/minio',
|
|
17
|
+
mailpit: 'axllent/mailpit',
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
/** Default Docker image versions for services */
|
|
@@ -22,6 +23,7 @@ export const DEFAULT_SERVICE_VERSIONS: Record<ComposeServiceName, string> = {
|
|
|
22
23
|
redis: '7-alpine',
|
|
23
24
|
rabbitmq: '3-management-alpine',
|
|
24
25
|
minio: 'latest',
|
|
26
|
+
mailpit: 'latest',
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
export interface ComposeOptions {
|
|
@@ -124,12 +126,20 @@ services:
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
if (serviceMap.has('minio')) {
|
|
127
|
-
yaml += ` -
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
129
|
+
yaml += ` - STORAGE_ENDPOINT=\${STORAGE_ENDPOINT:-http://minio:9000}
|
|
130
|
+
- STORAGE_ACCESS_KEY_ID=\${STORAGE_ACCESS_KEY_ID:-${imageName}}
|
|
131
|
+
- STORAGE_SECRET_ACCESS_KEY=\${STORAGE_SECRET_ACCESS_KEY:-${imageName}}
|
|
132
|
+
- STORAGE_BUCKET=\${STORAGE_BUCKET:-${imageName}}
|
|
133
|
+
- STORAGE_REGION=\${STORAGE_REGION:-eu-west-1}
|
|
134
|
+
- STORAGE_FORCE_PATH_STYLE=true
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (serviceMap.has('mailpit')) {
|
|
139
|
+
yaml += ` - SMTP_HOST=\${SMTP_HOST:-mailpit}
|
|
140
|
+
- SMTP_PORT=\${SMTP_PORT:-1025}
|
|
141
|
+
- SMTP_USER=\${SMTP_USER:-${imageName}}
|
|
142
|
+
- SMTP_PASS=\${SMTP_PASS:-${imageName}}
|
|
133
143
|
`;
|
|
134
144
|
}
|
|
135
145
|
|
|
@@ -230,12 +240,13 @@ services:
|
|
|
230
240
|
container_name: minio
|
|
231
241
|
restart: unless-stopped
|
|
232
242
|
entrypoint: sh
|
|
233
|
-
command: -c 'mkdir -p /data/\${
|
|
243
|
+
command: -c 'mkdir -p /data/\${STORAGE_BUCKET:-${imageName}} && /usr/bin/docker-entrypoint.sh server --console-address ":9001" /data'
|
|
234
244
|
environment:
|
|
235
|
-
MINIO_ROOT_USER: \${
|
|
236
|
-
MINIO_ROOT_PASSWORD: \${
|
|
245
|
+
MINIO_ROOT_USER: \${STORAGE_ACCESS_KEY_ID:-${imageName}}
|
|
246
|
+
MINIO_ROOT_PASSWORD: \${STORAGE_SECRET_ACCESS_KEY:-${imageName}}
|
|
237
247
|
ports:
|
|
238
|
-
- "
|
|
248
|
+
- "\${MINIO_API_PORT:-9000}:9000"
|
|
249
|
+
- "\${MINIO_CONSOLE_PORT:-9001}:9001"
|
|
239
250
|
volumes:
|
|
240
251
|
- minio_data:/data
|
|
241
252
|
healthcheck:
|
|
@@ -248,6 +259,28 @@ services:
|
|
|
248
259
|
`;
|
|
249
260
|
}
|
|
250
261
|
|
|
262
|
+
const mailpitImage = serviceMap.get('mailpit');
|
|
263
|
+
if (mailpitImage) {
|
|
264
|
+
yaml += `
|
|
265
|
+
mailpit:
|
|
266
|
+
image: ${mailpitImage}
|
|
267
|
+
container_name: mailpit
|
|
268
|
+
restart: unless-stopped
|
|
269
|
+
environment:
|
|
270
|
+
MP_SMTP_AUTH: \${SMTP_USER:-${imageName}}:\${SMTP_PASS:-${imageName}}
|
|
271
|
+
ports:
|
|
272
|
+
- "\${MAILPIT_UI_PORT:-8025}:8025" # Web UI
|
|
273
|
+
- "\${MAILPIT_SMTP_PORT:-1025}:1025" # SMTP
|
|
274
|
+
healthcheck:
|
|
275
|
+
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025"]
|
|
276
|
+
interval: 5s
|
|
277
|
+
timeout: 5s
|
|
278
|
+
retries: 5
|
|
279
|
+
networks:
|
|
280
|
+
- app-network
|
|
281
|
+
`;
|
|
282
|
+
}
|
|
283
|
+
|
|
251
284
|
// Add volumes
|
|
252
285
|
yaml += `
|
|
253
286
|
volumes:
|
|
@@ -366,9 +399,11 @@ services:
|
|
|
366
399
|
for (const [appName, app] of apps) {
|
|
367
400
|
yaml += generateAppService(appName, app, apps, {
|
|
368
401
|
registry,
|
|
402
|
+
projectName: workspace.name,
|
|
369
403
|
hasPostgres,
|
|
370
404
|
hasRedis,
|
|
371
405
|
hasMinio,
|
|
406
|
+
hasMail,
|
|
372
407
|
});
|
|
373
408
|
}
|
|
374
409
|
|
|
@@ -419,9 +454,16 @@ services:
|
|
|
419
454
|
image: axllent/mailpit:latest
|
|
420
455
|
container_name: ${workspace.name}-mailpit
|
|
421
456
|
restart: unless-stopped
|
|
457
|
+
environment:
|
|
458
|
+
MP_SMTP_AUTH: \${SMTP_USER:-${workspace.name}}:\${SMTP_PASS:-${workspace.name}}
|
|
422
459
|
ports:
|
|
423
|
-
- "8025:8025" # Web UI
|
|
424
|
-
- "1025:1025" # SMTP
|
|
460
|
+
- "\${MAILPIT_UI_PORT:-8025}:8025" # Web UI
|
|
461
|
+
- "\${MAILPIT_SMTP_PORT:-1025}:1025" # SMTP
|
|
462
|
+
healthcheck:
|
|
463
|
+
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025"]
|
|
464
|
+
interval: 5s
|
|
465
|
+
timeout: 5s
|
|
466
|
+
retries: 5
|
|
425
467
|
networks:
|
|
426
468
|
- workspace-network
|
|
427
469
|
`;
|
|
@@ -434,12 +476,13 @@ services:
|
|
|
434
476
|
container_name: ${workspace.name}-minio
|
|
435
477
|
restart: unless-stopped
|
|
436
478
|
entrypoint: sh
|
|
437
|
-
command: -c 'mkdir -p /data/\${
|
|
479
|
+
command: -c 'mkdir -p /data/\${STORAGE_BUCKET:-${workspace.name}} && /usr/bin/docker-entrypoint.sh server --console-address ":9001" /data'
|
|
438
480
|
environment:
|
|
439
|
-
MINIO_ROOT_USER: \${
|
|
440
|
-
MINIO_ROOT_PASSWORD: \${
|
|
481
|
+
MINIO_ROOT_USER: \${STORAGE_ACCESS_KEY_ID:-${workspace.name}}
|
|
482
|
+
MINIO_ROOT_PASSWORD: \${STORAGE_SECRET_ACCESS_KEY:-${workspace.name}}
|
|
441
483
|
ports:
|
|
442
|
-
- "
|
|
484
|
+
- "\${MINIO_API_PORT:-9000}:9000"
|
|
485
|
+
- "\${MINIO_CONSOLE_PORT:-9001}:9001"
|
|
443
486
|
volumes:
|
|
444
487
|
- minio_data:/data
|
|
445
488
|
healthcheck:
|
|
@@ -525,12 +568,15 @@ function generateAppService(
|
|
|
525
568
|
allApps: [string, NormalizedAppConfig][],
|
|
526
569
|
options: {
|
|
527
570
|
registry?: string;
|
|
571
|
+
projectName: string;
|
|
528
572
|
hasPostgres: boolean;
|
|
529
573
|
hasRedis: boolean;
|
|
530
574
|
hasMinio: boolean;
|
|
575
|
+
hasMail: boolean;
|
|
531
576
|
},
|
|
532
577
|
): string {
|
|
533
|
-
const { registry, hasPostgres, hasRedis, hasMinio } =
|
|
578
|
+
const { registry, projectName, hasPostgres, hasRedis, hasMinio, hasMail } =
|
|
579
|
+
options;
|
|
534
580
|
const imageRef = registry ? `\${REGISTRY:-${registry}}/` : '';
|
|
535
581
|
|
|
536
582
|
// Health check path - frontends use /, backends use /health
|
|
@@ -575,12 +621,19 @@ function generateAppService(
|
|
|
575
621
|
`;
|
|
576
622
|
}
|
|
577
623
|
if (hasMinio) {
|
|
578
|
-
yaml += ` -
|
|
579
|
-
-
|
|
580
|
-
-
|
|
581
|
-
-
|
|
582
|
-
-
|
|
583
|
-
-
|
|
624
|
+
yaml += ` - STORAGE_ENDPOINT=\${STORAGE_ENDPOINT:-http://minio:9000}
|
|
625
|
+
- STORAGE_ACCESS_KEY_ID=\${STORAGE_ACCESS_KEY_ID:-${projectName}}
|
|
626
|
+
- STORAGE_SECRET_ACCESS_KEY=\${STORAGE_SECRET_ACCESS_KEY:-${projectName}}
|
|
627
|
+
- STORAGE_BUCKET=\${STORAGE_BUCKET:-${projectName}}
|
|
628
|
+
- STORAGE_REGION=\${STORAGE_REGION:-eu-west-1}
|
|
629
|
+
- STORAGE_FORCE_PATH_STYLE=true
|
|
630
|
+
`;
|
|
631
|
+
}
|
|
632
|
+
if (hasMail) {
|
|
633
|
+
yaml += ` - SMTP_HOST=\${SMTP_HOST:-mailpit}
|
|
634
|
+
- SMTP_PORT=\${SMTP_PORT:-1025}
|
|
635
|
+
- SMTP_USER=\${SMTP_USER:-${projectName}}
|
|
636
|
+
- SMTP_PASS=\${SMTP_PASS:-${projectName}}
|
|
584
637
|
`;
|
|
585
638
|
}
|
|
586
639
|
}
|
|
@@ -598,6 +651,7 @@ function generateAppService(
|
|
|
598
651
|
if (hasPostgres) dependencies.push('postgres');
|
|
599
652
|
if (hasRedis) dependencies.push('redis');
|
|
600
653
|
if (hasMinio) dependencies.push('minio');
|
|
654
|
+
if (hasMail) dependencies.push('mailpit');
|
|
601
655
|
}
|
|
602
656
|
|
|
603
657
|
if (dependencies.length > 0) {
|
|
@@ -156,8 +156,7 @@ export function generateDockerFiles(
|
|
|
156
156
|
- '\${MAILPIT_SMTP_HOST_PORT:-1025}:1025'
|
|
157
157
|
- '\${MAILPIT_UI_HOST_PORT:-8025}:8025'
|
|
158
158
|
environment:
|
|
159
|
-
|
|
160
|
-
MP_SMTP_AUTH_ALLOW_INSECURE: 1`);
|
|
159
|
+
MP_SMTP_AUTH: \${SMTP_USER:-${options.name}}:\${SMTP_PASS:-${options.name}}`);
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
// Build docker-compose.yml
|
package/src/init/index.ts
CHANGED
|
@@ -331,6 +331,7 @@ export async function initCommand(
|
|
|
331
331
|
if (services.db) secretServices.push('postgres');
|
|
332
332
|
if (services.cache) secretServices.push('redis');
|
|
333
333
|
if (services.storage) secretServices.push('minio');
|
|
334
|
+
if (services.mail) secretServices.push('mailpit');
|
|
334
335
|
|
|
335
336
|
const devSecrets = createStageSecrets('development', secretServices, {
|
|
336
337
|
projectName: name,
|
package/src/init/versions.ts
CHANGED
|
@@ -42,7 +42,7 @@ export const GEEKMIDAS_VERSIONS = {
|
|
|
42
42
|
'@geekmidas/rate-limit': '~2.0.0',
|
|
43
43
|
'@geekmidas/schema': '~1.0.0',
|
|
44
44
|
'@geekmidas/services': '~1.0.1',
|
|
45
|
-
'@geekmidas/storage': '~2.0.
|
|
45
|
+
'@geekmidas/storage': '~2.0.1',
|
|
46
46
|
'@geekmidas/studio': '~1.0.0',
|
|
47
47
|
'@geekmidas/telescope': '~1.0.0',
|
|
48
48
|
'@geekmidas/testkit': '~1.0.5',
|
|
@@ -259,10 +259,10 @@ describe('generateConnectionUrls', () => {
|
|
|
259
259
|
expect(urls.DATABASE_URL).toBeDefined();
|
|
260
260
|
expect(urls.REDIS_URL).toBeDefined();
|
|
261
261
|
expect(urls.RABBITMQ_URL).toBeDefined();
|
|
262
|
-
expect(urls.
|
|
262
|
+
expect(urls.STORAGE_ENDPOINT).toBe('http://minio:9000');
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
-
it('should generate
|
|
265
|
+
it('should generate STORAGE_ENDPOINT for minio', () => {
|
|
266
266
|
const urls = generateConnectionUrls({
|
|
267
267
|
minio: {
|
|
268
268
|
host: 'localhost',
|
|
@@ -273,7 +273,7 @@ describe('generateConnectionUrls', () => {
|
|
|
273
273
|
},
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
-
expect(urls.
|
|
276
|
+
expect(urls.STORAGE_ENDPOINT).toBe('http://localhost:9000');
|
|
277
277
|
expect(urls.DATABASE_URL).toBeUndefined();
|
|
278
278
|
});
|
|
279
279
|
});
|
|
@@ -308,11 +308,11 @@ describe('createStageSecrets', () => {
|
|
|
308
308
|
expect(secrets.urls.RABBITMQ_URL).toBeDefined();
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
-
it('should generate
|
|
311
|
+
it('should generate STORAGE_ENDPOINT for minio', () => {
|
|
312
312
|
const secrets = createStageSecrets('production', ['minio']);
|
|
313
313
|
|
|
314
314
|
expect(secrets.services.minio).toBeDefined();
|
|
315
|
-
expect(secrets.urls.
|
|
315
|
+
expect(secrets.urls.STORAGE_ENDPOINT).toBe('http://localhost:9000');
|
|
316
316
|
});
|
|
317
317
|
});
|
|
318
318
|
|
|
@@ -353,19 +353,19 @@ describe('toEmbeddableSecrets', () => {
|
|
|
353
353
|
},
|
|
354
354
|
},
|
|
355
355
|
urls: {
|
|
356
|
-
|
|
356
|
+
STORAGE_ENDPOINT: 'http://localhost:9000',
|
|
357
357
|
},
|
|
358
358
|
custom: {},
|
|
359
359
|
};
|
|
360
360
|
|
|
361
361
|
const embeddable = toEmbeddableSecrets(secrets);
|
|
362
362
|
|
|
363
|
-
expect(embeddable.
|
|
364
|
-
expect(embeddable.
|
|
365
|
-
expect(embeddable.
|
|
366
|
-
expect(embeddable.
|
|
367
|
-
expect(embeddable.
|
|
368
|
-
expect(embeddable.
|
|
363
|
+
expect(embeddable.STORAGE_ENDPOINT).toBe('http://localhost:9000');
|
|
364
|
+
expect(embeddable.STORAGE_ACCESS_KEY_ID).toBe('myaccesskey');
|
|
365
|
+
expect(embeddable.STORAGE_SECRET_ACCESS_KEY).toBe('mysecretkey');
|
|
366
|
+
expect(embeddable.STORAGE_BUCKET).toBe('my-bucket');
|
|
367
|
+
expect(embeddable.STORAGE_REGION).toBe('eu-west-1');
|
|
368
|
+
expect(embeddable.STORAGE_FORCE_PATH_STYLE).toBe('true');
|
|
369
369
|
});
|
|
370
370
|
|
|
371
371
|
it('should handle all services and custom secrets together', () => {
|
package/src/secrets/generator.ts
CHANGED
|
@@ -40,6 +40,11 @@ const SERVICE_DEFAULTS: Record<
|
|
|
40
40
|
username: 'app',
|
|
41
41
|
bucket: 'app',
|
|
42
42
|
},
|
|
43
|
+
mailpit: {
|
|
44
|
+
host: 'localhost',
|
|
45
|
+
port: 1025,
|
|
46
|
+
username: 'app',
|
|
47
|
+
},
|
|
43
48
|
};
|
|
44
49
|
|
|
45
50
|
/**
|
|
@@ -124,7 +129,12 @@ export function generateConnectionUrls(
|
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
if (services.minio) {
|
|
127
|
-
urls.
|
|
132
|
+
urls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (services.mailpit) {
|
|
136
|
+
urls.SMTP_HOST = services.mailpit.host;
|
|
137
|
+
urls.SMTP_PORT = String(services.mailpit.port);
|
|
128
138
|
}
|
|
129
139
|
|
|
130
140
|
return urls;
|
|
@@ -145,9 +155,15 @@ export function createStageSecrets(
|
|
|
145
155
|
const now = new Date().toISOString();
|
|
146
156
|
const serviceCredentials = generateServicesCredentials(services);
|
|
147
157
|
|
|
148
|
-
// Override
|
|
149
|
-
if (options?.projectName
|
|
150
|
-
serviceCredentials.postgres
|
|
158
|
+
// Override service defaults with project-derived names if provided
|
|
159
|
+
if (options?.projectName) {
|
|
160
|
+
if (serviceCredentials.postgres) {
|
|
161
|
+
serviceCredentials.postgres.database = `${options.projectName.replace(/-/g, '_')}_dev`;
|
|
162
|
+
}
|
|
163
|
+
if (serviceCredentials.minio) {
|
|
164
|
+
serviceCredentials.minio.bucket = options.projectName;
|
|
165
|
+
serviceCredentials.minio.username = options.projectName;
|
|
166
|
+
}
|
|
151
167
|
}
|
|
152
168
|
|
|
153
169
|
const urls = generateConnectionUrls(serviceCredentials);
|
package/src/secrets/index.ts
CHANGED
|
@@ -129,8 +129,8 @@ export async function secretsInitCommand(
|
|
|
129
129
|
if (secrets.urls.RABBITMQ_URL) {
|
|
130
130
|
logger.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
131
131
|
}
|
|
132
|
-
if (secrets.urls.
|
|
133
|
-
logger.log(`
|
|
132
|
+
if (secrets.urls.STORAGE_ENDPOINT) {
|
|
133
|
+
logger.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
if (Object.keys(secrets.custom).length > 0) {
|
|
@@ -260,8 +260,8 @@ export async function secretsShowCommand(
|
|
|
260
260
|
` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`,
|
|
261
261
|
);
|
|
262
262
|
}
|
|
263
|
-
if (secrets.urls.
|
|
264
|
-
logger.log(`
|
|
263
|
+
if (secrets.urls.STORAGE_ENDPOINT) {
|
|
264
|
+
logger.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
// Show custom secrets
|
package/src/secrets/storage.ts
CHANGED
|
@@ -209,11 +209,17 @@ export function toEmbeddableSecrets(secrets: StageSecrets): EmbeddableSecrets {
|
|
|
209
209
|
RABBITMQ_VHOST: secrets.services.rabbitmq.vhost ?? '/',
|
|
210
210
|
}),
|
|
211
211
|
...(secrets.services.minio && {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
STORAGE_ACCESS_KEY_ID: secrets.services.minio.username,
|
|
213
|
+
STORAGE_SECRET_ACCESS_KEY: secrets.services.minio.password,
|
|
214
|
+
STORAGE_BUCKET: secrets.services.minio.bucket ?? 'app',
|
|
215
|
+
STORAGE_REGION: 'eu-west-1',
|
|
216
|
+
STORAGE_FORCE_PATH_STYLE: 'true',
|
|
217
|
+
}),
|
|
218
|
+
...(secrets.services.mailpit && {
|
|
219
|
+
SMTP_HOST: secrets.services.mailpit.host,
|
|
220
|
+
SMTP_PORT: String(secrets.services.mailpit.port),
|
|
221
|
+
SMTP_USER: secrets.services.mailpit.username,
|
|
222
|
+
SMTP_PASS: secrets.services.mailpit.password,
|
|
217
223
|
}),
|
|
218
224
|
};
|
|
219
225
|
}
|
package/src/secrets/types.ts
CHANGED
|
@@ -28,13 +28,16 @@ export interface StageSecrets {
|
|
|
28
28
|
redis?: ServiceCredentials;
|
|
29
29
|
rabbitmq?: ServiceCredentials;
|
|
30
30
|
minio?: ServiceCredentials;
|
|
31
|
+
mailpit?: ServiceCredentials;
|
|
31
32
|
};
|
|
32
33
|
/** Generated connection URLs */
|
|
33
34
|
urls: {
|
|
34
35
|
DATABASE_URL?: string;
|
|
35
36
|
REDIS_URL?: string;
|
|
36
37
|
RABBITMQ_URL?: string;
|
|
37
|
-
|
|
38
|
+
STORAGE_ENDPOINT?: string;
|
|
39
|
+
SMTP_HOST?: string;
|
|
40
|
+
SMTP_PORT?: string;
|
|
38
41
|
};
|
|
39
42
|
/** Custom user-defined secrets */
|
|
40
43
|
custom: Record<string, string>;
|
|
@@ -151,9 +151,9 @@ describe('reconcileSecrets', () => {
|
|
|
151
151
|
expect(result!.services.minio).toBeDefined();
|
|
152
152
|
expect(result!.services.minio!.host).toBe('localhost');
|
|
153
153
|
expect(result!.services.minio!.port).toBe(9000);
|
|
154
|
-
expect(result!.services.minio!.bucket).toBe('
|
|
154
|
+
expect(result!.services.minio!.bucket).toBe('test-project');
|
|
155
155
|
expect(result!.services.minio!.password).toHaveLength(32);
|
|
156
|
-
expect(result!.urls.
|
|
156
|
+
expect(result!.urls.STORAGE_ENDPOINT).toBe('http://localhost:9000');
|
|
157
157
|
// Existing postgres should be preserved
|
|
158
158
|
expect(result!.services.postgres).toEqual(secrets.services.postgres);
|
|
159
159
|
});
|
|
@@ -175,7 +175,7 @@ describe('reconcileSecrets', () => {
|
|
|
175
175
|
password: 'mysecret',
|
|
176
176
|
bucket: 'my-bucket',
|
|
177
177
|
};
|
|
178
|
-
secrets.urls.
|
|
178
|
+
secrets.urls.STORAGE_ENDPOINT = 'http://localhost:9000';
|
|
179
179
|
|
|
180
180
|
const result = reconcileSecrets(secrets, workspace);
|
|
181
181
|
|
package/src/setup/index.ts
CHANGED
|
@@ -173,6 +173,11 @@ export function reconcileSecrets(
|
|
|
173
173
|
for (const { key, name } of serviceMap) {
|
|
174
174
|
if (workspace.services[key] && !result.services[name]) {
|
|
175
175
|
const creds = generateServiceCredentials(name);
|
|
176
|
+
// Override defaults with project-derived names
|
|
177
|
+
if (name === 'minio') {
|
|
178
|
+
creds.bucket = workspace.name;
|
|
179
|
+
creds.username = workspace.name;
|
|
180
|
+
}
|
|
176
181
|
result = {
|
|
177
182
|
...result,
|
|
178
183
|
services: { ...result.services, [name]: creds },
|
package/src/test/index.ts
CHANGED
|
@@ -6,10 +6,10 @@ import { sniffAppEnvironment } from '../deploy/sniffer';
|
|
|
6
6
|
import {
|
|
7
7
|
createCredentialsPreload,
|
|
8
8
|
loadEnvFiles,
|
|
9
|
-
loadPortState,
|
|
10
9
|
parseComposePortMappings,
|
|
11
10
|
resolveServicePorts,
|
|
12
11
|
rewriteUrlsWithPorts,
|
|
12
|
+
startComposeServices,
|
|
13
13
|
startWorkspaceServices,
|
|
14
14
|
} from '../dev/index';
|
|
15
15
|
import { readStageSecrets, toEmbeddableSecrets } from '../secrets/storage';
|
|
@@ -121,19 +121,17 @@ export async function testCommand(options: TestOptions = {}): Promise<void> {
|
|
|
121
121
|
);
|
|
122
122
|
}
|
|
123
123
|
} catch {
|
|
124
|
-
// Not in a workspace —
|
|
124
|
+
// Not in a workspace — start Docker services from local docker-compose.yml
|
|
125
125
|
const composePath = join(cwd, 'docker-compose.yml');
|
|
126
126
|
const mappings = parseComposePortMappings(composePath);
|
|
127
127
|
if (mappings.length > 0) {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
mappings,
|
|
134
|
-
});
|
|
128
|
+
const resolvedPorts = await resolveServicePorts(cwd);
|
|
129
|
+
await startComposeServices(cwd, resolvedPorts.dockerEnv, secretsEnv);
|
|
130
|
+
|
|
131
|
+
if (resolvedPorts.mappings.length > 0) {
|
|
132
|
+
secretsEnv = rewriteUrlsWithPorts(secretsEnv, resolvedPorts);
|
|
135
133
|
console.log(
|
|
136
|
-
` 🔌 Applied ${Object.keys(ports).length} port mapping(s)`,
|
|
134
|
+
` 🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`,
|
|
137
135
|
);
|
|
138
136
|
}
|
|
139
137
|
}
|
package/src/types.ts
CHANGED
|
@@ -78,7 +78,12 @@ export interface ServiceConfig {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/** Supported docker-compose service names */
|
|
81
|
-
export type ComposeServiceName =
|
|
81
|
+
export type ComposeServiceName =
|
|
82
|
+
| 'postgres'
|
|
83
|
+
| 'redis'
|
|
84
|
+
| 'rabbitmq'
|
|
85
|
+
| 'minio'
|
|
86
|
+
| 'mailpit';
|
|
82
87
|
|
|
83
88
|
/** Services configuration - can be boolean (use defaults) or object with version */
|
|
84
89
|
export type ComposeServicesConfig = {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fullstack-secrets-BC9t9wWB.cjs","names":["SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n>","service: ComposeServiceName","services: ComposeServiceName[]","result: StageSecrets['services']","creds: ServiceCredentials","services: StageSecrets['services']","urls: StageSecrets['urls']","stage: string","options?: { projectName?: string }","secrets: StageSecrets","newCreds: ServiceCredentials","appName: string","password: string","projectName: string","workspace: NormalizedWorkspace","customs: Record<string, string>","frontendPorts: number[]","upperName","secrets: StageSecrets","workspaceRoot: string"],"sources":["../src/secrets/generator.ts","../src/setup/fullstack-secrets.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { ComposeServiceName } from '../types';\nimport type { ServiceCredentials, StageSecrets } from './types';\n\n/**\n * Generate a secure random password using URL-safe base64 characters.\n * @param length Password length (default: 32)\n */\nexport function generateSecurePassword(length = 32): string {\n\treturn randomBytes(Math.ceil((length * 3) / 4))\n\t\t.toString('base64url')\n\t\t.slice(0, length);\n}\n\n/** Default service configurations (localhost for local dev via Docker port mapping) */\nconst SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n> = {\n\tpostgres: {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'app',\n\t\tdatabase: 'app',\n\t},\n\tredis: {\n\t\thost: 'localhost',\n\t\tport: 6379,\n\t\tusername: 'default',\n\t},\n\trabbitmq: {\n\t\thost: 'localhost',\n\t\tport: 5672,\n\t\tusername: 'app',\n\t\tvhost: '/',\n\t},\n\tminio: {\n\t\thost: 'localhost',\n\t\tport: 9000,\n\t\tusername: 'app',\n\t\tbucket: 'app',\n\t},\n};\n\n/**\n * Generate credentials for a specific service.\n */\nexport function generateServiceCredentials(\n\tservice: ComposeServiceName,\n): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS[service];\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t};\n}\n\n/**\n * Generate credentials for multiple services.\n */\nexport function generateServicesCredentials(\n\tservices: ComposeServiceName[],\n): StageSecrets['services'] {\n\tconst result: StageSecrets['services'] = {};\n\n\tfor (const service of services) {\n\t\tresult[service] = generateServiceCredentials(service);\n\t}\n\n\treturn result;\n}\n\n/**\n * Generate connection URL for PostgreSQL.\n */\nexport function generatePostgresUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;\n}\n\n/**\n * Generate connection URL for Redis.\n */\nexport function generateRedisUrl(creds: ServiceCredentials): string {\n\tconst { password, host, port } = creds;\n\treturn `redis://:${encodeURIComponent(password)}@${host}:${port}`;\n}\n\n/**\n * Generate connection URL for RabbitMQ.\n */\nexport function generateRabbitmqUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, vhost } = creds;\n\tconst encodedVhost = encodeURIComponent(vhost ?? '/');\n\treturn `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;\n}\n\n/**\n * Generate endpoint URL for MinIO (S3-compatible).\n */\nexport function generateMinioEndpoint(creds: ServiceCredentials): string {\n\tconst { host, port } = creds;\n\treturn `http://${host}:${port}`;\n}\n\n/**\n * Generate connection URLs from service credentials.\n */\nexport function generateConnectionUrls(\n\tservices: StageSecrets['services'],\n): StageSecrets['urls'] {\n\tconst urls: StageSecrets['urls'] = {};\n\n\tif (services.postgres) {\n\t\turls.DATABASE_URL = generatePostgresUrl(services.postgres);\n\t}\n\n\tif (services.redis) {\n\t\turls.REDIS_URL = generateRedisUrl(services.redis);\n\t}\n\n\tif (services.rabbitmq) {\n\t\turls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);\n\t}\n\n\tif (services.minio) {\n\t\turls.S3_ENDPOINT = generateMinioEndpoint(services.minio);\n\t}\n\n\treturn urls;\n}\n\n/**\n * Create a new StageSecrets object with generated credentials.\n * @param stage - The deployment stage (e.g., 'development', 'production')\n * @param services - List of services to generate credentials for\n * @param options - Optional configuration\n * @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')\n */\nexport function createStageSecrets(\n\tstage: string,\n\tservices: ComposeServiceName[],\n\toptions?: { projectName?: string },\n): StageSecrets {\n\tconst now = new Date().toISOString();\n\tconst serviceCredentials = generateServicesCredentials(services);\n\n\t// Override postgres database name with project-derived name if provided\n\tif (options?.projectName && serviceCredentials.postgres) {\n\t\tserviceCredentials.postgres.database = `${options.projectName.replace(/-/g, '_')}_dev`;\n\t}\n\n\tconst urls = generateConnectionUrls(serviceCredentials);\n\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\tservices: serviceCredentials,\n\t\turls,\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Rotate password for a specific service.\n */\nexport function rotateServicePassword(\n\tsecrets: StageSecrets,\n\tservice: ComposeServiceName,\n): StageSecrets {\n\tconst currentCreds = secrets.services[service];\n\tif (!currentCreds) {\n\t\tthrow new Error(`Service \"${service}\" not configured in secrets`);\n\t}\n\n\tconst newCreds: ServiceCredentials = {\n\t\t...currentCreds,\n\t\tpassword: generateSecurePassword(),\n\t};\n\n\tconst newServices = {\n\t\t...secrets.services,\n\t\t[service]: newCreds,\n\t};\n\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tservices: newServices,\n\t\turls: generateConnectionUrls(newServices),\n\t};\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { generateSecurePassword } from '../secrets/generator.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/**\n * Generate a secure random password for database users.\n * Uses a combination of timestamp and random bytes for uniqueness.\n */\nexport function generateDbPassword(): string {\n\treturn `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Generate database URL for an app.\n * All apps connect to the same database, but use different users/schemas.\n */\nexport function generateDbUrl(\n\tappName: string,\n\tpassword: string,\n\tprojectName: string,\n\thost = 'localhost',\n\tport = 5432,\n): string {\n\tconst userName = appName.replace(/-/g, '_');\n\tconst dbName = `${projectName.replace(/-/g, '_')}_dev`;\n\treturn `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;\n}\n\n/**\n * Generate fullstack-aware custom secrets for a workspace.\n *\n * Generates:\n * - Common secrets: NODE_ENV, PORT, LOG_LEVEL, JWT_SECRET\n * - Per-app database passwords and URLs for backend apps with db service\n * - Better-auth secrets for apps using the better-auth framework\n */\nexport function generateFullstackCustomSecrets(\n\tworkspace: NormalizedWorkspace,\n): Record<string, string> {\n\tconst hasDb = !!workspace.services.db;\n\tconst customs: Record<string, string> = {\n\t\tNODE_ENV: 'development',\n\t\tPORT: '3000',\n\t\tLOG_LEVEL: 'debug',\n\t\tJWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n\t};\n\n\tif (!hasDb) {\n\t\treturn customs;\n\t}\n\n\t// Collect all frontend ports for trusted origins\n\tconst frontendPorts: number[] = [];\n\n\tfor (const [appName, appConfig] of Object.entries(workspace.apps)) {\n\t\tif (appConfig.type === 'frontend') {\n\t\t\tfrontendPorts.push(appConfig.port);\n\t\t\tconst upperName = appName.toUpperCase();\n\t\t\tcustoms[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Backend apps with database: generate per-app DB passwords and URLs\n\t\tconst password = generateDbPassword();\n\t\tconst upperName = appName.toUpperCase();\n\n\t\tcustoms[`${upperName}_DATABASE_URL`] = generateDbUrl(\n\t\t\tappName,\n\t\t\tpassword,\n\t\t\tworkspace.name,\n\t\t);\n\t\tcustoms[`${upperName}_DB_PASSWORD`] = password;\n\n\t\t// Better-auth framework secrets\n\t\tif (appConfig.framework === 'better-auth') {\n\t\t\tcustoms.AUTH_PORT = String(appConfig.port);\n\t\t\tcustoms.AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t\tcustoms.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;\n\t\t\tcustoms.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t}\n\t}\n\n\t// Generate trusted origins for better-auth (all app ports)\n\tif (customs.BETTER_AUTH_SECRET) {\n\t\tconst allPorts = Object.values(workspace.apps).map((a) => a.port);\n\t\tcustoms.BETTER_AUTH_TRUSTED_ORIGINS = allPorts\n\t\t\t.map((p) => `http://localhost:${p}`)\n\t\t\t.join(',');\n\t}\n\n\treturn customs;\n}\n\n/**\n * Extract *_DB_PASSWORD keys from secrets and write docker/.env.\n *\n * The docker/.env file contains database passwords that the PostgreSQL\n * init script reads to create per-app database users.\n */\nexport async function writeDockerEnvFromSecrets(\n\tsecrets: StageSecrets,\n\tworkspaceRoot: string,\n): Promise<void> {\n\tconst dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) =>\n\t\tkey.endsWith('_DB_PASSWORD'),\n\t);\n\n\tif (dbPasswordEntries.length === 0) {\n\t\treturn;\n\t}\n\n\tconst envContent = `# Auto-generated docker environment file\n# Contains database passwords for docker-compose postgres init\n# This file is gitignored - do not commit to version control\n${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join('\\n')}\n`;\n\n\tconst envPath = join(workspaceRoot, 'docker', '.env');\n\tawait mkdir(dirname(envPath), { recursive: true });\n\tawait writeFile(envPath, envContent);\n}\n"],"mappings":";;;;;;;;;;AAQA,SAAgB,uBAAuB,SAAS,IAAY;AAC3D,QAAO,6BAAY,KAAK,KAAM,SAAS,IAAK,EAAE,CAAC,CAC7C,SAAS,YAAY,CACrB,MAAM,GAAG,OAAO;AAClB;;AAGD,MAAMA,mBAGF;CACH,UAAU;EACT,MAAM;EACN,MAAM;EACN,UAAU;EACV,UAAU;CACV;CACD,OAAO;EACN,MAAM;EACN,MAAM;EACN,UAAU;CACV;CACD,UAAU;EACT,MAAM;EACN,MAAM;EACN,UAAU;EACV,OAAO;CACP;CACD,OAAO;EACN,MAAM;EACN,MAAM;EACN,UAAU;EACV,QAAQ;CACR;AACD;;;;AAKD,SAAgB,2BACfC,SACqB;CACrB,MAAM,WAAW,iBAAiB;AAClC,QAAO;EACN,GAAG;EACH,UAAU,wBAAwB;CAClC;AACD;;;;AAKD,SAAgB,4BACfC,UAC2B;CAC3B,MAAMC,SAAmC,CAAE;AAE3C,MAAK,MAAM,WAAW,SACrB,QAAO,WAAW,2BAA2B,QAAQ;AAGtD,QAAO;AACP;;;;AAKD,SAAgB,oBAAoBC,OAAmC;CACtE,MAAM,EAAE,UAAU,UAAU,MAAM,MAAM,UAAU,GAAG;AACrD,SAAQ,eAAe,SAAS,GAAG,mBAAmB,SAAS,CAAC,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS;AAC5F;;;;AAKD,SAAgB,iBAAiBA,OAAmC;CACnE,MAAM,EAAE,UAAU,MAAM,MAAM,GAAG;AACjC,SAAQ,WAAW,mBAAmB,SAAS,CAAC,GAAG,KAAK,GAAG,KAAK;AAChE;;;;AAKD,SAAgB,oBAAoBA,OAAmC;CACtE,MAAM,EAAE,UAAU,UAAU,MAAM,MAAM,OAAO,GAAG;CAClD,MAAM,eAAe,mBAAmB,SAAS,IAAI;AACrD,SAAQ,SAAS,SAAS,GAAG,mBAAmB,SAAS,CAAC,GAAG,KAAK,GAAG,KAAK,GAAG,aAAa;AAC1F;;;;AAKD,SAAgB,sBAAsBA,OAAmC;CACxE,MAAM,EAAE,MAAM,MAAM,GAAG;AACvB,SAAQ,SAAS,KAAK,GAAG,KAAK;AAC9B;;;;AAKD,SAAgB,uBACfC,UACuB;CACvB,MAAMC,OAA6B,CAAE;AAErC,KAAI,SAAS,SACZ,MAAK,eAAe,oBAAoB,SAAS,SAAS;AAG3D,KAAI,SAAS,MACZ,MAAK,YAAY,iBAAiB,SAAS,MAAM;AAGlD,KAAI,SAAS,SACZ,MAAK,eAAe,oBAAoB,SAAS,SAAS;AAG3D,KAAI,SAAS,MACZ,MAAK,cAAc,sBAAsB,SAAS,MAAM;AAGzD,QAAO;AACP;;;;;;;;AASD,SAAgB,mBACfC,OACAL,UACAM,SACe;CACf,MAAM,MAAM,qBAAI,QAAO,aAAa;CACpC,MAAM,qBAAqB,4BAA4B,SAAS;AAGhE,KAAI,SAAS,eAAe,mBAAmB,SAC9C,oBAAmB,SAAS,YAAY,EAAE,QAAQ,YAAY,QAAQ,MAAM,IAAI,CAAC;CAGlF,MAAM,OAAO,uBAAuB,mBAAmB;AAEvD,QAAO;EACN;EACA,WAAW;EACX,WAAW;EACX,UAAU;EACV;EACA,QAAQ,CAAE;CACV;AACD;;;;AAKD,SAAgB,sBACfC,SACAR,SACe;CACf,MAAM,eAAe,QAAQ,SAAS;AACtC,MAAK,aACJ,OAAM,IAAI,OAAO,WAAW,QAAQ;CAGrC,MAAMS,WAA+B;EACpC,GAAG;EACH,UAAU,wBAAwB;CAClC;CAED,MAAM,cAAc;EACnB,GAAG,QAAQ;GACV,UAAU;CACX;AAED,QAAO;EACN,GAAG;EACH,WAAW,qBAAI,QAAO,aAAa;EACnC,UAAU;EACV,MAAM,uBAAuB,YAAY;CACzC;AACD;;;;;;;;ACtLD,SAAgB,qBAA6B;AAC5C,SAAQ,EAAE,KAAK,KAAK,CAAC,SAAS,GAAG,CAAC,EAAE,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE,CAAC;AAC9G;;;;;AAMD,SAAgB,cACfC,SACAC,UACAC,aACA,OAAO,aACP,OAAO,MACE;CACT,MAAM,WAAW,QAAQ,QAAQ,MAAM,IAAI;CAC3C,MAAM,UAAU,EAAE,YAAY,QAAQ,MAAM,IAAI,CAAC;AACjD,SAAQ,eAAe,SAAS,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO;AACtE;;;;;;;;;AAUD,SAAgB,+BACfC,WACyB;CACzB,MAAM,UAAU,UAAU,SAAS;CACnC,MAAMC,UAAkC;EACvC,UAAU;EACV,MAAM;EACN,WAAW;EACX,aAAa,MAAM,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE,CAAC;CACrE;AAED,MAAK,MACJ,QAAO;CAIR,MAAMC,gBAA0B,CAAE;AAElC,MAAK,MAAM,CAAC,SAAS,UAAU,IAAI,OAAO,QAAQ,UAAU,KAAK,EAAE;AAClE,MAAI,UAAU,SAAS,YAAY;AAClC,iBAAc,KAAK,UAAU,KAAK;GAClC,MAAMC,cAAY,QAAQ,aAAa;AACvC,YAAS,EAAEA,YAAU,UAAU,mBAAmB,UAAU,KAAK;AACjE;EACA;EAGD,MAAM,WAAW,oBAAoB;EACrC,MAAM,YAAY,QAAQ,aAAa;AAEvC,WAAS,EAAE,UAAU,kBAAkB,cACtC,SACA,UACA,UAAU,KACV;AACD,WAAS,EAAE,UAAU,iBAAiB;AAGtC,MAAI,UAAU,cAAc,eAAe;AAC1C,WAAQ,YAAY,OAAO,UAAU,KAAK;AAC1C,WAAQ,YAAY,mBAAmB,UAAU,KAAK;AACtD,WAAQ,sBAAsB,cAAc,KAAK,KAAK,CAAC,GAAG,uBAAuB,GAAG,CAAC;AACrF,WAAQ,mBAAmB,mBAAmB,UAAU,KAAK;EAC7D;CACD;AAGD,KAAI,QAAQ,oBAAoB;EAC/B,MAAM,WAAW,OAAO,OAAO,UAAU,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK;AACjE,UAAQ,8BAA8B,SACpC,IAAI,CAAC,OAAO,mBAAmB,EAAE,EAAE,CACnC,KAAK,IAAI;CACX;AAED,QAAO;AACP;;;;;;;AAQD,eAAsB,0BACrBC,SACAC,eACgB;CAChB,MAAM,oBAAoB,OAAO,QAAQ,QAAQ,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,KACrE,IAAI,SAAS,eAAe,CAC5B;AAED,KAAI,kBAAkB,WAAW,EAChC;CAGD,MAAM,cAAc;;;EAGnB,kBAAkB,IAAI,CAAC,CAAC,KAAK,MAAM,MAAM,EAAE,IAAI,GAAG,MAAM,EAAE,CAAC,KAAK,KAAK,CAAC;;CAGvE,MAAM,UAAU,oBAAK,eAAe,UAAU,OAAO;AACrD,OAAM,4BAAM,uBAAQ,QAAQ,EAAE,EAAE,WAAW,KAAM,EAAC;AAClD,OAAM,gCAAU,SAAS,WAAW;AACpC"}
|