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