buncargo 1.0.29 → 3.2.0
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/dist/bin.d.ts +1 -12
- package/dist/bin.js +261 -253
- package/dist/cli/bin.d.ts +13 -0
- package/dist/cli/bin.js +317 -0
- package/dist/cli/commands/help.d.ts +1 -0
- package/dist/cli/commands/runtime.d.ts +5 -0
- package/dist/cli/commands/version.d.ts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/run-cli.d.ts +30 -0
- package/dist/cli.d.ts +1 -22
- package/dist/cli.js +5 -13
- package/dist/config/config.d.ts +1 -0
- package/dist/config/define-config.d.ts +13 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +15 -0
- package/dist/config/merge-configs.d.ts +3 -0
- package/dist/config/validate-config.d.ts +3 -0
- package/dist/config.d.ts +1 -72
- package/dist/config.js +12 -12
- package/dist/core/docker.d.ts +1 -83
- package/dist/core/docker.js +35 -32
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +123 -118
- package/dist/core/network.js +2 -2
- package/dist/core/ports.js +1 -1
- package/dist/core/process.js +1 -1
- package/dist/core/quick-tunnel/cloudflared-process.d.ts +10 -0
- package/dist/core/quick-tunnel/constants.d.ts +9 -0
- package/dist/core/quick-tunnel/index.d.ts +17 -0
- package/dist/core/quick-tunnel/install.d.ts +1 -0
- package/dist/core/tunnel.d.ts +34 -0
- package/dist/core/utils.js +2 -2
- package/dist/core/watchdog-runner.js +45 -42
- package/dist/core/watchdog.d.ts +1 -0
- package/dist/core/watchdog.js +4 -2
- package/dist/docker/index.d.ts +1 -0
- package/dist/docker/index.js +38 -0
- package/dist/docker/runtime.d.ts +87 -0
- package/dist/docker/runtime.js +37 -0
- package/dist/docker-compose/compose.d.ts +1 -0
- package/dist/docker-compose/generated-file.d.ts +7 -0
- package/dist/docker-compose/index.d.ts +3 -0
- package/dist/docker-compose/index.js +15 -0
- package/dist/docker-compose/model.d.ts +6 -0
- package/dist/docker-compose/services/clickhouse.d.ts +16 -0
- package/dist/docker-compose/services/define-docker-service.d.ts +41 -0
- package/dist/docker-compose/services/index.d.ts +23 -0
- package/dist/docker-compose/services/index.js +17 -0
- package/dist/docker-compose/services/postgres.d.ts +12 -0
- package/dist/docker-compose/services/redis.d.ts +12 -0
- package/dist/docker-compose/services/shared.d.ts +7 -0
- package/dist/docker-compose/yaml.d.ts +2 -0
- package/dist/environment/create-dev-environment.d.ts +23 -0
- package/dist/environment/index.d.ts +1 -0
- package/dist/environment/index.js +15 -0
- package/dist/environment/logging.d.ts +17 -0
- package/dist/environment/only-apps.d.ts +10 -0
- package/dist/environment/seeding.d.ts +9 -0
- package/dist/environment.d.ts +1 -23
- package/dist/environment.js +12 -14
- package/dist/index-045jksh5.js +147 -0
- package/dist/index-08wa79cs.js +125 -117
- package/dist/index-0kxnae3z.js +335 -0
- package/dist/index-1mdrf7nz.js +51 -43
- package/dist/index-1yvbwj4k.js +262 -242
- package/dist/index-23ev345g.js +475 -0
- package/dist/index-2ckr49sf.js +228 -0
- package/dist/index-2f47khe5.js +376 -369
- package/dist/index-2fr3g85b.js +220 -183
- package/dist/index-38xnzpa6.js +450 -0
- package/dist/index-3eyrdxw9.js +577 -0
- package/dist/index-3h3dhtf2.js +51 -43
- package/dist/index-42x95209.js +51 -43
- package/dist/index-4gp0az1g.js +145 -0
- package/dist/index-4xrxh8yv.js +72 -0
- package/dist/index-5aq985p4.js +250 -0
- package/dist/index-5gmws6ah.js +181 -0
- package/dist/index-5hka0tff.js +78 -76
- package/dist/index-5rfqps4b.js +3 -0
- package/dist/index-5t9jxqm0.js +428 -0
- package/dist/index-6c1w1xk5.js +101 -0
- package/dist/index-6cmex7m5.js +72 -0
- package/dist/index-6d6x175r.js +572 -0
- package/dist/index-6fm7mvwj.js +118 -97
- package/dist/index-6srpc523.js +127 -128
- package/dist/index-731rzzfp.js +157 -142
- package/dist/index-75y4cg2z.js +51 -43
- package/dist/index-7ja4ywyj.js +126 -127
- package/dist/index-7v19es2e.js +666 -0
- package/dist/index-8bw1cmz4.js +531 -0
- package/dist/index-8hbbj1mp.js +120 -121
- package/dist/index-8xj2p5n5.js +118 -97
- package/dist/index-9wyhzw0h.js +574 -0
- package/dist/index-ag90ry8t.js +576 -0
- package/dist/index-bj79tw5w.js +0 -0
- package/dist/index-bnk6nr0g.js +73 -0
- package/dist/index-brbbzyks.js +72 -0
- package/dist/index-byeqyjrz.js +72 -0
- package/dist/index-c0dr6mcv.js +123 -0
- package/dist/index-cty0bcry.js +235 -218
- package/dist/index-d8tyv5se.js +228 -0
- package/dist/index-d9efy0n4.js +176 -150
- package/dist/index-enj4zdma.js +574 -0
- package/dist/index-etfmqjjf.js +427 -0
- package/dist/index-fb29934k.js +172 -0
- package/dist/index-g50jw1yf.js +72 -0
- package/dist/index-g6eb5wdw.js +118 -117
- package/dist/index-ggq3yryx.js +99 -95
- package/dist/index-h70tce00.js +177 -0
- package/dist/index-hkxtfqtc.js +333 -0
- package/dist/index-k370bech.js +72 -0
- package/dist/index-kf3dhser.js +146 -143
- package/dist/index-ma6tgdb2.js +500 -0
- package/dist/index-mam0bcyz.js +123 -0
- package/dist/index-mm412dkp.js +274 -0
- package/dist/index-n8v18aeb.js +0 -0
- package/dist/index-ndnmnsej.js +378 -371
- package/dist/index-p8wty0e2.js +389 -379
- package/dist/index-qa8akv6y.js +666 -0
- package/dist/index-qfphr2fd.js +78 -76
- package/dist/index-qqmms8rs.js +51 -43
- package/dist/index-qw4093g2.js +51 -43
- package/dist/index-qzwpzjbx.js +121 -122
- package/dist/index-segbnm0h.js +146 -143
- package/dist/index-t0fj6gg1.js +112 -0
- package/dist/index-thdkwnv7.js +122 -0
- package/dist/index-tjbx2r2t.js +270 -0
- package/dist/index-tjqw9vtj.js +62 -54
- package/dist/index-vbpb89jy.js +248 -0
- package/dist/index-vg55rq0y.js +250 -0
- package/dist/index-vhs88xhe.js +99 -95
- package/dist/index-vs81yaks.js +244 -0
- package/dist/index-w8zxnjka.js +249 -0
- package/dist/index-wk2na3t9.js +385 -375
- package/dist/index-wz9x8g7z.js +383 -373
- package/dist/index-x249gyde.js +388 -378
- package/dist/index-x54nbgs7.js +355 -0
- package/dist/index-xkvd0nsd.js +187 -0
- package/dist/index-yedqxm1z.js +80 -0
- package/dist/index-yz4jfz7z.js +338 -0
- package/dist/index-zfjzzjkf.js +240 -199
- package/dist/index.d.ts +12 -8
- package/dist/index.js +56 -34
- package/dist/lint.d.ts +1 -46
- package/dist/lint.js +3 -7
- package/dist/loader/cache.d.ts +4 -0
- package/dist/loader/find-config-file.d.ts +2 -0
- package/dist/loader/index.d.ts +5 -0
- package/dist/loader/index.js +24 -0
- package/dist/loader/load-dev-env.d.ts +5 -0
- package/dist/loader/loader.d.ts +1 -0
- package/dist/loader.d.ts +1 -45
- package/dist/loader.js +22 -20
- package/dist/prisma/index.d.ts +1 -0
- package/dist/prisma/prisma.d.ts +29 -0
- package/dist/prisma.d.ts +1 -29
- package/dist/prisma.js +6 -10
- package/dist/src/bin.js +309 -0
- package/dist/src/cli.js +5 -0
- package/dist/src/config.js +15 -0
- package/dist/src/core/docker.js +38 -0
- package/dist/src/core/index.js +130 -0
- package/dist/src/core/network.js +9 -0
- package/dist/src/core/ports.js +23 -0
- package/dist/src/core/process.js +31 -0
- package/dist/src/core/utils.js +11 -0
- package/dist/src/core/watchdog-runner.js +69 -0
- package/dist/src/core/watchdog.js +28 -0
- package/dist/src/docker/runtime.js +37 -0
- package/dist/src/docker-compose/index.js +16 -0
- package/dist/src/docker-compose/services/index.js +17 -0
- package/dist/src/environment.js +12 -0
- package/dist/src/index.js +122 -0
- package/dist/src/lint.js +3 -0
- package/dist/src/loader.js +25 -0
- package/dist/src/prisma.js +6 -0
- package/dist/src/types.js +0 -0
- package/dist/typecheck/index.d.ts +1 -0
- package/dist/typecheck/index.js +7 -0
- package/dist/typecheck/typecheck.d.ts +46 -0
- package/dist/types/all-types.d.ts +544 -0
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/config.d.ts +6 -0
- package/dist/types/docker.d.ts +15 -0
- package/dist/types/environment.d.ts +8 -0
- package/dist/types/hooks.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +0 -0
- package/dist/types/prisma.d.ts +1 -0
- package/dist/types.d.ts +1 -399
- package/package.json +55 -48
- package/readme.md +365 -109
- package/src/cli/bin.ts +77 -0
- package/src/cli/commands/help.ts +39 -0
- package/src/cli/commands/runtime.ts +72 -0
- package/src/cli/commands/version.ts +4 -0
- package/src/cli/index.ts +1 -0
- package/{cli.ts → src/cli/run-cli.ts} +114 -10
- package/src/config/define-config.ts +30 -0
- package/src/config/index.ts +3 -0
- package/src/config/merge-configs.ts +33 -0
- package/src/config/validate-config.ts +136 -0
- package/{core → src/core}/index.ts +2 -2
- package/{core → src/core}/ports.ts +5 -2
- package/{core → src/core}/process.ts +6 -2
- package/src/core/quick-tunnel/cloudflared-process.ts +83 -0
- package/src/core/quick-tunnel/constants.ts +31 -0
- package/src/core/quick-tunnel/index.ts +96 -0
- package/src/core/quick-tunnel/install.ts +160 -0
- package/src/core/tunnel.ts +165 -0
- package/{core → src/core}/utils.ts +1 -0
- package/{core → src/core}/watchdog.ts +5 -1
- package/src/docker/index.ts +1 -0
- package/{core/docker.ts → src/docker/runtime.ts} +11 -4
- package/src/docker-compose/generated-file.ts +45 -0
- package/src/docker-compose/index.ts +7 -0
- package/src/docker-compose/model.ts +197 -0
- package/src/docker-compose/services/clickhouse.ts +79 -0
- package/src/docker-compose/services/define-docker-service.ts +109 -0
- package/src/docker-compose/services/index.ts +67 -0
- package/src/docker-compose/services/postgres.ts +60 -0
- package/src/docker-compose/services/redis.ts +48 -0
- package/src/docker-compose/services/shared.ts +79 -0
- package/src/docker-compose/yaml.ts +88 -0
- package/{environment.ts → src/environment/create-dev-environment.ts} +214 -141
- package/src/environment/index.ts +1 -0
- package/src/environment/logging.ts +115 -0
- package/src/environment/only-apps.ts +34 -0
- package/src/environment/seeding.ts +57 -0
- package/{index.ts → src/index.ts} +52 -20
- package/src/loader/cache.ts +23 -0
- package/src/loader/find-config-file.ts +29 -0
- package/src/loader/index.ts +17 -0
- package/src/loader/load-dev-env.ts +38 -0
- package/src/prisma/index.ts +1 -0
- package/{prisma.ts → src/prisma/prisma.ts} +4 -2
- package/src/typecheck/index.ts +1 -0
- package/{types.ts → src/types/all-types.ts} +186 -8
- package/src/types/index.ts +1 -0
- package/bin.ts +0 -192
- package/config.ts +0 -194
- package/loader.ts +0 -126
- /package/{core → src/core}/network.ts +0 -0
- /package/{core → src/core}/watchdog-runner.ts +0 -0
- /package/{lint.ts → src/typecheck/typecheck.ts} +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { loadDevEnv } from "../../loader";
|
|
2
|
+
import { runCli } from "../run-cli";
|
|
3
|
+
|
|
4
|
+
export async function loadEnv() {
|
|
5
|
+
try {
|
|
6
|
+
return await loadDevEnv();
|
|
7
|
+
} catch (error) {
|
|
8
|
+
console.error(`❌ ${error instanceof Error ? error.message : error}`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function handleDev(args: string[]): Promise<void> {
|
|
14
|
+
const env = await loadEnv();
|
|
15
|
+
await runCli(env, { args });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function handlePrisma(args: string[]): Promise<void> {
|
|
19
|
+
const env = await loadEnv();
|
|
20
|
+
|
|
21
|
+
if (!env.prisma) {
|
|
22
|
+
console.error("❌ Prisma is not configured in your dev config.");
|
|
23
|
+
console.error("");
|
|
24
|
+
console.error(" Add prisma to your config:");
|
|
25
|
+
console.error("");
|
|
26
|
+
console.error(" export default defineDevConfig({");
|
|
27
|
+
console.error(" ...");
|
|
28
|
+
console.error(" prisma: {");
|
|
29
|
+
console.error(" cwd: 'packages/prisma'");
|
|
30
|
+
console.error(" }");
|
|
31
|
+
console.error(" })");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const running = await env.isRunning();
|
|
36
|
+
if (!running) {
|
|
37
|
+
console.log("🐳 Starting database container...");
|
|
38
|
+
await env.start({ startServers: false, wait: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const exitCode = await env.prisma.run(args);
|
|
42
|
+
process.exit(exitCode);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function handleEnv(): Promise<void> {
|
|
46
|
+
const env = await loadEnv();
|
|
47
|
+
console.log(
|
|
48
|
+
JSON.stringify(
|
|
49
|
+
{
|
|
50
|
+
projectName: env.projectName,
|
|
51
|
+
ports: env.ports,
|
|
52
|
+
urls: env.urls,
|
|
53
|
+
portOffset: env.portOffset,
|
|
54
|
+
isWorktree: env.isWorktree,
|
|
55
|
+
localIp: env.localIp,
|
|
56
|
+
root: env.root,
|
|
57
|
+
},
|
|
58
|
+
null,
|
|
59
|
+
2,
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function handleTypecheck(): Promise<void> {
|
|
65
|
+
const env = await loadEnv();
|
|
66
|
+
const { runWorkspaceTypecheck } = await import("../../typecheck");
|
|
67
|
+
const result = await runWorkspaceTypecheck({
|
|
68
|
+
root: env.root,
|
|
69
|
+
verbose: true,
|
|
70
|
+
});
|
|
71
|
+
process.exit(result.success ? 0 : 1);
|
|
72
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./run-cli";
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { killProcessesOnAppPorts } from "
|
|
3
|
-
import {
|
|
2
|
+
import { killProcessesOnAppPorts } from "../core/process";
|
|
3
|
+
import {
|
|
4
|
+
type PublicTunnel,
|
|
5
|
+
resolveExposeTargets,
|
|
6
|
+
startPublicTunnels,
|
|
7
|
+
stopPublicTunnels,
|
|
8
|
+
} from "../core/tunnel";
|
|
9
|
+
import { spawnWatchdog, startHeartbeat, stopHeartbeat } from "../core/watchdog";
|
|
4
10
|
import type {
|
|
5
11
|
AppConfig,
|
|
6
12
|
CliOptions,
|
|
7
13
|
DevEnvironment,
|
|
8
14
|
ServiceConfig,
|
|
9
|
-
} from "
|
|
15
|
+
} from "../types";
|
|
10
16
|
|
|
11
17
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
12
18
|
// CLI Runner
|
|
@@ -20,6 +26,7 @@ const ACCEPTED_FLAGS = [
|
|
|
20
26
|
"--migrate",
|
|
21
27
|
"--seed",
|
|
22
28
|
"--up-only",
|
|
29
|
+
"--expose",
|
|
23
30
|
] as const;
|
|
24
31
|
|
|
25
32
|
/**
|
|
@@ -36,12 +43,15 @@ Options:
|
|
|
36
43
|
--migrate Run migrations and exit
|
|
37
44
|
--seed Run migrations and seeders, then exit
|
|
38
45
|
--up-only Start containers and run migrations, then exit (no dev servers)
|
|
46
|
+
--expose Expose configured targets via public quick tunnels
|
|
39
47
|
|
|
40
48
|
Examples:
|
|
41
49
|
bun dev Start dev environment with all services
|
|
42
50
|
bun dev --seed Run migrations and seed the database
|
|
43
51
|
bun dev --down Stop all containers
|
|
44
52
|
bun dev --reset Stop containers and remove all data
|
|
53
|
+
bun dev --expose Expose all targets with expose: true
|
|
54
|
+
bun dev --expose=api,web Expose specific targets
|
|
45
55
|
`);
|
|
46
56
|
}
|
|
47
57
|
|
|
@@ -50,7 +60,13 @@ Examples:
|
|
|
50
60
|
*/
|
|
51
61
|
function getUnknownFlags(args: string[]): string[] {
|
|
52
62
|
return args.filter(
|
|
53
|
-
(arg) =>
|
|
63
|
+
(arg) =>
|
|
64
|
+
arg.startsWith("--") &&
|
|
65
|
+
!ACCEPTED_FLAGS.includes(
|
|
66
|
+
(arg.includes("=")
|
|
67
|
+
? arg.split("=")[0]
|
|
68
|
+
: arg) as (typeof ACCEPTED_FLAGS)[number],
|
|
69
|
+
),
|
|
54
70
|
);
|
|
55
71
|
}
|
|
56
72
|
|
|
@@ -71,14 +87,37 @@ export async function runCli<
|
|
|
71
87
|
TApps extends Record<string, AppConfig>,
|
|
72
88
|
>(
|
|
73
89
|
env: DevEnvironment<TServices, TApps>,
|
|
74
|
-
options: CliOptions
|
|
90
|
+
options: CliOptions & {
|
|
91
|
+
/** Substitute tunnel helpers (used by CLI integration tests). */
|
|
92
|
+
cliTestTunnel?: {
|
|
93
|
+
resolveExposeTargets: typeof resolveExposeTargets;
|
|
94
|
+
startPublicTunnels: typeof startPublicTunnels;
|
|
95
|
+
stopPublicTunnels: typeof stopPublicTunnels;
|
|
96
|
+
};
|
|
97
|
+
} = {},
|
|
75
98
|
): Promise<void> {
|
|
76
99
|
const {
|
|
77
100
|
args = process.argv.slice(2),
|
|
78
101
|
watchdog = true,
|
|
79
102
|
watchdogTimeout = 10,
|
|
80
103
|
devServersCommand,
|
|
104
|
+
cliTestTunnel,
|
|
81
105
|
} = options;
|
|
106
|
+
const tunnelApi = cliTestTunnel ?? {
|
|
107
|
+
resolveExposeTargets,
|
|
108
|
+
startPublicTunnels,
|
|
109
|
+
stopPublicTunnels,
|
|
110
|
+
};
|
|
111
|
+
const exposeRequested = hasFlag(args, "--expose");
|
|
112
|
+
const exposeValue = getFlagValue(args, "--expose");
|
|
113
|
+
let tunnels: PublicTunnel[] = [];
|
|
114
|
+
|
|
115
|
+
async function cleanupTunnels(): Promise<void> {
|
|
116
|
+
env.clearPublicUrls();
|
|
117
|
+
if (tunnels.length === 0) return;
|
|
118
|
+
await tunnelApi.stopPublicTunnels(tunnels);
|
|
119
|
+
tunnels = [];
|
|
120
|
+
}
|
|
82
121
|
|
|
83
122
|
// Handle --help
|
|
84
123
|
if (args.includes("--help")) {
|
|
@@ -89,7 +128,9 @@ export async function runCli<
|
|
|
89
128
|
// Validate flags
|
|
90
129
|
const unknownFlags = getUnknownFlags(args);
|
|
91
130
|
if (unknownFlags.length > 0) {
|
|
92
|
-
console.error(
|
|
131
|
+
console.error(
|
|
132
|
+
`❌ Unknown flag${unknownFlags.length > 1 ? "s" : ""}: ${unknownFlags.join(", ")}`,
|
|
133
|
+
);
|
|
93
134
|
console.error("");
|
|
94
135
|
printHelp();
|
|
95
136
|
process.exit(1);
|
|
@@ -98,6 +139,7 @@ export async function runCli<
|
|
|
98
139
|
// Handle --down (no need to start anything)
|
|
99
140
|
if (args.includes("--down")) {
|
|
100
141
|
env.logInfo();
|
|
142
|
+
await cleanupTunnels();
|
|
101
143
|
await env.stop();
|
|
102
144
|
process.exit(0);
|
|
103
145
|
}
|
|
@@ -105,6 +147,7 @@ export async function runCli<
|
|
|
105
147
|
// Handle --reset (no need to start anything)
|
|
106
148
|
if (args.includes("--reset")) {
|
|
107
149
|
env.logInfo();
|
|
150
|
+
await cleanupTunnels();
|
|
108
151
|
await env.stop({ removeVolumes: true });
|
|
109
152
|
process.exit(0);
|
|
110
153
|
}
|
|
@@ -112,12 +155,55 @@ export async function runCli<
|
|
|
112
155
|
// All other paths need containers + migrations
|
|
113
156
|
// Skip automatic seeding when --seed flag is used (CLI handles it explicitly)
|
|
114
157
|
const skipSeed = args.includes("--seed");
|
|
115
|
-
await env.start({
|
|
158
|
+
await env.start({
|
|
159
|
+
startServers: false,
|
|
160
|
+
wait: true,
|
|
161
|
+
skipSeed,
|
|
162
|
+
skipEnvironmentLog: exposeRequested,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (exposeRequested) {
|
|
166
|
+
const { targets, unknownNames, notEnabledNames } =
|
|
167
|
+
tunnelApi.resolveExposeTargets(env, exposeValue);
|
|
168
|
+
if (unknownNames.length > 0) {
|
|
169
|
+
console.error(
|
|
170
|
+
`❌ Unknown expose target${unknownNames.length > 1 ? "s" : ""}: ${unknownNames.join(", ")}`,
|
|
171
|
+
);
|
|
172
|
+
await cleanupTunnels();
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
if (notEnabledNames.length > 0) {
|
|
176
|
+
console.error(
|
|
177
|
+
`❌ Target${notEnabledNames.length > 1 ? "s" : ""} missing expose: true: ${notEnabledNames.join(", ")}`,
|
|
178
|
+
);
|
|
179
|
+
console.error(
|
|
180
|
+
" Mark these in dev.config.ts with expose: true or remove them from --expose.",
|
|
181
|
+
);
|
|
182
|
+
await cleanupTunnels();
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
if (targets.length === 0) {
|
|
186
|
+
console.error(
|
|
187
|
+
"❌ No expose targets selected. Add expose: true to services/apps or pass names with --expose=<name>.",
|
|
188
|
+
);
|
|
189
|
+
await cleanupTunnels();
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
tunnels = await tunnelApi.startPublicTunnels(targets);
|
|
194
|
+
env.setPublicUrls(
|
|
195
|
+
Object.fromEntries(
|
|
196
|
+
tunnels.map((tunnel) => [tunnel.name, tunnel.publicUrl]),
|
|
197
|
+
) as typeof env.publicUrls,
|
|
198
|
+
);
|
|
199
|
+
env.logInfo("Dev Environment", tunnels);
|
|
200
|
+
}
|
|
116
201
|
|
|
117
202
|
// Handle --migrate (exit after migrations)
|
|
118
203
|
if (args.includes("--migrate")) {
|
|
119
204
|
console.log("");
|
|
120
205
|
console.log("✅ Migrations applied successfully");
|
|
206
|
+
await cleanupTunnels();
|
|
121
207
|
process.exit(0);
|
|
122
208
|
}
|
|
123
209
|
|
|
@@ -135,10 +221,12 @@ export async function runCli<
|
|
|
135
221
|
if (result.stdout) {
|
|
136
222
|
console.error(result.stdout);
|
|
137
223
|
}
|
|
224
|
+
await cleanupTunnels();
|
|
138
225
|
process.exit(1);
|
|
139
226
|
}
|
|
140
227
|
console.log("");
|
|
141
228
|
console.log("✅ Seeding complete");
|
|
229
|
+
await cleanupTunnels();
|
|
142
230
|
process.exit(0);
|
|
143
231
|
}
|
|
144
232
|
|
|
@@ -147,6 +235,7 @@ export async function runCli<
|
|
|
147
235
|
console.log("");
|
|
148
236
|
console.log("✅ Containers started. Environment ready.");
|
|
149
237
|
console.log("");
|
|
238
|
+
await cleanupTunnels();
|
|
150
239
|
process.exit(0);
|
|
151
240
|
}
|
|
152
241
|
|
|
@@ -155,6 +244,7 @@ export async function runCli<
|
|
|
155
244
|
await spawnWatchdog(env.projectName, env.root, {
|
|
156
245
|
timeoutMinutes: watchdogTimeout,
|
|
157
246
|
verbose: true,
|
|
247
|
+
composeFile: env.composeFile,
|
|
158
248
|
});
|
|
159
249
|
startHeartbeat(env.projectName);
|
|
160
250
|
}
|
|
@@ -166,6 +256,7 @@ export async function runCli<
|
|
|
166
256
|
console.log("✅ Containers ready. No apps configured.");
|
|
167
257
|
// Keep process alive if no apps
|
|
168
258
|
await new Promise(() => {});
|
|
259
|
+
await cleanupTunnels();
|
|
169
260
|
return;
|
|
170
261
|
}
|
|
171
262
|
|
|
@@ -177,10 +268,16 @@ export async function runCli<
|
|
|
177
268
|
console.log("🔧 Starting dev servers...");
|
|
178
269
|
console.log("");
|
|
179
270
|
|
|
180
|
-
await runCommand(command, env.root, env.buildEnvVars()
|
|
271
|
+
await runCommand(command, env.root, env.buildEnvVars(), {
|
|
272
|
+
onSignal: async () => {
|
|
273
|
+
await cleanupTunnels();
|
|
274
|
+
stopHeartbeat();
|
|
275
|
+
},
|
|
276
|
+
});
|
|
181
277
|
|
|
182
278
|
// Clean up heartbeat on exit
|
|
183
279
|
stopHeartbeat();
|
|
280
|
+
await cleanupTunnels();
|
|
184
281
|
}
|
|
185
282
|
|
|
186
283
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -228,7 +325,11 @@ function runCommand(
|
|
|
228
325
|
command: string,
|
|
229
326
|
cwd: string,
|
|
230
327
|
envVars: Record<string, string>,
|
|
328
|
+
options: {
|
|
329
|
+
onSignal?: () => void | Promise<void>;
|
|
330
|
+
} = {},
|
|
231
331
|
): Promise<void> {
|
|
332
|
+
const { onSignal } = options;
|
|
232
333
|
return new Promise((resolve, reject) => {
|
|
233
334
|
const proc = spawn(command, [], {
|
|
234
335
|
cwd,
|
|
@@ -249,6 +350,9 @@ function runCommand(
|
|
|
249
350
|
|
|
250
351
|
// Handle SIGINT/SIGTERM
|
|
251
352
|
const cleanup = () => {
|
|
353
|
+
if (onSignal) {
|
|
354
|
+
void onSignal();
|
|
355
|
+
}
|
|
252
356
|
proc.kill("SIGTERM");
|
|
253
357
|
};
|
|
254
358
|
|
|
@@ -262,10 +366,10 @@ function runCommand(
|
|
|
262
366
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
263
367
|
|
|
264
368
|
/**
|
|
265
|
-
* Check if a CLI flag is present.
|
|
369
|
+
* Check if a CLI flag is present (including `--flag=value` form).
|
|
266
370
|
*/
|
|
267
371
|
export function hasFlag(args: string[], flag: string): boolean {
|
|
268
|
-
return args.
|
|
372
|
+
return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
269
373
|
}
|
|
270
374
|
|
|
271
375
|
/**
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AppConfig,
|
|
3
|
+
DevConfig,
|
|
4
|
+
DevHooks,
|
|
5
|
+
DevOptions,
|
|
6
|
+
DockerComposeGenerationOptions,
|
|
7
|
+
EnvVarsBuilder,
|
|
8
|
+
MigrationConfig,
|
|
9
|
+
PrismaConfig,
|
|
10
|
+
SeedConfig,
|
|
11
|
+
ServiceConfig,
|
|
12
|
+
} from "../types";
|
|
13
|
+
|
|
14
|
+
export function defineDevConfig<
|
|
15
|
+
TServices extends Record<string, ServiceConfig>,
|
|
16
|
+
TApps extends Record<string, AppConfig> = Record<string, never>,
|
|
17
|
+
>(config: {
|
|
18
|
+
projectPrefix: string;
|
|
19
|
+
services: TServices;
|
|
20
|
+
apps?: TApps;
|
|
21
|
+
envVars?: EnvVarsBuilder<TServices, TApps>;
|
|
22
|
+
hooks?: DevHooks<TServices, TApps>;
|
|
23
|
+
migrations?: MigrationConfig[];
|
|
24
|
+
seed?: SeedConfig<TServices, TApps>;
|
|
25
|
+
prisma?: PrismaConfig;
|
|
26
|
+
options?: DevOptions;
|
|
27
|
+
docker?: DockerComposeGenerationOptions;
|
|
28
|
+
}): DevConfig<TServices, TApps> {
|
|
29
|
+
return config as DevConfig<TServices, TApps>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AppConfig, DevConfig, ServiceConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
export function mergeConfigs<
|
|
4
|
+
TServices extends Record<string, ServiceConfig>,
|
|
5
|
+
TApps extends Record<string, AppConfig>,
|
|
6
|
+
>(
|
|
7
|
+
base: DevConfig<TServices, TApps>,
|
|
8
|
+
overrides: Partial<DevConfig<TServices, TApps>>,
|
|
9
|
+
): DevConfig<TServices, TApps> {
|
|
10
|
+
return {
|
|
11
|
+
...base,
|
|
12
|
+
...overrides,
|
|
13
|
+
services: { ...base.services, ...overrides.services } as TServices,
|
|
14
|
+
apps: { ...base.apps, ...overrides.apps } as TApps,
|
|
15
|
+
hooks: { ...base.hooks, ...overrides.hooks },
|
|
16
|
+
migrations: overrides.migrations ?? base.migrations,
|
|
17
|
+
seed: overrides.seed ?? base.seed,
|
|
18
|
+
options: { ...base.options, ...overrides.options },
|
|
19
|
+
docker: { ...base.docker, ...overrides.docker },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function definePartialConfig<
|
|
24
|
+
TServices extends Record<string, ServiceConfig> = Record<
|
|
25
|
+
string,
|
|
26
|
+
ServiceConfig
|
|
27
|
+
>,
|
|
28
|
+
TApps extends Record<string, AppConfig> = Record<string, AppConfig>,
|
|
29
|
+
>(
|
|
30
|
+
config: Partial<DevConfig<TServices, TApps>>,
|
|
31
|
+
): Partial<DevConfig<TServices, TApps>> {
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { isAbsolute, normalize } from "node:path";
|
|
2
|
+
import type { AppConfig, DevConfig, ServiceConfig } from "../types";
|
|
3
|
+
|
|
4
|
+
const BUILTIN_DOCKER_PRESETS = new Set(["postgres", "redis", "clickhouse"]);
|
|
5
|
+
|
|
6
|
+
function inferBuiltInPreset(serviceName: string): string | null {
|
|
7
|
+
const normalized = serviceName.toLowerCase();
|
|
8
|
+
return BUILTIN_DOCKER_PRESETS.has(normalized) ? normalized : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function validateConfig<
|
|
12
|
+
TServices extends Record<string, ServiceConfig>,
|
|
13
|
+
TApps extends Record<string, AppConfig>,
|
|
14
|
+
>(config: DevConfig<TServices, TApps>): string[] {
|
|
15
|
+
const errors: string[] = [];
|
|
16
|
+
const composeServiceNames = new Set<string>();
|
|
17
|
+
|
|
18
|
+
if (!config.projectPrefix) {
|
|
19
|
+
errors.push("projectPrefix is required");
|
|
20
|
+
} else if (!/^[a-z][a-z0-9-]*$/.test(config.projectPrefix)) {
|
|
21
|
+
errors.push(
|
|
22
|
+
"projectPrefix must start with a letter and contain only lowercase letters, numbers, and hyphens",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!config.services || Object.keys(config.services).length === 0) {
|
|
27
|
+
errors.push("At least one service is required");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const [name, service] of Object.entries(config.services ?? {})) {
|
|
31
|
+
if (!service.port || typeof service.port !== "number") {
|
|
32
|
+
errors.push(`Service "${name}" must have a valid port number`);
|
|
33
|
+
}
|
|
34
|
+
if (service.port < 1 || service.port > 65535) {
|
|
35
|
+
errors.push(`Service "${name}" port must be between 1 and 65535`);
|
|
36
|
+
}
|
|
37
|
+
if (
|
|
38
|
+
service.secondaryPort !== undefined &&
|
|
39
|
+
(service.secondaryPort < 1 || service.secondaryPort > 65535)
|
|
40
|
+
) {
|
|
41
|
+
errors.push(
|
|
42
|
+
`Service "${name}" secondaryPort must be between 1 and 65535`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const composeServiceName = service.serviceName ?? name;
|
|
47
|
+
if (composeServiceNames.has(composeServiceName)) {
|
|
48
|
+
errors.push(
|
|
49
|
+
`Duplicate compose service name "${composeServiceName}". Use unique serviceName values.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
composeServiceNames.add(composeServiceName);
|
|
53
|
+
|
|
54
|
+
const dockerConfig = service.docker;
|
|
55
|
+
const preset = inferBuiltInPreset(name);
|
|
56
|
+
if (!dockerConfig && !preset) {
|
|
57
|
+
errors.push(
|
|
58
|
+
`Service "${name}" must define docker config (helper or raw) because it has no built-in preset.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
dockerConfig &&
|
|
63
|
+
typeof dockerConfig === "object" &&
|
|
64
|
+
"kind" in dockerConfig &&
|
|
65
|
+
dockerConfig.kind === "preset"
|
|
66
|
+
) {
|
|
67
|
+
const presetName = dockerConfig.preset;
|
|
68
|
+
if (
|
|
69
|
+
typeof presetName !== "string" ||
|
|
70
|
+
!BUILTIN_DOCKER_PRESETS.has(presetName)
|
|
71
|
+
) {
|
|
72
|
+
errors.push(
|
|
73
|
+
`Service "${name}" has invalid docker preset "${presetName}".`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (config.docker?.writeStrategy) {
|
|
80
|
+
const writeStrategy = config.docker.writeStrategy;
|
|
81
|
+
if (writeStrategy !== "always" && writeStrategy !== "if-missing") {
|
|
82
|
+
errors.push(
|
|
83
|
+
`docker.writeStrategy "${String(writeStrategy)}" is invalid. Use "always" or "if-missing".`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (config.docker?.generatedFile) {
|
|
89
|
+
const generatedFile = config.docker.generatedFile;
|
|
90
|
+
if (isAbsolute(generatedFile)) {
|
|
91
|
+
errors.push(
|
|
92
|
+
"docker.generatedFile must be a relative path inside the repo.",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const normalized = normalize(generatedFile).replace(/\\/g, "/");
|
|
96
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
97
|
+
errors.push(
|
|
98
|
+
"docker.generatedFile cannot point outside the repository root.",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const [name, app] of Object.entries(config.apps ?? {})) {
|
|
104
|
+
if (!app.port || typeof app.port !== "number") {
|
|
105
|
+
errors.push(`App "${name}" must have a valid port number`);
|
|
106
|
+
}
|
|
107
|
+
if (!app.devCommand) {
|
|
108
|
+
errors.push(`App "${name}" must have a devCommand`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const migration of config.migrations ?? []) {
|
|
113
|
+
if (!migration.name) {
|
|
114
|
+
errors.push("Migration must have a name");
|
|
115
|
+
}
|
|
116
|
+
if (!migration.command) {
|
|
117
|
+
errors.push(`Migration "${migration.name}" must have a command`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (config.seed && !config.seed.command) {
|
|
122
|
+
errors.push("Seed must have a command");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return errors;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function assertValidConfig<
|
|
129
|
+
TServices extends Record<string, ServiceConfig>,
|
|
130
|
+
TApps extends Record<string, AppConfig>,
|
|
131
|
+
>(config: DevConfig<TServices, TApps>): void {
|
|
132
|
+
const errors = validateConfig(config);
|
|
133
|
+
if (errors.length > 0) {
|
|
134
|
+
throw new Error(`Invalid dev config:\n - ${errors.join("\n - ")}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Re-export
|
|
2
|
-
export * from "./docker";
|
|
1
|
+
// Re-export core runtime utilities only.
|
|
3
2
|
export * from "./network";
|
|
4
3
|
export * from "./ports";
|
|
5
4
|
export * from "./process";
|
|
5
|
+
export * from "./tunnel";
|
|
6
6
|
export * from "./utils";
|
|
7
7
|
export * from "./watchdog";
|
|
@@ -155,7 +155,8 @@ export function computeDevIdentity(options: DevIdentityOptions): DevIdentity {
|
|
|
155
155
|
const worktree = isWorktree(root);
|
|
156
156
|
const worktreeSuffix =
|
|
157
157
|
worktree && worktreeIsolation ? getWorktreeProjectSuffix(root) : null;
|
|
158
|
-
const projectSuffix =
|
|
158
|
+
const projectSuffix =
|
|
159
|
+
[suffix, worktreeSuffix].filter(Boolean).join("-") || undefined;
|
|
159
160
|
const projectName = getProjectName(projectPrefix, projectSuffix, root);
|
|
160
161
|
const portOffset = calculatePortOffset(suffix, root);
|
|
161
162
|
|
|
@@ -249,7 +250,9 @@ function buildServiceUrl(
|
|
|
249
250
|
case "mysql":
|
|
250
251
|
return `mysql://${user}:${password}@${ctx.host}:${ctx.port}/${database}`;
|
|
251
252
|
case "mongodb":
|
|
252
|
-
return
|
|
253
|
+
return database
|
|
254
|
+
? `mongodb://${ctx.host}:${ctx.port}/${database}`
|
|
255
|
+
: `mongodb://${ctx.host}:${ctx.port}`;
|
|
253
256
|
default:
|
|
254
257
|
return null;
|
|
255
258
|
}
|
|
@@ -395,11 +395,15 @@ export async function killProcessesOnAppPorts(
|
|
|
395
395
|
killedAny = true;
|
|
396
396
|
}
|
|
397
397
|
if (verbose) {
|
|
398
|
-
console.log(
|
|
398
|
+
console.log(
|
|
399
|
+
`⚠️ Port ${port} (${name}) is in use by process ${existingPid}`,
|
|
400
|
+
);
|
|
399
401
|
}
|
|
400
402
|
const killed = await killProcessOnPortAndWait(port, { verbose });
|
|
401
403
|
if (!killed && verbose) {
|
|
402
|
-
console.log(
|
|
404
|
+
console.log(
|
|
405
|
+
` ⚠️ Could not kill process on port ${port}, server may fail to start`,
|
|
406
|
+
);
|
|
403
407
|
}
|
|
404
408
|
}
|
|
405
409
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn cloudflared and parse the quick-tunnel public URL from output.
|
|
3
|
+
* Derived from unjs/untun (MIT), originally forked from node-cloudflared.
|
|
4
|
+
*/
|
|
5
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
6
|
+
import { cloudflaredBinPath } from "./constants";
|
|
7
|
+
|
|
8
|
+
const urlRegex = /\|\s+(https?:\/\/\S+)/;
|
|
9
|
+
|
|
10
|
+
export function startCloudflaredTunnel(
|
|
11
|
+
options: Record<string, string | number | null>,
|
|
12
|
+
): {
|
|
13
|
+
url: Promise<string>;
|
|
14
|
+
child: ChildProcess;
|
|
15
|
+
stop: () => boolean;
|
|
16
|
+
} {
|
|
17
|
+
const args: string[] = ["tunnel"];
|
|
18
|
+
for (const [key, value] of Object.entries(options)) {
|
|
19
|
+
if (typeof value === "string") {
|
|
20
|
+
args.push(`${key}`, value);
|
|
21
|
+
} else if (typeof value === "number") {
|
|
22
|
+
args.push(`${key}`, value.toString());
|
|
23
|
+
} else if (value === null) {
|
|
24
|
+
args.push(`${key}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (args.length === 1) {
|
|
28
|
+
args.push("--url", "localhost:8080");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const child = spawn(cloudflaredBinPath, args, {
|
|
32
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (process.env.DEBUG) {
|
|
36
|
+
child.stdout?.pipe(process.stdout);
|
|
37
|
+
child.stderr?.pipe(process.stderr);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let settled = false;
|
|
41
|
+
let urlResolver!: (value: string | PromiseLike<string>) => void;
|
|
42
|
+
let urlRejector!: (reason: unknown) => void;
|
|
43
|
+
const url = new Promise<string>((resolve, reject) => {
|
|
44
|
+
urlResolver = (v) => {
|
|
45
|
+
if (!settled) {
|
|
46
|
+
settled = true;
|
|
47
|
+
resolve(v);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
urlRejector = (e) => {
|
|
51
|
+
if (!settled) {
|
|
52
|
+
settled = true;
|
|
53
|
+
reject(e);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const parser = (data: Buffer) => {
|
|
59
|
+
const str = data.toString();
|
|
60
|
+
|
|
61
|
+
const urlMatch = str.match(urlRegex);
|
|
62
|
+
if (urlMatch) {
|
|
63
|
+
urlResolver(urlMatch[1] ?? "");
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
child.stdout?.on("data", parser).on("error", urlRejector);
|
|
67
|
+
child.stderr?.on("data", parser).on("error", urlRejector);
|
|
68
|
+
|
|
69
|
+
child.on("exit", (code, signal) => {
|
|
70
|
+
if (!settled) {
|
|
71
|
+
urlRejector(
|
|
72
|
+
new Error(
|
|
73
|
+
`cloudflared exited before a tunnel URL was parsed (code=${code}, signal=${signal ?? "none"})`,
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
child.on("error", urlRejector);
|
|
79
|
+
|
|
80
|
+
const stop = () => child.kill("SIGINT");
|
|
81
|
+
|
|
82
|
+
return { url, child, stop };
|
|
83
|
+
}
|