kavoru 0.8.1 → 0.8.9
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 +2 -2
- package/package.json +1 -1
- package/src/args.ts +3 -2
- package/src/cli.ts +3 -2
- package/src/features.ts +198 -40
- package/src/prompts.ts +8 -0
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ During setup you can pick which integrations to scaffold. Core is always include
|
|
|
47
47
|
| ID | Feature |
|
|
48
48
|
| ----------- | ---------------------- |
|
|
49
49
|
| `auth` | JWT authentication |
|
|
50
|
-
| `
|
|
50
|
+
| `postgres` | PostgreSQL + Prisma (includes Docker Postgres) |
|
|
51
51
|
| `otel` | OpenTelemetry |
|
|
52
52
|
| `sentry` | Sentry + Spotlight |
|
|
53
53
|
| `kafka` | Kafka producer/consumer|
|
|
@@ -71,7 +71,7 @@ bunx kavoru@latest .
|
|
|
71
71
|
bunx kavoru@latest my-api --minimal
|
|
72
72
|
|
|
73
73
|
# Pick specific features
|
|
74
|
-
bunx kavoru@latest my-api --features auth,
|
|
74
|
+
bunx kavoru@latest my-api --features auth,postgres,otel,sentry
|
|
75
75
|
|
|
76
76
|
# Full stack minus Kafka and Docker
|
|
77
77
|
bunx kavoru@latest my-api --no-features kafka,docker
|
package/package.json
CHANGED
package/src/args.ts
CHANGED
|
@@ -33,12 +33,13 @@ Options:
|
|
|
33
33
|
--no-features <list> Comma-separated features to exclude
|
|
34
34
|
|
|
35
35
|
Features:
|
|
36
|
-
auth,
|
|
36
|
+
auth, postgres, otel, sentry, kafka, websocket, resend, cron, docker
|
|
37
|
+
(prisma is accepted as an alias for postgres)
|
|
37
38
|
|
|
38
39
|
Examples:
|
|
39
40
|
bunx kavoru@latest my-api
|
|
40
41
|
bunx kavoru@latest my-api --minimal
|
|
41
|
-
bunx kavoru@latest my-api --features auth,
|
|
42
|
+
bunx kavoru@latest my-api --features auth,postgres,otel
|
|
42
43
|
bunx kavoru@latest my-api --no-features kafka,docker,resend
|
|
43
44
|
`;
|
|
44
45
|
|
package/src/cli.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
MINIMAL_FEATURES,
|
|
11
11
|
applyFeatures,
|
|
12
12
|
formatFeatureSelection,
|
|
13
|
+
normalizeFeatureSelection,
|
|
13
14
|
parseFeatureExcludeList,
|
|
14
15
|
parseFeatureIncludeList,
|
|
15
16
|
type FeatureSelection,
|
|
@@ -72,7 +73,7 @@ export function resolveFeatureSelection(options: CliOptions): FeatureSelection {
|
|
|
72
73
|
return parseFeatureExcludeList(options.noFeatures, ALL_FEATURES);
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
return { ...ALL_FEATURES };
|
|
76
|
+
return normalizeFeatureSelection({ ...ALL_FEATURES });
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
async function resolveFeatureSelectionInteractive(
|
|
@@ -88,7 +89,7 @@ async function resolveFeatureSelectionInteractive(
|
|
|
88
89
|
return fromFlags;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
return promptFeatureSelection(fromFlags);
|
|
92
|
+
return normalizeFeatureSelection(await promptFeatureSelection(fromFlags));
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
export async function runCli(options: CliOptions): Promise<void> {
|
package/src/features.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { log } from "./log";
|
|
|
4
4
|
|
|
5
5
|
export type FeatureId =
|
|
6
6
|
| "auth"
|
|
7
|
-
| "
|
|
7
|
+
| "postgres"
|
|
8
8
|
| "otel"
|
|
9
9
|
| "sentry"
|
|
10
10
|
| "kafka"
|
|
@@ -13,6 +13,10 @@ export type FeatureId =
|
|
|
13
13
|
| "cron"
|
|
14
14
|
| "docker";
|
|
15
15
|
|
|
16
|
+
const FEATURE_ALIASES: Record<string, FeatureId> = {
|
|
17
|
+
prisma: "postgres",
|
|
18
|
+
};
|
|
19
|
+
|
|
16
20
|
export type FeatureSelection = Record<FeatureId, boolean>;
|
|
17
21
|
|
|
18
22
|
export type FeatureDef = {
|
|
@@ -28,9 +32,9 @@ export const FEATURES: FeatureDef[] = [
|
|
|
28
32
|
description: "Bearer auth, sign-in route, protected routes",
|
|
29
33
|
},
|
|
30
34
|
{
|
|
31
|
-
id: "
|
|
32
|
-
label: "
|
|
33
|
-
description: "Prisma 7
|
|
35
|
+
id: "postgres",
|
|
36
|
+
label: "PostgreSQL",
|
|
37
|
+
description: "Docker Postgres, Prisma 7, migrations, and seed",
|
|
34
38
|
},
|
|
35
39
|
{
|
|
36
40
|
id: "otel",
|
|
@@ -88,7 +92,7 @@ const FEATURE_PATHS: Record<FeatureId, string[]> = {
|
|
|
88
92
|
"src/constants/jwt.ts",
|
|
89
93
|
"src/models/schemas/signin.ts",
|
|
90
94
|
],
|
|
91
|
-
|
|
95
|
+
postgres: ["prisma.config.ts", "src/infra/prisma"],
|
|
92
96
|
otel: ["src/infra/telemetry"],
|
|
93
97
|
sentry: [
|
|
94
98
|
"src/infra/sentry",
|
|
@@ -115,7 +119,7 @@ const FEATURE_DEPENDENCIES: Partial<
|
|
|
115
119
|
Record<FeatureId, { dependencies?: string[]; devDependencies?: string[] }>
|
|
116
120
|
> = {
|
|
117
121
|
auth: { dependencies: ["@elysiajs/bearer", "@elysiajs/jwt"] },
|
|
118
|
-
|
|
122
|
+
postgres: {
|
|
119
123
|
dependencies: ["@prisma/adapter-pg", "@prisma/client"],
|
|
120
124
|
devDependencies: ["prisma"],
|
|
121
125
|
},
|
|
@@ -136,9 +140,42 @@ const FEATURE_DEPENDENCIES: Partial<
|
|
|
136
140
|
const FEATURE_SCRIPTS: Partial<Record<FeatureId, string[]>> = {
|
|
137
141
|
otel: ["otel:view", "otel:tui"],
|
|
138
142
|
sentry: ["sentry:spotlight"],
|
|
139
|
-
|
|
143
|
+
postgres: ["seed"],
|
|
140
144
|
};
|
|
141
145
|
|
|
146
|
+
function resolveFeatureId(raw: string): FeatureId | null {
|
|
147
|
+
const id = FEATURE_ALIASES[raw] ?? raw;
|
|
148
|
+
return FEATURE_IDS.includes(id as FeatureId) ? (id as FeatureId) : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function toPostgresName(packageName: string): string {
|
|
152
|
+
const normalized = packageName
|
|
153
|
+
.replace(/-/g, "_")
|
|
154
|
+
.replace(/[^a-z0-9_]/gi, "_")
|
|
155
|
+
.replace(/_+/g, "_")
|
|
156
|
+
.replace(/^_|_$/g, "");
|
|
157
|
+
return normalized || "app";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function buildDatabaseUrl(
|
|
161
|
+
packageName: string,
|
|
162
|
+
host: string,
|
|
163
|
+
port = 5432,
|
|
164
|
+
): string {
|
|
165
|
+
const name = toPostgresName(packageName);
|
|
166
|
+
return `postgresql://${name}:${name}@${host}:${port}/${name}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function normalizeFeatureSelection(
|
|
170
|
+
selection: FeatureSelection,
|
|
171
|
+
): FeatureSelection {
|
|
172
|
+
const next = { ...selection };
|
|
173
|
+
if (next.postgres) {
|
|
174
|
+
next.docker = true;
|
|
175
|
+
}
|
|
176
|
+
return next;
|
|
177
|
+
}
|
|
178
|
+
|
|
142
179
|
function enabledFeatures(selection: FeatureSelection): FeatureId[] {
|
|
143
180
|
return FEATURE_IDS.filter((id) => selection[id]);
|
|
144
181
|
}
|
|
@@ -159,9 +196,7 @@ export function parseFeatureIncludeList(input: string): FeatureSelection {
|
|
|
159
196
|
.map((part) => part.trim())
|
|
160
197
|
.filter(Boolean);
|
|
161
198
|
|
|
162
|
-
const unknown = requested.filter(
|
|
163
|
-
(id) => !FEATURE_IDS.includes(id as FeatureId),
|
|
164
|
-
);
|
|
199
|
+
const unknown = requested.filter((part) => resolveFeatureId(part) === null);
|
|
165
200
|
if (unknown.length > 0) {
|
|
166
201
|
throw new Error(
|
|
167
202
|
`Unknown feature(s): ${unknown.join(", ")}. Valid: ${FEATURE_IDS.join(", ")}`,
|
|
@@ -169,8 +204,12 @@ export function parseFeatureIncludeList(input: string): FeatureSelection {
|
|
|
169
204
|
}
|
|
170
205
|
|
|
171
206
|
const selection = { ...MINIMAL_FEATURES };
|
|
172
|
-
for (const
|
|
173
|
-
|
|
207
|
+
for (const part of requested) {
|
|
208
|
+
const id = resolveFeatureId(part);
|
|
209
|
+
if (id) selection[id] = true;
|
|
210
|
+
}
|
|
211
|
+
if (selection.postgres) {
|
|
212
|
+
selection.docker = true;
|
|
174
213
|
}
|
|
175
214
|
return selection;
|
|
176
215
|
}
|
|
@@ -183,13 +222,14 @@ export function parseFeatureExcludeList(
|
|
|
183
222
|
const unknown: string[] = [];
|
|
184
223
|
|
|
185
224
|
for (const raw of excluded) {
|
|
186
|
-
const
|
|
187
|
-
if (!
|
|
188
|
-
|
|
189
|
-
|
|
225
|
+
const part = raw.trim().toLowerCase();
|
|
226
|
+
if (!part) continue;
|
|
227
|
+
const id = resolveFeatureId(part);
|
|
228
|
+
if (!id) {
|
|
229
|
+
unknown.push(part);
|
|
190
230
|
continue;
|
|
191
231
|
}
|
|
192
|
-
selection[id
|
|
232
|
+
selection[id] = false;
|
|
193
233
|
}
|
|
194
234
|
|
|
195
235
|
if (unknown.length > 0) {
|
|
@@ -198,6 +238,10 @@ export function parseFeatureExcludeList(
|
|
|
198
238
|
);
|
|
199
239
|
}
|
|
200
240
|
|
|
241
|
+
if (!selection.docker) {
|
|
242
|
+
selection.postgres = false;
|
|
243
|
+
}
|
|
244
|
+
|
|
201
245
|
return selection;
|
|
202
246
|
}
|
|
203
247
|
|
|
@@ -349,11 +393,17 @@ export function buildEntryIndex(selection: FeatureSelection): string {
|
|
|
349
393
|
return [...imports, ...body].join("\n");
|
|
350
394
|
}
|
|
351
395
|
|
|
352
|
-
async function patchEntryIndex(
|
|
396
|
+
async function patchEntryIndex(
|
|
397
|
+
projectDir: string,
|
|
398
|
+
selection: FeatureSelection,
|
|
399
|
+
) {
|
|
353
400
|
await writeText(projectDir, "src/index.ts", buildEntryIndex(selection));
|
|
354
401
|
}
|
|
355
402
|
|
|
356
|
-
async function patchServerIndex(
|
|
403
|
+
async function patchServerIndex(
|
|
404
|
+
projectDir: string,
|
|
405
|
+
selection: FeatureSelection,
|
|
406
|
+
) {
|
|
357
407
|
const relativePath = "src/server/index.ts";
|
|
358
408
|
const current = await readText(projectDir, relativePath);
|
|
359
409
|
if (!current) return;
|
|
@@ -422,7 +472,7 @@ async function patchPackageJson(
|
|
|
422
472
|
}
|
|
423
473
|
}
|
|
424
474
|
|
|
425
|
-
if (!selection.
|
|
475
|
+
if (!selection.postgres && pkg.scripts) {
|
|
426
476
|
pkg.scripts.start = "bun run src/index.ts";
|
|
427
477
|
}
|
|
428
478
|
|
|
@@ -435,9 +485,11 @@ export function buildEnvExample(
|
|
|
435
485
|
): string {
|
|
436
486
|
const lines = ["NODE_ENV=development", "PORT=3131", ""];
|
|
437
487
|
|
|
438
|
-
if (selection.
|
|
488
|
+
if (selection.postgres) {
|
|
439
489
|
lines.push(
|
|
440
|
-
"
|
|
490
|
+
"# Start database: docker compose up -d postgres",
|
|
491
|
+
"# Host dev uses published port; Docker app overrides host in docker/app/.env",
|
|
492
|
+
`DATABASE_URL=${buildDatabaseUrl(packageName, "localhost")}`,
|
|
441
493
|
"",
|
|
442
494
|
);
|
|
443
495
|
}
|
|
@@ -507,7 +559,10 @@ async function patchEnvExample(
|
|
|
507
559
|
await writeText(projectDir, ".env", content);
|
|
508
560
|
}
|
|
509
561
|
|
|
510
|
-
async function patchDockerfile(
|
|
562
|
+
async function patchDockerfile(
|
|
563
|
+
projectDir: string,
|
|
564
|
+
selection: FeatureSelection,
|
|
565
|
+
) {
|
|
511
566
|
if (!selection.docker) return;
|
|
512
567
|
|
|
513
568
|
const relativePath = "docker/app/Dockerfile";
|
|
@@ -515,14 +570,19 @@ async function patchDockerfile(projectDir: string, selection: FeatureSelection)
|
|
|
515
570
|
if (!current) return;
|
|
516
571
|
|
|
517
572
|
let content = current;
|
|
518
|
-
if (!selection.
|
|
573
|
+
if (!selection.postgres) {
|
|
519
574
|
content = content.replace(/^\s*COPY prisma\.config\.ts \.\/.*\n/m, "");
|
|
575
|
+
content = content.replace(/^\s*RUN bunx prisma generate\n/m, "");
|
|
576
|
+
content = content.replace(
|
|
577
|
+
/^COPY docker\/app\/docker-entrypoint\.sh .*$\n/m,
|
|
578
|
+
"",
|
|
579
|
+
);
|
|
520
580
|
content = content.replace(
|
|
521
|
-
|
|
581
|
+
/^RUN sed -i 's\/\\r\$\/\/' \/app\/docker-entrypoint\.sh && chmod \+x \/app\/docker-entrypoint\.sh\n/m,
|
|
522
582
|
"",
|
|
523
583
|
);
|
|
524
584
|
content = content.replace(
|
|
525
|
-
|
|
585
|
+
/^ENTRYPOINT \["\/bin\/sh", "\/app\/docker-entrypoint\.sh"\]\n/m,
|
|
526
586
|
"",
|
|
527
587
|
);
|
|
528
588
|
}
|
|
@@ -530,11 +590,47 @@ async function patchDockerfile(projectDir: string, selection: FeatureSelection)
|
|
|
530
590
|
await writeText(projectDir, relativePath, content);
|
|
531
591
|
}
|
|
532
592
|
|
|
533
|
-
|
|
593
|
+
const DOCKER_KAFKA_ENV = `# KRaft broker config (Confluent cp-kafka 7.6.1)
|
|
594
|
+
CLUSTER_ID=MkU3OEVBNTcwNTJENDM2Qk
|
|
595
|
+
KAFKA_NODE_ID=0
|
|
596
|
+
KAFKA_PROCESS_ROLES=broker,controller
|
|
597
|
+
KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
|
|
598
|
+
KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://localhost:9094
|
|
599
|
+
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT
|
|
600
|
+
KAFKA_CONTROLLER_QUORUM_VOTERS=0@kafka:9093
|
|
601
|
+
KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER
|
|
602
|
+
KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT
|
|
603
|
+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1
|
|
604
|
+
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1
|
|
605
|
+
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1
|
|
606
|
+
KAFKA_LOG_DIRS=/tmp/kraft-combined-logs
|
|
607
|
+
`;
|
|
608
|
+
|
|
609
|
+
const DOCKER_OTEL_ENV =
|
|
610
|
+
"# otel-dev runs with CLI flags in Dockerfile; add overrides here if needed.\n";
|
|
611
|
+
|
|
612
|
+
const DOCKER_SPOTLIGHT_ENV =
|
|
613
|
+
"# Official Spotlight image; add overrides here if needed.\n";
|
|
614
|
+
|
|
615
|
+
function buildDockerPostgresEnv(packageName: string): string {
|
|
616
|
+
const name = toPostgresName(packageName);
|
|
617
|
+
return `POSTGRES_USER=${name}
|
|
618
|
+
POSTGRES_PASSWORD=${name}
|
|
619
|
+
POSTGRES_DB=${name}
|
|
620
|
+
`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function buildDockerAppEnv(
|
|
624
|
+
selection: FeatureSelection,
|
|
625
|
+
packageName: string,
|
|
626
|
+
): string {
|
|
534
627
|
const lines = [
|
|
535
628
|
"# Docker-only overrides (loaded after root .env)",
|
|
536
629
|
"NODE_ENV=production",
|
|
537
630
|
];
|
|
631
|
+
if (selection.postgres) {
|
|
632
|
+
lines.push(`DATABASE_URL=${buildDatabaseUrl(packageName, "postgres")}`);
|
|
633
|
+
}
|
|
538
634
|
if (selection.kafka) {
|
|
539
635
|
lines.push("KAFKA_BROKERS=kafka:9092");
|
|
540
636
|
}
|
|
@@ -547,11 +643,45 @@ function buildDockerAppEnv(selection: FeatureSelection): string {
|
|
|
547
643
|
return `${lines.join("\n")}\n`;
|
|
548
644
|
}
|
|
549
645
|
|
|
646
|
+
function buildAppDependsOn(selection: FeatureSelection): string {
|
|
647
|
+
const deps: string[] = [];
|
|
648
|
+
if (selection.postgres) {
|
|
649
|
+
deps.push(` postgres:
|
|
650
|
+
condition: service_healthy`);
|
|
651
|
+
}
|
|
652
|
+
if (selection.kafka) {
|
|
653
|
+
deps.push(` kafka:
|
|
654
|
+
condition: service_started`);
|
|
655
|
+
}
|
|
656
|
+
if (deps.length === 0) return "";
|
|
657
|
+
return ` depends_on:
|
|
658
|
+
${deps.join("\n")}
|
|
659
|
+
`;
|
|
660
|
+
}
|
|
661
|
+
|
|
550
662
|
function generateDockerCompose(selection: FeatureSelection): string {
|
|
551
|
-
const appDependsOn = selection
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
663
|
+
const appDependsOn = buildAppDependsOn(selection);
|
|
664
|
+
|
|
665
|
+
const postgresService = selection.postgres
|
|
666
|
+
? `
|
|
667
|
+
postgres:
|
|
668
|
+
build:
|
|
669
|
+
context: docker/postgres
|
|
670
|
+
hostname: postgres
|
|
671
|
+
ports:
|
|
672
|
+
- "\${POSTGRES_PORT:-5432}:5432"
|
|
673
|
+
env_file:
|
|
674
|
+
- docker/postgres/.env
|
|
675
|
+
volumes:
|
|
676
|
+
- postgres_data:/var/lib/postgresql/data
|
|
677
|
+
networks:
|
|
678
|
+
- app_network
|
|
679
|
+
healthcheck:
|
|
680
|
+
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
|
681
|
+
interval: 5s
|
|
682
|
+
timeout: 5s
|
|
683
|
+
retries: 5
|
|
684
|
+
restart: unless-stopped
|
|
555
685
|
`
|
|
556
686
|
: "";
|
|
557
687
|
|
|
@@ -609,7 +739,7 @@ function generateDockerCompose(selection: FeatureSelection): string {
|
|
|
609
739
|
target: build
|
|
610
740
|
args:
|
|
611
741
|
PORT: \${PORT:-3131}
|
|
612
|
-
command:
|
|
742
|
+
command: ./server
|
|
613
743
|
volumes:
|
|
614
744
|
- ./src:/app/src
|
|
615
745
|
networks:
|
|
@@ -622,27 +752,35 @@ function generateDockerCompose(selection: FeatureSelection): string {
|
|
|
622
752
|
- "\${PORT:-3131}:\${PORT:-3131}"
|
|
623
753
|
restart: unless-stopped
|
|
624
754
|
env_file:
|
|
625
|
-
- .env
|
|
755
|
+
- path: .env
|
|
756
|
+
required: false
|
|
626
757
|
- docker/app/.env
|
|
627
758
|
${appDependsOn} healthcheck:
|
|
628
|
-
test: ["CMD", "curl", "-f", "http://localhost:\${PORT}/healthz"]
|
|
759
|
+
test: ["CMD", "curl", "-f", "http://localhost:\${PORT:-3131}/healthz"]
|
|
629
760
|
interval: 600s
|
|
630
761
|
timeout: 300s
|
|
631
762
|
retries: 1
|
|
632
|
-
start_period:
|
|
633
|
-
${kafkaService}${otelService}${spotlightService}
|
|
763
|
+
start_period: 90s
|
|
764
|
+
${postgresService}${kafkaService}${otelService}${spotlightService}
|
|
634
765
|
networks:
|
|
635
766
|
app_network:
|
|
636
767
|
driver: bridge
|
|
637
|
-
`;
|
|
768
|
+
${selection.postgres ? "\nvolumes:\n postgres_data:\n" : ""}`;
|
|
638
769
|
}
|
|
639
770
|
|
|
640
771
|
async function patchDockerCompose(
|
|
641
772
|
projectDir: string,
|
|
642
773
|
selection: FeatureSelection,
|
|
774
|
+
packageName: string,
|
|
643
775
|
) {
|
|
644
776
|
if (!selection.docker) return;
|
|
645
777
|
|
|
778
|
+
if (!selection.postgres) {
|
|
779
|
+
await removePaths(projectDir, [
|
|
780
|
+
"docker/postgres",
|
|
781
|
+
"docker/app/docker-entrypoint.sh",
|
|
782
|
+
]);
|
|
783
|
+
}
|
|
646
784
|
if (!selection.kafka) {
|
|
647
785
|
await removePaths(projectDir, ["docker/kafka"]);
|
|
648
786
|
}
|
|
@@ -656,9 +794,29 @@ async function patchDockerCompose(
|
|
|
656
794
|
await writeText(
|
|
657
795
|
projectDir,
|
|
658
796
|
"docker/app/.env",
|
|
659
|
-
buildDockerAppEnv(selection),
|
|
797
|
+
buildDockerAppEnv(selection, packageName),
|
|
798
|
+
);
|
|
799
|
+
if (selection.postgres) {
|
|
800
|
+
await writeText(
|
|
801
|
+
projectDir,
|
|
802
|
+
"docker/postgres/.env",
|
|
803
|
+
buildDockerPostgresEnv(packageName),
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
if (selection.kafka) {
|
|
807
|
+
await writeText(projectDir, "docker/kafka/.env", DOCKER_KAFKA_ENV);
|
|
808
|
+
}
|
|
809
|
+
if (selection.otel) {
|
|
810
|
+
await writeText(projectDir, "docker/otel/.env", DOCKER_OTEL_ENV);
|
|
811
|
+
}
|
|
812
|
+
if (selection.sentry) {
|
|
813
|
+
await writeText(projectDir, "docker/spotlight/.env", DOCKER_SPOTLIGHT_ENV);
|
|
814
|
+
}
|
|
815
|
+
await writeText(
|
|
816
|
+
projectDir,
|
|
817
|
+
"docker-compose.yaml",
|
|
818
|
+
generateDockerCompose(selection),
|
|
660
819
|
);
|
|
661
|
-
await writeText(projectDir, "docker-compose.yaml", generateDockerCompose(selection));
|
|
662
820
|
}
|
|
663
821
|
|
|
664
822
|
export async function applyFeatures(
|
|
@@ -679,7 +837,7 @@ export async function applyFeatures(
|
|
|
679
837
|
await patchPackageJson(projectDir, selection);
|
|
680
838
|
await patchEnvExample(projectDir, selection, packageName);
|
|
681
839
|
await patchDockerfile(projectDir, selection);
|
|
682
|
-
await patchDockerCompose(projectDir, selection);
|
|
840
|
+
await patchDockerCompose(projectDir, selection, packageName);
|
|
683
841
|
|
|
684
842
|
if (disabled.length > 0) {
|
|
685
843
|
log.success("Feature selection applied");
|
package/src/prompts.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
ALL_FEATURES,
|
|
4
4
|
FEATURES,
|
|
5
5
|
MINIMAL_FEATURES,
|
|
6
|
+
normalizeFeatureSelection,
|
|
6
7
|
type FeatureId,
|
|
7
8
|
type FeatureSelection,
|
|
8
9
|
formatFeatureSelection,
|
|
@@ -154,6 +155,12 @@ export async function promptFeatureSelection(
|
|
|
154
155
|
const feature = FEATURES[activeIndex];
|
|
155
156
|
if (!feature) break;
|
|
156
157
|
selection[feature.id as FeatureId] = !selection[feature.id as FeatureId];
|
|
158
|
+
if (feature.id === "postgres" && selection.postgres) {
|
|
159
|
+
selection.docker = true;
|
|
160
|
+
}
|
|
161
|
+
if (feature.id === "docker" && !selection.docker) {
|
|
162
|
+
selection.postgres = false;
|
|
163
|
+
}
|
|
157
164
|
lineCount = renderCheckboxMenu(selection, activeIndex, lineCount);
|
|
158
165
|
break;
|
|
159
166
|
}
|
|
@@ -166,6 +173,7 @@ export async function promptFeatureSelection(
|
|
|
166
173
|
lineCount = renderCheckboxMenu(selection, activeIndex, lineCount);
|
|
167
174
|
break;
|
|
168
175
|
case "confirm":
|
|
176
|
+
Object.assign(selection, normalizeFeatureSelection(selection));
|
|
169
177
|
restoreTerminal(onData);
|
|
170
178
|
stdout.write("\n");
|
|
171
179
|
resolve(selection);
|