buncargo 1.0.5

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,111 @@
1
+ /**
2
+ * Watchdog Runner
3
+ *
4
+ * Monitors heartbeat file and shuts down containers after inactivity.
5
+ * Spawned as a detached process by the dev environment.
6
+ *
7
+ * Environment variables:
8
+ * WATCHDOG_PROJECT_NAME - Docker project name
9
+ * WATCHDOG_HEARTBEAT_FILE - Path to heartbeat file
10
+ * WATCHDOG_PID_FILE - Path to PID file
11
+ * WATCHDOG_TIMEOUT_MS - Idle timeout in milliseconds
12
+ * WATCHDOG_COMPOSE_ARG - Optional docker compose argument (e.g., "-f compose.yml")
13
+ */
14
+
15
+ import { execSync } from "node:child_process";
16
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
17
+
18
+ // Configuration from environment
19
+ const PROJECT_NAME = process.env.WATCHDOG_PROJECT_NAME ?? "";
20
+ const HEARTBEAT_FILE = process.env.WATCHDOG_HEARTBEAT_FILE ?? "";
21
+ const PID_FILE = process.env.WATCHDOG_PID_FILE ?? "";
22
+ const IDLE_TIMEOUT = parseInt(process.env.WATCHDOG_TIMEOUT_MS ?? "600000", 10);
23
+ const COMPOSE_ARG = process.env.WATCHDOG_COMPOSE_ARG ?? "";
24
+ const CHECK_INTERVAL = 60_000; // Check every minute
25
+
26
+ // Validate configuration
27
+ if (!PROJECT_NAME || !HEARTBEAT_FILE || !PID_FILE) {
28
+ console.error("[watchdog] Missing required environment variables");
29
+ process.exit(1);
30
+ }
31
+
32
+ // Type assertion after validation
33
+ const heartbeatFile: string = HEARTBEAT_FILE;
34
+ const pidFile: string = PID_FILE;
35
+
36
+ // Write PID file
37
+ writeFileSync(pidFile, process.pid.toString());
38
+
39
+ // Cleanup function
40
+ function cleanup(): void {
41
+ try {
42
+ unlinkSync(pidFile);
43
+ } catch {
44
+ // File may not exist
45
+ }
46
+ try {
47
+ unlinkSync(heartbeatFile);
48
+ } catch {
49
+ // File may not exist
50
+ }
51
+ }
52
+
53
+ // Handle signals
54
+ process.on("SIGTERM", () => {
55
+ cleanup();
56
+ process.exit(0);
57
+ });
58
+
59
+ process.on("SIGINT", () => {
60
+ cleanup();
61
+ process.exit(0);
62
+ });
63
+
64
+ console.log(`[watchdog] Started for ${PROJECT_NAME} (PID: ${process.pid})`);
65
+ console.log(`[watchdog] Idle timeout: ${IDLE_TIMEOUT / 60000} minutes`);
66
+
67
+ // Main watchdog loop
68
+ async function watchdog(): Promise<void> {
69
+ while (true) {
70
+ await new Promise((r) => setTimeout(r, CHECK_INTERVAL));
71
+
72
+ // Check if heartbeat file exists
73
+ if (!existsSync(heartbeatFile)) {
74
+ continue;
75
+ }
76
+
77
+ // Read last heartbeat timestamp
78
+ let lastBeat: number;
79
+ try {
80
+ const content = readFileSync(heartbeatFile, "utf-8");
81
+ lastBeat = parseInt(content, 10);
82
+ } catch {
83
+ continue;
84
+ }
85
+
86
+ if (Number.isNaN(lastBeat)) {
87
+ continue;
88
+ }
89
+
90
+ const elapsed = Date.now() - lastBeat;
91
+
92
+ if (elapsed > IDLE_TIMEOUT) {
93
+ console.log(
94
+ `[watchdog] No heartbeat for ${Math.ceil(elapsed / 60000)} minutes, shutting down...`,
95
+ );
96
+ try {
97
+ execSync(`docker compose ${COMPOSE_ARG} down`.trim(), {
98
+ env: { ...process.env, COMPOSE_PROJECT_NAME: PROJECT_NAME },
99
+ stdio: "ignore",
100
+ });
101
+ } catch {
102
+ // Ignore errors
103
+ }
104
+ console.log("[watchdog] Containers stopped");
105
+ cleanup();
106
+ process.exit(0);
107
+ }
108
+ }
109
+ }
110
+
111
+ watchdog();
@@ -0,0 +1,196 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { isProcessAlive } from "./process";
4
+
5
+ // ═══════════════════════════════════════════════════════════════════════════
6
+ // File Paths
7
+ // ═══════════════════════════════════════════════════════════════════════════
8
+
9
+ /**
10
+ * Get the heartbeat file path for a project.
11
+ */
12
+ export function getHeartbeatFile(projectName: string): string {
13
+ return `/tmp/${projectName}-heartbeat`;
14
+ }
15
+
16
+ /**
17
+ * Get the watchdog PID file path for a project.
18
+ */
19
+ export function getWatchdogPidFile(projectName: string): string {
20
+ return `/tmp/${projectName}-watchdog.pid`;
21
+ }
22
+
23
+ // ═══════════════════════════════════════════════════════════════════════════
24
+ // Heartbeat
25
+ // ═══════════════════════════════════════════════════════════════════════════
26
+
27
+ let heartbeatInterval: ReturnType<typeof setInterval> | null = null;
28
+
29
+ /**
30
+ * Start writing heartbeat to file.
31
+ * The heartbeat is used by the watchdog to detect idle state.
32
+ */
33
+ export function startHeartbeat(projectName: string, intervalMs = 30_000): void {
34
+ const heartbeatFile = getHeartbeatFile(projectName);
35
+
36
+ // Write initial heartbeat
37
+ writeFileSync(heartbeatFile, Date.now().toString());
38
+
39
+ // Update heartbeat at interval
40
+ heartbeatInterval = setInterval(() => {
41
+ writeFileSync(heartbeatFile, Date.now().toString());
42
+ }, intervalMs);
43
+ }
44
+
45
+ /**
46
+ * Stop writing heartbeat.
47
+ */
48
+ export function stopHeartbeat(): void {
49
+ if (heartbeatInterval) {
50
+ clearInterval(heartbeatInterval);
51
+ heartbeatInterval = null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Read the last heartbeat timestamp.
57
+ */
58
+ export function readHeartbeat(projectName: string): number | null {
59
+ const heartbeatFile = getHeartbeatFile(projectName);
60
+ try {
61
+ if (!existsSync(heartbeatFile)) return null;
62
+ const content = readFileSync(heartbeatFile, "utf-8");
63
+ const timestamp = parseInt(content, 10);
64
+ return Number.isNaN(timestamp) ? null : timestamp;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Remove the heartbeat file.
72
+ */
73
+ export function removeHeartbeatFile(projectName: string): void {
74
+ const heartbeatFile = getHeartbeatFile(projectName);
75
+ try {
76
+ unlinkSync(heartbeatFile);
77
+ } catch {
78
+ // File may not exist
79
+ }
80
+ }
81
+
82
+ // ═══════════════════════════════════════════════════════════════════════════
83
+ // Watchdog
84
+ // ═══════════════════════════════════════════════════════════════════════════
85
+
86
+ /**
87
+ * Check if watchdog is already running.
88
+ */
89
+ export function isWatchdogRunning(projectName: string): boolean {
90
+ const pidFile = getWatchdogPidFile(projectName);
91
+ try {
92
+ if (!existsSync(pidFile)) return false;
93
+ const content = readFileSync(pidFile, "utf-8");
94
+ const pid = parseInt(content, 10);
95
+ if (Number.isNaN(pid)) return false;
96
+ return isProcessAlive(pid);
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Get the watchdog PID if running.
104
+ */
105
+ export function getWatchdogPid(projectName: string): number | null {
106
+ const pidFile = getWatchdogPidFile(projectName);
107
+ try {
108
+ if (!existsSync(pidFile)) return null;
109
+ const content = readFileSync(pidFile, "utf-8");
110
+ const pid = parseInt(content, 10);
111
+ if (Number.isNaN(pid)) return null;
112
+ if (!isProcessAlive(pid)) return null;
113
+ return pid;
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Spawn watchdog as a detached process.
121
+ * The watchdog monitors the heartbeat file and shuts down containers after idle timeout.
122
+ */
123
+ export async function spawnWatchdog(
124
+ projectName: string,
125
+ root: string,
126
+ options: {
127
+ timeoutMinutes?: number;
128
+ verbose?: boolean;
129
+ composeFile?: string;
130
+ } = {},
131
+ ): Promise<void> {
132
+ const { timeoutMinutes = 10, verbose = true, composeFile } = options;
133
+
134
+ // Check if watchdog is already running
135
+ const existingPid = getWatchdogPid(projectName);
136
+ if (existingPid) {
137
+ if (verbose)
138
+ console.log(`✓ Watchdog already running (PID: ${existingPid})`);
139
+ return;
140
+ }
141
+
142
+ // Remove stale PID file if exists
143
+ const pidFile = getWatchdogPidFile(projectName);
144
+ try {
145
+ unlinkSync(pidFile);
146
+ } catch {
147
+ // File may not exist
148
+ }
149
+
150
+ // Get the path to the watchdog runner script
151
+ const watchdogScript = new URL("./watchdog-runner.ts", import.meta.url)
152
+ .pathname;
153
+
154
+ // Spawn watchdog as a separate process
155
+ const proc = spawn("bun", ["run", watchdogScript], {
156
+ cwd: root,
157
+ detached: true,
158
+ stdio: "ignore",
159
+ env: {
160
+ ...process.env,
161
+ WATCHDOG_PROJECT_NAME: projectName,
162
+ WATCHDOG_HEARTBEAT_FILE: getHeartbeatFile(projectName),
163
+ WATCHDOG_PID_FILE: pidFile,
164
+ WATCHDOG_TIMEOUT_MS: String(timeoutMinutes * 60 * 1000),
165
+ WATCHDOG_COMPOSE_ARG: composeFile ? `-f ${composeFile}` : "",
166
+ },
167
+ });
168
+
169
+ proc.unref();
170
+
171
+ if (verbose && proc.pid) {
172
+ console.log(`✓ Watchdog started (PID: ${proc.pid})`);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Stop the watchdog process.
178
+ */
179
+ export function stopWatchdog(projectName: string): void {
180
+ const pid = getWatchdogPid(projectName);
181
+ if (pid) {
182
+ try {
183
+ process.kill(pid, "SIGTERM");
184
+ } catch {
185
+ // Process may already be dead
186
+ }
187
+ }
188
+
189
+ // Clean up files
190
+ const pidFile = getWatchdogPidFile(projectName);
191
+ try {
192
+ unlinkSync(pidFile);
193
+ } catch {
194
+ // File may not exist
195
+ }
196
+ }
package/dist/bin.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI Entry Point for buncargo
4
+ *
5
+ * Usage:
6
+ * bunx buncargo dev # Start containers + dev servers
7
+ * bunx buncargo dev --down # Stop containers
8
+ * bunx buncargo dev --reset # Stop + remove volumes
9
+ * bunx buncargo prisma ... # Run prisma commands
10
+ * bunx buncargo help # Show help
11
+ */
12
+ export {};
package/dist/cli.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { AppConfig, CliOptions, DevEnvironment, ServiceConfig } from "./types";
2
+ /**
3
+ * Run the CLI for a dev environment.
4
+ * Handles common flags like --down, --reset, --up-only, --migrate, --seed, --lint.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { dev } from './dev.config'
9
+ * import { runCli } from 'buncargo'
10
+ *
11
+ * await runCli(dev)
12
+ * ```
13
+ */
14
+ export declare function runCli<TServices extends Record<string, ServiceConfig>, TApps extends Record<string, AppConfig>>(env: DevEnvironment<TServices, TApps>, options?: CliOptions): Promise<void>;
15
+ /**
16
+ * Check if a CLI flag is present.
17
+ */
18
+ export declare function hasFlag(args: string[], flag: string): boolean;
19
+ /**
20
+ * Get a flag value (e.g., --timeout=10 or --timeout 10).
21
+ */
22
+ export declare function getFlagValue(args: string[], flag: string): string | undefined;
@@ -0,0 +1,72 @@
1
+ import type { AppConfig, DevConfig, DevHooks, DevOptions, EnvVarsBuilder, MigrationConfig, PrismaConfig, SeedConfig, ServiceConfig } from "./types";
2
+ /**
3
+ * Define a dev environment configuration with full TypeScript inference.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * const config = defineDevConfig({
8
+ * projectPrefix: 'myapp',
9
+ * services: {
10
+ * postgres: { port: 5432, healthCheck: 'pg_isready' },
11
+ * redis: { port: 6379 },
12
+ * },
13
+ * apps: {
14
+ * api: { port: 3000, devCommand: 'bun run dev', cwd: 'apps/backend' },
15
+ * web: { port: 5173, devCommand: 'bun run dev', cwd: 'apps/frontend' },
16
+ * },
17
+ * envVars: (ports, urls) => ({
18
+ * DATABASE_URL: urls.postgres,
19
+ * REDIS_URL: urls.redis,
20
+ * API_PORT: String(ports.api),
21
+ * }),
22
+ * })
23
+ * ```
24
+ */
25
+ export declare function defineDevConfig<TServices extends Record<string, ServiceConfig>, TApps extends Record<string, AppConfig> = Record<string, never>>(config: {
26
+ /** Prefix for Docker project name (e.g., 'myapp' -> 'myapp-main') */
27
+ projectPrefix: string;
28
+ /** Docker Compose services to manage */
29
+ services: TServices;
30
+ /** Applications to start (optional) */
31
+ apps?: TApps;
32
+ /**
33
+ * Environment variables builder. Define all env vars here.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * envVars: (ports, urls, { localIp }) => ({
38
+ * DATABASE_URL: urls.postgres,
39
+ * BASE_URL: urls.api,
40
+ * VITE_PORT: ports.platform,
41
+ * EXPO_API_URL: `http://${localIp}:${ports.api}`
42
+ * })
43
+ * ```
44
+ */
45
+ envVars?: EnvVarsBuilder<TServices, TApps>;
46
+ /** Lifecycle hooks (optional) */
47
+ hooks?: DevHooks<TServices, TApps>;
48
+ /** Migrations to run after containers are ready (optional). Runs in parallel. */
49
+ migrations?: MigrationConfig[];
50
+ /** Seed configuration (optional). Runs after migrations, before servers. */
51
+ seed?: SeedConfig<TServices, TApps>;
52
+ /** Prisma configuration (optional). When set, dev.prisma is available. */
53
+ prisma?: PrismaConfig;
54
+ /** Additional options (optional) */
55
+ options?: DevOptions;
56
+ }): DevConfig<TServices, TApps>;
57
+ /**
58
+ * Validate a dev config and return any errors.
59
+ */
60
+ export declare function validateConfig<TServices extends Record<string, ServiceConfig>, TApps extends Record<string, AppConfig>>(config: DevConfig<TServices, TApps>): string[];
61
+ /**
62
+ * Validate config and throw if invalid.
63
+ */
64
+ export declare function assertValidConfig<TServices extends Record<string, ServiceConfig>, TApps extends Record<string, AppConfig>>(config: DevConfig<TServices, TApps>): void;
65
+ /**
66
+ * Merge two configs, with the second taking precedence.
67
+ */
68
+ export declare function mergeConfigs<TServices extends Record<string, ServiceConfig>, TApps extends Record<string, AppConfig>>(base: DevConfig<TServices, TApps>, overrides: Partial<DevConfig<TServices, TApps>>): DevConfig<TServices, TApps>;
69
+ /**
70
+ * Create a partial config that can be merged later.
71
+ */
72
+ export declare function definePartialConfig<TServices extends Record<string, ServiceConfig> = Record<string, ServiceConfig>, TApps extends Record<string, AppConfig> = Record<string, AppConfig>>(config: Partial<DevConfig<TServices, TApps>>): Partial<DevConfig<TServices, TApps>>;
@@ -0,0 +1,74 @@
1
+ import type { BuiltInHealthCheck, HealthCheckFn, ServiceConfig } from "../types";
2
+ export declare const POLL_INTERVAL = 250;
3
+ export declare const MAX_ATTEMPTS = 120;
4
+ /**
5
+ * Check if a specific container service is running using docker ps.
6
+ */
7
+ export declare function isContainerRunning(project: string, service: string): Promise<boolean>;
8
+ /**
9
+ * Check if all expected containers are running.
10
+ */
11
+ export declare function areContainersRunning(project: string, minCount?: number): Promise<boolean>;
12
+ export interface StartContainersOptions {
13
+ verbose?: boolean;
14
+ wait?: boolean;
15
+ composeFile?: string;
16
+ }
17
+ /**
18
+ * Start Docker Compose containers.
19
+ */
20
+ export declare function startContainers(root: string, projectName: string, envVars: Record<string, string>, options?: StartContainersOptions): void;
21
+ export interface StopContainersOptions {
22
+ verbose?: boolean;
23
+ removeVolumes?: boolean;
24
+ composeFile?: string;
25
+ }
26
+ /**
27
+ * Stop Docker Compose containers.
28
+ */
29
+ export declare function stopContainers(root: string, projectName: string, options?: StopContainersOptions): void;
30
+ /**
31
+ * Start a specific service only.
32
+ */
33
+ export declare function startService(root: string, projectName: string, serviceName: string, envVars: Record<string, string>, options?: {
34
+ verbose?: boolean;
35
+ composeFile?: string;
36
+ }): void;
37
+ export interface HealthCheckContext {
38
+ projectName?: string;
39
+ root?: string;
40
+ }
41
+ /**
42
+ * Create a health check function from a built-in type.
43
+ */
44
+ export declare function createBuiltInHealthCheck(type: BuiltInHealthCheck, serviceName: string, context?: HealthCheckContext): HealthCheckFn;
45
+ /**
46
+ * Wait for a service to be healthy.
47
+ */
48
+ export declare function waitForService(serviceName: string, config: ServiceConfig, port: number, options?: {
49
+ maxAttempts?: number;
50
+ pollInterval?: number;
51
+ projectName?: string;
52
+ root?: string;
53
+ }): Promise<void>;
54
+ /**
55
+ * Wait for all services to be healthy.
56
+ */
57
+ export declare function waitForAllServices(services: Record<string, ServiceConfig>, ports: Record<string, number>, options?: {
58
+ maxAttempts?: number;
59
+ pollInterval?: number;
60
+ verbose?: boolean;
61
+ projectName?: string;
62
+ root?: string;
63
+ }): Promise<void>;
64
+ /**
65
+ * Wait for a service to be healthy using a built-in health check type.
66
+ * Simpler API when you don't have a ServiceConfig object.
67
+ */
68
+ export declare function waitForServiceByType(serviceName: string, healthCheckType: BuiltInHealthCheck, port: number, options?: {
69
+ maxAttempts?: number;
70
+ pollInterval?: number;
71
+ verbose?: boolean;
72
+ projectName?: string;
73
+ root?: string;
74
+ }): Promise<void>;
@@ -0,0 +1,6 @@
1
+ export * from "./docker";
2
+ export * from "./network";
3
+ export * from "./ports";
4
+ export * from "./process";
5
+ export * from "./utils";
6
+ export * from "./watchdog";
@@ -0,0 +1,30 @@
1
+ import type { AppConfig } from "../types";
2
+ /**
3
+ * Gets the local IP address of the machine for mobile device connectivity.
4
+ * Prefers IPv4 addresses on non-internal interfaces.
5
+ */
6
+ export declare function getLocalIp(): string;
7
+ export interface WaitForServerOptions {
8
+ /** Timeout in milliseconds */
9
+ timeout?: number;
10
+ /** Polling interval in milliseconds */
11
+ interval?: number;
12
+ /** Log progress */
13
+ verbose?: boolean;
14
+ }
15
+ /**
16
+ * Wait for an HTTP server to respond.
17
+ */
18
+ export declare function waitForServer(url: string, options?: WaitForServerOptions): Promise<void>;
19
+ /**
20
+ * Wait for all dev servers to be ready.
21
+ */
22
+ export declare function waitForDevServers(apps: Record<string, AppConfig>, ports: Record<string, number>, options?: {
23
+ timeout?: number;
24
+ verbose?: boolean;
25
+ productionBuild?: boolean;
26
+ }): Promise<void>;
27
+ /**
28
+ * Check if a port is available (not in use).
29
+ */
30
+ export declare function isPortAvailable(port: number): Promise<boolean>;
@@ -0,0 +1,30 @@
1
+ import type { AppConfig, ServiceConfig } from "../types";
2
+ /**
3
+ * Find the monorepo root by looking for package.json with workspaces.
4
+ */
5
+ export declare function findMonorepoRoot(startDir?: string): string;
6
+ /**
7
+ * Get the worktree name from .git file (if in a worktree).
8
+ */
9
+ export declare function getWorktreeName(root?: string): string | null;
10
+ /**
11
+ * Check if the current directory is a git worktree.
12
+ */
13
+ export declare function isWorktree(root?: string): boolean;
14
+ /**
15
+ * Calculate port offset based on worktree name and optional suffix.
16
+ * Returns 0 for main branch, 10-99 for worktrees.
17
+ */
18
+ export declare function calculatePortOffset(suffix?: string, root?: string): number;
19
+ /**
20
+ * Generate Docker project name from prefix and directory.
21
+ */
22
+ export declare function getProjectName(prefix: string, suffix?: string, root?: string): string;
23
+ /**
24
+ * Compute all ports for services and apps with offset applied.
25
+ */
26
+ export declare function computePorts<TServices extends Record<string, ServiceConfig>, TApps extends Record<string, AppConfig>>(services: TServices, apps: TApps | undefined, offset: number): Record<string, number>;
27
+ /**
28
+ * Compute URLs for all services and apps.
29
+ */
30
+ export declare function computeUrls<TServices extends Record<string, ServiceConfig>, TApps extends Record<string, AppConfig>>(services: TServices, apps: TApps | undefined, ports: Record<string, number>, localIp: string): Record<string, string>;
@@ -0,0 +1,52 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ import type { AppConfig, DevServerPids, ExecOptions } from "../types";
3
+ export interface ExecResult {
4
+ exitCode: number;
5
+ stdout: string;
6
+ stderr: string;
7
+ }
8
+ /**
9
+ * Execute a shell command with environment variables.
10
+ */
11
+ export declare function exec(cmd: string, root: string, envVars: Record<string, string>, options?: ExecOptions): ExecResult;
12
+ /**
13
+ * Execute a shell command asynchronously.
14
+ */
15
+ export declare function execAsync(cmd: string, root: string, envVars: Record<string, string>, options?: ExecOptions): Promise<ExecResult>;
16
+ export interface SpawnDevServerOptions {
17
+ verbose?: boolean;
18
+ detached?: boolean;
19
+ isCI?: boolean;
20
+ }
21
+ /**
22
+ * Spawn a dev server as a detached process.
23
+ */
24
+ export declare function spawnDevServer(command: string, root: string, appCwd: string | undefined, envVars: Record<string, string>, options?: SpawnDevServerOptions): ChildProcess;
25
+ /**
26
+ * Start all configured dev servers.
27
+ */
28
+ export declare function startDevServers(apps: Record<string, AppConfig>, root: string, envVars: Record<string, string>, options?: {
29
+ verbose?: boolean;
30
+ productionBuild?: boolean;
31
+ isCI?: boolean;
32
+ }): DevServerPids;
33
+ /**
34
+ * Stop a process by PID.
35
+ */
36
+ export declare function stopProcess(pid: number): void;
37
+ /**
38
+ * Stop all processes by their PIDs.
39
+ */
40
+ export declare function stopAllProcesses(pids: DevServerPids, options?: {
41
+ verbose?: boolean;
42
+ }): void;
43
+ /**
44
+ * Check if a process is alive by sending signal 0.
45
+ */
46
+ export declare function isProcessAlive(pid: number): boolean;
47
+ /**
48
+ * Run production build for apps that have buildCommand configured.
49
+ */
50
+ export declare function buildApps(apps: Record<string, AppConfig>, root: string, envVars: Record<string, string>, options?: {
51
+ verbose?: boolean;
52
+ }): void;
@@ -0,0 +1,60 @@
1
+ import type { AppConfig, DevConfig, ServiceConfig } from "../types";
2
+ /**
3
+ * Core utility functions shared across modules.
4
+ */
5
+ /**
6
+ * Sleep for a given number of milliseconds.
7
+ */
8
+ export declare function sleep(ms: number): Promise<void>;
9
+ /**
10
+ * Detect if running in a CI environment.
11
+ */
12
+ export declare function isCI(): boolean;
13
+ /**
14
+ * Log the frontend port in a format that Vibe Kanban can detect.
15
+ * This is used to communicate the dev server port to external tools.
16
+ *
17
+ * @param port - The port number the frontend is running on
18
+ */
19
+ export declare function logFrontendPort(port: number | undefined): void;
20
+ /**
21
+ * Get an environment variable value from the config.
22
+ * Computes ports/urls and runs envVars to get the value.
23
+ *
24
+ * @param config - The dev config object (from defineDevConfig)
25
+ * @param name - The environment variable name
26
+ * @param options - Optional settings (log for Vibe Kanban detection)
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * // In vite.config.ts
31
+ * import { getEnvVar } from 'buncargo'
32
+ * import config from '../../dev.config'
33
+ *
34
+ * export default defineConfig(async ({ command }) => {
35
+ * const isDev = command === 'serve'
36
+ * const vitePort = isDev ? getEnvVar(config, 'VITE_PORT') : undefined
37
+ * const apiUrl = getEnvVar(config, 'VITE_API_URL')
38
+ * return {
39
+ * server: { port: vitePort, strictPort: true }
40
+ * }
41
+ * })
42
+ * ```
43
+ */
44
+ export declare function getEnvVar<TServices extends Record<string, ServiceConfig>, TApps extends Record<string, AppConfig>>(config: DevConfig<TServices, TApps>, name: string, options?: {
45
+ log?: boolean;
46
+ }): string | number | undefined;
47
+ /**
48
+ * Log the API URL in a format that tools can detect.
49
+ * This is used by Expo and other tools to find the API server.
50
+ *
51
+ * @param url - The API URL
52
+ */
53
+ export declare function logApiUrl(url: string): void;
54
+ /**
55
+ * Log the Expo API URL in a format that tools can detect.
56
+ * This is typically the local IP address for mobile device connectivity.
57
+ *
58
+ * @param url - The Expo API URL (usually http://<local-ip>:<port>)
59
+ */
60
+ export declare function logExpoApiUrl(url: string): void;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Watchdog Runner
3
+ *
4
+ * Monitors heartbeat file and shuts down containers after inactivity.
5
+ * Spawned as a detached process by the dev environment.
6
+ *
7
+ * Environment variables:
8
+ * WATCHDOG_PROJECT_NAME - Docker project name
9
+ * WATCHDOG_HEARTBEAT_FILE - Path to heartbeat file
10
+ * WATCHDOG_PID_FILE - Path to PID file
11
+ * WATCHDOG_TIMEOUT_MS - Idle timeout in milliseconds
12
+ * WATCHDOG_COMPOSE_ARG - Optional docker compose argument (e.g., "-f compose.yml")
13
+ */
14
+ export {};