Ctrl K
ComunidadGuías…

apps/web/scripts/run-api-dev.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
/**
 * Spawns the Weir .NET server like `scripts/dev-backend.ps1` (`dotnet watch run` on
 * apps/server/src/Weir.Host, host/port from scripts/dev-ports.json). Used by `npm run dev` so Vite
 * and the API start together. The server creates or migrates its own SQLite database on start.
 *
 * Records the spawned PID (and how to recognise it) in `.dev-api.pid` at the repo root so
 * `scripts/stop-dev-api-port.mjs` can stop *this* process later — never "whatever holds the
 * port", which would kill an installed Weir if one happens to be listening on the same port
 * (see that script's header for why).
 */
import { spawn, spawnSync } from "node:child_process";
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const webDir = path.join(__dirname, "..");
const repoRoot = path.resolve(webDir, "..", "..");
const serverProject = path.join(repoRoot, "apps", "server", "src", "Weir.Host");
const portsPath = path.join(repoRoot, "scripts", "dev-ports.json");
const envFilePath = path.join(repoRoot, ".env");
const defaultDevHome = path.join(repoRoot, ".local-dev-home");
const defaultDevSessionSecret = "dev-session-secret-32-chars-minimum!!";
const pidFilePath = path.join(repoRoot, ".dev-api.pid");

function removePidFile() {
  try {
    rmSync(pidFilePath, { force: true });
  } catch {
    /* ignore — best-effort cleanup */
  }
}

function readPorts() {
  const raw = readFileSync(portsPath, "utf8");
  const j = JSON.parse(raw);
  return j.development;
}

/** The repository `.env` (KEY=value lines); variables already in the environment win. */
function readDotEnv() {
  if (!existsSync(envFilePath)) {
    return {};
  }
  const values = {};
  for (const rawLine of readFileSync(envFilePath, "utf8").split(/\r?\n/)) {
    const line = rawLine.trim();
    if (!line || line.startsWith("#")) continue;
    const eq = line.indexOf("=");
    if (eq < 1) continue;
    const key = line.slice(0, eq).trim();
    let value = line.slice(eq + 1).trim();
    if (
      value.length >= 2 &&
      ((value.startsWith('"') && value.endsWith('"')) ||
        (value.startsWith("'") && value.endsWith("'")))
    ) {
      value = value.slice(1, -1);
    }
    if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) values[key] = value;
  }
  return values;
}

const dotnetProbe = spawnSync("dotnet", ["--version"], { stdio: "ignore", shell: false });
if (dotnetProbe.error || dotnetProbe.status !== 0) {
  console.error(
    "[dev-api] The .NET 10 SDK is required to run the API (dotnet was not found). " +
      "See docs/local-development.md.",
  );
  process.exit(1);
}

const { apiHost, apiPort: portFromFile } = readPorts();
const apiPort = process.env.WEIR_DEV_API_PORT?.trim()
  ? Number(process.env.WEIR_DEV_API_PORT.trim())
  : Number(portFromFile);

const fromFile = readDotEnv();
const childEnv = { ...fromFile };
for (const [key, value] of Object.entries(process.env)) {
  if (value !== undefined && value.trim() !== "") childEnv[key] = value;
}
childEnv.WEIR_HOME = (childEnv.WEIR_HOME || "").trim() || defaultDevHome;
childEnv.WEIR_SESSION_SECRET =
  (childEnv.WEIR_SESSION_SECRET || "").trim() || defaultDevSessionSecret;
// An unset WEIR_ENV now means production; this dev stack opts in to development explicitly
// (the developer exception page, and pairing localhost/127.0.0.1 as the same origin).
childEnv.WEIR_ENV = (childEnv.WEIR_ENV || "").trim() || "development";

const child = spawn(
  "dotnet",
  [
    "watch",
    "run",
    "--project",
    serverProject,
    "--",
    "--host",
    apiHost,
    "--port",
    String(apiPort),
  ],
  {
    cwd: repoRoot,
    stdio: "inherit",
    env: childEnv,
    shell: false,
    // Unix only: makes `child` the leader of its own process group so
    // stop-dev-api-port.mjs can stop `dotnet watch`'s children (the actual `dotnet exec` that
    // binds the port) along with it, not just the watcher. Windows tracks the parent/child
    // lineage itself, so `taskkill /T` there needs no flag on spawn.
    detached: process.platform !== "win32",
  },
);

// Recorded so stop-dev-api-port.mjs can find and verify *this* process later, instead of
// stopping whatever else happens to be listening on the port (which could be an installed
// Weir — see that script's header).
writeFileSync(
  pidFilePath,
  JSON.stringify(
    {
      pid: child.pid,
      // `dotnet` is a generic launcher shared by every .NET process on the machine; the
      // project path is what actually identifies *this* dev API, the same way the installer
      // only recognises a Weir process whose executable is inside its own install folder
      // (apps/tray/Weir.Tray/InstallProcesses.cs).
      serverProject,
      apiHost,
      apiPort,
      startedAt: new Date().toISOString(),
    },
    null,
    2,
  ),
);

function forward(signal) {
  try {
    child.kill(signal);
  } catch {
    /* ignore */
  }
}

process.on("SIGINT", () => forward("SIGINT"));
process.on("SIGTERM", () => forward("SIGTERM"));
process.on("exit", removePidFile);

child.on("exit", (code, signal) => {
  removePidFile();
  if (signal) {
    process.exit(1);
  }
  process.exit(code ?? 0);
});