buncargo 1.0.26 → 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.
- package/dist/bin.d.ts +1 -12
- package/dist/bin.js +261 -252
- package/dist/cli/bin.d.ts +13 -0
- package/dist/cli/bin.js +315 -0
- package/dist/cli/commands/help.d.ts +1 -0
- package/dist/cli/commands/runtime.d.ts +5 -0
- package/dist/cli/commands/version.d.ts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/run-cli.d.ts +22 -0
- package/dist/cli.d.ts +1 -22
- package/dist/cli.js +5 -13
- package/dist/config/config.d.ts +1 -0
- package/dist/config/define-config.d.ts +13 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +15 -0
- package/dist/config/merge-configs.d.ts +3 -0
- package/dist/config/validate-config.d.ts +3 -0
- package/dist/config.d.ts +1 -72
- package/dist/config.js +12 -12
- package/dist/core/docker.d.ts +1 -74
- package/dist/core/docker.js +35 -26
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +123 -108
- package/dist/core/network.js +2 -2
- package/dist/core/ports.d.ts +22 -0
- package/dist/core/ports.js +5 -1
- package/dist/core/process.js +1 -1
- package/dist/core/tunnel.d.ts +33 -0
- package/dist/core/utils.js +2 -2
- package/dist/core/watchdog-runner.js +45 -42
- package/dist/core/watchdog.d.ts +1 -0
- package/dist/core/watchdog.js +4 -2
- package/dist/docker/index.d.ts +1 -0
- package/dist/docker/index.js +38 -0
- package/dist/docker/runtime.d.ts +87 -0
- package/dist/docker/runtime.js +37 -0
- package/dist/docker-compose/compose.d.ts +1 -0
- package/dist/docker-compose/generated-file.d.ts +7 -0
- package/dist/docker-compose/index.d.ts +3 -0
- package/dist/docker-compose/index.js +15 -0
- package/dist/docker-compose/model.d.ts +6 -0
- package/dist/docker-compose/services/clickhouse.d.ts +16 -0
- package/dist/docker-compose/services/define-docker-service.d.ts +41 -0
- package/dist/docker-compose/services/index.d.ts +23 -0
- package/dist/docker-compose/services/index.js +17 -0
- package/dist/docker-compose/services/postgres.d.ts +12 -0
- package/dist/docker-compose/services/redis.d.ts +12 -0
- package/dist/docker-compose/services/shared.d.ts +7 -0
- package/dist/docker-compose/yaml.d.ts +2 -0
- package/dist/environment/create-dev-environment.d.ts +23 -0
- package/dist/environment/index.d.ts +1 -0
- package/dist/environment/index.js +15 -0
- package/dist/environment/logging.d.ts +17 -0
- package/dist/environment/seeding.d.ts +9 -0
- package/dist/environment.d.ts +1 -23
- package/dist/environment.js +12 -14
- package/dist/index-045jksh5.js +147 -0
- package/dist/index-08wa79cs.js +125 -117
- package/dist/index-0kxnae3z.js +335 -0
- package/dist/index-1mdrf7nz.js +66 -0
- package/dist/index-1yvbwj4k.js +262 -242
- package/dist/index-23ev345g.js +475 -0
- package/dist/index-2ckr49sf.js +228 -0
- package/dist/index-2f47khe5.js +376 -369
- package/dist/index-2fr3g85b.js +220 -183
- package/dist/index-38xnzpa6.js +450 -0
- package/dist/index-3h3dhtf2.js +51 -43
- package/dist/index-42x95209.js +51 -43
- package/dist/index-4gp0az1g.js +145 -0
- package/dist/index-4xrxh8yv.js +72 -0
- package/dist/index-5gmws6ah.js +181 -0
- package/dist/index-5hka0tff.js +78 -76
- package/dist/index-5rfqps4b.js +3 -0
- package/dist/index-5t9jxqm0.js +428 -0
- package/dist/index-6c1w1xk5.js +101 -0
- package/dist/index-6fm7mvwj.js +118 -97
- package/dist/index-6srpc523.js +127 -128
- package/dist/index-731rzzfp.js +187 -0
- package/dist/index-75y4cg2z.js +51 -43
- package/dist/index-7ja4ywyj.js +126 -127
- package/dist/index-8bw1cmz4.js +531 -0
- package/dist/index-8hbbj1mp.js +120 -121
- package/dist/index-8xj2p5n5.js +145 -0
- package/dist/index-bj79tw5w.js +0 -0
- package/dist/index-bnk6nr0g.js +73 -0
- package/dist/index-brbbzyks.js +72 -0
- package/dist/index-c0dr6mcv.js +123 -0
- package/dist/index-cty0bcry.js +235 -218
- package/dist/index-d8tyv5se.js +228 -0
- package/dist/index-d9efy0n4.js +176 -150
- package/dist/index-etfmqjjf.js +427 -0
- package/dist/index-fb29934k.js +172 -0
- package/dist/index-g50jw1yf.js +72 -0
- package/dist/index-g6eb5wdw.js +118 -117
- package/dist/index-ggq3yryx.js +99 -95
- package/dist/index-h70tce00.js +177 -0
- package/dist/index-hkxtfqtc.js +333 -0
- package/dist/index-kf3dhser.js +146 -143
- package/dist/index-ma6tgdb2.js +500 -0
- package/dist/index-mam0bcyz.js +123 -0
- package/dist/index-mm412dkp.js +274 -0
- package/dist/index-n8v18aeb.js +0 -0
- package/dist/index-ndnmnsej.js +378 -371
- package/dist/index-p8wty0e2.js +389 -379
- package/dist/index-qfphr2fd.js +100 -0
- package/dist/index-qqmms8rs.js +51 -43
- package/dist/index-qw4093g2.js +51 -43
- package/dist/index-qzwpzjbx.js +121 -122
- package/dist/index-segbnm0h.js +146 -143
- package/dist/index-t0fj6gg1.js +112 -0
- package/dist/index-thdkwnv7.js +122 -0
- package/dist/index-tjbx2r2t.js +270 -0
- package/dist/index-tjqw9vtj.js +62 -54
- package/dist/index-vbpb89jy.js +248 -0
- package/dist/index-vhs88xhe.js +99 -95
- package/dist/index-w8zxnjka.js +249 -0
- package/dist/index-wk2na3t9.js +404 -0
- package/dist/index-wz9x8g7z.js +383 -373
- package/dist/index-x249gyde.js +388 -378
- package/dist/index-xkvd0nsd.js +187 -0
- package/dist/index-yedqxm1z.js +80 -0
- package/dist/index-zfjzzjkf.js +266 -0
- package/dist/index.d.ts +12 -8
- package/dist/index.js +66 -35
- package/dist/lint.d.ts +1 -46
- package/dist/lint.js +3 -7
- package/dist/loader/cache.d.ts +4 -0
- package/dist/loader/find-config-file.d.ts +2 -0
- package/dist/loader/index.d.ts +5 -0
- package/dist/loader/index.js +24 -0
- package/dist/loader/load-dev-env.d.ts +5 -0
- package/dist/loader/loader.d.ts +1 -0
- package/dist/loader.d.ts +1 -45
- package/dist/loader.js +22 -20
- package/dist/prisma/index.d.ts +1 -0
- package/dist/prisma/prisma.d.ts +29 -0
- package/dist/prisma.d.ts +1 -29
- package/dist/prisma.js +6 -10
- package/dist/src/bin.js +309 -0
- package/dist/src/cli.js +5 -0
- package/dist/src/config.js +15 -0
- package/dist/src/core/docker.js +38 -0
- package/dist/src/core/index.js +130 -0
- package/dist/src/core/network.js +9 -0
- package/dist/src/core/ports.js +23 -0
- package/dist/src/core/process.js +31 -0
- package/dist/src/core/utils.js +11 -0
- package/dist/src/core/watchdog-runner.js +69 -0
- package/dist/src/core/watchdog.js +28 -0
- package/dist/src/docker/runtime.js +37 -0
- package/dist/src/docker-compose/index.js +16 -0
- package/dist/src/docker-compose/services/index.js +17 -0
- package/dist/src/environment.js +12 -0
- package/dist/src/index.js +122 -0
- package/dist/src/lint.js +3 -0
- package/dist/src/loader.js +25 -0
- package/dist/src/prisma.js +6 -0
- package/dist/src/types.js +0 -0
- package/dist/typecheck/index.d.ts +1 -0
- package/dist/typecheck/index.js +7 -0
- package/dist/typecheck/typecheck.d.ts +46 -0
- package/dist/types/all-types.d.ts +501 -0
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/config.d.ts +6 -0
- package/dist/types/docker.d.ts +15 -0
- package/dist/types/environment.d.ts +8 -0
- package/dist/types/hooks.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +0 -0
- package/dist/types/prisma.d.ts +1 -0
- package/dist/types.d.ts +1 -393
- package/package.json +145 -140
- package/readme.md +358 -105
- package/src/cli/bin.ts +77 -0
- package/src/cli/commands/help.ts +39 -0
- package/src/cli/commands/runtime.ts +72 -0
- package/src/cli/commands/version.ts +4 -0
- package/src/cli/index.ts +1 -0
- package/{cli.ts → src/cli/run-cli.ts} +95 -6
- package/src/config/define-config.ts +30 -0
- package/src/config/index.ts +3 -0
- package/src/config/merge-configs.ts +33 -0
- package/src/config/validate-config.ts +136 -0
- package/{core → src/core}/index.ts +2 -2
- package/{core → src/core}/ports.ts +68 -1
- package/{core → src/core}/process.ts +6 -2
- package/src/core/tunnel.ts +151 -0
- package/{core → src/core}/utils.ts +1 -0
- package/{core → src/core}/watchdog.ts +5 -1
- package/src/docker/index.ts +1 -0
- package/{core/docker.ts → src/docker/runtime.ts} +40 -4
- package/src/docker-compose/generated-file.ts +45 -0
- package/src/docker-compose/index.ts +7 -0
- package/src/docker-compose/model.ts +197 -0
- package/src/docker-compose/services/clickhouse.ts +79 -0
- package/src/docker-compose/services/define-docker-service.ts +109 -0
- package/src/docker-compose/services/index.ts +67 -0
- package/src/docker-compose/services/postgres.ts +60 -0
- package/src/docker-compose/services/redis.ts +48 -0
- package/src/docker-compose/services/shared.ts +79 -0
- package/src/docker-compose/yaml.ts +88 -0
- package/{environment.ts → src/environment/create-dev-environment.ts} +101 -146
- package/src/environment/index.ts +1 -0
- package/src/environment/logging.ts +101 -0
- package/src/environment/seeding.ts +57 -0
- package/{index.ts → src/index.ts} +49 -15
- package/src/loader/cache.ts +23 -0
- package/src/loader/find-config-file.ts +29 -0
- package/src/loader/index.ts +17 -0
- package/src/loader/load-dev-env.ts +38 -0
- package/src/prisma/index.ts +1 -0
- package/{prisma.ts → src/prisma/prisma.ts} +4 -2
- package/src/typecheck/index.ts +1 -0
- package/{types.ts → src/types/all-types.ts} +137 -6
- package/src/types/index.ts +1 -0
- package/bin.ts +0 -191
- package/config.ts +0 -194
- package/loader.ts +0 -126
- /package/{core → src/core}/network.ts +0 -0
- /package/{core → src/core}/watchdog-runner.ts +0 -0
- /package/{lint.ts → src/typecheck/typecheck.ts} +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// src/docker/runtime.ts
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { sleep } from "./index-4gp0az1g.js";
|
|
4
|
+
|
|
5
|
+
var POLL_INTERVAL = 250;
|
|
6
|
+
var MAX_ATTEMPTS = 120;
|
|
7
|
+
var DOCKER_NOT_RUNNING_MESSAGE =
|
|
8
|
+
"Docker is not running. Please start Docker and try again.";
|
|
9
|
+
async function isContainerRunning(project, service) {
|
|
10
|
+
try {
|
|
11
|
+
const result = execSync(
|
|
12
|
+
`docker ps --filter "label=com.docker.compose.project=${project}" --filter "label=com.docker.compose.service=${service}" --format "{{.State}}"`,
|
|
13
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] },
|
|
14
|
+
);
|
|
15
|
+
return result.trim() === "running";
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function isDockerRunning() {
|
|
21
|
+
try {
|
|
22
|
+
execSync('docker info --format "{{.ServerVersion}}"', {
|
|
23
|
+
encoding: "utf-8",
|
|
24
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
25
|
+
});
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function assertDockerRunning() {
|
|
32
|
+
if (!isDockerRunning()) {
|
|
33
|
+
throw new Error(DOCKER_NOT_RUNNING_MESSAGE);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function areContainersRunning(project, minCount = 1) {
|
|
37
|
+
try {
|
|
38
|
+
const result = execSync(
|
|
39
|
+
`docker ps --filter "label=com.docker.compose.project=${project}" --format "{{.State}}"`,
|
|
40
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] },
|
|
41
|
+
);
|
|
42
|
+
const states = result
|
|
43
|
+
.trim()
|
|
44
|
+
.split(`
|
|
45
|
+
`)
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
if (states.length < minCount) return false;
|
|
48
|
+
return states.every((state) => state === "running");
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function getComposeArg(composeFile) {
|
|
54
|
+
return composeFile ? `-f "${composeFile}"` : "";
|
|
55
|
+
}
|
|
56
|
+
function startContainers(root, projectName, envVars, options = {}) {
|
|
57
|
+
const { verbose = true, wait = true, composeFile } = options;
|
|
58
|
+
assertDockerRunning();
|
|
59
|
+
if (verbose) console.log("\uD83D\uDC33 Starting Docker containers...");
|
|
60
|
+
const composeArg = getComposeArg(composeFile);
|
|
61
|
+
const waitFlag = wait ? "--wait" : "";
|
|
62
|
+
const cmd = `docker compose ${composeArg} up -d ${waitFlag}`.trim();
|
|
63
|
+
execSync(cmd, {
|
|
64
|
+
cwd: root,
|
|
65
|
+
env: { ...process.env, ...envVars, COMPOSE_PROJECT_NAME: projectName },
|
|
66
|
+
stdio: verbose ? "inherit" : "ignore",
|
|
67
|
+
});
|
|
68
|
+
if (verbose) console.log("✓ Containers started");
|
|
69
|
+
}
|
|
70
|
+
function stopContainers(root, projectName, options = {}) {
|
|
71
|
+
const { verbose = true, removeVolumes = false, composeFile } = options;
|
|
72
|
+
assertDockerRunning();
|
|
73
|
+
if (verbose) {
|
|
74
|
+
console.log(
|
|
75
|
+
removeVolumes
|
|
76
|
+
? "\uD83D\uDDD1️ Stopping containers and removing volumes..."
|
|
77
|
+
: "\uD83D\uDED1 Stopping containers...",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const composeArg = getComposeArg(composeFile);
|
|
81
|
+
const volumeFlag = removeVolumes ? "-v" : "";
|
|
82
|
+
const cmd = `docker compose ${composeArg} down ${volumeFlag}`.trim();
|
|
83
|
+
execSync(cmd, {
|
|
84
|
+
cwd: root,
|
|
85
|
+
env: { ...process.env, COMPOSE_PROJECT_NAME: projectName },
|
|
86
|
+
stdio: verbose ? "inherit" : "ignore",
|
|
87
|
+
});
|
|
88
|
+
if (verbose) console.log("✓ Containers stopped");
|
|
89
|
+
}
|
|
90
|
+
function startService(root, projectName, serviceName, envVars, options = {}) {
|
|
91
|
+
const { verbose = true, composeFile } = options;
|
|
92
|
+
assertDockerRunning();
|
|
93
|
+
if (verbose) console.log(`\uD83D\uDC33 Starting ${serviceName}...`);
|
|
94
|
+
const composeArg = getComposeArg(composeFile);
|
|
95
|
+
const cmd = `docker compose ${composeArg} up -d ${serviceName}`.trim();
|
|
96
|
+
execSync(cmd, {
|
|
97
|
+
cwd: root,
|
|
98
|
+
env: { ...process.env, ...envVars, COMPOSE_PROJECT_NAME: projectName },
|
|
99
|
+
stdio: verbose ? "inherit" : "ignore",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function createBuiltInHealthCheck(type, serviceName, context = {}) {
|
|
103
|
+
const { projectName, root } = context;
|
|
104
|
+
switch (type) {
|
|
105
|
+
case "pg_isready":
|
|
106
|
+
return async () => {
|
|
107
|
+
try {
|
|
108
|
+
const projectArg = projectName ? `-p ${projectName}` : "";
|
|
109
|
+
execSync(
|
|
110
|
+
`docker compose ${projectArg} exec -T ${serviceName} pg_isready -U postgres`,
|
|
111
|
+
{
|
|
112
|
+
cwd: root,
|
|
113
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
return true;
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
case "redis-cli":
|
|
122
|
+
return async () => {
|
|
123
|
+
try {
|
|
124
|
+
const projectArg = projectName ? `-p ${projectName}` : "";
|
|
125
|
+
execSync(
|
|
126
|
+
`docker compose ${projectArg} exec -T ${serviceName} redis-cli ping`,
|
|
127
|
+
{
|
|
128
|
+
cwd: root,
|
|
129
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
return true;
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
case "http":
|
|
138
|
+
return async (port) => {
|
|
139
|
+
try {
|
|
140
|
+
const controller = new AbortController();
|
|
141
|
+
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
|
142
|
+
try {
|
|
143
|
+
const response = await fetch(`http://localhost:${port}/`, {
|
|
144
|
+
signal: controller.signal,
|
|
145
|
+
});
|
|
146
|
+
clearTimeout(timeoutId);
|
|
147
|
+
return response.ok || response.status === 404;
|
|
148
|
+
} catch {
|
|
149
|
+
clearTimeout(timeoutId);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
case "tcp":
|
|
157
|
+
return async (port) => {
|
|
158
|
+
try {
|
|
159
|
+
const controller = new AbortController();
|
|
160
|
+
const timeoutId = setTimeout(() => controller.abort(), 1000);
|
|
161
|
+
try {
|
|
162
|
+
await fetch(`http://localhost:${port}/`, {
|
|
163
|
+
signal: controller.signal,
|
|
164
|
+
});
|
|
165
|
+
clearTimeout(timeoutId);
|
|
166
|
+
return true;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
clearTimeout(timeoutId);
|
|
169
|
+
if (
|
|
170
|
+
error instanceof Error &&
|
|
171
|
+
error.message.includes("ECONNREFUSED")
|
|
172
|
+
) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
default:
|
|
182
|
+
return async () => true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function waitForService(serviceName, config, port, options = {}) {
|
|
186
|
+
const {
|
|
187
|
+
maxAttempts = MAX_ATTEMPTS,
|
|
188
|
+
pollInterval = POLL_INTERVAL,
|
|
189
|
+
projectName,
|
|
190
|
+
root,
|
|
191
|
+
} = options;
|
|
192
|
+
if (config.healthCheck === false || config.healthCheck === undefined) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const healthCheckFn =
|
|
196
|
+
typeof config.healthCheck === "function"
|
|
197
|
+
? config.healthCheck
|
|
198
|
+
: createBuiltInHealthCheck(
|
|
199
|
+
config.healthCheck,
|
|
200
|
+
config.serviceName ?? serviceName,
|
|
201
|
+
{ projectName, root },
|
|
202
|
+
);
|
|
203
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
204
|
+
const isHealthy = await healthCheckFn(port);
|
|
205
|
+
if (isHealthy) return;
|
|
206
|
+
await sleep(pollInterval);
|
|
207
|
+
}
|
|
208
|
+
throw new Error(`Service ${serviceName} did not become ready in time`);
|
|
209
|
+
}
|
|
210
|
+
async function waitForAllServices(services, ports, options = {}) {
|
|
211
|
+
const { verbose = true, ...waitOptions } = options;
|
|
212
|
+
if (verbose) console.log("⏳ Waiting for services to be healthy...");
|
|
213
|
+
const promises = Object.entries(services).map(([name, config]) => {
|
|
214
|
+
const port = ports[name];
|
|
215
|
+
if (port === undefined) {
|
|
216
|
+
console.warn(
|
|
217
|
+
`⚠️ No port found for service ${name}, skipping health check`,
|
|
218
|
+
);
|
|
219
|
+
return Promise.resolve();
|
|
220
|
+
}
|
|
221
|
+
return waitForService(name, config, port, waitOptions);
|
|
222
|
+
});
|
|
223
|
+
await Promise.all(promises);
|
|
224
|
+
if (verbose) console.log("✓ All services healthy");
|
|
225
|
+
}
|
|
226
|
+
async function waitForServiceByType(
|
|
227
|
+
serviceName,
|
|
228
|
+
healthCheckType,
|
|
229
|
+
port,
|
|
230
|
+
options = {},
|
|
231
|
+
) {
|
|
232
|
+
const {
|
|
233
|
+
maxAttempts = MAX_ATTEMPTS,
|
|
234
|
+
pollInterval = POLL_INTERVAL,
|
|
235
|
+
verbose = false,
|
|
236
|
+
projectName,
|
|
237
|
+
root,
|
|
238
|
+
} = options;
|
|
239
|
+
const healthCheckFn = createBuiltInHealthCheck(healthCheckType, serviceName, {
|
|
240
|
+
projectName,
|
|
241
|
+
root,
|
|
242
|
+
});
|
|
243
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
244
|
+
const isHealthy = await healthCheckFn(port);
|
|
245
|
+
if (isHealthy) {
|
|
246
|
+
if (verbose) console.log(`✓ ${serviceName} is ready`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
await sleep(pollInterval);
|
|
250
|
+
}
|
|
251
|
+
throw new Error(`Service ${serviceName} did not become ready in time`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export {
|
|
255
|
+
POLL_INTERVAL,
|
|
256
|
+
MAX_ATTEMPTS,
|
|
257
|
+
DOCKER_NOT_RUNNING_MESSAGE,
|
|
258
|
+
isContainerRunning,
|
|
259
|
+
isDockerRunning,
|
|
260
|
+
assertDockerRunning,
|
|
261
|
+
areContainersRunning,
|
|
262
|
+
getComposeArg,
|
|
263
|
+
startContainers,
|
|
264
|
+
stopContainers,
|
|
265
|
+
startService,
|
|
266
|
+
createBuiltInHealthCheck,
|
|
267
|
+
waitForService,
|
|
268
|
+
waitForAllServices,
|
|
269
|
+
waitForServiceByType,
|
|
270
|
+
};
|
package/dist/index-tjqw9vtj.js
CHANGED
|
@@ -1,68 +1,76 @@
|
|
|
1
1
|
// config.ts
|
|
2
2
|
function defineDevConfig(config) {
|
|
3
|
-
|
|
3
|
+
return config;
|
|
4
4
|
}
|
|
5
5
|
function validateConfig(config) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
6
|
+
const errors = [];
|
|
7
|
+
if (!config.projectPrefix) {
|
|
8
|
+
errors.push("projectPrefix is required");
|
|
9
|
+
} else if (!/^[a-z][a-z0-9-]*$/.test(config.projectPrefix)) {
|
|
10
|
+
errors.push(
|
|
11
|
+
"projectPrefix must start with a letter and contain only lowercase letters, numbers, and hyphens",
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
if (!config.services || Object.keys(config.services).length === 0) {
|
|
15
|
+
errors.push("At least one service is required");
|
|
16
|
+
}
|
|
17
|
+
for (const [name, service] of Object.entries(config.services ?? {})) {
|
|
18
|
+
if (!service.port || typeof service.port !== "number") {
|
|
19
|
+
errors.push(`Service "${name}" must have a valid port number`);
|
|
20
|
+
}
|
|
21
|
+
if (service.port < 1 || service.port > 65535) {
|
|
22
|
+
errors.push(`Service "${name}" port must be between 1 and 65535`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const [name, app] of Object.entries(config.apps ?? {})) {
|
|
26
|
+
if (!app.port || typeof app.port !== "number") {
|
|
27
|
+
errors.push(`App "${name}" must have a valid port number`);
|
|
28
|
+
}
|
|
29
|
+
if (!app.devCommand) {
|
|
30
|
+
errors.push(`App "${name}" must have a devCommand`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const migration of config.migrations ?? []) {
|
|
34
|
+
if (!migration.name) {
|
|
35
|
+
errors.push("Migration must have a name");
|
|
36
|
+
}
|
|
37
|
+
if (!migration.command) {
|
|
38
|
+
errors.push(`Migration "${migration.name}" must have a command`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (config.seed && !config.seed.command) {
|
|
42
|
+
errors.push("Seed must have a command");
|
|
43
|
+
}
|
|
44
|
+
return errors;
|
|
43
45
|
}
|
|
44
46
|
function assertValidConfig(config) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
const errors = validateConfig(config);
|
|
48
|
+
if (errors.length > 0) {
|
|
49
|
+
throw new Error(`Invalid dev config:
|
|
48
50
|
- ${errors.join(`
|
|
49
51
|
- `)}`);
|
|
50
|
-
|
|
52
|
+
}
|
|
51
53
|
}
|
|
52
54
|
function mergeConfigs(base, overrides) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
return {
|
|
56
|
+
...base,
|
|
57
|
+
...overrides,
|
|
58
|
+
services: { ...base.services, ...overrides.services },
|
|
59
|
+
apps: { ...base.apps, ...overrides.apps },
|
|
60
|
+
hooks: { ...base.hooks, ...overrides.hooks },
|
|
61
|
+
migrations: overrides.migrations ?? base.migrations,
|
|
62
|
+
seed: overrides.seed ?? base.seed,
|
|
63
|
+
options: { ...base.options, ...overrides.options },
|
|
64
|
+
};
|
|
63
65
|
}
|
|
64
66
|
function definePartialConfig(config) {
|
|
65
|
-
|
|
67
|
+
return config;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
export {
|
|
70
|
+
export {
|
|
71
|
+
defineDevConfig,
|
|
72
|
+
validateConfig,
|
|
73
|
+
assertValidConfig,
|
|
74
|
+
mergeConfigs,
|
|
75
|
+
definePartialConfig,
|
|
76
|
+
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// src/docker-compose/generated-file.ts
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
buildPresetDockerService,
|
|
6
|
+
getDefaultPortBindings,
|
|
7
|
+
inferDockerPreset,
|
|
8
|
+
} from "./index-w8zxnjka.js";
|
|
9
|
+
|
|
10
|
+
// src/docker-compose/model.ts
|
|
11
|
+
function isObject(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function deepMergeNode(base, override) {
|
|
15
|
+
if (Array.isArray(base) || Array.isArray(override)) {
|
|
16
|
+
return override;
|
|
17
|
+
}
|
|
18
|
+
if (!isObject(base) || !isObject(override)) {
|
|
19
|
+
return override;
|
|
20
|
+
}
|
|
21
|
+
const merged = { ...base };
|
|
22
|
+
for (const key of Object.keys(override)) {
|
|
23
|
+
const baseValue = merged[key];
|
|
24
|
+
const overrideValue = override[key];
|
|
25
|
+
if (baseValue === undefined || overrideValue === undefined) {
|
|
26
|
+
merged[key] = overrideValue;
|
|
27
|
+
} else {
|
|
28
|
+
merged[key] = deepMergeNode(baseValue, overrideValue);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return merged;
|
|
32
|
+
}
|
|
33
|
+
function isPresetDefinition(value) {
|
|
34
|
+
return Boolean(
|
|
35
|
+
value &&
|
|
36
|
+
typeof value === "object" &&
|
|
37
|
+
"kind" in value &&
|
|
38
|
+
value.kind === "preset",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
function normalizeRawService(name, config, service) {
|
|
42
|
+
const normalized = { ...service };
|
|
43
|
+
if (!normalized.ports || normalized.ports.length === 0) {
|
|
44
|
+
normalized.ports = getDefaultPortBindings(name, config);
|
|
45
|
+
}
|
|
46
|
+
if (config.healthCheck === false) {
|
|
47
|
+
delete normalized.healthcheck;
|
|
48
|
+
}
|
|
49
|
+
return normalized;
|
|
50
|
+
}
|
|
51
|
+
function normalizeServiceConfig(name, config) {
|
|
52
|
+
const serviceName = config.serviceName ?? name;
|
|
53
|
+
const rawDefinition = config.docker;
|
|
54
|
+
if (isPresetDefinition(rawDefinition)) {
|
|
55
|
+
return {
|
|
56
|
+
kind: "preset",
|
|
57
|
+
serviceName,
|
|
58
|
+
preset: rawDefinition.preset,
|
|
59
|
+
serviceOverride: rawDefinition.service,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (rawDefinition) {
|
|
63
|
+
const inferredPreset = inferDockerPreset(name);
|
|
64
|
+
if (inferredPreset) {
|
|
65
|
+
return {
|
|
66
|
+
kind: "preset",
|
|
67
|
+
serviceName,
|
|
68
|
+
preset: inferredPreset,
|
|
69
|
+
serviceOverride: rawDefinition,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
kind: "raw",
|
|
74
|
+
serviceName,
|
|
75
|
+
service: normalizeRawService(name, config, rawDefinition),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const preset = inferDockerPreset(name);
|
|
79
|
+
if (!preset) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Service "${name}" has no docker preset and no docker definition. Add service.docker using helper or raw mode.`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
kind: "preset",
|
|
86
|
+
serviceName,
|
|
87
|
+
preset,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function resolveServiceDefinition(name, config) {
|
|
91
|
+
const normalized = normalizeServiceConfig(name, config);
|
|
92
|
+
if (normalized.kind === "raw") {
|
|
93
|
+
return {
|
|
94
|
+
serviceName: normalized.serviceName,
|
|
95
|
+
service: normalized.service,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const { service, volume } = buildPresetDockerService(normalized.preset, {
|
|
99
|
+
serviceKey: name,
|
|
100
|
+
config,
|
|
101
|
+
});
|
|
102
|
+
const mergedService = normalized.serviceOverride
|
|
103
|
+
? deepMergeNode(service, normalized.serviceOverride)
|
|
104
|
+
: service;
|
|
105
|
+
return {
|
|
106
|
+
serviceName: normalized.serviceName,
|
|
107
|
+
service: mergedService,
|
|
108
|
+
volume,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function buildComposeModel(services, docker) {
|
|
112
|
+
const composeServices = {};
|
|
113
|
+
const composeVolumes = {};
|
|
114
|
+
for (const [name, serviceConfig] of Object.entries(services)) {
|
|
115
|
+
const { serviceName, service, volume } = resolveServiceDefinition(
|
|
116
|
+
name,
|
|
117
|
+
serviceConfig,
|
|
118
|
+
);
|
|
119
|
+
composeServices[serviceName] = service;
|
|
120
|
+
if (volume) {
|
|
121
|
+
composeVolumes[volume] = {};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const [volumeName, volume] of Object.entries(docker?.volumes ?? {})) {
|
|
125
|
+
composeVolumes[volumeName] = volume;
|
|
126
|
+
}
|
|
127
|
+
const document = {
|
|
128
|
+
services: composeServices,
|
|
129
|
+
};
|
|
130
|
+
if (Object.keys(composeVolumes).length > 0) {
|
|
131
|
+
document.volumes = composeVolumes;
|
|
132
|
+
}
|
|
133
|
+
return document;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/docker-compose/yaml.ts
|
|
137
|
+
function isObject2(value) {
|
|
138
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
139
|
+
}
|
|
140
|
+
function formatScalar(value) {
|
|
141
|
+
if (value === null) return "null";
|
|
142
|
+
if (typeof value === "string") {
|
|
143
|
+
return JSON.stringify(value);
|
|
144
|
+
}
|
|
145
|
+
return String(value);
|
|
146
|
+
}
|
|
147
|
+
function formatKey(key) {
|
|
148
|
+
return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key) ? key : JSON.stringify(key);
|
|
149
|
+
}
|
|
150
|
+
function sortNode(node) {
|
|
151
|
+
if (Array.isArray(node)) {
|
|
152
|
+
return node.map(sortNode);
|
|
153
|
+
}
|
|
154
|
+
if (isObject2(node)) {
|
|
155
|
+
const sorted = {};
|
|
156
|
+
for (const key of Object.keys(node).sort()) {
|
|
157
|
+
const value = node[key];
|
|
158
|
+
if (value !== undefined) {
|
|
159
|
+
sorted[key] = sortNode(value);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return sorted;
|
|
163
|
+
}
|
|
164
|
+
return node;
|
|
165
|
+
}
|
|
166
|
+
function stringifyNode(node, indent = 0) {
|
|
167
|
+
const prefix = " ".repeat(indent);
|
|
168
|
+
if (
|
|
169
|
+
typeof node === "string" ||
|
|
170
|
+
typeof node === "number" ||
|
|
171
|
+
typeof node === "boolean" ||
|
|
172
|
+
node === null
|
|
173
|
+
) {
|
|
174
|
+
return `${prefix}${formatScalar(node)}`;
|
|
175
|
+
}
|
|
176
|
+
if (Array.isArray(node)) {
|
|
177
|
+
if (node.length === 0) return `${prefix}[]`;
|
|
178
|
+
return node
|
|
179
|
+
.map((item) => {
|
|
180
|
+
const isNested = typeof item === "object" && item !== null;
|
|
181
|
+
if (!isNested) {
|
|
182
|
+
return `${prefix}- ${formatScalar(item)}`;
|
|
183
|
+
}
|
|
184
|
+
return `${prefix}-
|
|
185
|
+
${stringifyNode(item, indent + 2)}`;
|
|
186
|
+
})
|
|
187
|
+
.join(`
|
|
188
|
+
`);
|
|
189
|
+
}
|
|
190
|
+
const entries = Object.entries(node).filter(
|
|
191
|
+
([, value]) => value !== undefined,
|
|
192
|
+
);
|
|
193
|
+
if (entries.length === 0) return `${prefix}{}`;
|
|
194
|
+
return entries
|
|
195
|
+
.map(([key, value]) => {
|
|
196
|
+
const formattedKey = formatKey(key);
|
|
197
|
+
const isNested = typeof value === "object" && value !== null;
|
|
198
|
+
if (!isNested) {
|
|
199
|
+
return `${prefix}${formattedKey}: ${formatScalar(value)}`;
|
|
200
|
+
}
|
|
201
|
+
return `${prefix}${formattedKey}:
|
|
202
|
+
${stringifyNode(value, indent + 2)}`;
|
|
203
|
+
})
|
|
204
|
+
.join(`
|
|
205
|
+
`);
|
|
206
|
+
}
|
|
207
|
+
function composeToYaml(document) {
|
|
208
|
+
const sorted = sortNode(document);
|
|
209
|
+
return `${stringifyNode(sorted)}
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/docker-compose/generated-file.ts
|
|
214
|
+
var DEFAULT_GENERATED_COMPOSE_FILE = ".buncargo/docker-compose.generated.yml";
|
|
215
|
+
function getGeneratedComposePath(root, docker) {
|
|
216
|
+
const generatedFile = docker?.generatedFile ?? DEFAULT_GENERATED_COMPOSE_FILE;
|
|
217
|
+
const absolutePath = isAbsolute(generatedFile)
|
|
218
|
+
? generatedFile
|
|
219
|
+
: resolve(root, generatedFile);
|
|
220
|
+
const relativePath = relative(root, absolutePath);
|
|
221
|
+
const composeFileArg =
|
|
222
|
+
relativePath && !relativePath.startsWith("..")
|
|
223
|
+
? relativePath
|
|
224
|
+
: absolutePath;
|
|
225
|
+
return { absolutePath, composeFileArg };
|
|
226
|
+
}
|
|
227
|
+
function writeGeneratedComposeFile(root, services, docker) {
|
|
228
|
+
const { absolutePath, composeFileArg } = getGeneratedComposePath(
|
|
229
|
+
root,
|
|
230
|
+
docker,
|
|
231
|
+
);
|
|
232
|
+
const writeStrategy = docker?.writeStrategy ?? "always";
|
|
233
|
+
const shouldWrite = writeStrategy === "always" || !existsSync(absolutePath);
|
|
234
|
+
if (shouldWrite) {
|
|
235
|
+
const composeModel = buildComposeModel(services, docker);
|
|
236
|
+
const yaml = composeToYaml(composeModel);
|
|
237
|
+
mkdirSync(dirname(absolutePath), { recursive: true });
|
|
238
|
+
writeFileSync(absolutePath, yaml, "utf-8");
|
|
239
|
+
}
|
|
240
|
+
return composeFileArg;
|
|
241
|
+
}
|
|
242
|
+
export {
|
|
243
|
+
buildComposeModel,
|
|
244
|
+
composeToYaml,
|
|
245
|
+
DEFAULT_GENERATED_COMPOSE_FILE,
|
|
246
|
+
getGeneratedComposePath,
|
|
247
|
+
writeGeneratedComposeFile,
|
|
248
|
+
};
|