buncargo 1.0.29 → 3.0.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 (221) 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 +315 -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 +22 -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/tunnel.d.ts +33 -0
  29. package/dist/core/utils.js +2 -2
  30. package/dist/core/watchdog-runner.js +45 -42
  31. package/dist/core/watchdog.d.ts +1 -0
  32. package/dist/core/watchdog.js +4 -2
  33. package/dist/docker/index.d.ts +1 -0
  34. package/dist/docker/index.js +38 -0
  35. package/dist/docker/runtime.d.ts +87 -0
  36. package/dist/docker/runtime.js +37 -0
  37. package/dist/docker-compose/compose.d.ts +1 -0
  38. package/dist/docker-compose/generated-file.d.ts +7 -0
  39. package/dist/docker-compose/index.d.ts +3 -0
  40. package/dist/docker-compose/index.js +15 -0
  41. package/dist/docker-compose/model.d.ts +6 -0
  42. package/dist/docker-compose/services/clickhouse.d.ts +16 -0
  43. package/dist/docker-compose/services/define-docker-service.d.ts +41 -0
  44. package/dist/docker-compose/services/index.d.ts +23 -0
  45. package/dist/docker-compose/services/index.js +17 -0
  46. package/dist/docker-compose/services/postgres.d.ts +12 -0
  47. package/dist/docker-compose/services/redis.d.ts +12 -0
  48. package/dist/docker-compose/services/shared.d.ts +7 -0
  49. package/dist/docker-compose/yaml.d.ts +2 -0
  50. package/dist/environment/create-dev-environment.d.ts +23 -0
  51. package/dist/environment/index.d.ts +1 -0
  52. package/dist/environment/index.js +15 -0
  53. package/dist/environment/logging.d.ts +17 -0
  54. package/dist/environment/seeding.d.ts +9 -0
  55. package/dist/environment.d.ts +1 -23
  56. package/dist/environment.js +12 -14
  57. package/dist/index-045jksh5.js +147 -0
  58. package/dist/index-08wa79cs.js +125 -117
  59. package/dist/index-0kxnae3z.js +335 -0
  60. package/dist/index-1mdrf7nz.js +51 -43
  61. package/dist/index-1yvbwj4k.js +262 -242
  62. package/dist/index-23ev345g.js +475 -0
  63. package/dist/index-2ckr49sf.js +228 -0
  64. package/dist/index-2f47khe5.js +376 -369
  65. package/dist/index-2fr3g85b.js +220 -183
  66. package/dist/index-38xnzpa6.js +450 -0
  67. package/dist/index-3h3dhtf2.js +51 -43
  68. package/dist/index-42x95209.js +51 -43
  69. package/dist/index-4gp0az1g.js +145 -0
  70. package/dist/index-4xrxh8yv.js +72 -0
  71. package/dist/index-5gmws6ah.js +181 -0
  72. package/dist/index-5hka0tff.js +78 -76
  73. package/dist/index-5rfqps4b.js +3 -0
  74. package/dist/index-5t9jxqm0.js +428 -0
  75. package/dist/index-6c1w1xk5.js +101 -0
  76. package/dist/index-6fm7mvwj.js +118 -97
  77. package/dist/index-6srpc523.js +127 -128
  78. package/dist/index-731rzzfp.js +157 -142
  79. package/dist/index-75y4cg2z.js +51 -43
  80. package/dist/index-7ja4ywyj.js +126 -127
  81. package/dist/index-8bw1cmz4.js +531 -0
  82. package/dist/index-8hbbj1mp.js +120 -121
  83. package/dist/index-8xj2p5n5.js +118 -97
  84. package/dist/index-bj79tw5w.js +0 -0
  85. package/dist/index-bnk6nr0g.js +73 -0
  86. package/dist/index-brbbzyks.js +72 -0
  87. package/dist/index-c0dr6mcv.js +123 -0
  88. package/dist/index-cty0bcry.js +235 -218
  89. package/dist/index-d8tyv5se.js +228 -0
  90. package/dist/index-d9efy0n4.js +176 -150
  91. package/dist/index-etfmqjjf.js +427 -0
  92. package/dist/index-fb29934k.js +172 -0
  93. package/dist/index-g50jw1yf.js +72 -0
  94. package/dist/index-g6eb5wdw.js +118 -117
  95. package/dist/index-ggq3yryx.js +99 -95
  96. package/dist/index-h70tce00.js +177 -0
  97. package/dist/index-hkxtfqtc.js +333 -0
  98. package/dist/index-kf3dhser.js +146 -143
  99. package/dist/index-ma6tgdb2.js +500 -0
  100. package/dist/index-mam0bcyz.js +123 -0
  101. package/dist/index-mm412dkp.js +274 -0
  102. package/dist/index-n8v18aeb.js +0 -0
  103. package/dist/index-ndnmnsej.js +378 -371
  104. package/dist/index-p8wty0e2.js +389 -379
  105. package/dist/index-qfphr2fd.js +78 -76
  106. package/dist/index-qqmms8rs.js +51 -43
  107. package/dist/index-qw4093g2.js +51 -43
  108. package/dist/index-qzwpzjbx.js +121 -122
  109. package/dist/index-segbnm0h.js +146 -143
  110. package/dist/index-t0fj6gg1.js +112 -0
  111. package/dist/index-thdkwnv7.js +122 -0
  112. package/dist/index-tjbx2r2t.js +270 -0
  113. package/dist/index-tjqw9vtj.js +62 -54
  114. package/dist/index-vbpb89jy.js +248 -0
  115. package/dist/index-vhs88xhe.js +99 -95
  116. package/dist/index-w8zxnjka.js +249 -0
  117. package/dist/index-wk2na3t9.js +385 -375
  118. package/dist/index-wz9x8g7z.js +383 -373
  119. package/dist/index-x249gyde.js +388 -378
  120. package/dist/index-xkvd0nsd.js +187 -0
  121. package/dist/index-yedqxm1z.js +80 -0
  122. package/dist/index-zfjzzjkf.js +240 -199
  123. package/dist/index.d.ts +12 -8
  124. package/dist/index.js +56 -35
  125. package/dist/lint.d.ts +1 -46
  126. package/dist/lint.js +3 -7
  127. package/dist/loader/cache.d.ts +4 -0
  128. package/dist/loader/find-config-file.d.ts +2 -0
  129. package/dist/loader/index.d.ts +5 -0
  130. package/dist/loader/index.js +24 -0
  131. package/dist/loader/load-dev-env.d.ts +5 -0
  132. package/dist/loader/loader.d.ts +1 -0
  133. package/dist/loader.d.ts +1 -45
  134. package/dist/loader.js +22 -20
  135. package/dist/prisma/index.d.ts +1 -0
  136. package/dist/prisma/prisma.d.ts +29 -0
  137. package/dist/prisma.d.ts +1 -29
  138. package/dist/prisma.js +6 -10
  139. package/dist/src/bin.js +309 -0
  140. package/dist/src/cli.js +5 -0
  141. package/dist/src/config.js +15 -0
  142. package/dist/src/core/docker.js +38 -0
  143. package/dist/src/core/index.js +130 -0
  144. package/dist/src/core/network.js +9 -0
  145. package/dist/src/core/ports.js +23 -0
  146. package/dist/src/core/process.js +31 -0
  147. package/dist/src/core/utils.js +11 -0
  148. package/dist/src/core/watchdog-runner.js +69 -0
  149. package/dist/src/core/watchdog.js +28 -0
  150. package/dist/src/docker/runtime.js +37 -0
  151. package/dist/src/docker-compose/index.js +16 -0
  152. package/dist/src/docker-compose/services/index.js +17 -0
  153. package/dist/src/environment.js +12 -0
  154. package/dist/src/index.js +122 -0
  155. package/dist/src/lint.js +3 -0
  156. package/dist/src/loader.js +25 -0
  157. package/dist/src/prisma.js +6 -0
  158. package/dist/src/types.js +0 -0
  159. package/dist/typecheck/index.d.ts +1 -0
  160. package/dist/typecheck/index.js +7 -0
  161. package/dist/typecheck/typecheck.d.ts +46 -0
  162. package/dist/types/all-types.d.ts +501 -0
  163. package/dist/types/cli.d.ts +1 -0
  164. package/dist/types/config.d.ts +6 -0
  165. package/dist/types/docker.d.ts +15 -0
  166. package/dist/types/environment.d.ts +8 -0
  167. package/dist/types/hooks.d.ts +9 -0
  168. package/dist/types/index.d.ts +1 -0
  169. package/dist/types/index.js +0 -0
  170. package/dist/types/prisma.d.ts +1 -0
  171. package/dist/types.d.ts +1 -399
  172. package/package.json +145 -140
  173. package/readme.md +349 -109
  174. package/src/cli/bin.ts +77 -0
  175. package/src/cli/commands/help.ts +39 -0
  176. package/src/cli/commands/runtime.ts +72 -0
  177. package/src/cli/commands/version.ts +4 -0
  178. package/src/cli/index.ts +1 -0
  179. package/{cli.ts → src/cli/run-cli.ts} +95 -6
  180. package/src/config/define-config.ts +30 -0
  181. package/src/config/index.ts +3 -0
  182. package/src/config/merge-configs.ts +33 -0
  183. package/src/config/validate-config.ts +136 -0
  184. package/{core → src/core}/index.ts +2 -2
  185. package/{core → src/core}/ports.ts +5 -2
  186. package/{core → src/core}/process.ts +6 -2
  187. package/src/core/tunnel.ts +151 -0
  188. package/{core → src/core}/utils.ts +1 -0
  189. package/{core → src/core}/watchdog.ts +5 -1
  190. package/src/docker/index.ts +1 -0
  191. package/{core/docker.ts → src/docker/runtime.ts} +11 -4
  192. package/src/docker-compose/generated-file.ts +45 -0
  193. package/src/docker-compose/index.ts +7 -0
  194. package/src/docker-compose/model.ts +197 -0
  195. package/src/docker-compose/services/clickhouse.ts +79 -0
  196. package/src/docker-compose/services/define-docker-service.ts +109 -0
  197. package/src/docker-compose/services/index.ts +67 -0
  198. package/src/docker-compose/services/postgres.ts +60 -0
  199. package/src/docker-compose/services/redis.ts +48 -0
  200. package/src/docker-compose/services/shared.ts +79 -0
  201. package/src/docker-compose/yaml.ts +88 -0
  202. package/{environment.ts → src/environment/create-dev-environment.ts} +93 -130
  203. package/src/environment/index.ts +1 -0
  204. package/src/environment/logging.ts +101 -0
  205. package/src/environment/seeding.ts +57 -0
  206. package/{index.ts → src/index.ts} +49 -20
  207. package/src/loader/cache.ts +23 -0
  208. package/src/loader/find-config-file.ts +29 -0
  209. package/src/loader/index.ts +17 -0
  210. package/src/loader/load-dev-env.ts +38 -0
  211. package/src/prisma/index.ts +1 -0
  212. package/{prisma.ts → src/prisma/prisma.ts} +4 -2
  213. package/src/typecheck/index.ts +1 -0
  214. package/{types.ts → src/types/all-types.ts} +130 -5
  215. package/src/types/index.ts +1 -0
  216. package/bin.ts +0 -192
  217. package/config.ts +0 -194
  218. package/loader.ts +0 -126
  219. /package/{core → src/core}/network.ts +0 -0
  220. /package/{core → src/core}/watchdog-runner.ts +0 -0
  221. /package/{lint.ts → src/typecheck/typecheck.ts} +0 -0
@@ -0,0 +1,67 @@
1
+ import type {
2
+ DockerComposeServiceRaw,
3
+ DockerPresetName,
4
+ ServiceConfig,
5
+ } from "../../types";
6
+ import type { DockerServicePreset } from "./define-docker-service";
7
+
8
+ export type {
9
+ DockerServicePreset,
10
+ DockerServicePresetDefaults,
11
+ PresetServiceSharedOptions,
12
+ } from "./define-docker-service";
13
+
14
+ import {
15
+ type ClickhouseServiceOptions,
16
+ clickhouseDockerService,
17
+ } from "./clickhouse";
18
+ import { type PostgresServiceOptions, postgresDockerService } from "./postgres";
19
+ import { type RedisServiceOptions, redisDockerService } from "./redis";
20
+
21
+ const PRESET_SERVICES = {
22
+ postgres: postgresDockerService,
23
+ redis: redisDockerService,
24
+ clickhouse: clickhouseDockerService,
25
+ } satisfies Record<DockerPresetName, DockerServicePreset>;
26
+
27
+ export { clickhouseDockerService, postgresDockerService, redisDockerService };
28
+ export type {
29
+ ClickhouseServiceOptions,
30
+ PostgresServiceOptions,
31
+ RedisServiceOptions,
32
+ };
33
+
34
+ export type CustomServiceOptions = ServiceConfig & {
35
+ docker: DockerComposeServiceRaw;
36
+ };
37
+
38
+ /**
39
+ * Public service builders for dev.config.ts.
40
+ * Core owns this surface so defaults and preset mapping live in one place.
41
+ */
42
+ export const service = {
43
+ postgres: postgresDockerService.toServiceConfig,
44
+ redis: redisDockerService.toServiceConfig,
45
+ clickhouse: clickhouseDockerService.toServiceConfig,
46
+
47
+ custom(options: CustomServiceOptions): ServiceConfig {
48
+ return options;
49
+ },
50
+ };
51
+
52
+ export function inferDockerPreset(
53
+ serviceKey: string,
54
+ ): DockerPresetName | undefined {
55
+ const normalized = serviceKey.toLowerCase();
56
+ if (Object.hasOwn(PRESET_SERVICES, normalized)) {
57
+ return normalized as DockerPresetName;
58
+ }
59
+ return undefined;
60
+ }
61
+
62
+ export function buildPresetDockerService(
63
+ preset: DockerPresetName,
64
+ input: Parameters<DockerServicePreset["build"]>[0],
65
+ ): ReturnType<DockerServicePreset["build"]> {
66
+ return PRESET_SERVICES[preset].build(input);
67
+ }
@@ -0,0 +1,60 @@
1
+ import type {
2
+ BuiltInHealthCheck,
3
+ DockerComposeHealthcheckRaw,
4
+ DockerComposeServiceRaw,
5
+ } from "../../types";
6
+ import { defineDockerService } from "./define-docker-service";
7
+ import { getDefaultPortBindings, resolveHealthcheck } from "./shared";
8
+
9
+ export type PostgresServiceOptions = {
10
+ port?: number;
11
+ expose?: boolean;
12
+ healthCheck?: BuiltInHealthCheck | false;
13
+ serviceName?: string;
14
+ database?: string;
15
+ user?: string;
16
+ password?: string;
17
+ docker?: DockerComposeServiceRaw;
18
+ };
19
+
20
+ export const postgresDockerService =
21
+ defineDockerService<PostgresServiceOptions>({
22
+ preset: "postgres",
23
+ defaults: {
24
+ port: 5432,
25
+ healthCheck: "pg_isready",
26
+ },
27
+ build: ({ serviceKey, config }) => {
28
+ const user = config.user ?? "postgres";
29
+ const password = config.password ?? "postgres";
30
+ const database = config.database ?? "postgres";
31
+ const defaultHealthcheck: DockerComposeHealthcheckRaw = {
32
+ test: ["CMD-SHELL", `pg_isready -U ${user}`],
33
+ interval: "250ms",
34
+ timeout: "5s",
35
+ retries: 20,
36
+ };
37
+
38
+ return {
39
+ service: {
40
+ image: "pgvector/pgvector:pg16",
41
+ ports: getDefaultPortBindings(serviceKey, config, "postgres"),
42
+ volumes: [`${serviceKey}_data:/var/lib/postgresql/data`],
43
+ environment: {
44
+ POSTGRES_USER: user,
45
+ POSTGRES_PASSWORD: password,
46
+ POSTGRES_DB: database,
47
+ },
48
+ healthcheck: resolveHealthcheck(
49
+ config.healthCheck,
50
+ defaultHealthcheck,
51
+ {
52
+ internalPort: 5432,
53
+ user,
54
+ },
55
+ ),
56
+ },
57
+ volume: `${serviceKey}_data`,
58
+ };
59
+ },
60
+ });
@@ -0,0 +1,48 @@
1
+ import type {
2
+ BuiltInHealthCheck,
3
+ DockerComposeHealthcheckRaw,
4
+ DockerComposeServiceRaw,
5
+ } from "../../types";
6
+ import { defineDockerService } from "./define-docker-service";
7
+ import { getDefaultPortBindings, resolveHealthcheck } from "./shared";
8
+
9
+ export type RedisServiceOptions = {
10
+ port?: number;
11
+ expose?: boolean;
12
+ healthCheck?: BuiltInHealthCheck | false;
13
+ serviceName?: string;
14
+ database?: string;
15
+ user?: string;
16
+ password?: string;
17
+ docker?: DockerComposeServiceRaw;
18
+ };
19
+
20
+ export const redisDockerService = defineDockerService<RedisServiceOptions>({
21
+ preset: "redis",
22
+ defaults: {
23
+ port: 6379,
24
+ healthCheck: "redis-cli",
25
+ },
26
+ build: ({ serviceKey, config }) => {
27
+ const defaultHealthcheck: DockerComposeHealthcheckRaw = {
28
+ test: ["CMD", "redis-cli", "ping"],
29
+ interval: "250ms",
30
+ timeout: "5s",
31
+ retries: 20,
32
+ };
33
+
34
+ return {
35
+ service: {
36
+ image: "redis:7-alpine",
37
+ ports: getDefaultPortBindings(serviceKey, config, "redis"),
38
+ healthcheck: resolveHealthcheck(
39
+ config.healthCheck,
40
+ defaultHealthcheck,
41
+ {
42
+ internalPort: 6379,
43
+ },
44
+ ),
45
+ },
46
+ };
47
+ },
48
+ });
@@ -0,0 +1,79 @@
1
+ import type {
2
+ DockerComposeHealthcheckRaw,
3
+ DockerPresetName,
4
+ ServiceConfig,
5
+ } from "../../types";
6
+
7
+ const DEFAULT_HEALTHCHECK_SETTINGS = {
8
+ interval: "250ms",
9
+ timeout: "5s",
10
+ retries: 20,
11
+ } as const;
12
+
13
+ export function getPortEnvName(portKey: string): string {
14
+ return `${portKey.toUpperCase()}_PORT`;
15
+ }
16
+
17
+ export function getDefaultPortBindings(
18
+ serviceKey: string,
19
+ config: ServiceConfig,
20
+ preset?: DockerPresetName,
21
+ ): string[] {
22
+ const envName = getPortEnvName(serviceKey);
23
+ const bindings: string[] = [];
24
+
25
+ const defaultInternalPort =
26
+ preset === "postgres"
27
+ ? 5432
28
+ : preset === "redis"
29
+ ? 6379
30
+ : preset === "clickhouse"
31
+ ? 8123
32
+ : config.port;
33
+
34
+ bindings.push(`\${${envName}:-${config.port}}:${defaultInternalPort}`);
35
+
36
+ if (config.secondaryPort !== undefined) {
37
+ const secondaryEnv = getPortEnvName(`${serviceKey}Secondary`);
38
+ const secondaryInternal =
39
+ preset === "clickhouse" ? 9000 : config.secondaryPort;
40
+ bindings.push(
41
+ `\${${secondaryEnv}:-${config.secondaryPort}}:${secondaryInternal}`,
42
+ );
43
+ }
44
+
45
+ return bindings;
46
+ }
47
+
48
+ export function resolveHealthcheck(
49
+ healthCheck: ServiceConfig["healthCheck"] | undefined,
50
+ fallback: DockerComposeHealthcheckRaw | undefined,
51
+ options: { internalPort: number; user?: string },
52
+ ): DockerComposeHealthcheckRaw | undefined {
53
+ if (healthCheck === false) return undefined;
54
+ if (typeof healthCheck === "function") return fallback;
55
+ if (!healthCheck) return fallback;
56
+
57
+ switch (healthCheck) {
58
+ case "pg_isready":
59
+ return {
60
+ test: ["CMD-SHELL", `pg_isready -U ${options.user ?? "postgres"}`],
61
+ ...DEFAULT_HEALTHCHECK_SETTINGS,
62
+ };
63
+ case "redis-cli":
64
+ return {
65
+ test: ["CMD", "redis-cli", "ping"],
66
+ ...DEFAULT_HEALTHCHECK_SETTINGS,
67
+ };
68
+ case "http":
69
+ return {
70
+ test: [
71
+ "CMD-SHELL",
72
+ `wget -qO- http://127.0.0.1:${options.internalPort}/ping || exit 1`,
73
+ ],
74
+ ...DEFAULT_HEALTHCHECK_SETTINGS,
75
+ };
76
+ default:
77
+ return fallback;
78
+ }
79
+ }
@@ -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,34 +1,38 @@
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 { isCI as isCIEnv, logExpoApiUrl, logFrontendPort } from "../core/utils";
22
16
  import {
23
17
  spawnWatchdog as spawnWatchdogFn,
24
18
  startHeartbeat as startHeartbeatFn,
25
19
  stopHeartbeat as stopHeartbeatFn,
26
20
  stopWatchdog as stopWatchdogFn,
27
- } from "./core/watchdog";
28
- import { createPrismaRunner } from "./prisma";
21
+ } from "../core/watchdog";
22
+ import {
23
+ areContainersRunning,
24
+ startContainers,
25
+ stopContainers,
26
+ } from "../docker/runtime";
27
+ import {
28
+ getGeneratedComposePath,
29
+ writeGeneratedComposeFile,
30
+ } from "../docker-compose";
31
+ import { createPrismaRunner } from "../prisma";
29
32
  import type {
30
33
  AppConfig,
31
34
  ComputedPorts,
35
+ ComputedPublicUrls,
32
36
  ComputedUrls,
33
37
  DevConfig,
34
38
  DevEnvironment,
@@ -39,34 +43,9 @@ import type {
39
43
  ServiceConfig,
40
44
  StartOptions,
41
45
  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
- }
46
+ } from "../types";
47
+ import { logEnvironmentInfo } from "./logging";
48
+ import { createCheckTableHelper, createSeedCheckContext } from "./seeding";
70
49
 
71
50
  // ═══════════════════════════════════════════════════════════════════════════
72
51
  // Environment Factory
@@ -115,6 +94,14 @@ export function createDevEnvironment<
115
94
 
116
95
  const services = config.services;
117
96
  const apps = (config.apps ?? {}) as TApps;
97
+ const composeFile = getGeneratedComposePath(
98
+ root,
99
+ config.docker,
100
+ ).composeFileArg;
101
+
102
+ function ensureComposeFile(): string {
103
+ return writeGeneratedComposeFile(root, services, config.docker);
104
+ }
118
105
 
119
106
  // Compute ports and URLs
120
107
  const ports = computePorts(services, apps, portOffset) as ComputedPorts<
@@ -125,6 +112,22 @@ export function createDevEnvironment<
125
112
  TServices,
126
113
  TApps
127
114
  >;
115
+ const publicUrls: Record<string, string> = {};
116
+
117
+ function setPublicUrls(urlsInput: Record<string, string>): void {
118
+ for (const key of Object.keys(publicUrls)) {
119
+ delete publicUrls[key];
120
+ }
121
+ for (const [key, value] of Object.entries(urlsInput)) {
122
+ publicUrls[key] = value;
123
+ }
124
+ }
125
+
126
+ function clearPublicUrls(): void {
127
+ for (const key of Object.keys(publicUrls)) {
128
+ delete publicUrls[key];
129
+ }
130
+ }
128
131
 
129
132
  // Build environment variables
130
133
  function buildEnvVars(production = false): Record<string, string> {
@@ -145,12 +148,19 @@ export function createDevEnvironment<
145
148
  baseEnv[envName] = url;
146
149
  }
147
150
 
151
+ // Add public URL environment variables when tunnels are active
152
+ for (const [name, url] of Object.entries(publicUrls)) {
153
+ const envName = `${name.toUpperCase()}_PUBLIC_URL`;
154
+ baseEnv[envName] = url;
155
+ }
156
+
148
157
  // Call user's envVars function if provided
149
158
  if (config.envVars) {
150
159
  const userEnv = config.envVars(ports, urls, {
151
160
  projectName,
152
161
  localIp,
153
162
  portOffset,
163
+ publicUrls: publicUrls as ComputedPublicUrls<TServices, TApps>,
154
164
  });
155
165
  for (const [key, value] of Object.entries(userEnv)) {
156
166
  baseEnv[key] = String(value);
@@ -169,6 +179,7 @@ export function createDevEnvironment<
169
179
  projectName,
170
180
  ports,
171
181
  urls,
182
+ publicUrls: publicUrls as ComputedPublicUrls<TServices, TApps>,
172
183
  root,
173
184
  isCI: isCIEnv(),
174
185
  portOffset,
@@ -205,6 +216,7 @@ export function createDevEnvironment<
205
216
  } = startOptions;
206
217
 
207
218
  const envVars = buildEnvVars(productionBuild);
219
+ ensureComposeFile();
208
220
 
209
221
  // Log environment info
210
222
  if (verbose) {
@@ -224,7 +236,7 @@ export function createDevEnvironment<
224
236
  startContainers(root, projectName, envVars, {
225
237
  verbose,
226
238
  wait,
227
- composeFile: config.options?.composeFile,
239
+ composeFile,
228
240
  });
229
241
  }
230
242
 
@@ -258,19 +270,19 @@ export function createDevEnvironment<
258
270
  }),
259
271
  );
260
272
 
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);
273
+ // Check for failures
274
+ for (const { name, result } of migrationResults) {
275
+ if (result.exitCode !== 0) {
276
+ console.error(`❌ Migration "${name}" failed`);
277
+ if (result.stdout) {
278
+ console.error(result.stdout);
279
+ }
280
+ if (result.stderr) {
281
+ console.error(result.stderr);
282
+ }
283
+ throw new Error(`Migration "${name}" failed`);
270
284
  }
271
- throw new Error(`Migration "${name}" failed`);
272
285
  }
273
- }
274
286
 
275
287
  if (verbose) console.log("✓ Migrations complete");
276
288
  }
@@ -286,35 +298,14 @@ export function createDevEnvironment<
286
298
 
287
299
  // Check if seeding is needed using check function
288
300
  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 },
301
+ const checkTable = createCheckTableHelper<TServices, TApps>(
302
+ urls as Record<string, string>,
303
+ exec,
303
304
  );
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(),
305
+ const seedCheckContext = createSeedCheckContext(
306
+ getHookContext(),
315
307
  checkTable,
316
- };
317
-
308
+ );
318
309
  shouldSeed = await config.seed.check(seedCheckContext);
319
310
  }
320
311
 
@@ -380,6 +371,7 @@ export function createDevEnvironment<
380
371
 
381
372
  async function stop(stopOptions: StopOptions = {}): Promise<void> {
382
373
  const { verbose = true, removeVolumes = false } = stopOptions;
374
+ ensureComposeFile();
383
375
 
384
376
  // Run beforeStop hook
385
377
  if (config.hooks?.beforeStop) {
@@ -389,7 +381,7 @@ export function createDevEnvironment<
389
381
  stopContainers(root, projectName, {
390
382
  verbose,
391
383
  removeVolumes,
392
- composeFile: config.options?.composeFile,
384
+ composeFile,
393
385
  });
394
386
  }
395
387
 
@@ -438,54 +430,17 @@ export function createDevEnvironment<
438
430
  // ─────────────────────────────────────────────────────────────────────────
439
431
 
440
432
  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
- }
457
- }
458
-
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
- }
472
- }
473
-
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
- ),
483
- );
484
- if (projectSuffix) {
485
- console.log(formatDimLabel("Suffix:", projectSuffix));
486
- }
487
- console.log(formatDimLabel("Local IP:", localIp));
488
- console.log("");
433
+ logEnvironmentInfo({
434
+ label,
435
+ projectName,
436
+ services,
437
+ apps,
438
+ ports: ports as Record<string, number>,
439
+ localIp,
440
+ worktree,
441
+ portOffset,
442
+ projectSuffix,
443
+ });
489
444
  }
490
445
 
491
446
  async function waitForServerUrl(
@@ -511,7 +466,7 @@ export function createDevEnvironment<
511
466
  await spawnWatchdogFn(projectName, root, {
512
467
  timeoutMinutes,
513
468
  verbose: true,
514
- composeFile: config.options?.composeFile,
469
+ composeFile,
515
470
  });
516
471
  }
517
472
 
@@ -554,11 +509,14 @@ export function createDevEnvironment<
554
509
  projectName,
555
510
  ports,
556
511
  urls,
512
+ publicUrls: publicUrls as ComputedPublicUrls<TServices, TApps>,
513
+ services,
557
514
  apps,
558
515
  portOffset,
559
516
  isWorktree: worktree,
560
517
  localIp,
561
518
  root,
519
+ composeFile,
562
520
 
563
521
  // Container management
564
522
  start,
@@ -573,6 +531,11 @@ export function createDevEnvironment<
573
531
 
574
532
  // Utilities
575
533
  buildEnvVars,
534
+ setPublicUrls: (urlsInput) => {
535
+ setPublicUrls(urlsInput as Record<string, string>);
536
+ },
537
+ clearPublicUrls,
538
+ ensureComposeFile,
576
539
  exec,
577
540
  waitForServer: waitForServerUrl,
578
541
  logInfo,