business-stack 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -19,6 +19,18 @@ Check your environment:
19
19
  npx business-stack doctor
20
20
  ```
21
21
 
22
+ ### Using `npx` (no install, or one-off runs)
23
+
24
+ `npx` runs the same `business-stack` binary as a global or local install. Use a version tag so npm does not cache an old release:
25
+
26
+ ```bash
27
+ npx --yes business-stack@latest doctor
28
+ npx --yes business-stack@latest setup --yes
29
+ npx --yes business-stack@latest start
30
+ ```
31
+
32
+ On **Windows Command Prompt**, use the same commands; if `npx` is slow the first time, it is downloading the package.
33
+
22
34
  ## Installation (recommended: project-level)
23
35
 
24
36
  **Prefer a local (project) install** so the stack, generated `.env` files, and SQLite database live under your project’s `node_modules/business-stack/`, match npm’s default `npm i business-stack`, and avoid permission or PATH issues from global installs.
@@ -111,10 +111,29 @@ function writeEnvFile(filePath, content) {
111
111
  fs.writeFileSync(filePath, `${content.trim()}\n`, "utf8");
112
112
  }
113
113
 
114
+ /** Resolve `bun` / `uv` to a concrete path on Windows (`where`); avoids spawn ENOENT for .cmd shims. */
115
+ function resolveExecutable(cmd) {
116
+ if (process.platform !== "win32") {
117
+ return cmd;
118
+ }
119
+ const r = spawnSync("where.exe", [cmd], {
120
+ encoding: "utf8",
121
+ windowsHide: true,
122
+ });
123
+ if (r.status !== 0 || !r.stdout) {
124
+ return cmd;
125
+ }
126
+ const first = r.stdout
127
+ .split(/\r?\n/)
128
+ .map((s) => s.trim())
129
+ .find(Boolean);
130
+ return first || cmd;
131
+ }
132
+
114
133
  function cmdExists(cmd) {
115
134
  const isWin = process.platform === "win32";
116
135
  const which = isWin ? "where" : "which";
117
- const r = spawnSync(which, [cmd], { encoding: "utf8" });
136
+ const r = spawnSync(which, [cmd], { encoding: "utf8", windowsHide: isWin });
118
137
  return r.status === 0;
119
138
  }
120
139
 
@@ -274,19 +293,62 @@ function envFileLooksConfigured(stackRoot) {
274
293
  return hasExistingSecrets(stackRoot);
275
294
  }
276
295
 
277
- function runCmd(cmd, args, cwd, extraEnv) {
278
- const r = spawnSync(cmd, args, {
296
+ function runCmd(label, cmd, args, cwd, extraEnv) {
297
+ console.error(`business-stack: ${label}…`);
298
+ const exe = resolveExecutable(cmd);
299
+ const r = spawnSync(exe, args, {
279
300
  cwd,
280
301
  stdio: "inherit",
281
302
  env: { ...process.env, ...extraEnv },
282
303
  shell: false,
304
+ windowsHide: process.platform === "win32",
283
305
  });
306
+ if (r.error) {
307
+ console.error(`business-stack: ${label} failed:`, r.error.message);
308
+ console.error(` Command: ${exe} ${args.join(" ")}`);
309
+ console.error(` cwd: ${cwd}`);
310
+ process.exit(1);
311
+ }
312
+ if (r.signal) {
313
+ console.error(`business-stack: ${label} killed (${r.signal})`);
314
+ process.exit(1);
315
+ }
284
316
  if (r.status !== 0) {
285
317
  process.exit(r.status ?? 1);
286
318
  }
287
319
  }
288
320
 
289
- function runStart(opts) {
321
+ /**
322
+ * Wait for turbo + servers. Async so Commander keeps the Node event loop alive on Windows
323
+ * until the child exits (avoids exiting immediately with no output).
324
+ */
325
+ function waitForChild(child, label) {
326
+ return new Promise((resolve) => {
327
+ let settled = false;
328
+ const finish = (code) => {
329
+ if (settled) return;
330
+ settled = true;
331
+ resolve(code ?? 0);
332
+ };
333
+
334
+ child.on("error", (err) => {
335
+ console.error(`business-stack: ${label} — failed to spawn process:`, err.message);
336
+ console.error(" Is Bun installed and on PATH? Run: business-stack doctor");
337
+ finish(1);
338
+ });
339
+
340
+ child.on("exit", (code, sig) => {
341
+ if (sig) {
342
+ console.error(`business-stack: ${label} — child exited with signal ${sig}`);
343
+ finish(1);
344
+ return;
345
+ }
346
+ finish(code ?? 0);
347
+ });
348
+ });
349
+ }
350
+
351
+ async function runStart(opts) {
290
352
  const stackRoot = getStackRoot();
291
353
  if (!envFileLooksConfigured(stackRoot)) {
292
354
  console.error(
@@ -295,6 +357,9 @@ function runStart(opts) {
295
357
  process.exit(1);
296
358
  }
297
359
 
360
+ console.error("business-stack: starting (install → sync → build → migrate → servers)…");
361
+ console.error(`business-stack: stack root ${stackRoot}`);
362
+
298
363
  const fromDb = readStackSecretsEnv(stackRoot);
299
364
  const childEnv = { ...process.env };
300
365
  for (const [k, v] of Object.entries(fromDb)) {
@@ -304,43 +369,53 @@ function runStart(opts) {
304
369
  }
305
370
 
306
371
  if (!opts.skipInstall) {
307
- runCmd("bun", ["install"], stackRoot, childEnv);
372
+ runCmd("bun install (workspaces)", "bun", ["install"], stackRoot, childEnv);
308
373
  }
309
- runCmd("uv", ["sync"], path.join(stackRoot, "backend"), childEnv);
374
+ runCmd("uv sync (backend)", "uv", ["sync"], path.join(stackRoot, "backend"), childEnv);
310
375
  if (!opts.skipBuild) {
311
- runCmd("bun", ["run", "build"], stackRoot, childEnv);
376
+ runCmd("bun run build", "bun", ["run", "build"], stackRoot, childEnv);
312
377
  }
313
378
  if (!opts.skipMigrate) {
314
- runCmd("uv", ["run", "alembic", "upgrade", "head"], path.join(stackRoot, "backend"), childEnv);
379
+ runCmd(
380
+ "alembic upgrade head",
381
+ "uv",
382
+ ["run", "alembic", "upgrade", "head"],
383
+ path.join(stackRoot, "backend"),
384
+ childEnv,
385
+ );
315
386
  }
316
387
 
317
- const child = spawn("bun", ["run", "start"], {
388
+ console.error("business-stack: launching production servers (turbo start)…");
389
+
390
+ const bunExe = resolveExecutable("bun");
391
+ const child = spawn(bunExe, ["run", "start"], {
318
392
  cwd: stackRoot,
319
393
  stdio: "inherit",
320
394
  env: childEnv,
321
395
  shell: false,
396
+ windowsHide: process.platform === "win32",
322
397
  });
323
398
 
324
- function shutdown(signal) {
399
+ const shutdown = () => {
325
400
  try {
326
- if (process.platform === "win32") {
401
+ if (process.platform === "win32" && child.pid) {
327
402
  spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
328
403
  stdio: "ignore",
404
+ windowsHide: true,
329
405
  });
330
- } else {
331
- child.kill(signal);
406
+ } else if (child.pid) {
407
+ child.kill("SIGINT");
332
408
  }
333
409
  } catch {
334
410
  /* ignore */
335
411
  }
336
- }
412
+ };
337
413
 
338
- process.on("SIGINT", () => shutdown("SIGINT"));
339
- process.on("SIGTERM", () => shutdown("SIGTERM"));
340
- child.on("exit", (code, sig) => {
341
- if (sig) process.exit(1);
342
- process.exit(code ?? 0);
343
- });
414
+ process.once("SIGINT", shutdown);
415
+ process.once("SIGTERM", shutdown);
416
+
417
+ const code = await waitForChild(child, "turbo start");
418
+ process.exit(code);
344
419
  }
345
420
 
346
421
  function runDev() {
@@ -352,13 +427,13 @@ function runDev() {
352
427
  childEnv[k] = String(v);
353
428
  }
354
429
  }
355
- runCmd("bun", ["run", "dev"], stackRoot, childEnv);
430
+ runCmd("bun run dev", "bun", ["run", "dev"], stackRoot, childEnv);
356
431
  }
357
432
 
358
433
  program
359
434
  .name("business-stack")
360
435
  .description("Run the business-stack monorepo (Next + Hono gateway + FastAPI)")
361
- .version("0.1.1");
436
+ .version("0.1.3");
362
437
 
363
438
  program
364
439
  .command("doctor")
@@ -385,9 +460,9 @@ program
385
460
  .option("--skip-install", "Skip bun install")
386
461
  .option("--skip-build", "Skip turbo build")
387
462
  .option("--skip-migrate", "Skip alembic upgrade")
388
- .action((o) => {
463
+ .action(async (o) => {
389
464
  try {
390
- runStart(o);
465
+ await runStart(o);
391
466
  } catch (e) {
392
467
  console.error(e instanceof Error ? e.message : e);
393
468
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "business-stack",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Next.js + Hono gateway + FastAPI monorepo",
5
5
  "license": "UNLICENSED",
6
6
  "packageManager": "bun@1.3.1",