ensure-running 1.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.
@@ -0,0 +1,959 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/services/docker/utils/Exec.ts
13
+ import { execFile as nodeExecFile } from "child_process";
14
+ async function exec(command, args = []) {
15
+ return new Promise((resolve, reject) => {
16
+ nodeExecFile(
17
+ command,
18
+ args,
19
+ {
20
+ encoding: "utf8",
21
+ windowsHide: true
22
+ },
23
+ (err, stdout, stderr) => {
24
+ if (err) {
25
+ reject(err);
26
+ return;
27
+ }
28
+ resolve({
29
+ stdout: String(stdout ?? ""),
30
+ stderr: String(stderr ?? "")
31
+ });
32
+ }
33
+ );
34
+ });
35
+ }
36
+ async function execShell(shellCommand) {
37
+ const shell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
38
+ const shellFlag = process.platform === "win32" ? "/c" : "-c";
39
+ return exec(shell, [shellFlag, shellCommand]);
40
+ }
41
+ var init_Exec = __esm({
42
+ "src/services/docker/utils/Exec.ts"() {
43
+ "use strict";
44
+ }
45
+ });
46
+
47
+ // src/services/docker/utils/Spawn.ts
48
+ import { spawn as nodeSpawn } from "child_process";
49
+ function spawn2(command, args = [], options = {}) {
50
+ return nodeSpawn(command, args, {
51
+ detached: options.detached ?? false,
52
+ stdio: options.stdio ?? "ignore",
53
+ windowsHide: options.windowsHide ?? true
54
+ });
55
+ }
56
+ function spawnDetached(command, args = []) {
57
+ const child = spawn2(command, args, {
58
+ detached: true,
59
+ stdio: "ignore"
60
+ });
61
+ child.unref();
62
+ return child;
63
+ }
64
+ var init_Spawn = __esm({
65
+ "src/services/docker/utils/Spawn.ts"() {
66
+ "use strict";
67
+ }
68
+ });
69
+
70
+ // src/services/docker/utils/Sleep.ts
71
+ function sleep(ms) {
72
+ return new Promise((resolve) => {
73
+ setTimeout(resolve, ms);
74
+ });
75
+ }
76
+ var init_Sleep = __esm({
77
+ "src/services/docker/utils/Sleep.ts"() {
78
+ "use strict";
79
+ }
80
+ });
81
+
82
+ // src/services/docker/utils/Timeout.ts
83
+ async function withTimeout(promise, timeoutMs, message = "Operation timed out") {
84
+ let timer;
85
+ const timeoutPromise = new Promise((_, reject) => {
86
+ timer = setTimeout(() => {
87
+ reject(new Error(message));
88
+ }, timeoutMs);
89
+ });
90
+ try {
91
+ return await Promise.race([promise, timeoutPromise]);
92
+ } finally {
93
+ if (timer !== void 0) {
94
+ clearTimeout(timer);
95
+ }
96
+ }
97
+ }
98
+ var init_Timeout = __esm({
99
+ "src/services/docker/utils/Timeout.ts"() {
100
+ "use strict";
101
+ }
102
+ });
103
+
104
+ // src/services/docker/utils/Which.ts
105
+ async function which(command) {
106
+ try {
107
+ if (process.platform === "win32") {
108
+ const result = await exec("where.exe", [command]);
109
+ const firstLine = result.stdout.split(/\r?\n/).find((line) => line.trim().length > 0);
110
+ return firstLine?.trim();
111
+ }
112
+ try {
113
+ const result = await exec("which", [command]);
114
+ const path3 = result.stdout.trim();
115
+ return path3.length > 0 ? path3 : void 0;
116
+ } catch {
117
+ const result = await execShell(`command -v ${command}`);
118
+ const path3 = result.stdout.trim();
119
+ return path3.length > 0 ? path3 : void 0;
120
+ }
121
+ } catch {
122
+ return void 0;
123
+ }
124
+ }
125
+ async function commandExists(command) {
126
+ const path3 = await which(command);
127
+ return path3 !== void 0;
128
+ }
129
+ var init_Which = __esm({
130
+ "src/services/docker/utils/Which.ts"() {
131
+ "use strict";
132
+ init_Exec();
133
+ }
134
+ });
135
+
136
+ // src/services/docker/utils/Poll.ts
137
+ async function poll(predicate, options) {
138
+ const deadline = Date.now() + options.timeout;
139
+ while (Date.now() < deadline) {
140
+ if (await predicate()) {
141
+ return true;
142
+ }
143
+ const remaining = deadline - Date.now();
144
+ if (remaining <= 0) {
145
+ break;
146
+ }
147
+ await sleep(Math.min(options.interval, remaining));
148
+ }
149
+ return false;
150
+ }
151
+ var init_Poll = __esm({
152
+ "src/services/docker/utils/Poll.ts"() {
153
+ "use strict";
154
+ init_Sleep();
155
+ }
156
+ });
157
+
158
+ // src/services/docker/utils/Retry.ts
159
+ async function retry(fn, options) {
160
+ let lastError;
161
+ for (let attempt = 1; attempt <= options.attempts; attempt++) {
162
+ try {
163
+ return await fn();
164
+ } catch (err) {
165
+ lastError = err;
166
+ if (attempt < options.attempts) {
167
+ await sleep(options.delayMs);
168
+ }
169
+ }
170
+ }
171
+ throw lastError;
172
+ }
173
+ var init_Retry = __esm({
174
+ "src/services/docker/utils/Retry.ts"() {
175
+ "use strict";
176
+ init_Sleep();
177
+ }
178
+ });
179
+
180
+ // src/services/docker/utils/index.ts
181
+ var utils_exports = {};
182
+ __export(utils_exports, {
183
+ commandExists: () => commandExists,
184
+ exec: () => exec,
185
+ execShell: () => execShell,
186
+ poll: () => poll,
187
+ retry: () => retry,
188
+ sleep: () => sleep,
189
+ spawn: () => spawn2,
190
+ spawnDetached: () => spawnDetached,
191
+ which: () => which,
192
+ withTimeout: () => withTimeout
193
+ });
194
+ var init_utils = __esm({
195
+ "src/services/docker/utils/index.ts"() {
196
+ "use strict";
197
+ init_Exec();
198
+ init_Spawn();
199
+ init_Sleep();
200
+ init_Timeout();
201
+ init_Which();
202
+ init_Poll();
203
+ init_Retry();
204
+ }
205
+ });
206
+
207
+ // src/cli/Cli.ts
208
+ import { readFileSync } from "fs";
209
+ import { fileURLToPath } from "url";
210
+
211
+ // src/core/argv/Argv.ts
212
+ function normalizeArgv(argv) {
213
+ const prefix = argv.slice(0, Math.min(argv.length, 4));
214
+ let scriptIndex = -1;
215
+ for (let index = 0; index < prefix.length; index++) {
216
+ const arg = prefix[index] ?? "";
217
+ if (arg.endsWith(".ts")) {
218
+ scriptIndex = index;
219
+ }
220
+ }
221
+ if (scriptIndex === -1) {
222
+ for (let index = 0; index < prefix.length; index++) {
223
+ const arg = prefix[index] ?? "";
224
+ if ((arg.endsWith(".mjs") || arg.endsWith(".cjs") || arg.endsWith(".js")) && (arg.includes("/") || arg.includes("\\"))) {
225
+ scriptIndex = index;
226
+ }
227
+ }
228
+ }
229
+ if (scriptIndex >= 0) {
230
+ return argv.slice(scriptIndex + 1);
231
+ }
232
+ if (argv.length >= 2 && (argv[0].includes("node") || argv[0].endsWith("node.exe"))) {
233
+ return argv.slice(2);
234
+ }
235
+ return [...argv];
236
+ }
237
+
238
+ // src/core/service/DefineEnsureService.ts
239
+ function defineEnsureService(service) {
240
+ return service;
241
+ }
242
+
243
+ // src/core/registry/ServiceRegistry.ts
244
+ var ServiceRegistry = class {
245
+ services = /* @__PURE__ */ new Map();
246
+ /**
247
+ * Registers a service by id and optional aliases.
248
+ */
249
+ register(service) {
250
+ this.services.set(service.id, service);
251
+ for (const alias of service.aliases ?? []) {
252
+ this.services.set(alias, service);
253
+ }
254
+ }
255
+ /**
256
+ * Resolves a service by id or alias.
257
+ */
258
+ resolve(name) {
259
+ return this.services.get(name);
260
+ }
261
+ /**
262
+ * Returns registered services without alias duplicates.
263
+ */
264
+ list() {
265
+ const seen = /* @__PURE__ */ new Set();
266
+ for (const service of this.services.values()) {
267
+ seen.add(service);
268
+ }
269
+ return [...seen];
270
+ }
271
+ };
272
+
273
+ // src/core/runner/Runner.ts
274
+ import { spawn } from "child_process";
275
+ var CommandSeparator = "--";
276
+ var CommandSeparatorError = class extends Error {
277
+ constructor(token) {
278
+ super(
279
+ `Unexpected argument "${token}". Use "${CommandSeparator}" before the command (e.g. er docker postgres ${CommandSeparator} vite dev).`
280
+ );
281
+ this.name = "CommandSeparatorError";
282
+ }
283
+ };
284
+ var MissingCommandError = class extends Error {
285
+ constructor() {
286
+ super(`Command separator "${CommandSeparator}" must be followed by a command.`);
287
+ this.name = "MissingCommandError";
288
+ }
289
+ };
290
+ function parseInvocation(argv, registry) {
291
+ let remaining = normalizeArgv(argv);
292
+ const services = [];
293
+ while (remaining.length > 0) {
294
+ const service = registry.resolve(remaining[0] ?? "");
295
+ if (service === void 0) {
296
+ break;
297
+ }
298
+ remaining = remaining.slice(1);
299
+ const parsed = service.parseArgs(remaining);
300
+ services.push({
301
+ service,
302
+ options: parsed.options
303
+ });
304
+ remaining = parsed.remaining;
305
+ }
306
+ let command;
307
+ if (remaining.length === 0) {
308
+ command = void 0;
309
+ } else if (remaining[0] === CommandSeparator) {
310
+ command = remaining.slice(1);
311
+ if (command.length === 0) {
312
+ throw new MissingCommandError();
313
+ }
314
+ } else {
315
+ throw new CommandSeparatorError(remaining[0] ?? "");
316
+ }
317
+ return {
318
+ services,
319
+ command
320
+ };
321
+ }
322
+ async function runInvocation(invocation) {
323
+ for (const entry of invocation.services) {
324
+ const exitCode = await entry.service.run(entry.options);
325
+ if (exitCode !== 0) {
326
+ return exitCode;
327
+ }
328
+ }
329
+ if (invocation.command === void 0) {
330
+ return 0;
331
+ }
332
+ return runCommand(invocation.command);
333
+ }
334
+ function runCommand(command) {
335
+ const [executable, ...args] = command;
336
+ if (executable === void 0) {
337
+ return Promise.resolve(1);
338
+ }
339
+ return new Promise((resolve) => {
340
+ const child = spawn(executable, args, {
341
+ stdio: "inherit",
342
+ shell: process.platform === "win32"
343
+ });
344
+ child.on("error", () => {
345
+ resolve(1);
346
+ });
347
+ child.on("close", (code) => {
348
+ resolve(code ?? 1);
349
+ });
350
+ });
351
+ }
352
+
353
+ // src/services/docker/commands/Commands.ts
354
+ init_utils();
355
+ var DockerCliVersionPattern = /Docker version\s+([^\s,]+)/i;
356
+ function parseDockerCliVersion(stdout) {
357
+ const match = stdout.match(DockerCliVersionPattern);
358
+ return match?.[1];
359
+ }
360
+ async function resolveDockerExecutable() {
361
+ return which("docker");
362
+ }
363
+ async function isDockerDaemonReachable(executable = "docker") {
364
+ try {
365
+ await exec(executable, ["info"]);
366
+ return true;
367
+ } catch {
368
+ return false;
369
+ }
370
+ }
371
+ async function runDockerVersion(executable = "docker") {
372
+ let clientVersion;
373
+ try {
374
+ const result = await exec(executable, ["--version"]);
375
+ clientVersion = parseDockerCliVersion(result.stdout);
376
+ } catch {
377
+ return { serverReachable: false };
378
+ }
379
+ if (clientVersion === void 0) {
380
+ return { serverReachable: false };
381
+ }
382
+ let serverReachable = false;
383
+ try {
384
+ const serverResult = await exec(executable, [
385
+ "version",
386
+ "--format",
387
+ "{{.Server.Version}}"
388
+ ]);
389
+ serverReachable = serverResult.stdout.trim().length > 0;
390
+ } catch {
391
+ serverReachable = false;
392
+ }
393
+ return { clientVersion, serverReachable };
394
+ }
395
+ async function validateDockerInstallation(executable) {
396
+ try {
397
+ const result = await exec(executable, ["--version"]);
398
+ return parseDockerCliVersion(result.stdout) !== void 0;
399
+ } catch {
400
+ return false;
401
+ }
402
+ }
403
+ async function runDockerDesktopStart(executable = "docker") {
404
+ await exec(executable, ["desktop", "start"]);
405
+ }
406
+
407
+ // src/services/docker/detect/Detect.ts
408
+ async function detectDocker() {
409
+ const executable = await resolveDockerExecutable();
410
+ if (executable === void 0) {
411
+ return {
412
+ installed: false,
413
+ running: false
414
+ };
415
+ }
416
+ const installed = await validateDockerInstallation(executable);
417
+ if (!installed) {
418
+ return {
419
+ installed: false,
420
+ running: false,
421
+ executable
422
+ };
423
+ }
424
+ const running = await isDockerDaemonReachable(executable);
425
+ let version;
426
+ if (installed) {
427
+ const versionInfo = await runDockerVersion(executable);
428
+ version = versionInfo.clientVersion;
429
+ }
430
+ return {
431
+ installed: true,
432
+ running,
433
+ version,
434
+ executable
435
+ };
436
+ }
437
+
438
+ // src/services/docker/errors/Errors.ts
439
+ var DockerError = class extends Error {
440
+ code;
441
+ constructor(message, code, options) {
442
+ super(message, options);
443
+ this.name = "DockerError";
444
+ this.code = code;
445
+ }
446
+ };
447
+ var DockerNotInstalledError = class extends DockerError {
448
+ constructor(message = "Docker is not installed. Install Docker and ensure the CLI is on your PATH.") {
449
+ super(message, "NOT_INSTALLED" /* NOT_INSTALLED */);
450
+ this.name = "DockerNotInstalledError";
451
+ }
452
+ };
453
+ var DockerNotRunningError = class extends DockerError {
454
+ constructor(message = "Docker is installed but the daemon is not running. Start Docker or pass autoStart: true.") {
455
+ super(message, "NOT_RUNNING" /* NOT_RUNNING */);
456
+ this.name = "DockerNotRunningError";
457
+ }
458
+ };
459
+ var DockerStartError = class extends DockerError {
460
+ constructor(message = "Failed to start Docker. Start Docker manually and try again.", options) {
461
+ super(message, "START_FAILED" /* START_FAILED */, options);
462
+ this.name = "DockerStartError";
463
+ }
464
+ };
465
+ var DockerTimeoutError = class extends DockerError {
466
+ constructor(timeoutMs) {
467
+ const seconds = Math.round(timeoutMs / 1e3);
468
+ super(
469
+ `Timed out after ${seconds}s waiting for the Docker daemon to become ready.`,
470
+ "TIMEOUT" /* TIMEOUT */
471
+ );
472
+ this.name = "DockerTimeoutError";
473
+ }
474
+ };
475
+
476
+ // src/services/docker/logger/Logger.ts
477
+ var noop = () => void 0;
478
+ function resolveLogger(logger) {
479
+ return {
480
+ info: logger?.info ?? noop,
481
+ warn: logger?.warn ?? noop,
482
+ debug: logger?.debug ?? noop
483
+ };
484
+ }
485
+
486
+ // src/services/docker/platforms/Platforms.ts
487
+ import { access } from "fs/promises";
488
+ import path from "path";
489
+
490
+ // src/services/docker/process/Process.ts
491
+ init_utils();
492
+ function startDockerdDetached() {
493
+ spawnDetached("dockerd", []);
494
+ }
495
+ async function openMacApplication(appName) {
496
+ const { exec: exec2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
497
+ await exec2("open", ["-a", appName]);
498
+ }
499
+ function launchWindowsExecutable(executablePath) {
500
+ spawnDetached(executablePath, []);
501
+ }
502
+
503
+ // src/services/docker/platforms/Platforms.ts
504
+ init_utils();
505
+ async function startDockerLinux(logger, _dockerExecutable = "docker") {
506
+ const errors = [];
507
+ if (await commandExists("systemctl")) {
508
+ try {
509
+ logger.debug("Attempting: systemctl start docker");
510
+ await exec("systemctl", ["start", "docker"]);
511
+ logger.info("Started Docker via systemctl.");
512
+ return;
513
+ } catch (err) {
514
+ errors.push(err instanceof Error ? err : new Error(String(err)));
515
+ logger.warn("systemctl start docker failed.");
516
+ }
517
+ }
518
+ if (await commandExists("service")) {
519
+ try {
520
+ logger.debug("Attempting: service docker start");
521
+ await exec("service", ["docker", "start"]);
522
+ logger.info("Started Docker via service.");
523
+ return;
524
+ } catch (err) {
525
+ errors.push(err instanceof Error ? err : new Error(String(err)));
526
+ logger.warn("service docker start failed.");
527
+ }
528
+ }
529
+ if (await commandExists("dockerd")) {
530
+ try {
531
+ logger.debug("Attempting: dockerd (detached)");
532
+ startDockerdDetached();
533
+ logger.info("Started dockerd in the background.");
534
+ return;
535
+ } catch (err) {
536
+ errors.push(err instanceof Error ? err : new Error(String(err)));
537
+ logger.warn("dockerd detached start failed.");
538
+ }
539
+ }
540
+ const aggregate = new Error(
541
+ "No supported Docker start method found on Linux (systemctl, service, or dockerd)."
542
+ );
543
+ if (errors.length > 0) {
544
+ aggregate.cause = errors[errors.length - 1];
545
+ }
546
+ throw aggregate;
547
+ }
548
+ function getWindowsDockerDesktopCandidates() {
549
+ const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
550
+ const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
551
+ const localAppData = process.env.LOCALAPPDATA ?? "";
552
+ const programData = process.env.ProgramData ?? "";
553
+ const candidates = [
554
+ path.join(programFiles, "Docker", "Docker", "Docker Desktop.exe"),
555
+ path.join(programFilesX86, "Docker", "Docker", "Docker Desktop.exe"),
556
+ localAppData ? path.join(localAppData, "Docker", "Docker Desktop.exe") : void 0,
557
+ programData ? path.join(programData, "Docker", "Docker Desktop.exe") : void 0
558
+ ];
559
+ return candidates.filter((candidate) => candidate !== void 0);
560
+ }
561
+ async function findWindowsDockerDesktop() {
562
+ for (const candidate of getWindowsDockerDesktopCandidates()) {
563
+ try {
564
+ await access(candidate);
565
+ return candidate;
566
+ } catch {
567
+ continue;
568
+ }
569
+ }
570
+ const pathHit = await Promise.resolve().then(() => (init_utils(), utils_exports)).then((utils) => utils.which("Docker Desktop.exe"));
571
+ return pathHit;
572
+ }
573
+ async function startDockerWindows(logger, dockerExecutable = "docker") {
574
+ try {
575
+ logger.debug("Attempting: docker desktop start");
576
+ await runDockerDesktopStart(dockerExecutable);
577
+ logger.info("Started Docker Desktop via CLI.");
578
+ return;
579
+ } catch (err) {
580
+ logger.warn("docker desktop start failed, searching for Docker Desktop.exe.");
581
+ const desktopPath = await findWindowsDockerDesktop();
582
+ if (desktopPath === void 0) {
583
+ throw err instanceof Error ? err : new Error(String(err));
584
+ }
585
+ logger.debug(`Launching: ${desktopPath}`);
586
+ launchWindowsExecutable(desktopPath);
587
+ logger.info("Launched Docker Desktop executable.");
588
+ }
589
+ }
590
+ async function startDockerMacOS(logger, dockerExecutable = "docker") {
591
+ try {
592
+ logger.debug("Attempting: docker desktop start");
593
+ await runDockerDesktopStart(dockerExecutable);
594
+ logger.info("Started Docker Desktop via CLI.");
595
+ return;
596
+ } catch {
597
+ logger.warn("docker desktop start failed, trying open -a Docker.");
598
+ await openMacApplication("Docker");
599
+ logger.info("Opened Docker.app.");
600
+ }
601
+ }
602
+ async function startDocker(logger, dockerExecutable = "docker") {
603
+ if (process.platform === "win32") {
604
+ await startDockerWindows(logger, dockerExecutable);
605
+ return;
606
+ }
607
+ if (process.platform === "darwin") {
608
+ await startDockerMacOS(logger, dockerExecutable);
609
+ return;
610
+ }
611
+ await startDockerLinux(logger, dockerExecutable);
612
+ }
613
+
614
+ // src/services/docker/types/Types.ts
615
+ var DefaultEnsureDockerOptions = {
616
+ timeout: 12e4,
617
+ interval: 1e3,
618
+ autoStart: true
619
+ };
620
+
621
+ // src/services/docker/wait/Wait.ts
622
+ init_utils();
623
+ async function waitForDocker(options) {
624
+ const executable = options.executable ?? "docker";
625
+ const logger = options.logger;
626
+ logger?.debug("Waiting for Docker daemon...");
627
+ const ready = await poll(async () => isDockerDaemonReachable(executable), {
628
+ timeout: options.timeout,
629
+ interval: options.interval
630
+ });
631
+ if (!ready) {
632
+ throw new DockerTimeoutError(options.timeout);
633
+ }
634
+ logger?.info("Docker is ready.");
635
+ }
636
+
637
+ // src/services/docker/ensure/Ensure.ts
638
+ async function isDockerRunning() {
639
+ try {
640
+ const result = await detectDocker();
641
+ return result.installed && result.running;
642
+ } catch {
643
+ return false;
644
+ }
645
+ }
646
+ async function ensureDockerRunning(options) {
647
+ const timeout = options?.timeout ?? DefaultEnsureDockerOptions.timeout;
648
+ const interval = options?.interval ?? DefaultEnsureDockerOptions.interval;
649
+ const autoStart = options?.autoStart ?? DefaultEnsureDockerOptions.autoStart;
650
+ const logger = resolveLogger(options?.logger);
651
+ logger.info("Checking Docker installation...");
652
+ const detection = await detectDocker();
653
+ if (!detection.installed) {
654
+ throw new DockerNotInstalledError();
655
+ }
656
+ logger.info("Docker found.");
657
+ if (detection.running) {
658
+ logger.info("Docker daemon is already running.");
659
+ return;
660
+ }
661
+ logger.warn("Docker daemon not running.");
662
+ if (!autoStart) {
663
+ throw new DockerNotRunningError();
664
+ }
665
+ logger.info("Attempting to start Docker...");
666
+ const executable = detection.executable ?? "docker";
667
+ try {
668
+ await startDocker(logger, executable);
669
+ } catch (err) {
670
+ throw new DockerStartError(void 0, {
671
+ cause: err instanceof Error ? err : new Error(String(err))
672
+ });
673
+ }
674
+ await waitForDocker({
675
+ timeout,
676
+ interval,
677
+ executable,
678
+ logger
679
+ });
680
+ }
681
+
682
+ // src/services/docker/service/DockerService.ts
683
+ function parseDockerServiceArgs(argv) {
684
+ const remaining = [...argv];
685
+ let mode = "ENSURE" /* ENSURE */;
686
+ let timeout = DefaultEnsureDockerOptions.timeout;
687
+ let interval = DefaultEnsureDockerOptions.interval;
688
+ let autoStart = DefaultEnsureDockerOptions.autoStart;
689
+ let quiet = false;
690
+ while (remaining.length > 0) {
691
+ const arg = remaining[0];
692
+ if (arg === "--check") {
693
+ mode = "CHECK" /* CHECK */;
694
+ remaining.shift();
695
+ continue;
696
+ }
697
+ if (arg === "-q" || arg === "--quiet") {
698
+ quiet = true;
699
+ remaining.shift();
700
+ continue;
701
+ }
702
+ if (arg === "--no-auto-start" || arg === "--no-start") {
703
+ autoStart = false;
704
+ remaining.shift();
705
+ continue;
706
+ }
707
+ if (arg === "--timeout") {
708
+ remaining.shift();
709
+ timeout = parsePositiveInteger(remaining.shift(), "--timeout");
710
+ continue;
711
+ }
712
+ if (arg === "--interval") {
713
+ remaining.shift();
714
+ interval = parsePositiveInteger(remaining.shift(), "--interval");
715
+ continue;
716
+ }
717
+ if (arg === CommandSeparator) {
718
+ break;
719
+ }
720
+ if (arg.startsWith("-")) {
721
+ throw new Error(`Unknown docker option: ${arg}`);
722
+ }
723
+ break;
724
+ }
725
+ return {
726
+ options: {
727
+ mode,
728
+ timeout,
729
+ interval,
730
+ autoStart,
731
+ quiet
732
+ },
733
+ remaining
734
+ };
735
+ }
736
+ async function runDockerService(options) {
737
+ const logger = options.quiet ? void 0 : createConsoleLogger();
738
+ try {
739
+ if (options.mode === "CHECK" /* CHECK */) {
740
+ const running = await isDockerRunning();
741
+ if (!running && !options.quiet) {
742
+ console.error("Docker is not running.");
743
+ }
744
+ return running ? 0 : 1;
745
+ }
746
+ await ensureDockerRunning({
747
+ timeout: options.timeout,
748
+ interval: options.interval,
749
+ autoStart: options.autoStart,
750
+ logger
751
+ });
752
+ return 0;
753
+ } catch (err) {
754
+ if (!options.quiet) {
755
+ const message = err instanceof Error ? err.message : String(err);
756
+ console.error(message);
757
+ }
758
+ return 1;
759
+ }
760
+ }
761
+ function printDockerServiceHelp() {
762
+ console.log(`docker [options]
763
+
764
+ Ensure Docker is installed, started, and ready.
765
+
766
+ Options:
767
+ --check Exit 0 when the daemon is reachable; do not auto-start
768
+ --timeout <ms> Max wait for daemon readiness (default: ${DefaultEnsureDockerOptions.timeout})
769
+ --interval <ms> Poll interval between checks (default: ${DefaultEnsureDockerOptions.interval})
770
+ --no-auto-start Fail when the daemon is down instead of starting Docker
771
+ -q, --quiet Suppress progress output`);
772
+ }
773
+ var dockerService = defineEnsureService({
774
+ id: "docker",
775
+ aliases: ["d"],
776
+ parseArgs: parseDockerServiceArgs,
777
+ run: runDockerService,
778
+ printHelp: printDockerServiceHelp
779
+ });
780
+ function parsePositiveInteger(value, flag) {
781
+ if (value === void 0) {
782
+ throw new Error(`Missing value for ${flag}`);
783
+ }
784
+ const parsed = Number(value);
785
+ if (!Number.isInteger(parsed) || parsed <= 0) {
786
+ throw new Error(`${flag} expects a positive integer, received: ${value}`);
787
+ }
788
+ return parsed;
789
+ }
790
+ function createConsoleLogger() {
791
+ return {
792
+ info: (message) => {
793
+ console.log(message);
794
+ },
795
+ warn: (message) => {
796
+ console.warn(message);
797
+ },
798
+ debug: (message) => {
799
+ console.debug(message);
800
+ }
801
+ };
802
+ }
803
+
804
+ // src/services/LoadCustomServices.ts
805
+ import { existsSync } from "fs";
806
+ import { readdir } from "fs/promises";
807
+ import path2 from "path";
808
+ import { pathToFileURL } from "url";
809
+
810
+ // src/services/AssertEnsureService.ts
811
+ function assertEnsureService(value, source) {
812
+ if (value === null || typeof value !== "object") {
813
+ throw new Error(`${source}: export default must be an object`);
814
+ }
815
+ const candidate = value;
816
+ if (typeof candidate.id !== "string" || candidate.id.length === 0) {
817
+ throw new Error(`${source}: export default must define a non-empty string "id"`);
818
+ }
819
+ if (typeof candidate.parseArgs !== "function") {
820
+ throw new Error(`${source}: export default must define "parseArgs(argv)"`);
821
+ }
822
+ if (typeof candidate.run !== "function") {
823
+ throw new Error(`${source}: export default must define "run(options)"`);
824
+ }
825
+ if (candidate.printHelp !== void 0 && typeof candidate.printHelp !== "function") {
826
+ throw new Error(`${source}: "printHelp" must be a function when provided`);
827
+ }
828
+ return value;
829
+ }
830
+
831
+ // src/services/LoadCustomServices.ts
832
+ var SERVICE_FILE_PATTERN = /\.(?:[cm]?js|mjs|ts)$/i;
833
+ function findCustomServicesDir(startDir) {
834
+ let current = path2.resolve(startDir);
835
+ while (true) {
836
+ const candidate = path2.join(current, ".er", "services");
837
+ if (existsSync(candidate)) {
838
+ return candidate;
839
+ }
840
+ const parent = path2.dirname(current);
841
+ if (parent === current) {
842
+ return void 0;
843
+ }
844
+ current = parent;
845
+ }
846
+ }
847
+ async function loadCustomServices(registry, cwd = process.cwd()) {
848
+ const dir = findCustomServicesDir(cwd);
849
+ if (dir === void 0) {
850
+ return;
851
+ }
852
+ const entries = await readdir(dir, { withFileTypes: true });
853
+ const files = entries.filter((entry) => entry.isFile() && SERVICE_FILE_PATTERN.test(entry.name)).map((entry) => path2.join(dir, entry.name)).sort();
854
+ for (const filePath of files) {
855
+ const service = await importCustomService(filePath);
856
+ registry.register(service);
857
+ }
858
+ }
859
+ async function importCustomService(filePath) {
860
+ const ext = path2.extname(filePath).toLowerCase();
861
+ let loaded;
862
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
863
+ const { createJiti } = await import("jiti");
864
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
865
+ loaded = jiti(filePath);
866
+ } else {
867
+ const module = await import(pathToFileURL(filePath).href);
868
+ loaded = module.default ?? module;
869
+ }
870
+ return assertEnsureService(loaded, filePath);
871
+ }
872
+
873
+ // src/services/CreateServiceRegistry.ts
874
+ async function createServiceRegistry(cwd = process.cwd()) {
875
+ const registry = createBuiltInServiceRegistry();
876
+ await loadCustomServices(registry, cwd);
877
+ return registry;
878
+ }
879
+ function createBuiltInServiceRegistry() {
880
+ const registry = new ServiceRegistry();
881
+ registry.register(dockerService);
882
+ return registry;
883
+ }
884
+
885
+ // src/cli/Cli.ts
886
+ function readPackageVersion() {
887
+ const packageJsonPath = fileURLToPath(new URL("../../package.json", import.meta.url));
888
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
889
+ return packageJson.version;
890
+ }
891
+ function printCliHelp(registry) {
892
+ const serviceLines = registry.list().map((service) => ` ${service.id.padEnd(10)} ${getServiceSummary(service)}`).join("\n");
893
+ console.log(`Usage: ensure-running | er [service [flags...]]... [--] [command...]
894
+
895
+ Ensure local services are ready, then optionally run a command.
896
+
897
+ Services:
898
+ ${serviceLines}
899
+
900
+ Custom services:
901
+ Place files in .er/services/ with export default defineEnsureService({ ... })
902
+
903
+ Examples:
904
+ er docker
905
+ er docker --check
906
+ er docker postgres -- vite dev
907
+ er docker --timeout 60000 -- npm test
908
+
909
+ Use "--" to separate services from the command you want to run.
910
+
911
+ Global options:
912
+ -h, --help Show this help message
913
+ -v, --version Print CLI version
914
+ service --help Show service-specific help`);
915
+ }
916
+ async function runCli(argv, registry) {
917
+ const resolvedRegistry = registry ?? await createServiceRegistry();
918
+ const normalized = normalizeArgv(argv);
919
+ if (normalized.length === 0 || normalized[0] === "-h" || normalized[0] === "--help") {
920
+ printCliHelp(resolvedRegistry);
921
+ return 0;
922
+ }
923
+ if (normalized[0] === "-v" || normalized[0] === "--version") {
924
+ console.log(readPackageVersion());
925
+ return 0;
926
+ }
927
+ if (normalized.length >= 2 && (normalized[1] === "--help" || normalized[1] === "-h")) {
928
+ const service = resolvedRegistry.resolve(normalized[0] ?? "");
929
+ if (service?.printHelp !== void 0) {
930
+ service.printHelp();
931
+ return 0;
932
+ }
933
+ }
934
+ try {
935
+ const invocation = parseInvocation(normalized, resolvedRegistry);
936
+ if (invocation.services.length === 0 && invocation.command === void 0) {
937
+ printCliHelp(resolvedRegistry);
938
+ return 1;
939
+ }
940
+ return await runInvocation(invocation);
941
+ } catch (err) {
942
+ const message = err instanceof Error ? err.message : String(err);
943
+ console.error(message);
944
+ printCliHelp(resolvedRegistry);
945
+ return 1;
946
+ }
947
+ }
948
+ function getServiceSummary(service) {
949
+ if (service.id === "docker") {
950
+ return "Ensure Docker CLI is installed and the daemon is ready";
951
+ }
952
+ return "Ensure service is ready";
953
+ }
954
+
955
+ // src/bin/ensure-running.ts
956
+ runCli(process.argv).then((exitCode) => {
957
+ process.exit(exitCode);
958
+ });
959
+ //# sourceMappingURL=ensure-running.js.map