buncargo 3.0.0 → 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/cli/bin.js +10 -8
- package/dist/cli/index.js +2 -2
- package/dist/cli/run-cli.d.ts +10 -2
- 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 +3 -2
- package/dist/environment/index.js +2 -2
- package/dist/environment/logging.d.ts +6 -6
- package/dist/environment/only-apps.d.ts +10 -0
- package/dist/index-3eyrdxw9.js +577 -0
- package/dist/index-5aq985p4.js +250 -0
- package/dist/index-6cmex7m5.js +72 -0
- package/dist/index-6d6x175r.js +572 -0
- package/dist/index-7v19es2e.js +666 -0
- package/dist/index-9wyhzw0h.js +574 -0
- package/dist/index-ag90ry8t.js +576 -0
- package/dist/index-byeqyjrz.js +72 -0
- package/dist/index-enj4zdma.js +574 -0
- package/dist/index-k370bech.js +72 -0
- package/dist/index-qa8akv6y.js +666 -0
- package/dist/index-vg55rq0y.js +250 -0
- package/dist/index-vs81yaks.js +244 -0
- package/dist/index-x54nbgs7.js +355 -0
- package/dist/index-yz4jfz7z.js +338 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -8
- package/dist/loader/index.js +3 -3
- package/dist/types/all-types.d.ts +46 -3
- package/package.json +147 -145
- package/readme.md +16 -0
- package/src/cli/run-cli.ts +27 -12
- 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 +22 -8
- package/src/environment/create-dev-environment.ts +123 -13
- package/src/environment/logging.ts +34 -20
- package/src/environment/only-apps.ts +34 -0
- package/src/index.ts +3 -0
- package/src/types/all-types.ts +56 -3
package/dist/index.js
CHANGED
|
@@ -2,19 +2,16 @@ import"./index-bj79tw5w.js";
|
|
|
2
2
|
import {
|
|
3
3
|
getFlagValue,
|
|
4
4
|
hasFlag,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
startPublicTunnels,
|
|
8
|
-
stopPublicTunnels
|
|
9
|
-
} from "./index-0kxnae3z.js";
|
|
5
|
+
runCli
|
|
6
|
+
} from "./index-5aq985p4.js";
|
|
10
7
|
import {
|
|
11
8
|
clearDevEnvCache,
|
|
12
9
|
getDevEnv,
|
|
13
10
|
loadDevEnv
|
|
14
|
-
} from "./index-
|
|
11
|
+
} from "./index-byeqyjrz.js";
|
|
15
12
|
import {
|
|
16
13
|
createDevEnvironment
|
|
17
|
-
} from "./index-
|
|
14
|
+
} from "./index-7v19es2e.js";
|
|
18
15
|
import {
|
|
19
16
|
DOCKER_NOT_RUNNING_MESSAGE,
|
|
20
17
|
MAX_ATTEMPTS,
|
|
@@ -52,7 +49,11 @@ import {
|
|
|
52
49
|
service,
|
|
53
50
|
writeGeneratedComposeFile
|
|
54
51
|
} from "./index-5t9jxqm0.js";
|
|
55
|
-
import
|
|
52
|
+
import {
|
|
53
|
+
resolveExposeTargets,
|
|
54
|
+
startPublicTunnels,
|
|
55
|
+
stopPublicTunnels
|
|
56
|
+
} from "./index-x54nbgs7.js";
|
|
56
57
|
import {
|
|
57
58
|
getHeartbeatFile,
|
|
58
59
|
getWatchdogPidFile,
|
package/dist/loader/index.js
CHANGED
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
findConfigFile,
|
|
5
5
|
getDevEnv,
|
|
6
6
|
loadDevEnv
|
|
7
|
-
} from "../index-
|
|
8
|
-
import"../index-
|
|
7
|
+
} from "../index-byeqyjrz.js";
|
|
8
|
+
import"../index-7v19es2e.js";
|
|
9
9
|
import"../index-d8tyv5se.js";
|
|
10
10
|
import"../index-c0dr6mcv.js";
|
|
11
11
|
import"../index-fb29934k.js";
|
|
12
12
|
import"../index-5t9jxqm0.js";
|
|
13
|
-
import"../index-
|
|
13
|
+
import"../index-x54nbgs7.js";
|
|
14
14
|
import"../index-mam0bcyz.js";
|
|
15
15
|
import"../index-mm412dkp.js";
|
|
16
16
|
import"../index-t0fj6gg1.js";
|
|
@@ -382,6 +382,10 @@ export interface StartOptions {
|
|
|
382
382
|
suffix?: string;
|
|
383
383
|
/** Skip automatic seeding (useful when CLI handles seeding separately). Default: false */
|
|
384
384
|
skipSeed?: boolean;
|
|
385
|
+
/** Skip the initial `logInfo` banner (CLI uses this with `--expose`, then logs once with tunnel URLs). Default: false */
|
|
386
|
+
skipEnvironmentLog?: boolean;
|
|
387
|
+
/** If set, start and wait for only these app names (must exist in `apps`). */
|
|
388
|
+
onlyApps?: string[];
|
|
385
389
|
}
|
|
386
390
|
/**
|
|
387
391
|
* Options for stopping the dev environment.
|
|
@@ -398,6 +402,33 @@ export interface StopOptions {
|
|
|
398
402
|
export interface DevServerPids {
|
|
399
403
|
[appName: string]: number;
|
|
400
404
|
}
|
|
405
|
+
/** Tunnel rows passed to `logInfo` for public URL lines (matches `PublicTunnel` without `close`) */
|
|
406
|
+
export interface DevEnvironmentTunnelLog {
|
|
407
|
+
kind: "service" | "app";
|
|
408
|
+
name: string;
|
|
409
|
+
localUrl: string;
|
|
410
|
+
publicUrl: string;
|
|
411
|
+
}
|
|
412
|
+
/** Active tunnel with teardown — same shape as core `PublicTunnel`. */
|
|
413
|
+
export type PublicTunnelHandle = DevEnvironmentTunnelLog & {
|
|
414
|
+
close: () => Promise<void>;
|
|
415
|
+
};
|
|
416
|
+
/** Options for {@link DevEnvironment.openPublicTunnels}. */
|
|
417
|
+
export interface OpenPublicTunnelsOptions {
|
|
418
|
+
/** Subset of expose targets by name; omit for all `expose: true` services/apps. */
|
|
419
|
+
names?: string[];
|
|
420
|
+
/**
|
|
421
|
+
* Wait for these apps' HTTP health endpoints before opening tunnels.
|
|
422
|
+
* Servers must already be listening on their ports.
|
|
423
|
+
*/
|
|
424
|
+
waitForHealthy?: string[];
|
|
425
|
+
}
|
|
426
|
+
/** Result of {@link DevEnvironment.openPublicTunnels}. */
|
|
427
|
+
export interface OpenPublicTunnelsResult<TServices extends Record<string, ServiceConfig>, TApps extends Record<string, AppConfig>> {
|
|
428
|
+
publicUrls: ComputedPublicUrls<TServices, TApps>;
|
|
429
|
+
tunnels: PublicTunnelHandle[];
|
|
430
|
+
close: () => Promise<void>;
|
|
431
|
+
}
|
|
401
432
|
/**
|
|
402
433
|
* The main dev environment interface returned by createDevEnvironment().
|
|
403
434
|
*/
|
|
@@ -436,6 +467,8 @@ export interface DevEnvironment<TServices extends Record<string, ServiceConfig>,
|
|
|
436
467
|
startServers(options?: {
|
|
437
468
|
productionBuild?: boolean;
|
|
438
469
|
verbose?: boolean;
|
|
470
|
+
/** If set, start and wait for only these app names (must exist in `apps`). */
|
|
471
|
+
onlyApps?: string[];
|
|
439
472
|
}): Promise<DevServerPids>;
|
|
440
473
|
/** Stop a process by PID */
|
|
441
474
|
stopProcess(pid: number): void;
|
|
@@ -443,8 +476,13 @@ export interface DevEnvironment<TServices extends Record<string, ServiceConfig>,
|
|
|
443
476
|
waitForServers(options?: {
|
|
444
477
|
timeout?: number;
|
|
445
478
|
productionBuild?: boolean;
|
|
479
|
+
/** If set, wait only for these app names (must exist in `apps`). */
|
|
480
|
+
onlyApps?: string[];
|
|
446
481
|
}): Promise<void>;
|
|
447
|
-
/**
|
|
482
|
+
/**
|
|
483
|
+
* Build environment variables for shell commands.
|
|
484
|
+
* Call **after** {@link setPublicUrls} or {@link openPublicTunnels} so `envVars` and `*_PUBLIC_URL` reflect tunnel URLs.
|
|
485
|
+
*/
|
|
448
486
|
buildEnvVars(production?: boolean): Record<string, string>;
|
|
449
487
|
/** Set public tunnel URLs used by envVars and *_PUBLIC_URL injection */
|
|
450
488
|
setPublicUrls(urls: ComputedPublicUrls<TServices, TApps>): void;
|
|
@@ -460,8 +498,13 @@ export interface DevEnvironment<TServices extends Record<string, ServiceConfig>,
|
|
|
460
498
|
}>;
|
|
461
499
|
/** Wait for an HTTP server to respond */
|
|
462
500
|
waitForServer(url: string, timeout?: number): Promise<void>;
|
|
463
|
-
/** Log environment info to console */
|
|
464
|
-
logInfo(label?: string): void;
|
|
501
|
+
/** Log environment info to console; pass `tunnels` to show public URLs next to services/apps */
|
|
502
|
+
logInfo(label?: string, tunnels?: DevEnvironmentTunnelLog[]): void;
|
|
503
|
+
/**
|
|
504
|
+
* Resolve expose targets, start public quick tunnels, and apply {@link setPublicUrls}.
|
|
505
|
+
* Call {@link buildEnvVars} after this resolves when spawning processes that need `EXPO_PUBLIC_*` / `*_PUBLIC_URL`.
|
|
506
|
+
*/
|
|
507
|
+
openPublicTunnels(options?: OpenPublicTunnelsOptions): Promise<OpenPublicTunnelsResult<TServices, TApps>>;
|
|
465
508
|
/**
|
|
466
509
|
* Get the Expo API URL (http://<local-ip>:<api-port>) and log it for detection.
|
|
467
510
|
* Used by tools like Vibe Kanban to find the API server for mobile testing.
|
package/package.json
CHANGED
|
@@ -1,147 +1,149 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
2
|
+
"name": "buncargo",
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"description": "A Bun-powered development environment CLI for managing Docker Compose services, dev servers, and environment variables",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/HansKristoffer/buncargo.git"
|
|
13
|
+
},
|
|
14
|
+
"author": "Kristoffer Hansen",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"bun",
|
|
17
|
+
"dev-tools",
|
|
18
|
+
"docker",
|
|
19
|
+
"docker-compose",
|
|
20
|
+
"development",
|
|
21
|
+
"cli",
|
|
22
|
+
"monorepo",
|
|
23
|
+
"dev-server",
|
|
24
|
+
"environment"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"bun": ">=1.0.0"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"src/**/*.ts",
|
|
31
|
+
"dist",
|
|
32
|
+
"!src/**/*.test.ts"
|
|
33
|
+
],
|
|
34
|
+
"bin": {
|
|
35
|
+
"dev-tools": "./dist/cli/bin.js",
|
|
36
|
+
"buncargo": "./dist/cli/bin.js"
|
|
37
|
+
},
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"bun": "./src/index.ts",
|
|
42
|
+
"import": "./dist/index.js",
|
|
43
|
+
"default": "./dist/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./types": {
|
|
46
|
+
"types": "./dist/types/index.d.ts",
|
|
47
|
+
"bun": "./src/types/index.ts",
|
|
48
|
+
"import": "./dist/types/index.js",
|
|
49
|
+
"default": "./dist/types/index.js"
|
|
50
|
+
},
|
|
51
|
+
"./config": {
|
|
52
|
+
"types": "./dist/config/index.d.ts",
|
|
53
|
+
"bun": "./src/config/index.ts",
|
|
54
|
+
"import": "./dist/config/index.js",
|
|
55
|
+
"default": "./dist/config/index.js"
|
|
56
|
+
},
|
|
57
|
+
"./environment": {
|
|
58
|
+
"types": "./dist/environment/index.d.ts",
|
|
59
|
+
"bun": "./src/environment/index.ts",
|
|
60
|
+
"import": "./dist/environment/index.js",
|
|
61
|
+
"default": "./dist/environment/index.js"
|
|
62
|
+
},
|
|
63
|
+
"./core/ports": {
|
|
64
|
+
"types": "./dist/core/ports.d.ts",
|
|
65
|
+
"bun": "./src/core/ports.ts",
|
|
66
|
+
"import": "./dist/core/ports.js",
|
|
67
|
+
"default": "./dist/core/ports.js"
|
|
68
|
+
},
|
|
69
|
+
"./core/network": {
|
|
70
|
+
"types": "./dist/core/network.d.ts",
|
|
71
|
+
"bun": "./src/core/network.ts",
|
|
72
|
+
"import": "./dist/core/network.js",
|
|
73
|
+
"default": "./dist/core/network.js"
|
|
74
|
+
},
|
|
75
|
+
"./core/process": {
|
|
76
|
+
"types": "./dist/core/process.d.ts",
|
|
77
|
+
"bun": "./src/core/process.ts",
|
|
78
|
+
"import": "./dist/core/process.js",
|
|
79
|
+
"default": "./dist/core/process.js"
|
|
80
|
+
},
|
|
81
|
+
"./core/watchdog": {
|
|
82
|
+
"types": "./dist/core/watchdog.d.ts",
|
|
83
|
+
"bun": "./src/core/watchdog.ts",
|
|
84
|
+
"import": "./dist/core/watchdog.js",
|
|
85
|
+
"default": "./dist/core/watchdog.js"
|
|
86
|
+
},
|
|
87
|
+
"./core/utils": {
|
|
88
|
+
"types": "./dist/core/utils.d.ts",
|
|
89
|
+
"bun": "./src/core/utils.ts",
|
|
90
|
+
"import": "./dist/core/utils.js",
|
|
91
|
+
"default": "./dist/core/utils.js"
|
|
92
|
+
},
|
|
93
|
+
"./cli": {
|
|
94
|
+
"types": "./dist/cli/index.d.ts",
|
|
95
|
+
"bun": "./src/cli/index.ts",
|
|
96
|
+
"import": "./dist/cli/index.js",
|
|
97
|
+
"default": "./dist/cli/index.js"
|
|
98
|
+
},
|
|
99
|
+
"./typecheck": {
|
|
100
|
+
"types": "./dist/typecheck/index.d.ts",
|
|
101
|
+
"bun": "./src/typecheck/index.ts",
|
|
102
|
+
"import": "./dist/typecheck/index.js",
|
|
103
|
+
"default": "./dist/typecheck/index.js"
|
|
104
|
+
},
|
|
105
|
+
"./loader": {
|
|
106
|
+
"types": "./dist/loader/index.d.ts",
|
|
107
|
+
"bun": "./src/loader/index.ts",
|
|
108
|
+
"import": "./dist/loader/index.js",
|
|
109
|
+
"default": "./dist/loader/index.js"
|
|
110
|
+
},
|
|
111
|
+
"./docker": {
|
|
112
|
+
"types": "./dist/docker/index.d.ts",
|
|
113
|
+
"bun": "./src/docker/index.ts",
|
|
114
|
+
"import": "./dist/docker/index.js",
|
|
115
|
+
"default": "./dist/docker/index.js"
|
|
116
|
+
},
|
|
117
|
+
"./docker-compose": {
|
|
118
|
+
"types": "./dist/docker-compose/index.d.ts",
|
|
119
|
+
"bun": "./src/docker-compose/index.ts",
|
|
120
|
+
"import": "./dist/docker-compose/index.js",
|
|
121
|
+
"default": "./dist/docker-compose/index.js"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"scripts": {
|
|
125
|
+
"build": "bun run build:js && bun run build:types",
|
|
126
|
+
"build:js": "bun build ./src/index.ts ./src/cli/bin.ts ./src/cli/index.ts ./src/config/index.ts ./src/environment/index.ts ./src/loader/index.ts ./src/typecheck/index.ts ./src/types/index.ts ./src/core/network.ts ./src/core/ports.ts ./src/core/process.ts ./src/core/utils.ts ./src/core/watchdog.ts ./src/docker/index.ts ./src/docker-compose/index.ts --outdir ./dist --root ./src --target node --packages external --splitting",
|
|
127
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
128
|
+
"prepublishOnly": "bun run build",
|
|
129
|
+
"publish:patch": "npm version patch && npm publish",
|
|
130
|
+
"publish:minor": "npm version minor && npm publish",
|
|
131
|
+
"publish:major": "npm version major && npm publish",
|
|
132
|
+
"lint": "bun run typecheck && biome check src example",
|
|
133
|
+
"lint:write": "bun run typecheck && biome check --fix src example && biome format src example",
|
|
134
|
+
"typecheck": "tsgo --incremental",
|
|
135
|
+
"test": "bun test",
|
|
136
|
+
"test:integration-cloudflared": "bun test src/core/quick-tunnel/quick-tunnel.test.ts",
|
|
137
|
+
"test:integration-cloudflared-e2e": "BUNCARGO_TEST_CLOUDFLARED_E2E=1 bun test src/core/quick-tunnel/quick-tunnel.test.ts"
|
|
138
|
+
},
|
|
139
|
+
"devDependencies": {
|
|
140
|
+
"@types/bun": "1.3.2",
|
|
141
|
+
"@biomejs/biome": "2.3.4",
|
|
142
|
+
"@typescript/native-preview": "7.0.0-dev.20260127.1",
|
|
143
|
+
"typescript": "^5.7.0"
|
|
144
|
+
},
|
|
145
|
+
"dependencies": {
|
|
146
|
+
"fast-glob": "^3.3.3",
|
|
147
|
+
"picocolors": "^1.1.1"
|
|
148
|
+
}
|
|
147
149
|
}
|
package/readme.md
CHANGED
|
@@ -179,6 +179,8 @@ apps: {
|
|
|
179
179
|
}
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
+
Use `onlyApps` on `start()` or `startServers()` to launch and wait for only those named apps (same env injection and health checks as when all apps run).
|
|
183
|
+
|
|
182
184
|
## Environment Variables
|
|
183
185
|
|
|
184
186
|
The `envVars` function builds all env vars from computed ports and URLs:
|
|
@@ -193,6 +195,14 @@ envVars: (ports, urls, { localIp, publicUrls }) => ({
|
|
|
193
195
|
})
|
|
194
196
|
```
|
|
195
197
|
|
|
198
|
+
`buildEnvVars()` always includes, for each service/app name `foo`:
|
|
199
|
+
|
|
200
|
+
- `FOO_PORT` — assigned port
|
|
201
|
+
- `FOO_URL` — local URL (LAN)
|
|
202
|
+
- `FOO_PUBLIC_URL` — only while a public tunnel is active for that name
|
|
203
|
+
|
|
204
|
+
Your `envVars` callback receives `publicUrls` and typically maps client bundles, e.g. `EXPO_PUBLIC_API_URL: publicUrls.api ?? urls.api`.
|
|
205
|
+
|
|
196
206
|
These are injected into:
|
|
197
207
|
- Docker Compose services
|
|
198
208
|
- Dev server processes
|
|
@@ -246,6 +256,12 @@ envVars: (_ports, urls, { publicUrls }) => ({
|
|
|
246
256
|
})
|
|
247
257
|
```
|
|
248
258
|
|
|
259
|
+
**CLI vs programmatic ordering:** `bunx buncargo dev --expose` starts Cloudflare quick tunnels after containers and migrations but **before** the interactive dev-server command (e.g. `concurrently`) runs. Until those servers are listening on their ports, tunnel traffic can briefly error. For “servers first, then public URLs,” use the API: e.g. `await dev.startServers({ onlyApps: ['api', 'platform'] })`, then `await dev.openPublicTunnels({ waitForHealthy: ['api', 'platform'] })` (optional; waits HTTP health first), then read `dev.buildEnvVars()` for spawned children.
|
|
260
|
+
|
|
261
|
+
**Expo hybrid:** buncargo is suited to exposing **API** and **platform** (Vite, etc.) for devices on cellular. **Metro** often uses Expo’s own tunnel (`expo start --tunnel`); you usually do **not** add Metro as a buncargo `app` unless you want buncargo to start it. Wire `EXPO_PUBLIC_*` from `publicUrls.* ?? urls.*` in `envVars`.
|
|
262
|
+
|
|
263
|
+
**Programmatic helper:** `openPublicTunnels({ names?, waitForHealthy? })` applies tunnel URLs via `setPublicUrls` and returns `close()` to stop tunnels and clear public URLs. Call `buildEnvVars()` after `openPublicTunnels` resolves so `envVars` and `*_PUBLIC_URL` see the tunnel origins.
|
|
264
|
+
|
|
249
265
|
## Lifecycle Hooks
|
|
250
266
|
|
|
251
267
|
Run code at specific points in the startup/shutdown cycle:
|
package/src/cli/run-cli.ts
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
stopPublicTunnels,
|
|
8
8
|
} from "../core/tunnel";
|
|
9
9
|
import { spawnWatchdog, startHeartbeat, stopHeartbeat } from "../core/watchdog";
|
|
10
|
-
import { logPublicUrls } from "../environment/logging";
|
|
11
10
|
import type {
|
|
12
11
|
AppConfig,
|
|
13
12
|
CliOptions,
|
|
@@ -88,14 +87,27 @@ export async function runCli<
|
|
|
88
87
|
TApps extends Record<string, AppConfig>,
|
|
89
88
|
>(
|
|
90
89
|
env: DevEnvironment<TServices, TApps>,
|
|
91
|
-
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
|
+
} = {},
|
|
92
98
|
): Promise<void> {
|
|
93
99
|
const {
|
|
94
100
|
args = process.argv.slice(2),
|
|
95
101
|
watchdog = true,
|
|
96
102
|
watchdogTimeout = 10,
|
|
97
103
|
devServersCommand,
|
|
104
|
+
cliTestTunnel,
|
|
98
105
|
} = options;
|
|
106
|
+
const tunnelApi = cliTestTunnel ?? {
|
|
107
|
+
resolveExposeTargets,
|
|
108
|
+
startPublicTunnels,
|
|
109
|
+
stopPublicTunnels,
|
|
110
|
+
};
|
|
99
111
|
const exposeRequested = hasFlag(args, "--expose");
|
|
100
112
|
const exposeValue = getFlagValue(args, "--expose");
|
|
101
113
|
let tunnels: PublicTunnel[] = [];
|
|
@@ -103,7 +115,7 @@ export async function runCli<
|
|
|
103
115
|
async function cleanupTunnels(): Promise<void> {
|
|
104
116
|
env.clearPublicUrls();
|
|
105
117
|
if (tunnels.length === 0) return;
|
|
106
|
-
await stopPublicTunnels(tunnels);
|
|
118
|
+
await tunnelApi.stopPublicTunnels(tunnels);
|
|
107
119
|
tunnels = [];
|
|
108
120
|
}
|
|
109
121
|
|
|
@@ -143,13 +155,16 @@ export async function runCli<
|
|
|
143
155
|
// All other paths need containers + migrations
|
|
144
156
|
// Skip automatic seeding when --seed flag is used (CLI handles it explicitly)
|
|
145
157
|
const skipSeed = args.includes("--seed");
|
|
146
|
-
await env.start({
|
|
158
|
+
await env.start({
|
|
159
|
+
startServers: false,
|
|
160
|
+
wait: true,
|
|
161
|
+
skipSeed,
|
|
162
|
+
skipEnvironmentLog: exposeRequested,
|
|
163
|
+
});
|
|
147
164
|
|
|
148
165
|
if (exposeRequested) {
|
|
149
|
-
const { targets, unknownNames, notEnabledNames } =
|
|
150
|
-
env,
|
|
151
|
-
exposeValue,
|
|
152
|
-
);
|
|
166
|
+
const { targets, unknownNames, notEnabledNames } =
|
|
167
|
+
tunnelApi.resolveExposeTargets(env, exposeValue);
|
|
153
168
|
if (unknownNames.length > 0) {
|
|
154
169
|
console.error(
|
|
155
170
|
`❌ Unknown expose target${unknownNames.length > 1 ? "s" : ""}: ${unknownNames.join(", ")}`,
|
|
@@ -175,13 +190,13 @@ export async function runCli<
|
|
|
175
190
|
process.exit(1);
|
|
176
191
|
}
|
|
177
192
|
|
|
178
|
-
tunnels = await startPublicTunnels(targets);
|
|
193
|
+
tunnels = await tunnelApi.startPublicTunnels(targets);
|
|
179
194
|
env.setPublicUrls(
|
|
180
195
|
Object.fromEntries(
|
|
181
196
|
tunnels.map((tunnel) => [tunnel.name, tunnel.publicUrl]),
|
|
182
197
|
) as typeof env.publicUrls,
|
|
183
198
|
);
|
|
184
|
-
|
|
199
|
+
env.logInfo("Dev Environment", tunnels);
|
|
185
200
|
}
|
|
186
201
|
|
|
187
202
|
// Handle --migrate (exit after migrations)
|
|
@@ -351,10 +366,10 @@ function runCommand(
|
|
|
351
366
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
352
367
|
|
|
353
368
|
/**
|
|
354
|
-
* Check if a CLI flag is present.
|
|
369
|
+
* Check if a CLI flag is present (including `--flag=value` form).
|
|
355
370
|
*/
|
|
356
371
|
export function hasFlag(args: string[], flag: string): boolean {
|
|
357
|
-
return args.
|
|
372
|
+
return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
358
373
|
}
|
|
359
374
|
|
|
360
375
|
/**
|
|
@@ -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
|
+
}
|