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.
Files changed (246) hide show
  1. package/dist/bin.d.ts +1 -12
  2. package/dist/bin.js +261 -253
  3. package/dist/cli/bin.d.ts +13 -0
  4. package/dist/cli/bin.js +317 -0
  5. package/dist/cli/commands/help.d.ts +1 -0
  6. package/dist/cli/commands/runtime.d.ts +5 -0
  7. package/dist/cli/commands/version.d.ts +1 -0
  8. package/dist/cli/index.d.ts +1 -0
  9. package/dist/cli/index.js +14 -0
  10. package/dist/cli/run-cli.d.ts +30 -0
  11. package/dist/cli.d.ts +1 -22
  12. package/dist/cli.js +5 -13
  13. package/dist/config/config.d.ts +1 -0
  14. package/dist/config/define-config.d.ts +13 -0
  15. package/dist/config/index.d.ts +3 -0
  16. package/dist/config/index.js +15 -0
  17. package/dist/config/merge-configs.d.ts +3 -0
  18. package/dist/config/validate-config.d.ts +3 -0
  19. package/dist/config.d.ts +1 -72
  20. package/dist/config.js +12 -12
  21. package/dist/core/docker.d.ts +1 -83
  22. package/dist/core/docker.js +35 -32
  23. package/dist/core/index.d.ts +1 -1
  24. package/dist/core/index.js +123 -118
  25. package/dist/core/network.js +2 -2
  26. package/dist/core/ports.js +1 -1
  27. package/dist/core/process.js +1 -1
  28. package/dist/core/quick-tunnel/cloudflared-process.d.ts +10 -0
  29. package/dist/core/quick-tunnel/constants.d.ts +9 -0
  30. package/dist/core/quick-tunnel/index.d.ts +17 -0
  31. package/dist/core/quick-tunnel/install.d.ts +1 -0
  32. package/dist/core/tunnel.d.ts +34 -0
  33. package/dist/core/utils.js +2 -2
  34. package/dist/core/watchdog-runner.js +45 -42
  35. package/dist/core/watchdog.d.ts +1 -0
  36. package/dist/core/watchdog.js +4 -2
  37. package/dist/docker/index.d.ts +1 -0
  38. package/dist/docker/index.js +38 -0
  39. package/dist/docker/runtime.d.ts +87 -0
  40. package/dist/docker/runtime.js +37 -0
  41. package/dist/docker-compose/compose.d.ts +1 -0
  42. package/dist/docker-compose/generated-file.d.ts +7 -0
  43. package/dist/docker-compose/index.d.ts +3 -0
  44. package/dist/docker-compose/index.js +15 -0
  45. package/dist/docker-compose/model.d.ts +6 -0
  46. package/dist/docker-compose/services/clickhouse.d.ts +16 -0
  47. package/dist/docker-compose/services/define-docker-service.d.ts +41 -0
  48. package/dist/docker-compose/services/index.d.ts +23 -0
  49. package/dist/docker-compose/services/index.js +17 -0
  50. package/dist/docker-compose/services/postgres.d.ts +12 -0
  51. package/dist/docker-compose/services/redis.d.ts +12 -0
  52. package/dist/docker-compose/services/shared.d.ts +7 -0
  53. package/dist/docker-compose/yaml.d.ts +2 -0
  54. package/dist/environment/create-dev-environment.d.ts +23 -0
  55. package/dist/environment/index.d.ts +1 -0
  56. package/dist/environment/index.js +15 -0
  57. package/dist/environment/logging.d.ts +17 -0
  58. package/dist/environment/only-apps.d.ts +10 -0
  59. package/dist/environment/seeding.d.ts +9 -0
  60. package/dist/environment.d.ts +1 -23
  61. package/dist/environment.js +12 -14
  62. package/dist/index-045jksh5.js +147 -0
  63. package/dist/index-08wa79cs.js +125 -117
  64. package/dist/index-0kxnae3z.js +335 -0
  65. package/dist/index-1mdrf7nz.js +51 -43
  66. package/dist/index-1yvbwj4k.js +262 -242
  67. package/dist/index-23ev345g.js +475 -0
  68. package/dist/index-2ckr49sf.js +228 -0
  69. package/dist/index-2f47khe5.js +376 -369
  70. package/dist/index-2fr3g85b.js +220 -183
  71. package/dist/index-38xnzpa6.js +450 -0
  72. package/dist/index-3eyrdxw9.js +577 -0
  73. package/dist/index-3h3dhtf2.js +51 -43
  74. package/dist/index-42x95209.js +51 -43
  75. package/dist/index-4gp0az1g.js +145 -0
  76. package/dist/index-4xrxh8yv.js +72 -0
  77. package/dist/index-5aq985p4.js +250 -0
  78. package/dist/index-5gmws6ah.js +181 -0
  79. package/dist/index-5hka0tff.js +78 -76
  80. package/dist/index-5rfqps4b.js +3 -0
  81. package/dist/index-5t9jxqm0.js +428 -0
  82. package/dist/index-6c1w1xk5.js +101 -0
  83. package/dist/index-6cmex7m5.js +72 -0
  84. package/dist/index-6d6x175r.js +572 -0
  85. package/dist/index-6fm7mvwj.js +118 -97
  86. package/dist/index-6srpc523.js +127 -128
  87. package/dist/index-731rzzfp.js +157 -142
  88. package/dist/index-75y4cg2z.js +51 -43
  89. package/dist/index-7ja4ywyj.js +126 -127
  90. package/dist/index-7v19es2e.js +666 -0
  91. package/dist/index-8bw1cmz4.js +531 -0
  92. package/dist/index-8hbbj1mp.js +120 -121
  93. package/dist/index-8xj2p5n5.js +118 -97
  94. package/dist/index-9wyhzw0h.js +574 -0
  95. package/dist/index-ag90ry8t.js +576 -0
  96. package/dist/index-bj79tw5w.js +0 -0
  97. package/dist/index-bnk6nr0g.js +73 -0
  98. package/dist/index-brbbzyks.js +72 -0
  99. package/dist/index-byeqyjrz.js +72 -0
  100. package/dist/index-c0dr6mcv.js +123 -0
  101. package/dist/index-cty0bcry.js +235 -218
  102. package/dist/index-d8tyv5se.js +228 -0
  103. package/dist/index-d9efy0n4.js +176 -150
  104. package/dist/index-enj4zdma.js +574 -0
  105. package/dist/index-etfmqjjf.js +427 -0
  106. package/dist/index-fb29934k.js +172 -0
  107. package/dist/index-g50jw1yf.js +72 -0
  108. package/dist/index-g6eb5wdw.js +118 -117
  109. package/dist/index-ggq3yryx.js +99 -95
  110. package/dist/index-h70tce00.js +177 -0
  111. package/dist/index-hkxtfqtc.js +333 -0
  112. package/dist/index-k370bech.js +72 -0
  113. package/dist/index-kf3dhser.js +146 -143
  114. package/dist/index-ma6tgdb2.js +500 -0
  115. package/dist/index-mam0bcyz.js +123 -0
  116. package/dist/index-mm412dkp.js +274 -0
  117. package/dist/index-n8v18aeb.js +0 -0
  118. package/dist/index-ndnmnsej.js +378 -371
  119. package/dist/index-p8wty0e2.js +389 -379
  120. package/dist/index-qa8akv6y.js +666 -0
  121. package/dist/index-qfphr2fd.js +78 -76
  122. package/dist/index-qqmms8rs.js +51 -43
  123. package/dist/index-qw4093g2.js +51 -43
  124. package/dist/index-qzwpzjbx.js +121 -122
  125. package/dist/index-segbnm0h.js +146 -143
  126. package/dist/index-t0fj6gg1.js +112 -0
  127. package/dist/index-thdkwnv7.js +122 -0
  128. package/dist/index-tjbx2r2t.js +270 -0
  129. package/dist/index-tjqw9vtj.js +62 -54
  130. package/dist/index-vbpb89jy.js +248 -0
  131. package/dist/index-vg55rq0y.js +250 -0
  132. package/dist/index-vhs88xhe.js +99 -95
  133. package/dist/index-vs81yaks.js +244 -0
  134. package/dist/index-w8zxnjka.js +249 -0
  135. package/dist/index-wk2na3t9.js +385 -375
  136. package/dist/index-wz9x8g7z.js +383 -373
  137. package/dist/index-x249gyde.js +388 -378
  138. package/dist/index-x54nbgs7.js +355 -0
  139. package/dist/index-xkvd0nsd.js +187 -0
  140. package/dist/index-yedqxm1z.js +80 -0
  141. package/dist/index-yz4jfz7z.js +338 -0
  142. package/dist/index-zfjzzjkf.js +240 -199
  143. package/dist/index.d.ts +12 -8
  144. package/dist/index.js +56 -34
  145. package/dist/lint.d.ts +1 -46
  146. package/dist/lint.js +3 -7
  147. package/dist/loader/cache.d.ts +4 -0
  148. package/dist/loader/find-config-file.d.ts +2 -0
  149. package/dist/loader/index.d.ts +5 -0
  150. package/dist/loader/index.js +24 -0
  151. package/dist/loader/load-dev-env.d.ts +5 -0
  152. package/dist/loader/loader.d.ts +1 -0
  153. package/dist/loader.d.ts +1 -45
  154. package/dist/loader.js +22 -20
  155. package/dist/prisma/index.d.ts +1 -0
  156. package/dist/prisma/prisma.d.ts +29 -0
  157. package/dist/prisma.d.ts +1 -29
  158. package/dist/prisma.js +6 -10
  159. package/dist/src/bin.js +309 -0
  160. package/dist/src/cli.js +5 -0
  161. package/dist/src/config.js +15 -0
  162. package/dist/src/core/docker.js +38 -0
  163. package/dist/src/core/index.js +130 -0
  164. package/dist/src/core/network.js +9 -0
  165. package/dist/src/core/ports.js +23 -0
  166. package/dist/src/core/process.js +31 -0
  167. package/dist/src/core/utils.js +11 -0
  168. package/dist/src/core/watchdog-runner.js +69 -0
  169. package/dist/src/core/watchdog.js +28 -0
  170. package/dist/src/docker/runtime.js +37 -0
  171. package/dist/src/docker-compose/index.js +16 -0
  172. package/dist/src/docker-compose/services/index.js +17 -0
  173. package/dist/src/environment.js +12 -0
  174. package/dist/src/index.js +122 -0
  175. package/dist/src/lint.js +3 -0
  176. package/dist/src/loader.js +25 -0
  177. package/dist/src/prisma.js +6 -0
  178. package/dist/src/types.js +0 -0
  179. package/dist/typecheck/index.d.ts +1 -0
  180. package/dist/typecheck/index.js +7 -0
  181. package/dist/typecheck/typecheck.d.ts +46 -0
  182. package/dist/types/all-types.d.ts +544 -0
  183. package/dist/types/cli.d.ts +1 -0
  184. package/dist/types/config.d.ts +6 -0
  185. package/dist/types/docker.d.ts +15 -0
  186. package/dist/types/environment.d.ts +8 -0
  187. package/dist/types/hooks.d.ts +9 -0
  188. package/dist/types/index.d.ts +1 -0
  189. package/dist/types/index.js +0 -0
  190. package/dist/types/prisma.d.ts +1 -0
  191. package/dist/types.d.ts +1 -399
  192. package/package.json +55 -48
  193. package/readme.md +365 -109
  194. package/src/cli/bin.ts +77 -0
  195. package/src/cli/commands/help.ts +39 -0
  196. package/src/cli/commands/runtime.ts +72 -0
  197. package/src/cli/commands/version.ts +4 -0
  198. package/src/cli/index.ts +1 -0
  199. package/{cli.ts → src/cli/run-cli.ts} +114 -10
  200. package/src/config/define-config.ts +30 -0
  201. package/src/config/index.ts +3 -0
  202. package/src/config/merge-configs.ts +33 -0
  203. package/src/config/validate-config.ts +136 -0
  204. package/{core → src/core}/index.ts +2 -2
  205. package/{core → src/core}/ports.ts +5 -2
  206. package/{core → src/core}/process.ts +6 -2
  207. package/src/core/quick-tunnel/cloudflared-process.ts +83 -0
  208. package/src/core/quick-tunnel/constants.ts +31 -0
  209. package/src/core/quick-tunnel/index.ts +96 -0
  210. package/src/core/quick-tunnel/install.ts +160 -0
  211. package/src/core/tunnel.ts +165 -0
  212. package/{core → src/core}/utils.ts +1 -0
  213. package/{core → src/core}/watchdog.ts +5 -1
  214. package/src/docker/index.ts +1 -0
  215. package/{core/docker.ts → src/docker/runtime.ts} +11 -4
  216. package/src/docker-compose/generated-file.ts +45 -0
  217. package/src/docker-compose/index.ts +7 -0
  218. package/src/docker-compose/model.ts +197 -0
  219. package/src/docker-compose/services/clickhouse.ts +79 -0
  220. package/src/docker-compose/services/define-docker-service.ts +109 -0
  221. package/src/docker-compose/services/index.ts +67 -0
  222. package/src/docker-compose/services/postgres.ts +60 -0
  223. package/src/docker-compose/services/redis.ts +48 -0
  224. package/src/docker-compose/services/shared.ts +79 -0
  225. package/src/docker-compose/yaml.ts +88 -0
  226. package/{environment.ts → src/environment/create-dev-environment.ts} +214 -141
  227. package/src/environment/index.ts +1 -0
  228. package/src/environment/logging.ts +115 -0
  229. package/src/environment/only-apps.ts +34 -0
  230. package/src/environment/seeding.ts +57 -0
  231. package/{index.ts → src/index.ts} +52 -20
  232. package/src/loader/cache.ts +23 -0
  233. package/src/loader/find-config-file.ts +29 -0
  234. package/src/loader/index.ts +17 -0
  235. package/src/loader/load-dev-env.ts +38 -0
  236. package/src/prisma/index.ts +1 -0
  237. package/{prisma.ts → src/prisma/prisma.ts} +4 -2
  238. package/src/typecheck/index.ts +1 -0
  239. package/{types.ts → src/types/all-types.ts} +186 -8
  240. package/src/types/index.ts +1 -0
  241. package/bin.ts +0 -192
  242. package/config.ts +0 -194
  243. package/loader.ts +0 -126
  244. /package/{core → src/core}/network.ts +0 -0
  245. /package/{core → src/core}/watchdog-runner.ts +0 -0
  246. /package/{lint.ts → src/typecheck/typecheck.ts} +0 -0
@@ -0,0 +1,88 @@
1
+ import type { DockerComposeNode } from "../types";
2
+ import type { ComposeDocument } from "./model";
3
+
4
+ function isObject(
5
+ value: DockerComposeNode,
6
+ ): value is Record<string, DockerComposeNode | undefined> {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+
10
+ function formatScalar(value: string | number | boolean | null): string {
11
+ if (value === null) return "null";
12
+ if (typeof value === "string") {
13
+ return JSON.stringify(value);
14
+ }
15
+ return String(value);
16
+ }
17
+
18
+ function formatKey(key: string): string {
19
+ return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key) ? key : JSON.stringify(key);
20
+ }
21
+
22
+ function sortNode(node: DockerComposeNode): DockerComposeNode {
23
+ if (Array.isArray(node)) {
24
+ return node.map(sortNode);
25
+ }
26
+ if (isObject(node)) {
27
+ const sorted: Record<string, DockerComposeNode | undefined> = {};
28
+ for (const key of Object.keys(node).sort()) {
29
+ const value = node[key];
30
+ if (value !== undefined) {
31
+ sorted[key] = sortNode(value);
32
+ }
33
+ }
34
+ return sorted;
35
+ }
36
+ return node;
37
+ }
38
+
39
+ function stringifyNode(node: DockerComposeNode, indent = 0): string {
40
+ const prefix = " ".repeat(indent);
41
+
42
+ if (
43
+ typeof node === "string" ||
44
+ typeof node === "number" ||
45
+ typeof node === "boolean" ||
46
+ node === null
47
+ ) {
48
+ return `${prefix}${formatScalar(node)}`;
49
+ }
50
+
51
+ if (Array.isArray(node)) {
52
+ if (node.length === 0) return `${prefix}[]`;
53
+ return node
54
+ .map((item) => {
55
+ const isNested = typeof item === "object" && item !== null;
56
+ if (!isNested) {
57
+ return `${prefix}- ${formatScalar(
58
+ item as string | number | boolean | null,
59
+ )}`;
60
+ }
61
+ return `${prefix}-\n${stringifyNode(item, indent + 2)}`;
62
+ })
63
+ .join("\n");
64
+ }
65
+
66
+ const entries = Object.entries(node).filter(
67
+ ([, value]) => value !== undefined,
68
+ ) as Array<[string, DockerComposeNode]>;
69
+ if (entries.length === 0) return `${prefix}{}`;
70
+
71
+ return entries
72
+ .map(([key, value]) => {
73
+ const formattedKey = formatKey(key);
74
+ const isNested = typeof value === "object" && value !== null;
75
+ if (!isNested) {
76
+ return `${prefix}${formattedKey}: ${formatScalar(
77
+ value as string | number | boolean | null,
78
+ )}`;
79
+ }
80
+ return `${prefix}${formattedKey}:\n${stringifyNode(value, indent + 2)}`;
81
+ })
82
+ .join("\n");
83
+ }
84
+
85
+ export function composeToYaml(document: ComposeDocument): string {
86
+ const sorted = sortNode(document as DockerComposeNode);
87
+ return `${stringifyNode(sorted)}\n`;
88
+ }
@@ -1,72 +1,61 @@
1
- import pc from "picocolors";
2
- import { assertValidConfig } from "./config";
3
- import {
4
- areContainersRunning,
5
- startContainers,
6
- stopContainers,
7
- } from "./core/docker";
8
- import { getLocalIp, waitForDevServers, waitForServer } from "./core/network";
1
+ import { assertValidConfig } from "../config";
2
+ import { getLocalIp, waitForDevServers, waitForServer } from "../core/network";
9
3
  import {
10
4
  computeDevIdentity,
11
5
  computePorts,
12
6
  computeUrls,
13
7
  findMonorepoRoot,
14
- } from "./core/ports";
8
+ } from "../core/ports";
15
9
  import {
16
10
  buildApps,
17
11
  execAsync,
18
12
  startDevServers,
19
13
  stopProcess as stopProcessFn,
20
- } from "./core/process";
21
- import { isCI as isCIEnv, logExpoApiUrl, logFrontendPort } from "./core/utils";
14
+ } from "../core/process";
15
+ import {
16
+ type PublicTunnel,
17
+ resolveExposeTargets,
18
+ startPublicTunnels,
19
+ stopPublicTunnels,
20
+ } from "../core/tunnel";
21
+ import { isCI as isCIEnv, logExpoApiUrl, logFrontendPort } from "../core/utils";
22
22
  import {
23
23
  spawnWatchdog as spawnWatchdogFn,
24
24
  startHeartbeat as startHeartbeatFn,
25
25
  stopHeartbeat as stopHeartbeatFn,
26
26
  stopWatchdog as stopWatchdogFn,
27
- } from "./core/watchdog";
28
- import { createPrismaRunner } from "./prisma";
27
+ } from "../core/watchdog";
28
+ import {
29
+ areContainersRunning,
30
+ startContainers,
31
+ stopContainers,
32
+ } from "../docker/runtime";
33
+ import {
34
+ getGeneratedComposePath,
35
+ writeGeneratedComposeFile,
36
+ } from "../docker-compose";
37
+ import { createPrismaRunner } from "../prisma";
29
38
  import type {
30
39
  AppConfig,
31
40
  ComputedPorts,
41
+ ComputedPublicUrls,
32
42
  ComputedUrls,
33
43
  DevConfig,
34
44
  DevEnvironment,
45
+ DevEnvironmentTunnelLog,
35
46
  DevServerPids,
36
47
  ExecOptions,
37
48
  HookContext,
49
+ OpenPublicTunnelsOptions,
50
+ OpenPublicTunnelsResult,
38
51
  PrismaRunner,
39
52
  ServiceConfig,
40
53
  StartOptions,
41
54
  StopOptions,
42
- } from "./types";
43
-
44
- // ═══════════════════════════════════════════════════════════════════════════
45
- // Console Output Formatting (Vite-inspired)
46
- // ═══════════════════════════════════════════════════════════════════════════
47
-
48
- /**
49
- * Format a URL with colored port number (Vite-style).
50
- */
51
- function formatUrl(url: string): string {
52
- return pc.cyan(
53
- url.replace(/:(\d+)(\/?)/, (_, port, slash) => `:${pc.bold(port)}${slash}`),
54
- );
55
- }
56
-
57
- /**
58
- * Format a label with arrow prefix (Vite-style).
59
- */
60
- function formatLabel(label: string, value: string, arrow = "➜"): string {
61
- return ` ${pc.green(arrow)} ${pc.bold(label.padEnd(10))} ${value}`;
62
- }
63
-
64
- /**
65
- * Format a dim label (for secondary info).
66
- */
67
- function formatDimLabel(label: string, value: string): string {
68
- return ` ${pc.dim("•")} ${pc.dim(label.padEnd(10))} ${pc.dim(value)}`;
69
- }
55
+ } from "../types";
56
+ import { logEnvironmentInfo } from "./logging";
57
+ import { assertOnlyAppNames, pickApps } from "./only-apps";
58
+ import { createCheckTableHelper, createSeedCheckContext } from "./seeding";
70
59
 
71
60
  // ═══════════════════════════════════════════════════════════════════════════
72
61
  // Environment Factory
@@ -115,6 +104,14 @@ export function createDevEnvironment<
115
104
 
116
105
  const services = config.services;
117
106
  const apps = (config.apps ?? {}) as TApps;
107
+ const composeFile = getGeneratedComposePath(
108
+ root,
109
+ config.docker,
110
+ ).composeFileArg;
111
+
112
+ function ensureComposeFile(): string {
113
+ return writeGeneratedComposeFile(root, services, config.docker);
114
+ }
118
115
 
119
116
  // Compute ports and URLs
120
117
  const ports = computePorts(services, apps, portOffset) as ComputedPorts<
@@ -125,6 +122,22 @@ export function createDevEnvironment<
125
122
  TServices,
126
123
  TApps
127
124
  >;
125
+ const publicUrls: Record<string, string> = {};
126
+
127
+ function setPublicUrls(urlsInput: Record<string, string>): void {
128
+ for (const key of Object.keys(publicUrls)) {
129
+ delete publicUrls[key];
130
+ }
131
+ for (const [key, value] of Object.entries(urlsInput)) {
132
+ publicUrls[key] = value;
133
+ }
134
+ }
135
+
136
+ function clearPublicUrls(): void {
137
+ for (const key of Object.keys(publicUrls)) {
138
+ delete publicUrls[key];
139
+ }
140
+ }
128
141
 
129
142
  // Build environment variables
130
143
  function buildEnvVars(production = false): Record<string, string> {
@@ -145,12 +158,19 @@ export function createDevEnvironment<
145
158
  baseEnv[envName] = url;
146
159
  }
147
160
 
161
+ // Add public URL environment variables when tunnels are active
162
+ for (const [name, url] of Object.entries(publicUrls)) {
163
+ const envName = `${name.toUpperCase()}_PUBLIC_URL`;
164
+ baseEnv[envName] = url;
165
+ }
166
+
148
167
  // Call user's envVars function if provided
149
168
  if (config.envVars) {
150
169
  const userEnv = config.envVars(ports, urls, {
151
170
  projectName,
152
171
  localIp,
153
172
  portOffset,
173
+ publicUrls: publicUrls as ComputedPublicUrls<TServices, TApps>,
154
174
  });
155
175
  for (const [key, value] of Object.entries(userEnv)) {
156
176
  baseEnv[key] = String(value);
@@ -169,6 +189,7 @@ export function createDevEnvironment<
169
189
  projectName,
170
190
  ports,
171
191
  urls,
192
+ publicUrls: publicUrls as ComputedPublicUrls<TServices, TApps>,
172
193
  root,
173
194
  isCI: isCIEnv(),
174
195
  portOffset,
@@ -202,12 +223,18 @@ export function createDevEnvironment<
202
223
  startServers: shouldStartServers = true,
203
224
  productionBuild = isCI,
204
225
  skipSeed = false,
226
+ skipEnvironmentLog = false,
227
+ onlyApps,
205
228
  } = startOptions;
206
229
 
230
+ assertOnlyAppNames(Object.keys(apps), onlyApps);
231
+ const appsToStart = pickApps(apps, onlyApps);
232
+
207
233
  const envVars = buildEnvVars(productionBuild);
234
+ ensureComposeFile();
208
235
 
209
236
  // Log environment info
210
- if (verbose) {
237
+ if (verbose && !skipEnvironmentLog) {
211
238
  logInfo(productionBuild ? "Production Environment" : "Dev Environment");
212
239
  }
213
240
 
@@ -224,7 +251,7 @@ export function createDevEnvironment<
224
251
  startContainers(root, projectName, envVars, {
225
252
  verbose,
226
253
  wait,
227
- composeFile: config.options?.composeFile,
254
+ composeFile,
228
255
  });
229
256
  }
230
257
 
@@ -258,19 +285,19 @@ export function createDevEnvironment<
258
285
  }),
259
286
  );
260
287
 
261
- // Check for failures
262
- for (const { name, result } of migrationResults) {
263
- if (result.exitCode !== 0) {
264
- console.error(`❌ Migration "${name}" failed`);
265
- if (result.stdout) {
266
- console.error(result.stdout);
267
- }
268
- if (result.stderr) {
269
- console.error(result.stderr);
288
+ // Check for failures
289
+ for (const { name, result } of migrationResults) {
290
+ if (result.exitCode !== 0) {
291
+ console.error(`❌ Migration "${name}" failed`);
292
+ if (result.stdout) {
293
+ console.error(result.stdout);
294
+ }
295
+ if (result.stderr) {
296
+ console.error(result.stderr);
297
+ }
298
+ throw new Error(`Migration "${name}" failed`);
270
299
  }
271
- throw new Error(`Migration "${name}" failed`);
272
300
  }
273
- }
274
301
 
275
302
  if (verbose) console.log("✓ Migrations complete");
276
303
  }
@@ -286,35 +313,14 @@ export function createDevEnvironment<
286
313
 
287
314
  // Check if seeding is needed using check function
288
315
  if (config.seed.check) {
289
- // Create checkTable helper function with typed service parameter
290
- const checkTable = async (
291
- tableName: string,
292
- service?: keyof TServices,
293
- ): Promise<boolean> => {
294
- const serviceName = (service ?? "postgres") as string;
295
- const serviceUrl = (urls as Record<string, string>)[serviceName];
296
- if (!serviceUrl) {
297
- console.warn(`⚠️ Service "${serviceName}" not found for checkTable`);
298
- return true; // Default to seeding if service not found
299
- }
300
- const checkResult = await exec(
301
- `psql "${serviceUrl}" -tAc 'SELECT COUNT(*) FROM "${tableName}" LIMIT 1'`,
302
- { throwOnError: false },
316
+ const checkTable = createCheckTableHelper<TServices, TApps>(
317
+ urls as Record<string, string>,
318
+ exec,
303
319
  );
304
- const count = checkResult.stdout.trim();
305
- const shouldSeed = checkResult.exitCode !== 0 || count === "0" || count === "";
306
- if (!shouldSeed) {
307
- console.log(` 📊 Table "${tableName}" has ${count} rows`);
308
- }
309
- return shouldSeed;
310
- };
311
-
312
- // Build seed check context with helpers
313
- const seedCheckContext = {
314
- ...getHookContext(),
320
+ const seedCheckContext = createSeedCheckContext(
321
+ getHookContext(),
315
322
  checkTable,
316
- };
317
-
323
+ );
318
324
  shouldSeed = await config.seed.check(seedCheckContext);
319
325
  }
320
326
 
@@ -339,7 +345,7 @@ export function createDevEnvironment<
339
345
  }
340
346
 
341
347
  // Start servers if requested
342
- if (shouldStartServers && Object.keys(apps).length > 0) {
348
+ if (shouldStartServers && Object.keys(appsToStart).length > 0) {
343
349
  // Run beforeServers hook
344
350
  if (config.hooks?.beforeServers) {
345
351
  await config.hooks.beforeServers(getHookContext());
@@ -347,11 +353,11 @@ export function createDevEnvironment<
347
353
 
348
354
  // Build if production
349
355
  if (productionBuild) {
350
- buildApps(apps, root, envVars, { verbose });
356
+ buildApps(appsToStart, root, envVars, { verbose });
351
357
  }
352
358
 
353
359
  // Start servers
354
- const pids = await startDevServers(apps, root, envVars, ports, {
360
+ const pids = await startDevServers(appsToStart, root, envVars, ports, {
355
361
  verbose,
356
362
  productionBuild,
357
363
  isCI,
@@ -359,7 +365,7 @@ export function createDevEnvironment<
359
365
 
360
366
  // Wait for servers to be ready
361
367
  if (verbose) console.log("⏳ Waiting for servers to be ready...");
362
- await waitForDevServers(apps, ports, {
368
+ await waitForDevServers(appsToStart, ports, {
363
369
  timeout: isCI ? 120000 : 60000,
364
370
  verbose,
365
371
  productionBuild,
@@ -380,6 +386,7 @@ export function createDevEnvironment<
380
386
 
381
387
  async function stop(stopOptions: StopOptions = {}): Promise<void> {
382
388
  const { verbose = true, removeVolumes = false } = stopOptions;
389
+ ensureComposeFile();
383
390
 
384
391
  // Run beforeStop hook
385
392
  if (config.hooks?.beforeStop) {
@@ -389,7 +396,7 @@ export function createDevEnvironment<
389
396
  stopContainers(root, projectName, {
390
397
  verbose,
391
398
  removeVolumes,
392
- composeFile: config.options?.composeFile,
399
+ composeFile,
393
400
  });
394
401
  }
395
402
 
@@ -408,84 +415,141 @@ export function createDevEnvironment<
408
415
  // ─────────────────────────────────────────────────────────────────────────
409
416
 
410
417
  async function startServersOnly(
411
- options: { productionBuild?: boolean; verbose?: boolean } = {},
418
+ options: {
419
+ productionBuild?: boolean;
420
+ verbose?: boolean;
421
+ onlyApps?: string[];
422
+ } = {},
412
423
  ): Promise<DevServerPids> {
413
- const { productionBuild = false, verbose = true } = options;
424
+ const { productionBuild = false, verbose = true, onlyApps } = options;
425
+ assertOnlyAppNames(Object.keys(apps), onlyApps);
426
+ const appsToStart = pickApps(apps, onlyApps);
414
427
  const envVars = buildEnvVars(productionBuild);
415
428
  const isCI = process.env.CI === "true";
416
429
 
430
+ if (Object.keys(appsToStart).length === 0) {
431
+ return {};
432
+ }
433
+
417
434
  // Build if production
418
435
  if (productionBuild) {
419
- buildApps(apps, root, envVars, { verbose });
436
+ buildApps(appsToStart, root, envVars, { verbose });
420
437
  }
421
438
 
422
- return startDevServers(apps, root, envVars, ports, {
439
+ const pids = await startDevServers(appsToStart, root, envVars, ports, {
423
440
  verbose,
424
441
  productionBuild,
425
442
  isCI,
426
443
  });
444
+
445
+ if (verbose) console.log("⏳ Waiting for servers to be ready...");
446
+ await waitForDevServers(appsToStart, ports, {
447
+ timeout: isCI ? 120000 : 60000,
448
+ verbose,
449
+ productionBuild,
450
+ });
451
+
452
+ return pids;
427
453
  }
428
454
 
429
455
  async function waitForServersReady(
430
- options: { timeout?: number; productionBuild?: boolean } = {},
456
+ options: {
457
+ timeout?: number;
458
+ productionBuild?: boolean;
459
+ onlyApps?: string[];
460
+ } = {},
431
461
  ): Promise<void> {
432
- const { timeout = 60000, productionBuild = false } = options;
433
- await waitForDevServers(apps, ports, { timeout, productionBuild });
462
+ const { timeout = 60000, productionBuild = false, onlyApps } = options;
463
+ assertOnlyAppNames(Object.keys(apps), onlyApps);
464
+ const appsToWait = pickApps(apps, onlyApps);
465
+ await waitForDevServers(appsToWait, ports, { timeout, productionBuild });
434
466
  }
435
467
 
436
- // ─────────────────────────────────────────────────────────────────────────
437
- // Utilities
438
- // ─────────────────────────────────────────────────────────────────────────
439
-
440
- function logInfo(label = "Docker Dev"): void {
441
- const serviceNames = Object.keys(services);
442
- const appNames = Object.keys(apps);
443
-
444
- console.log("");
445
- console.log(` ${pc.cyan(pc.bold(`🐳 ${label}`))}`);
446
- console.log(formatLabel("Project:", pc.white(projectName)));
447
-
448
- // Services section (Docker containers)
449
- if (serviceNames.length > 0) {
450
- console.log("");
451
- console.log(` ${pc.dim("─── Services ───")}`);
452
- for (const name of serviceNames) {
453
- const port = (ports as Record<string, number>)[name];
454
- const url = `localhost:${port}`;
455
- console.log(formatLabel(`${name}:`, formatUrl(`http://${url}`)));
456
- }
468
+ async function openPublicTunnels(
469
+ options: OpenPublicTunnelsOptions = {},
470
+ ): Promise<OpenPublicTunnelsResult<TServices, TApps>> {
471
+ const { names, waitForHealthy } = options;
472
+ const exposeList = names?.length ? names.join(",") : undefined;
473
+
474
+ if (waitForHealthy?.length) {
475
+ assertOnlyAppNames(Object.keys(apps), waitForHealthy);
476
+ const appsWait = pickApps(apps, waitForHealthy);
477
+ const isCI = process.env.CI === "true";
478
+ await waitForDevServers(appsWait, ports, {
479
+ timeout: isCI ? 120000 : 60000,
480
+ verbose: config.options?.verbose ?? true,
481
+ productionBuild: false,
482
+ });
457
483
  }
458
484
 
459
- // Apps section (Dev servers)
460
- if (appNames.length > 0) {
461
- console.log("");
462
- console.log(` ${pc.dim("─── Applications ───")}`);
463
- for (const name of appNames) {
464
- const port = (ports as Record<string, number>)[name];
465
- const localUrl = `http://localhost:${port}`;
466
- const networkUrl = `http://${localIp}:${port}`;
467
-
468
- console.log(` ${pc.green("➜")} ${pc.bold(pc.cyan(name))}`);
469
- console.log(` ${pc.dim("Local:")} ${formatUrl(localUrl)}`);
470
- console.log(` ${pc.dim("Network:")} ${formatUrl(networkUrl)}`);
471
- }
485
+ const { targets, unknownNames, notEnabledNames } = resolveExposeTargets(
486
+ {
487
+ services,
488
+ apps,
489
+ ports,
490
+ } as DevEnvironment<TServices, TApps>,
491
+ exposeList,
492
+ );
493
+
494
+ if (unknownNames.length > 0) {
495
+ throw new Error(`Unknown expose target(s): ${unknownNames.join(", ")}`);
496
+ }
497
+ if (notEnabledNames.length > 0) {
498
+ throw new Error(
499
+ `Target(s) missing expose: true: ${notEnabledNames.join(", ")}`,
500
+ );
501
+ }
502
+ if (targets.length === 0) {
503
+ throw new Error(
504
+ "No expose targets selected. Add expose: true to services/apps or pass names that have expose: true.",
505
+ );
472
506
  }
473
507
 
474
- // Environment info
475
- console.log("");
476
- console.log(` ${pc.dim("─── Environment ───")}`);
477
- console.log(formatDimLabel("Worktree:", worktree ? "yes" : "no"));
478
- console.log(
479
- formatDimLabel(
480
- "Port offset:",
481
- portOffset > 0 ? `+${portOffset}` : "none",
482
- ),
508
+ const tunnels = await startPublicTunnels(targets);
509
+ setPublicUrls(
510
+ Object.fromEntries(tunnels.map((t) => [t.name, t.publicUrl])),
483
511
  );
484
- if (projectSuffix) {
485
- console.log(formatDimLabel("Suffix:", projectSuffix));
512
+
513
+ let closed = false;
514
+ async function close(): Promise<void> {
515
+ if (closed) return;
516
+ closed = true;
517
+ await stopPublicTunnels(tunnels);
518
+ clearPublicUrls();
486
519
  }
487
- console.log(formatDimLabel("Local IP:", localIp));
488
- console.log("");
520
+
521
+ return {
522
+ publicUrls: { ...publicUrls } as ComputedPublicUrls<TServices, TApps>,
523
+ tunnels,
524
+ close,
525
+ };
526
+ }
527
+
528
+ // ─────────────────────────────────────────────────────────────────────────
529
+ // Utilities
530
+ // ─────────────────────────────────────────────────────────────────────────
531
+
532
+ function logInfo(label = "Docker Dev", tunnels?: PublicTunnel[]): void {
533
+ const tunnelRows: DevEnvironmentTunnelLog[] | undefined = tunnels?.map(
534
+ ({ kind, name, localUrl, publicUrl }) => ({
535
+ kind,
536
+ name,
537
+ localUrl,
538
+ publicUrl,
539
+ }),
540
+ );
541
+ logEnvironmentInfo({
542
+ label,
543
+ projectName,
544
+ services,
545
+ apps,
546
+ ports: ports as Record<string, number>,
547
+ localIp,
548
+ worktree,
549
+ portOffset,
550
+ projectSuffix,
551
+ tunnels: tunnelRows,
552
+ });
489
553
  }
490
554
 
491
555
  async function waitForServerUrl(
@@ -511,7 +575,7 @@ export function createDevEnvironment<
511
575
  await spawnWatchdogFn(projectName, root, {
512
576
  timeoutMinutes,
513
577
  verbose: true,
514
- composeFile: config.options?.composeFile,
578
+ composeFile,
515
579
  });
516
580
  }
517
581
 
@@ -554,11 +618,14 @@ export function createDevEnvironment<
554
618
  projectName,
555
619
  ports,
556
620
  urls,
621
+ publicUrls: publicUrls as ComputedPublicUrls<TServices, TApps>,
622
+ services,
557
623
  apps,
558
624
  portOffset,
559
625
  isWorktree: worktree,
560
626
  localIp,
561
627
  root,
628
+ composeFile,
562
629
 
563
630
  // Container management
564
631
  start,
@@ -573,9 +640,15 @@ export function createDevEnvironment<
573
640
 
574
641
  // Utilities
575
642
  buildEnvVars,
643
+ setPublicUrls: (urlsInput) => {
644
+ setPublicUrls(urlsInput as Record<string, string>);
645
+ },
646
+ clearPublicUrls,
647
+ ensureComposeFile,
576
648
  exec,
577
649
  waitForServer: waitForServerUrl,
578
650
  logInfo,
651
+ openPublicTunnels,
579
652
 
580
653
  // Vibe Kanban Integration
581
654
  getExpoApiUrl,
@@ -0,0 +1 @@
1
+ export { createDevEnvironment } from "./create-dev-environment";