buncargo 1.0.26 → 1.0.29

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/bin.ts CHANGED
@@ -186,6 +186,7 @@ async function main(): Promise<void> {
186
186
  }
187
187
 
188
188
  main().catch((error) => {
189
- console.error("❌ Fatal error:", error);
189
+ const message = error instanceof Error ? error.message : String(error);
190
+ console.error(`❌ ${message}`);
190
191
  process.exit(1);
191
192
  });
package/core/docker.ts CHANGED
@@ -12,6 +12,8 @@ import { sleep } from "./utils";
12
12
 
13
13
  export const POLL_INTERVAL = 250; // Fast polling for quicker startup
14
14
  export const MAX_ATTEMPTS = 120; // 30 seconds total (120 * 250ms)
15
+ export const DOCKER_NOT_RUNNING_MESSAGE =
16
+ "Docker is not running. Please start Docker and try again.";
15
17
 
16
18
  // ═══════════════════════════════════════════════════════════════════════════
17
19
  // Container Status Checks
@@ -35,6 +37,30 @@ export async function isContainerRunning(
35
37
  }
36
38
  }
37
39
 
40
+ /**
41
+ * Check if Docker daemon is running and reachable.
42
+ */
43
+ export function isDockerRunning(): boolean {
44
+ try {
45
+ execSync('docker info --format "{{.ServerVersion}}"', {
46
+ encoding: "utf-8",
47
+ stdio: ["pipe", "pipe", "pipe"],
48
+ });
49
+ return true;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Ensure Docker is running before attempting compose operations.
57
+ */
58
+ export function assertDockerRunning(): void {
59
+ if (!isDockerRunning()) {
60
+ throw new Error(DOCKER_NOT_RUNNING_MESSAGE);
61
+ }
62
+ }
63
+
38
64
  /**
39
65
  * Check if all expected containers are running.
40
66
  */
@@ -75,6 +101,7 @@ export function startContainers(
75
101
  options: StartContainersOptions = {},
76
102
  ): void {
77
103
  const { verbose = true, wait = true, composeFile } = options;
104
+ assertDockerRunning();
78
105
 
79
106
  if (verbose) console.log("🐳 Starting Docker containers...");
80
107
 
@@ -106,6 +133,7 @@ export function stopContainers(
106
133
  options: StopContainersOptions = {},
107
134
  ): void {
108
135
  const { verbose = true, removeVolumes = false, composeFile } = options;
136
+ assertDockerRunning();
109
137
 
110
138
  if (verbose) {
111
139
  console.log(
@@ -139,6 +167,7 @@ export function startService(
139
167
  options: { verbose?: boolean; composeFile?: string } = {},
140
168
  ): void {
141
169
  const { verbose = true, composeFile } = options;
170
+ assertDockerRunning();
142
171
 
143
172
  if (verbose) console.log(`🐳 Starting ${serviceName}...`);
144
173
 
package/core/ports.ts CHANGED
@@ -57,6 +57,28 @@ export function isWorktree(root?: string): boolean {
57
57
  return getWorktreeName(root) !== null;
58
58
  }
59
59
 
60
+ /**
61
+ * Sanitize a string for use as a Docker Compose project suffix.
62
+ */
63
+ function sanitizeProjectSuffix(value: string): string {
64
+ return value
65
+ .toLowerCase()
66
+ .replace(/[^a-z0-9-]/g, "-")
67
+ .replace(/-+/g, "-")
68
+ .replace(/^-+|-+$/g, "");
69
+ }
70
+
71
+ /**
72
+ * Get a sanitized worktree-derived suffix for Docker project isolation.
73
+ * Returns null when not running in a worktree.
74
+ */
75
+ export function getWorktreeProjectSuffix(root?: string): string | null {
76
+ const worktreeName = getWorktreeName(root);
77
+ if (!worktreeName) return null;
78
+ const sanitized = sanitizeProjectSuffix(worktreeName);
79
+ return sanitized || "worktree";
80
+ }
81
+
60
82
  // ═══════════════════════════════════════════════════════════════════════════
61
83
  // Port Offset Calculation
62
84
  // ═══════════════════════════════════════════════════════════════════════════
@@ -104,6 +126,48 @@ export function getProjectName(
104
126
  return suffix ? `${baseName}-${suffix}` : baseName;
105
127
  }
106
128
 
129
+ export interface DevIdentityOptions {
130
+ projectPrefix: string;
131
+ suffix?: string;
132
+ root?: string;
133
+ worktreeIsolation?: boolean;
134
+ }
135
+
136
+ export interface DevIdentity {
137
+ worktree: boolean;
138
+ worktreeSuffix: string | null;
139
+ projectSuffix?: string;
140
+ projectName: string;
141
+ portOffset: number;
142
+ }
143
+
144
+ /**
145
+ * Compute all identity values used by the dev environment in one place.
146
+ */
147
+ export function computeDevIdentity(options: DevIdentityOptions): DevIdentity {
148
+ const {
149
+ projectPrefix,
150
+ suffix,
151
+ root: providedRoot,
152
+ worktreeIsolation = true,
153
+ } = options;
154
+ const root = providedRoot ?? findMonorepoRoot();
155
+ const worktree = isWorktree(root);
156
+ const worktreeSuffix =
157
+ worktree && worktreeIsolation ? getWorktreeProjectSuffix(root) : null;
158
+ const projectSuffix = [suffix, worktreeSuffix].filter(Boolean).join("-") || undefined;
159
+ const projectName = getProjectName(projectPrefix, projectSuffix, root);
160
+ const portOffset = calculatePortOffset(suffix, root);
161
+
162
+ return {
163
+ worktree,
164
+ worktreeSuffix,
165
+ projectSuffix,
166
+ projectName,
167
+ portOffset,
168
+ };
169
+ }
170
+
107
171
  // ═══════════════════════════════════════════════════════════════════════════
108
172
  // Port Computation
109
173
  // ═══════════════════════════════════════════════════════════════════════════
package/dist/bin.js CHANGED
@@ -4,15 +4,15 @@ import {
4
4
  } from "./index-segbnm0h.js";
5
5
  import {
6
6
  loadDevEnv
7
- } from "./index-42x95209.js";
8
- import"./index-p8wty0e2.js";
7
+ } from "./index-1mdrf7nz.js";
8
+ import"./index-wk2na3t9.js";
9
9
  import"./index-ggq3yryx.js";
10
10
  import"./index-1yvbwj4k.js";
11
11
  import"./index-tjqw9vtj.js";
12
- import"./index-5hka0tff.js";
13
- import"./index-2fr3g85b.js";
14
- import"./index-6fm7mvwj.js";
15
- import"./index-08wa79cs.js";
12
+ import"./index-qfphr2fd.js";
13
+ import"./index-zfjzzjkf.js";
14
+ import"./index-8xj2p5n5.js";
15
+ import"./index-731rzzfp.js";
16
16
  import {
17
17
  __commonJS,
18
18
  __require
@@ -22,7 +22,7 @@ import {
22
22
  var require_package = __commonJS((exports, module) => {
23
23
  module.exports = {
24
24
  name: "buncargo",
25
- version: "1.0.26",
25
+ version: "1.0.29",
26
26
  description: "A Bun-powered development environment CLI for managing Docker Compose services, dev servers, and environment variables",
27
27
  type: "module",
28
28
  module: "./dist/index.js",
@@ -295,6 +295,7 @@ async function main() {
295
295
  }
296
296
  }
297
297
  main().catch((error) => {
298
- console.error("❌ Fatal error:", error);
298
+ const message = error instanceof Error ? error.message : String(error);
299
+ console.error(`❌ ${message}`);
299
300
  process.exit(1);
300
301
  });
@@ -1,10 +1,19 @@
1
1
  import type { BuiltInHealthCheck, HealthCheckFn, ServiceConfig } from "../types";
2
2
  export declare const POLL_INTERVAL = 250;
3
3
  export declare const MAX_ATTEMPTS = 120;
4
+ export declare const DOCKER_NOT_RUNNING_MESSAGE = "Docker is not running. Please start Docker and try again.";
4
5
  /**
5
6
  * Check if a specific container service is running using docker ps.
6
7
  */
7
8
  export declare function isContainerRunning(project: string, service: string): Promise<boolean>;
9
+ /**
10
+ * Check if Docker daemon is running and reachable.
11
+ */
12
+ export declare function isDockerRunning(): boolean;
13
+ /**
14
+ * Ensure Docker is running before attempting compose operations.
15
+ */
16
+ export declare function assertDockerRunning(): void;
8
17
  /**
9
18
  * Check if all expected containers are running.
10
19
  */
@@ -1,18 +1,21 @@
1
1
  import {
2
+ DOCKER_NOT_RUNNING_MESSAGE,
2
3
  MAX_ATTEMPTS,
3
4
  POLL_INTERVAL,
4
5
  areContainersRunning,
6
+ assertDockerRunning,
5
7
  createBuiltInHealthCheck,
6
8
  isContainerRunning,
9
+ isDockerRunning,
7
10
  startContainers,
8
11
  startService,
9
12
  stopContainers,
10
13
  waitForAllServices,
11
14
  waitForService,
12
15
  waitForServiceByType
13
- } from "../index-2fr3g85b.js";
14
- import"../index-6fm7mvwj.js";
15
- import"../index-08wa79cs.js";
16
+ } from "../index-zfjzzjkf.js";
17
+ import"../index-8xj2p5n5.js";
18
+ import"../index-731rzzfp.js";
16
19
  import"../index-qnx9j3qa.js";
17
20
  export {
18
21
  waitForServiceByType,
@@ -21,9 +24,12 @@ export {
21
24
  stopContainers,
22
25
  startService,
23
26
  startContainers,
27
+ isDockerRunning,
24
28
  isContainerRunning,
25
29
  createBuiltInHealthCheck,
30
+ assertDockerRunning,
26
31
  areContainersRunning,
27
32
  POLL_INTERVAL,
28
- MAX_ATTEMPTS
33
+ MAX_ATTEMPTS,
34
+ DOCKER_NOT_RUNNING_MESSAGE
29
35
  };
@@ -26,18 +26,21 @@ import {
26
26
  stopProcess
27
27
  } from "../index-1yvbwj4k.js";
28
28
  import {
29
+ DOCKER_NOT_RUNNING_MESSAGE,
29
30
  MAX_ATTEMPTS,
30
31
  POLL_INTERVAL,
31
32
  areContainersRunning,
33
+ assertDockerRunning,
32
34
  createBuiltInHealthCheck,
33
35
  isContainerRunning,
36
+ isDockerRunning,
34
37
  startContainers,
35
38
  startService,
36
39
  stopContainers,
37
40
  waitForAllServices,
38
41
  waitForService,
39
42
  waitForServiceByType
40
- } from "../index-2fr3g85b.js";
43
+ } from "../index-zfjzzjkf.js";
41
44
  import {
42
45
  getEnvVar,
43
46
  getLocalIp,
@@ -49,16 +52,18 @@ import {
49
52
  sleep,
50
53
  waitForDevServers,
51
54
  waitForServer
52
- } from "../index-6fm7mvwj.js";
55
+ } from "../index-8xj2p5n5.js";
53
56
  import {
54
57
  calculatePortOffset,
58
+ computeDevIdentity,
55
59
  computePorts,
56
60
  computeUrls,
57
61
  findMonorepoRoot,
58
62
  getProjectName,
59
63
  getWorktreeName,
64
+ getWorktreeProjectSuffix,
60
65
  isWorktree
61
- } from "../index-08wa79cs.js";
66
+ } from "../index-731rzzfp.js";
62
67
  import"../index-qnx9j3qa.js";
63
68
  export {
64
69
  waitForServiceByType,
@@ -91,8 +96,10 @@ export {
91
96
  isProcessAlive,
92
97
  isPortInUse,
93
98
  isPortAvailable,
99
+ isDockerRunning,
94
100
  isContainerRunning,
95
101
  isCI,
102
+ getWorktreeProjectSuffix,
96
103
  getWorktreeName,
97
104
  getWatchdogPidFile,
98
105
  getWatchdogPid,
@@ -107,9 +114,12 @@ export {
107
114
  createBuiltInHealthCheck,
108
115
  computeUrls,
109
116
  computePorts,
117
+ computeDevIdentity,
110
118
  calculatePortOffset,
111
119
  buildApps,
120
+ assertDockerRunning,
112
121
  areContainersRunning,
113
122
  POLL_INTERVAL,
114
- MAX_ATTEMPTS
123
+ MAX_ATTEMPTS,
124
+ DOCKER_NOT_RUNNING_MESSAGE
115
125
  };
@@ -3,8 +3,8 @@ import {
3
3
  isPortAvailable,
4
4
  waitForDevServers,
5
5
  waitForServer
6
- } from "../index-6fm7mvwj.js";
7
- import"../index-08wa79cs.js";
6
+ } from "../index-8xj2p5n5.js";
7
+ import"../index-731rzzfp.js";
8
8
  import"../index-qnx9j3qa.js";
9
9
  export {
10
10
  waitForServer,
@@ -11,6 +11,11 @@ export declare function getWorktreeName(root?: string): string | null;
11
11
  * Check if the current directory is a git worktree.
12
12
  */
13
13
  export declare function isWorktree(root?: string): boolean;
14
+ /**
15
+ * Get a sanitized worktree-derived suffix for Docker project isolation.
16
+ * Returns null when not running in a worktree.
17
+ */
18
+ export declare function getWorktreeProjectSuffix(root?: string): string | null;
14
19
  /**
15
20
  * Calculate port offset based on worktree name and optional suffix.
16
21
  * Returns 0 for main branch, 10-99 for worktrees.
@@ -20,6 +25,23 @@ export declare function calculatePortOffset(suffix?: string, root?: string): num
20
25
  * Generate Docker project name from prefix and directory.
21
26
  */
22
27
  export declare function getProjectName(prefix: string, suffix?: string, root?: string): string;
28
+ export interface DevIdentityOptions {
29
+ projectPrefix: string;
30
+ suffix?: string;
31
+ root?: string;
32
+ worktreeIsolation?: boolean;
33
+ }
34
+ export interface DevIdentity {
35
+ worktree: boolean;
36
+ worktreeSuffix: string | null;
37
+ projectSuffix?: string;
38
+ projectName: string;
39
+ portOffset: number;
40
+ }
41
+ /**
42
+ * Compute all identity values used by the dev environment in one place.
43
+ */
44
+ export declare function computeDevIdentity(options: DevIdentityOptions): DevIdentity;
23
45
  /**
24
46
  * Compute all ports for services and apps with offset applied.
25
47
  */
@@ -1,19 +1,23 @@
1
1
  import {
2
2
  calculatePortOffset,
3
+ computeDevIdentity,
3
4
  computePorts,
4
5
  computeUrls,
5
6
  findMonorepoRoot,
6
7
  getProjectName,
7
8
  getWorktreeName,
9
+ getWorktreeProjectSuffix,
8
10
  isWorktree
9
- } from "../index-08wa79cs.js";
11
+ } from "../index-731rzzfp.js";
10
12
  import"../index-qnx9j3qa.js";
11
13
  export {
12
14
  isWorktree,
15
+ getWorktreeProjectSuffix,
13
16
  getWorktreeName,
14
17
  getProjectName,
15
18
  findMonorepoRoot,
16
19
  computeUrls,
17
20
  computePorts,
21
+ computeDevIdentity,
18
22
  calculatePortOffset
19
23
  };
@@ -5,8 +5,8 @@ import {
5
5
  logExpoApiUrl,
6
6
  logFrontendPort,
7
7
  sleep
8
- } from "../index-6fm7mvwj.js";
9
- import"../index-08wa79cs.js";
8
+ } from "../index-8xj2p5n5.js";
9
+ import"../index-731rzzfp.js";
10
10
  import"../index-qnx9j3qa.js";
11
11
  export {
12
12
  sleep,
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createDevEnvironment
3
- } from "./index-p8wty0e2.js";
3
+ } from "./index-wk2na3t9.js";
4
4
  import"./index-ggq3yryx.js";
5
5
  import"./index-1yvbwj4k.js";
6
6
  import"./index-tjqw9vtj.js";
7
- import"./index-5hka0tff.js";
8
- import"./index-2fr3g85b.js";
9
- import"./index-6fm7mvwj.js";
10
- import"./index-08wa79cs.js";
7
+ import"./index-qfphr2fd.js";
8
+ import"./index-zfjzzjkf.js";
9
+ import"./index-8xj2p5n5.js";
10
+ import"./index-731rzzfp.js";
11
11
  import"./index-qnx9j3qa.js";
12
12
  export {
13
13
  createDevEnvironment
@@ -0,0 +1,58 @@
1
+ import {
2
+ createDevEnvironment
3
+ } from "./index-wk2na3t9.js";
4
+
5
+ // loader.ts
6
+ import { existsSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ var CONFIG_FILES = [
9
+ "dev.config.ts",
10
+ "dev.config.js",
11
+ "dev-tools.config.ts",
12
+ "dev-tools.config.js"
13
+ ];
14
+ function findConfigFile(startDir) {
15
+ let currentDir = startDir;
16
+ while (true) {
17
+ for (const file of CONFIG_FILES) {
18
+ const configPath = join(currentDir, file);
19
+ if (existsSync(configPath)) {
20
+ return configPath;
21
+ }
22
+ }
23
+ const parentDir = dirname(currentDir);
24
+ if (parentDir === currentDir) {
25
+ return null;
26
+ }
27
+ currentDir = parentDir;
28
+ }
29
+ }
30
+ var cachedEnv = null;
31
+ async function loadDevEnv(options) {
32
+ if (cachedEnv && !options?.reload) {
33
+ return cachedEnv;
34
+ }
35
+ const cwd = options?.cwd ?? process.cwd();
36
+ const configPath = findConfigFile(cwd);
37
+ if (configPath) {
38
+ const mod = await import(configPath);
39
+ const config = mod.default;
40
+ if (!config?.projectPrefix || !config?.services) {
41
+ throw new Error(`Invalid config in "${configPath}". Use defineDevConfig() and export as default.`);
42
+ }
43
+ cachedEnv = createDevEnvironment(config);
44
+ return cachedEnv;
45
+ }
46
+ throw new Error(`No config file found. Create dev.config.ts with: export default defineDevConfig({ ... })`);
47
+ }
48
+ function getDevEnv() {
49
+ if (!cachedEnv) {
50
+ throw new Error("Dev environment not loaded. Call loadDevEnv() first.");
51
+ }
52
+ return cachedEnv;
53
+ }
54
+ function clearDevEnvCache() {
55
+ cachedEnv = null;
56
+ }
57
+
58
+ export { CONFIG_FILES, findConfigFile, loadDevEnv, getDevEnv, clearDevEnvCache };
@@ -0,0 +1,172 @@
1
+ // core/ports.ts
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ function findMonorepoRoot(startDir) {
5
+ let dir = startDir ?? process.cwd();
6
+ while (dir !== "/") {
7
+ try {
8
+ const pkgPath = resolve(dir, "package.json");
9
+ if (existsSync(pkgPath)) {
10
+ const content = readFileSync(pkgPath, "utf-8");
11
+ const pkg = JSON.parse(content);
12
+ if (pkg.workspaces) {
13
+ return dir;
14
+ }
15
+ }
16
+ } catch {}
17
+ dir = dirname(dir);
18
+ }
19
+ return process.cwd();
20
+ }
21
+ function getWorktreeName(root) {
22
+ const monorepoRoot = root ?? findMonorepoRoot();
23
+ const gitPath = resolve(monorepoRoot, ".git");
24
+ try {
25
+ if (!existsSync(gitPath) || !statSync(gitPath).isFile())
26
+ return null;
27
+ const content = readFileSync(gitPath, "utf-8").trim();
28
+ const match = content.match(/^gitdir:\s*(.+)$/);
29
+ if (!match?.[1])
30
+ return null;
31
+ return basename(match[1]);
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ function isWorktree(root) {
37
+ return getWorktreeName(root) !== null;
38
+ }
39
+ function sanitizeProjectSuffix(value) {
40
+ return value.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
41
+ }
42
+ function getWorktreeProjectSuffix(root) {
43
+ const worktreeName = getWorktreeName(root);
44
+ if (!worktreeName)
45
+ return null;
46
+ const sanitized = sanitizeProjectSuffix(worktreeName);
47
+ return sanitized || "worktree";
48
+ }
49
+ function simpleHash(str) {
50
+ let hash = 0;
51
+ for (let i = 0;i < str.length; i++) {
52
+ const char = str.charCodeAt(i);
53
+ hash = (hash << 5) - hash + char;
54
+ hash = hash & hash;
55
+ }
56
+ return Math.abs(hash);
57
+ }
58
+ function calculatePortOffset(suffix, root) {
59
+ const worktreeName = getWorktreeName(root);
60
+ if (!worktreeName)
61
+ return 0;
62
+ const hashInput = suffix ? `${worktreeName}-${suffix}` : worktreeName;
63
+ return 10 + simpleHash(hashInput) % 90;
64
+ }
65
+ function getProjectName(prefix, suffix, root) {
66
+ const monorepoRoot = root ?? findMonorepoRoot();
67
+ const dirName = basename(monorepoRoot);
68
+ const baseName = `${prefix}-${dirName.toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
69
+ return suffix ? `${baseName}-${suffix}` : baseName;
70
+ }
71
+ function computeDevIdentity(options) {
72
+ const {
73
+ projectPrefix,
74
+ suffix,
75
+ root: providedRoot,
76
+ worktreeIsolation = true
77
+ } = options;
78
+ const root = providedRoot ?? findMonorepoRoot();
79
+ const worktree = isWorktree(root);
80
+ const worktreeSuffix = worktree && worktreeIsolation ? getWorktreeProjectSuffix(root) : null;
81
+ const projectSuffix = [suffix, worktreeSuffix].filter(Boolean).join("-") || undefined;
82
+ const projectName = getProjectName(projectPrefix, projectSuffix, root);
83
+ const portOffset = calculatePortOffset(suffix, root);
84
+ return {
85
+ worktree,
86
+ worktreeSuffix,
87
+ projectSuffix,
88
+ projectName,
89
+ portOffset
90
+ };
91
+ }
92
+ function computePorts(services, apps, offset) {
93
+ const ports = {};
94
+ for (const [name, config] of Object.entries(services)) {
95
+ ports[name] = config.port + offset;
96
+ if (config.secondaryPort) {
97
+ ports[`${name}Secondary`] = config.secondaryPort + offset;
98
+ }
99
+ }
100
+ if (apps) {
101
+ for (const [name, config] of Object.entries(apps)) {
102
+ ports[name] = config.port + offset;
103
+ }
104
+ }
105
+ return ports;
106
+ }
107
+ var SERVICE_DEFAULTS = {
108
+ postgres: { user: "postgres", password: "postgres", database: "postgres" },
109
+ postgresql: { user: "postgres", password: "postgres", database: "postgres" },
110
+ redis: { user: "", password: "", database: "" },
111
+ clickhouse: { user: "default", password: "clickhouse", database: "default" },
112
+ mysql: { user: "root", password: "root", database: "mysql" },
113
+ mongodb: { user: "", password: "", database: "" }
114
+ };
115
+ function buildServiceUrl(serviceName, ctx, config) {
116
+ const defaults = SERVICE_DEFAULTS[serviceName];
117
+ if (!defaults && !config.database)
118
+ return null;
119
+ const user = config.user ?? defaults?.user ?? "";
120
+ const password = config.password ?? defaults?.password ?? "";
121
+ const database = config.database ?? defaults?.database ?? "";
122
+ switch (serviceName) {
123
+ case "postgres":
124
+ case "postgresql":
125
+ return `postgresql://${user}:${password}@${ctx.host}:${ctx.port}/${database}`;
126
+ case "redis":
127
+ return `redis://${ctx.host}:${ctx.port}`;
128
+ case "clickhouse":
129
+ return `http://${user}:${password}@${ctx.host}:${ctx.port}/${database}`;
130
+ case "mysql":
131
+ return `mysql://${user}:${password}@${ctx.host}:${ctx.port}/${database}`;
132
+ case "mongodb":
133
+ return `mongodb://${ctx.host}:${ctx.port}/${database}`;
134
+ default:
135
+ return null;
136
+ }
137
+ }
138
+ function computeUrls(services, apps, ports, localIp) {
139
+ const urls = {};
140
+ const host = "localhost";
141
+ for (const [name, config] of Object.entries(services)) {
142
+ const port = ports[name];
143
+ const secondaryPort = ports[`${name}Secondary`];
144
+ if (port === undefined)
145
+ continue;
146
+ const ctx = { port, secondaryPort, host, localIp };
147
+ if (config.urlTemplate) {
148
+ urls[name] = config.urlTemplate(ctx);
149
+ } else {
150
+ const builtUrl = buildServiceUrl(name, { port, host }, {
151
+ database: config.database,
152
+ user: config.user,
153
+ password: config.password
154
+ });
155
+ if (builtUrl) {
156
+ urls[name] = builtUrl;
157
+ } else {
158
+ urls[name] = `http://${host}:${port}`;
159
+ }
160
+ }
161
+ }
162
+ if (apps) {
163
+ for (const [name, _config] of Object.entries(apps)) {
164
+ const port = ports[name];
165
+ urls[name] = `http://${host}:${port}`;
166
+ urls[`${name}Local`] = `http://${localIp}:${port}`;
167
+ }
168
+ }
169
+ return urls;
170
+ }
171
+
172
+ export { findMonorepoRoot, getWorktreeName, isWorktree, getWorktreeProjectSuffix, calculatePortOffset, getProjectName, computeDevIdentity, computePorts, computeUrls };