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,197 @@
1
+ import type {
2
+ DockerComposeGenerationOptions,
3
+ DockerComposeNode,
4
+ DockerComposeServiceRaw,
5
+ DockerComposeVolumeRaw,
6
+ DockerPresetName,
7
+ DockerPresetServiceDefinition,
8
+ DockerServiceDefinition,
9
+ ServiceConfig,
10
+ } from "../types";
11
+ import { buildPresetDockerService, inferDockerPreset } from "./services";
12
+ import { getDefaultPortBindings } from "./services/shared";
13
+
14
+ export type ComposeDocument = {
15
+ services: Record<string, DockerComposeServiceRaw>;
16
+ volumes?: Record<string, DockerComposeVolumeRaw>;
17
+ };
18
+
19
+ function isObject(
20
+ value: DockerComposeNode,
21
+ ): value is Record<string, DockerComposeNode | undefined> {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+
25
+ function deepMergeNode(
26
+ base: DockerComposeNode,
27
+ override: DockerComposeNode,
28
+ ): DockerComposeNode {
29
+ if (Array.isArray(base) || Array.isArray(override)) {
30
+ return override;
31
+ }
32
+ if (!isObject(base) || !isObject(override)) {
33
+ return override;
34
+ }
35
+
36
+ const merged: Record<string, DockerComposeNode | undefined> = { ...base };
37
+ for (const key of Object.keys(override)) {
38
+ const baseValue = merged[key];
39
+ const overrideValue = override[key];
40
+ if (baseValue === undefined || overrideValue === undefined) {
41
+ merged[key] = overrideValue;
42
+ } else {
43
+ merged[key] = deepMergeNode(baseValue, overrideValue);
44
+ }
45
+ }
46
+ return merged;
47
+ }
48
+
49
+ function isPresetDefinition(
50
+ value: DockerServiceDefinition | undefined,
51
+ ): value is DockerPresetServiceDefinition {
52
+ return Boolean(
53
+ value &&
54
+ typeof value === "object" &&
55
+ "kind" in value &&
56
+ (value as { kind?: string }).kind === "preset",
57
+ );
58
+ }
59
+
60
+ function normalizeRawService(
61
+ name: string,
62
+ config: ServiceConfig,
63
+ service: DockerComposeServiceRaw,
64
+ ): DockerComposeServiceRaw {
65
+ const normalized = { ...service };
66
+ if (!normalized.ports || normalized.ports.length === 0) {
67
+ normalized.ports = getDefaultPortBindings(name, config);
68
+ }
69
+ if (config.healthCheck === false) {
70
+ delete normalized.healthcheck;
71
+ }
72
+ return normalized;
73
+ }
74
+
75
+ type NormalizedServiceConfig =
76
+ | {
77
+ kind: "preset";
78
+ serviceName: string;
79
+ preset: DockerPresetName;
80
+ serviceOverride?: DockerComposeServiceRaw;
81
+ }
82
+ | {
83
+ kind: "raw";
84
+ serviceName: string;
85
+ service: DockerComposeServiceRaw;
86
+ };
87
+
88
+ function normalizeServiceConfig(
89
+ name: string,
90
+ config: ServiceConfig,
91
+ ): NormalizedServiceConfig {
92
+ const serviceName = config.serviceName ?? name;
93
+ const rawDefinition = config.docker;
94
+
95
+ if (isPresetDefinition(rawDefinition)) {
96
+ return {
97
+ kind: "preset",
98
+ serviceName,
99
+ preset: rawDefinition.preset,
100
+ serviceOverride: rawDefinition.service,
101
+ };
102
+ }
103
+
104
+ if (rawDefinition) {
105
+ const inferredPreset = inferDockerPreset(name);
106
+ if (inferredPreset) {
107
+ return {
108
+ kind: "preset",
109
+ serviceName,
110
+ preset: inferredPreset,
111
+ serviceOverride: rawDefinition,
112
+ };
113
+ }
114
+ return {
115
+ kind: "raw",
116
+ serviceName,
117
+ service: normalizeRawService(name, config, rawDefinition),
118
+ };
119
+ }
120
+
121
+ const preset = inferDockerPreset(name);
122
+ if (!preset) {
123
+ throw new Error(
124
+ `Service "${name}" has no docker preset and no docker definition. Add service.docker using helper or raw mode.`,
125
+ );
126
+ }
127
+
128
+ return {
129
+ kind: "preset",
130
+ serviceName,
131
+ preset,
132
+ };
133
+ }
134
+
135
+ function resolveServiceDefinition(
136
+ name: string,
137
+ config: ServiceConfig,
138
+ ): {
139
+ serviceName: string;
140
+ service: DockerComposeServiceRaw;
141
+ volume?: string;
142
+ } {
143
+ const normalized = normalizeServiceConfig(name, config);
144
+ if (normalized.kind === "raw") {
145
+ return {
146
+ serviceName: normalized.serviceName,
147
+ service: normalized.service,
148
+ };
149
+ }
150
+
151
+ const { service, volume } = buildPresetDockerService(normalized.preset, {
152
+ serviceKey: name,
153
+ config,
154
+ });
155
+ const mergedService = normalized.serviceOverride
156
+ ? (deepMergeNode(
157
+ service as DockerComposeNode,
158
+ normalized.serviceOverride as DockerComposeNode,
159
+ ) as DockerComposeServiceRaw)
160
+ : service;
161
+ return {
162
+ serviceName: normalized.serviceName,
163
+ service: mergedService,
164
+ volume,
165
+ };
166
+ }
167
+
168
+ export function buildComposeModel(
169
+ services: Record<string, ServiceConfig>,
170
+ docker?: DockerComposeGenerationOptions,
171
+ ): ComposeDocument {
172
+ const composeServices: Record<string, DockerComposeServiceRaw> = {};
173
+ const composeVolumes: Record<string, DockerComposeVolumeRaw> = {};
174
+
175
+ for (const [name, serviceConfig] of Object.entries(services)) {
176
+ const { serviceName, service, volume } = resolveServiceDefinition(
177
+ name,
178
+ serviceConfig,
179
+ );
180
+ composeServices[serviceName] = service;
181
+ if (volume) {
182
+ composeVolumes[volume] = {};
183
+ }
184
+ }
185
+
186
+ for (const [volumeName, volume] of Object.entries(docker?.volumes ?? {})) {
187
+ composeVolumes[volumeName] = volume;
188
+ }
189
+
190
+ const document: ComposeDocument = {
191
+ services: composeServices,
192
+ };
193
+ if (Object.keys(composeVolumes).length > 0) {
194
+ document.volumes = composeVolumes;
195
+ }
196
+ return document;
197
+ }
@@ -0,0 +1,79 @@
1
+ import type {
2
+ BuiltInHealthCheck,
3
+ DockerComposeHealthcheckRaw,
4
+ DockerComposeServiceRaw,
5
+ ServiceConfig,
6
+ } from "../../types";
7
+ import { defineDockerService } from "./define-docker-service";
8
+ import { getDefaultPortBindings, resolveHealthcheck } from "./shared";
9
+
10
+ export type ClickhouseServiceOptions = {
11
+ port?: number;
12
+ secondaryPort?: number;
13
+ expose?: boolean;
14
+ healthCheck?: BuiltInHealthCheck | false;
15
+ serviceName?: string;
16
+ database?: string;
17
+ user?: string;
18
+ password?: string;
19
+ docker?: DockerComposeServiceRaw;
20
+ };
21
+
22
+ export type ClickhouseServiceConfig = ServiceConfig & {
23
+ secondaryPort: number;
24
+ };
25
+
26
+ export const clickhouseDockerService = defineDockerService<
27
+ ClickhouseServiceOptions,
28
+ ClickhouseServiceConfig
29
+ >({
30
+ preset: "clickhouse",
31
+ defaults: {
32
+ port: 8123,
33
+ secondaryPort: 9000,
34
+ healthCheck: "http",
35
+ },
36
+ enhanceServiceConfig: (base, options): ClickhouseServiceConfig => ({
37
+ ...base,
38
+ secondaryPort: options.secondaryPort ?? 9000,
39
+ }),
40
+ build: ({ serviceKey, config }) => {
41
+ const user = config.user ?? "default";
42
+ const password = config.password ?? "clickhouse";
43
+ const database = config.database ?? "default";
44
+ const defaultHealthcheck: DockerComposeHealthcheckRaw = {
45
+ test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8123/ping || exit 1"],
46
+ interval: "250ms",
47
+ timeout: "5s",
48
+ retries: 20,
49
+ };
50
+
51
+ return {
52
+ service: {
53
+ image: "clickhouse/clickhouse-server:24-alpine",
54
+ ports: getDefaultPortBindings(serviceKey, config, "clickhouse"),
55
+ volumes: [`${serviceKey}_data:/var/lib/clickhouse`],
56
+ environment: {
57
+ CLICKHOUSE_USER: user,
58
+ CLICKHOUSE_PASSWORD: password,
59
+ CLICKHOUSE_DB: database,
60
+ },
61
+ ulimits: {
62
+ nofile: {
63
+ soft: 262144,
64
+ hard: 262144,
65
+ },
66
+ },
67
+ healthcheck: resolveHealthcheck(
68
+ config.healthCheck,
69
+ defaultHealthcheck,
70
+ {
71
+ internalPort: 8123,
72
+ user,
73
+ },
74
+ ),
75
+ },
76
+ volume: `${serviceKey}_data`,
77
+ };
78
+ },
79
+ });
@@ -0,0 +1,109 @@
1
+ import type {
2
+ BuiltInHealthCheck,
3
+ DockerComposeServiceRaw,
4
+ DockerPresetName,
5
+ DockerPresetServiceDefinition,
6
+ ServiceConfig,
7
+ } from "../../types";
8
+
9
+ export interface DockerServiceFactoryInput {
10
+ serviceKey: string;
11
+ config: ServiceConfig;
12
+ }
13
+
14
+ export interface DockerServiceFactoryOutput {
15
+ service: DockerComposeServiceRaw;
16
+ volume?: string;
17
+ }
18
+
19
+ export type DockerServiceFactory = (
20
+ input: DockerServiceFactoryInput,
21
+ ) => DockerServiceFactoryOutput;
22
+
23
+ export type PresetServiceSharedOptions = Pick<
24
+ ServiceConfig,
25
+ "serviceName" | "database" | "user" | "password" | "expose"
26
+ > & {
27
+ port?: number;
28
+ healthCheck?: BuiltInHealthCheck | false;
29
+ docker?: DockerComposeServiceRaw;
30
+ };
31
+
32
+ export interface DockerServicePresetDefaults {
33
+ port: number;
34
+ healthCheck: BuiltInHealthCheck;
35
+ secondaryPort?: number;
36
+ }
37
+
38
+ export interface DockerServicePreset<
39
+ TOptions extends PresetServiceSharedOptions = PresetServiceSharedOptions,
40
+ TServiceConfig extends ServiceConfig = ServiceConfig,
41
+ > {
42
+ preset: DockerPresetName;
43
+ defaults: DockerServicePresetDefaults;
44
+ build: DockerServiceFactory;
45
+ createPresetDefinition(
46
+ service?: DockerComposeServiceRaw,
47
+ ): DockerPresetServiceDefinition;
48
+ toServiceConfig(options?: TOptions): TServiceConfig;
49
+ }
50
+
51
+ interface DefineDockerServiceInput<
52
+ TOptions extends PresetServiceSharedOptions = PresetServiceSharedOptions,
53
+ TServiceConfig extends ServiceConfig = ServiceConfig,
54
+ > {
55
+ preset: DockerPresetName;
56
+ defaults: DockerServicePresetDefaults;
57
+ build: DockerServiceFactory;
58
+ enhanceServiceConfig?: (
59
+ base: ServiceConfig,
60
+ options: TOptions,
61
+ ) => TServiceConfig;
62
+ }
63
+
64
+ /**
65
+ * Define a docker service preset as single source of truth.
66
+ * The same definition powers:
67
+ * - compose generation (`build`)
68
+ * - typed config helper defaults (`toServiceConfig`)
69
+ */
70
+ export function defineDockerService<
71
+ TOptions extends PresetServiceSharedOptions = PresetServiceSharedOptions,
72
+ TServiceConfig extends ServiceConfig = ServiceConfig,
73
+ >(
74
+ input: DefineDockerServiceInput<TOptions, TServiceConfig>,
75
+ ): DockerServicePreset<TOptions, TServiceConfig> {
76
+ function createPresetDefinition(
77
+ service?: DockerComposeServiceRaw,
78
+ ): DockerPresetServiceDefinition {
79
+ return {
80
+ kind: "preset",
81
+ preset: input.preset,
82
+ service,
83
+ };
84
+ }
85
+
86
+ function toServiceConfig(options = {} as TOptions): TServiceConfig {
87
+ const base: ServiceConfig = {
88
+ port: options.port ?? input.defaults.port,
89
+ expose: options.expose,
90
+ healthCheck: options.healthCheck ?? input.defaults.healthCheck,
91
+ database: options.database,
92
+ user: options.user,
93
+ password: options.password,
94
+ serviceName: options.serviceName,
95
+ docker: createPresetDefinition(options.docker),
96
+ };
97
+ return input.enhanceServiceConfig
98
+ ? input.enhanceServiceConfig(base, options)
99
+ : (base as TServiceConfig);
100
+ }
101
+
102
+ return {
103
+ preset: input.preset,
104
+ defaults: input.defaults,
105
+ build: input.build,
106
+ createPresetDefinition,
107
+ toServiceConfig,
108
+ };
109
+ }
@@ -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
+ }