buncargo 1.0.13 → 1.0.17

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
@@ -7,6 +7,7 @@
7
7
  * bunx buncargo dev # Start containers + dev servers
8
8
  * bunx buncargo dev --down # Stop containers
9
9
  * bunx buncargo dev --reset # Stop + remove volumes
10
+ * bunx buncargo typecheck # Run TypeScript typecheck
10
11
  * bunx buncargo prisma ... # Run prisma commands
11
12
  * bunx buncargo help # Show help
12
13
  */
@@ -82,6 +83,16 @@ async function handleEnv(): Promise<void> {
82
83
  );
83
84
  }
84
85
 
86
+ async function handleTypecheck(): Promise<void> {
87
+ const env = await loadEnv();
88
+ const { runWorkspaceTypecheck } = await import("./lint");
89
+ const result = await runWorkspaceTypecheck({
90
+ root: env.root,
91
+ verbose: true,
92
+ });
93
+ process.exit(result.success ? 0 : 1);
94
+ }
95
+
85
96
  function showHelp(): void {
86
97
  console.log(`
87
98
  buncargo - Development environment CLI
@@ -91,6 +102,7 @@ USAGE:
91
102
 
92
103
  COMMANDS:
93
104
  dev Start the development environment
105
+ typecheck Run TypeScript typecheck across workspaces
94
106
  prisma <args> Run Prisma CLI with correct DATABASE_URL
95
107
  env Print environment info as JSON
96
108
  help Show this help message
@@ -102,11 +114,11 @@ DEV OPTIONS:
102
114
  --reset Stop containers and remove volumes
103
115
  --migrate Run migrations only
104
116
  --seed Run seeders
105
- --lint Run typecheck (no Docker required)
106
117
 
107
118
  EXAMPLES:
108
119
  bunx buncargo dev # Start everything
109
120
  bunx buncargo dev --down # Stop containers
121
+ bunx buncargo typecheck # Run typecheck
110
122
  bunx buncargo prisma studio # Open Prisma Studio
111
123
  bunx buncargo env # Get ports/urls as JSON
112
124
 
@@ -157,6 +169,10 @@ async function main(): Promise<void> {
157
169
  await handleDev(commandArgs);
158
170
  break;
159
171
 
172
+ case "typecheck":
173
+ await handleTypecheck();
174
+ break;
175
+
160
176
  case "prisma":
161
177
  await handlePrisma(commandArgs);
162
178
  break;
package/cli.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { killProcessesOnAppPorts } from "./core/process";
2
3
  import { spawnWatchdog, startHeartbeat, stopHeartbeat } from "./core/watchdog";
3
4
  import type {
4
5
  AppConfig,
@@ -40,16 +41,6 @@ export async function runCli<
40
41
  // Log environment info
41
42
  env.logInfo();
42
43
 
43
- // Handle --lint (no Docker required)
44
- if (args.includes("--lint")) {
45
- const { runWorkspaceTypecheck } = await import("./lint");
46
- const result = await runWorkspaceTypecheck({
47
- root: env.root,
48
- verbose: true,
49
- });
50
- process.exit(result.success ? 0 : 1);
51
- }
52
-
53
44
  // Handle --down
54
45
  if (args.includes("--down")) {
55
46
  await env.stop();
@@ -119,6 +110,9 @@ export async function runCli<
119
110
  return;
120
111
  }
121
112
 
113
+ // Kill any existing processes on app ports before starting
114
+ await killProcessesOnAppPorts(env.apps, env.ports);
115
+
122
116
  // Start dev servers interactively
123
117
  console.log("");
124
118
  console.log("🔧 Starting dev servers...");
package/core/process.ts CHANGED
@@ -369,6 +369,42 @@ export async function killProcessOnPortAndWait(
369
369
  return released;
370
370
  }
371
371
 
372
+ /**
373
+ * Kill any existing processes using the specified app ports.
374
+ * Used before starting dev servers to ensure ports are available.
375
+ */
376
+ export async function killProcessesOnAppPorts(
377
+ apps: Record<string, AppConfig>,
378
+ ports: Record<string, number>,
379
+ options: { verbose?: boolean } = {},
380
+ ): Promise<void> {
381
+ const { verbose = true } = options;
382
+ const appNames = Object.keys(apps);
383
+ if (appNames.length === 0) return;
384
+
385
+ let killedAny = false;
386
+
387
+ for (const name of appNames) {
388
+ const port = ports[name];
389
+ if (port === undefined) continue;
390
+
391
+ const existingPid = getProcessOnPort(port);
392
+ if (existingPid !== null) {
393
+ if (!killedAny && verbose) {
394
+ console.log("");
395
+ killedAny = true;
396
+ }
397
+ if (verbose) {
398
+ console.log(`⚠️ Port ${port} (${name}) is in use by process ${existingPid}`);
399
+ }
400
+ const killed = await killProcessOnPortAndWait(port, { verbose });
401
+ if (!killed && verbose) {
402
+ console.log(` ⚠️ Could not kill process on port ${port}, server may fail to start`);
403
+ }
404
+ }
405
+ }
406
+ }
407
+
372
408
  // ═══════════════════════════════════════════════════════════════════════════
373
409
  // Process Management
374
410
  // ═══════════════════════════════════════════════════════════════════════════
package/dist/bin.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * bunx buncargo dev # Start containers + dev servers
7
7
  * bunx buncargo dev --down # Stop containers
8
8
  * bunx buncargo dev --reset # Stop + remove volumes
9
+ * bunx buncargo typecheck # Run TypeScript typecheck
9
10
  * bunx buncargo prisma ... # Run prisma commands
10
11
  * bunx buncargo help # Show help
11
12
  */
package/dist/bin.js CHANGED
@@ -1,27 +1,28 @@
1
1
  #!/usr/bin/env bun
2
2
  import {
3
3
  runCli
4
- } from "./index-6srpc523.js";
4
+ } from "./index-8hbbj1mp.js";
5
5
  import {
6
6
  loadDevEnv
7
- } from "./index-75y4cg2z.js";
8
- import"./index-ndnmnsej.js";
9
- import"./index-vhs88xhe.js";
10
- import"./index-cty0bcry.js";
7
+ } from "./index-qw4093g2.js";
8
+ import"./index-2f47khe5.js";
9
+ import"./index-ggq3yryx.js";
10
+ import"./index-1yvbwj4k.js";
11
11
  import"./index-tjqw9vtj.js";
12
12
  import"./index-5hka0tff.js";
13
13
  import"./index-2fr3g85b.js";
14
14
  import"./index-6fm7mvwj.js";
15
15
  import"./index-08wa79cs.js";
16
16
  import {
17
- __commonJS
17
+ __commonJS,
18
+ __require
18
19
  } from "./index-qnx9j3qa.js";
19
20
 
20
21
  // package.json
21
22
  var require_package = __commonJS((exports, module) => {
22
23
  module.exports = {
23
24
  name: "buncargo",
24
- version: "1.0.13",
25
+ version: "1.0.17",
25
26
  description: "A Bun-powered development environment CLI for managing Docker Compose services, dev servers, and environment variables",
26
27
  type: "module",
27
28
  module: "./dist/index.js",
@@ -211,6 +212,15 @@ async function handleEnv() {
211
212
  root: env.root
212
213
  }, null, 2));
213
214
  }
215
+ async function handleTypecheck() {
216
+ const env = await loadEnv();
217
+ const { runWorkspaceTypecheck } = await import("./lint.js");
218
+ const result = await runWorkspaceTypecheck({
219
+ root: env.root,
220
+ verbose: true
221
+ });
222
+ process.exit(result.success ? 0 : 1);
223
+ }
214
224
  function showHelp() {
215
225
  console.log(`
216
226
  buncargo - Development environment CLI
@@ -220,6 +230,7 @@ USAGE:
220
230
 
221
231
  COMMANDS:
222
232
  dev Start the development environment
233
+ typecheck Run TypeScript typecheck across workspaces
223
234
  prisma <args> Run Prisma CLI with correct DATABASE_URL
224
235
  env Print environment info as JSON
225
236
  help Show this help message
@@ -231,11 +242,11 @@ DEV OPTIONS:
231
242
  --reset Stop containers and remove volumes
232
243
  --migrate Run migrations only
233
244
  --seed Run seeders
234
- --lint Run typecheck (no Docker required)
235
245
 
236
246
  EXAMPLES:
237
247
  bunx buncargo dev # Start everything
238
248
  bunx buncargo dev --down # Stop containers
249
+ bunx buncargo typecheck # Run typecheck
239
250
  bunx buncargo prisma studio # Open Prisma Studio
240
251
  bunx buncargo env # Get ports/urls as JSON
241
252
 
@@ -271,6 +282,9 @@ async function main() {
271
282
  case "dev":
272
283
  await handleDev(commandArgs);
273
284
  break;
285
+ case "typecheck":
286
+ await handleTypecheck();
287
+ break;
274
288
  case "prisma":
275
289
  await handlePrisma(commandArgs);
276
290
  break;
package/dist/cli.js CHANGED
@@ -2,9 +2,9 @@ import {
2
2
  getFlagValue,
3
3
  hasFlag,
4
4
  runCli
5
- } from "./index-6srpc523.js";
6
- import"./index-vhs88xhe.js";
7
- import"./index-cty0bcry.js";
5
+ } from "./index-8hbbj1mp.js";
6
+ import"./index-ggq3yryx.js";
7
+ import"./index-1yvbwj4k.js";
8
8
  import"./index-qnx9j3qa.js";
9
9
  export {
10
10
  runCli,
@@ -9,7 +9,7 @@ import {
9
9
  startHeartbeat,
10
10
  stopHeartbeat,
11
11
  stopWatchdog
12
- } from "../index-vhs88xhe.js";
12
+ } from "../index-ggq3yryx.js";
13
13
  import {
14
14
  buildApps,
15
15
  exec,
@@ -19,11 +19,12 @@ import {
19
19
  isProcessAlive,
20
20
  killProcessOnPort,
21
21
  killProcessOnPortAndWait,
22
+ killProcessesOnAppPorts,
22
23
  spawnDevServer,
23
24
  startDevServers,
24
25
  stopAllProcesses,
25
26
  stopProcess
26
- } from "../index-cty0bcry.js";
27
+ } from "../index-1yvbwj4k.js";
27
28
  import {
28
29
  MAX_ATTEMPTS,
29
30
  POLL_INTERVAL,
@@ -82,6 +83,7 @@ export {
82
83
  logFrontendPort,
83
84
  logExpoApiUrl,
84
85
  logApiUrl,
86
+ killProcessesOnAppPorts,
85
87
  killProcessOnPortAndWait,
86
88
  killProcessOnPort,
87
89
  isWorktree,
@@ -64,6 +64,13 @@ export declare function killProcessOnPortAndWait(port: number, options?: {
64
64
  verbose?: boolean;
65
65
  timeout?: number;
66
66
  }): Promise<boolean>;
67
+ /**
68
+ * Kill any existing processes using the specified app ports.
69
+ * Used before starting dev servers to ensure ports are available.
70
+ */
71
+ export declare function killProcessesOnAppPorts(apps: Record<string, AppConfig>, ports: Record<string, number>, options?: {
72
+ verbose?: boolean;
73
+ }): Promise<void>;
67
74
  /**
68
75
  * Stop a process by PID.
69
76
  */
@@ -7,17 +7,19 @@ import {
7
7
  isProcessAlive,
8
8
  killProcessOnPort,
9
9
  killProcessOnPortAndWait,
10
+ killProcessesOnAppPorts,
10
11
  spawnDevServer,
11
12
  startDevServers,
12
13
  stopAllProcesses,
13
14
  stopProcess
14
- } from "../index-cty0bcry.js";
15
+ } from "../index-1yvbwj4k.js";
15
16
  import"../index-qnx9j3qa.js";
16
17
  export {
17
18
  stopProcess,
18
19
  stopAllProcesses,
19
20
  startDevServers,
20
21
  spawnDevServer,
22
+ killProcessesOnAppPorts,
21
23
  killProcessOnPortAndWait,
22
24
  killProcessOnPort,
23
25
  isProcessAlive,
@@ -9,8 +9,8 @@ import {
9
9
  startHeartbeat,
10
10
  stopHeartbeat,
11
11
  stopWatchdog
12
- } from "../index-vhs88xhe.js";
13
- import"../index-cty0bcry.js";
12
+ } from "../index-ggq3yryx.js";
13
+ import"../index-1yvbwj4k.js";
14
14
  import"../index-qnx9j3qa.js";
15
15
  export {
16
16
  stopWatchdog,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createDevEnvironment
3
- } from "./index-ndnmnsej.js";
4
- import"./index-vhs88xhe.js";
5
- import"./index-cty0bcry.js";
3
+ } from "./index-2f47khe5.js";
4
+ import"./index-ggq3yryx.js";
5
+ import"./index-1yvbwj4k.js";
6
6
  import"./index-tjqw9vtj.js";
7
7
  import"./index-5hka0tff.js";
8
8
  import"./index-2fr3g85b.js";
@@ -0,0 +1,274 @@
1
+ // core/process.ts
2
+ import {
3
+ execSync,
4
+ spawn
5
+ } from "node:child_process";
6
+ import { platform } from "node:os";
7
+ import { resolve } from "node:path";
8
+ function exec(cmd, root, envVars, options = {}) {
9
+ const { cwd, verbose = false, env = {}, throwOnError = true } = options;
10
+ const workingDir = cwd ? resolve(root, cwd) : root;
11
+ const fullEnv = { ...process.env, ...envVars, ...env };
12
+ try {
13
+ const stdout = execSync(cmd, {
14
+ cwd: workingDir,
15
+ env: fullEnv,
16
+ encoding: "utf-8",
17
+ stdio: verbose ? "inherit" : ["pipe", "pipe", "pipe"]
18
+ });
19
+ return {
20
+ exitCode: 0,
21
+ stdout: typeof stdout === "string" ? stdout : "",
22
+ stderr: ""
23
+ };
24
+ } catch (error) {
25
+ const execError = error;
26
+ const result = {
27
+ exitCode: execError.status ?? 1,
28
+ stdout: execError.stdout ?? "",
29
+ stderr: execError.stderr ?? ""
30
+ };
31
+ if (throwOnError) {
32
+ throw new Error(`Command failed with exit code ${result.exitCode}: ${cmd}
33
+ ${result.stderr}`);
34
+ }
35
+ return result;
36
+ }
37
+ }
38
+ async function execAsync(cmd, root, envVars, options = {}) {
39
+ return new Promise((resolve2) => {
40
+ const result = exec(cmd, root, envVars, {
41
+ ...options,
42
+ throwOnError: false
43
+ });
44
+ resolve2(result);
45
+ });
46
+ }
47
+ async function spawnDevServer(command, root, appCwd, envVars, options = {}) {
48
+ const {
49
+ verbose = false,
50
+ detached = true,
51
+ isCI = false,
52
+ killExisting = true,
53
+ port
54
+ } = options;
55
+ if (killExisting && port !== undefined) {
56
+ const existingPid = getProcessOnPort(port);
57
+ if (existingPid !== null) {
58
+ if (verbose) {
59
+ console.log(` ⚠️ Port ${port} is in use by process ${existingPid}`);
60
+ }
61
+ await killProcessOnPortAndWait(port, { verbose });
62
+ }
63
+ }
64
+ const parts = command.split(" ");
65
+ const cmd = parts[0];
66
+ const args = parts.slice(1);
67
+ if (!cmd) {
68
+ throw new Error("Command cannot be empty");
69
+ }
70
+ const workingDir = appCwd ? resolve(root, appCwd) : root;
71
+ const spawnOptions = {
72
+ cwd: workingDir,
73
+ env: { ...process.env, ...envVars },
74
+ detached,
75
+ stdio: isCI || verbose ? "inherit" : "ignore"
76
+ };
77
+ const proc = spawn(cmd, args, spawnOptions);
78
+ if (detached && proc.unref) {
79
+ proc.unref();
80
+ }
81
+ return proc;
82
+ }
83
+ async function startDevServers(apps, root, envVars, ports, options = {}) {
84
+ const {
85
+ verbose = true,
86
+ productionBuild = false,
87
+ isCI = false,
88
+ killExisting = true
89
+ } = options;
90
+ const pids = {};
91
+ if (verbose) {
92
+ console.log(productionBuild ? "\uD83D\uDE80 Starting production servers..." : "\uD83D\uDD27 Starting dev servers...");
93
+ }
94
+ for (const [name, config] of Object.entries(apps)) {
95
+ const command = productionBuild ? config.prodCommand ?? config.devCommand : config.devCommand;
96
+ const port = ports[name];
97
+ const proc = await spawnDevServer(command, root, config.cwd, envVars, {
98
+ verbose,
99
+ isCI,
100
+ killExisting,
101
+ port
102
+ });
103
+ if (proc.pid) {
104
+ pids[name] = proc.pid;
105
+ if (verbose) {
106
+ console.log(` ${name} PID: ${proc.pid}`);
107
+ }
108
+ }
109
+ }
110
+ return pids;
111
+ }
112
+ function getProcessOnPort(port) {
113
+ try {
114
+ const os = platform();
115
+ let output;
116
+ if (os === "win32") {
117
+ output = execSync(`netstat -ano | findstr :${port}`, {
118
+ encoding: "utf-8",
119
+ stdio: ["pipe", "pipe", "pipe"]
120
+ });
121
+ const lines = output.trim().split(`
122
+ `);
123
+ for (const line of lines) {
124
+ if (line.includes("LISTENING")) {
125
+ const parts = line.trim().split(/\s+/);
126
+ const pid = Number.parseInt(parts[parts.length - 1], 10);
127
+ if (!Number.isNaN(pid) && pid > 0) {
128
+ return pid;
129
+ }
130
+ }
131
+ }
132
+ } else {
133
+ output = execSync(`lsof -ti :${port}`, {
134
+ encoding: "utf-8",
135
+ stdio: ["pipe", "pipe", "pipe"]
136
+ });
137
+ const pid = Number.parseInt(output.trim().split(`
138
+ `)[0], 10);
139
+ if (!Number.isNaN(pid) && pid > 0) {
140
+ return pid;
141
+ }
142
+ }
143
+ return null;
144
+ } catch {
145
+ return null;
146
+ }
147
+ }
148
+ function isPortInUse(port) {
149
+ return getProcessOnPort(port) !== null;
150
+ }
151
+ function killProcessOnPort(port, options = {}) {
152
+ const { verbose = false, signal = "SIGTERM" } = options;
153
+ const pid = getProcessOnPort(port);
154
+ if (pid === null) {
155
+ return false;
156
+ }
157
+ try {
158
+ if (verbose) {
159
+ console.log(` Killing process ${pid} on port ${port}`);
160
+ }
161
+ process.kill(pid, signal);
162
+ return true;
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
167
+ async function killProcessOnPortAndWait(port, options = {}) {
168
+ const { verbose = false, timeout = 5000 } = options;
169
+ const pid = getProcessOnPort(port);
170
+ if (pid === null) {
171
+ return false;
172
+ }
173
+ if (verbose) {
174
+ console.log(` Killing process ${pid} on port ${port}...`);
175
+ }
176
+ try {
177
+ process.kill(pid, "SIGTERM");
178
+ } catch {
179
+ return false;
180
+ }
181
+ const startTime = Date.now();
182
+ const checkInterval = 100;
183
+ while (Date.now() - startTime < timeout) {
184
+ await new Promise((resolve2) => setTimeout(resolve2, checkInterval));
185
+ if (!isPortInUse(port)) {
186
+ if (verbose) {
187
+ console.log(` ✓ Port ${port} released`);
188
+ }
189
+ return true;
190
+ }
191
+ }
192
+ if (verbose) {
193
+ console.log(` Process ${pid} didn't exit, sending SIGKILL...`);
194
+ }
195
+ try {
196
+ process.kill(pid, "SIGKILL");
197
+ } catch {}
198
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
199
+ const released = !isPortInUse(port);
200
+ if (verbose) {
201
+ if (released) {
202
+ console.log(` ✓ Port ${port} released after SIGKILL`);
203
+ } else {
204
+ console.log(` ⚠ Port ${port} still in use`);
205
+ }
206
+ }
207
+ return released;
208
+ }
209
+ async function killProcessesOnAppPorts(apps, ports, options = {}) {
210
+ const { verbose = true } = options;
211
+ const appNames = Object.keys(apps);
212
+ if (appNames.length === 0)
213
+ return;
214
+ let killedAny = false;
215
+ for (const name of appNames) {
216
+ const port = ports[name];
217
+ if (port === undefined)
218
+ continue;
219
+ const existingPid = getProcessOnPort(port);
220
+ if (existingPid !== null) {
221
+ if (!killedAny && verbose) {
222
+ console.log("");
223
+ killedAny = true;
224
+ }
225
+ if (verbose) {
226
+ console.log(`⚠️ Port ${port} (${name}) is in use by process ${existingPid}`);
227
+ }
228
+ const killed = await killProcessOnPortAndWait(port, { verbose });
229
+ if (!killed && verbose) {
230
+ console.log(` ⚠️ Could not kill process on port ${port}, server may fail to start`);
231
+ }
232
+ }
233
+ }
234
+ }
235
+ function stopProcess(pid) {
236
+ try {
237
+ process.kill(pid, "SIGTERM");
238
+ } catch {}
239
+ }
240
+ function stopAllProcesses(pids, options = {}) {
241
+ const { verbose = true } = options;
242
+ for (const [name, pid] of Object.entries(pids)) {
243
+ if (pid) {
244
+ if (verbose)
245
+ console.log(` Stopping ${name} (PID: ${pid})`);
246
+ stopProcess(pid);
247
+ }
248
+ }
249
+ }
250
+ function isProcessAlive(pid) {
251
+ try {
252
+ process.kill(pid, 0);
253
+ return true;
254
+ } catch {
255
+ return false;
256
+ }
257
+ }
258
+ function buildApps(apps, root, envVars, options = {}) {
259
+ const { verbose = true } = options;
260
+ for (const [name, config] of Object.entries(apps)) {
261
+ if (config.buildCommand) {
262
+ if (verbose)
263
+ console.log(`\uD83D\uDD28 Building ${name}...`);
264
+ exec(config.buildCommand, root, envVars, {
265
+ cwd: config.cwd,
266
+ verbose
267
+ });
268
+ }
269
+ }
270
+ if (verbose)
271
+ console.log("✓ Build complete");
272
+ }
273
+
274
+ export { exec, execAsync, spawnDevServer, startDevServers, getProcessOnPort, isPortInUse, killProcessOnPort, killProcessOnPortAndWait, killProcessesOnAppPorts, stopProcess, stopAllProcesses, isProcessAlive, buildApps };