@withone/cli 1.57.0 → 2.0.0

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.
@@ -1456,12 +1456,333 @@ var postgresPlugin = {
1456
1456
  };
1457
1457
 
1458
1458
  // src/lib/memory/plugins/embedded-postgres/index.ts
1459
+ import path2 from "path";
1460
+
1461
+ // src/lib/memory/plugins/embedded-postgres/daemon.ts
1459
1462
  import fs from "fs";
1460
1463
  import net from "net";
1461
1464
  import path from "path";
1462
1465
  import { createRequire } from "module";
1463
- import { spawn } from "child_process";
1466
+ import { spawn, spawnSync } from "child_process";
1464
1467
  var requireFromHere = createRequire(import.meta.url);
1468
+ var PG_PORT_OFFSET = 1e3;
1469
+ var PORT_SEARCH_SPAN = 20;
1470
+ var LOG_MAX_BYTES = 10 * 1024 * 1024;
1471
+ var LOG_KEEP_BYTES = 1024 * 1024;
1472
+ var DEFAULT_STOP_TIMEOUT_MS = 15e3;
1473
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1474
+ function pidFilePath(dataDir) {
1475
+ return path.join(dataDir, ".pgserve.json");
1476
+ }
1477
+ function readPidFile(dataDir) {
1478
+ try {
1479
+ return JSON.parse(fs.readFileSync(pidFilePath(dataDir), "utf8"));
1480
+ } catch {
1481
+ return null;
1482
+ }
1483
+ }
1484
+ function writePidFile(dataDir, rec) {
1485
+ fs.mkdirSync(dataDir, { recursive: true });
1486
+ fs.writeFileSync(pidFilePath(dataDir), JSON.stringify(rec, null, 2), { mode: 384 });
1487
+ }
1488
+ function isPidAlive(pid) {
1489
+ try {
1490
+ process.kill(pid, 0);
1491
+ return true;
1492
+ } catch {
1493
+ return false;
1494
+ }
1495
+ }
1496
+ async function waitForExit(pid, timeoutMs) {
1497
+ const deadline = Date.now() + timeoutMs;
1498
+ while (isPidAlive(pid)) {
1499
+ if (Date.now() >= deadline) return false;
1500
+ await sleep(50);
1501
+ }
1502
+ return true;
1503
+ }
1504
+ function signal(pid, sig) {
1505
+ try {
1506
+ process.kill(pid, sig);
1507
+ } catch {
1508
+ }
1509
+ }
1510
+ function canBind(host, port) {
1511
+ return new Promise((resolve) => {
1512
+ const server = net.createServer();
1513
+ server.once("error", () => resolve(false));
1514
+ server.listen({ port, host, exclusive: true }, () => server.close(() => resolve(true)));
1515
+ });
1516
+ }
1517
+ async function portPairFree(host, port) {
1518
+ return port + PG_PORT_OFFSET <= 65535 && await canBind(host, port) && await canBind("127.0.0.1", port + PG_PORT_OFFSET);
1519
+ }
1520
+ async function choosePort(host, preferred, explicit) {
1521
+ if (await portPairFree(host, preferred)) return preferred;
1522
+ const hint = 'set a different "memory.embedded-postgres.port" in ~/.one/config.json';
1523
+ if (explicit) {
1524
+ throw new Error(
1525
+ `Port ${preferred} (or ${preferred + PG_PORT_OFFSET}, its internal Postgres port) on ${host} is already in use by another program, so the embedded Postgres can't start there. Free it, or ${hint}.`
1526
+ );
1527
+ }
1528
+ for (let port = preferred + 1; port <= preferred + PORT_SEARCH_SPAN; port++) {
1529
+ if (await portPairFree(host, port)) return port;
1530
+ }
1531
+ throw new Error(
1532
+ `Ports ${preferred}-${preferred + PORT_SEARCH_SPAN} on ${host} are all already in use, so the embedded Postgres can't start. Free one, or ${hint}.`
1533
+ );
1534
+ }
1535
+ function readRange(file, start, length) {
1536
+ const fd = fs.openSync(file, "r");
1537
+ try {
1538
+ const buf = Buffer.alloc(length);
1539
+ const n = fs.readSync(fd, buf, 0, length, start);
1540
+ return buf.subarray(0, n);
1541
+ } finally {
1542
+ fs.closeSync(fd);
1543
+ }
1544
+ }
1545
+ function fileSize(file) {
1546
+ try {
1547
+ return fs.statSync(file).size;
1548
+ } catch {
1549
+ return 0;
1550
+ }
1551
+ }
1552
+ function trimLog(logPath, maxBytes = LOG_MAX_BYTES, keepBytes = LOG_KEEP_BYTES) {
1553
+ const size = fileSize(logPath);
1554
+ if (size <= maxBytes) return;
1555
+ const tail = readRange(logPath, size - keepBytes, keepBytes);
1556
+ const nl = tail.indexOf(10);
1557
+ const kept = nl === -1 ? tail : tail.subarray(nl + 1);
1558
+ const notice = `[one] pgserve.log passed ${maxBytes} bytes; kept the last ${kept.length} (${(/* @__PURE__ */ new Date()).toISOString()})
1559
+ `;
1560
+ fs.writeFileSync(logPath, Buffer.concat([Buffer.from(notice), kept]));
1561
+ }
1562
+ function logSince(logPath, offset, maxBytes = 2e3) {
1563
+ const size = fileSize(logPath);
1564
+ const start = Math.max(offset, size - maxBytes);
1565
+ if (size <= start) return "";
1566
+ return readRange(logPath, start, size - start).toString("utf8").replace(/\x1b\[[0-9;]*m/g, "").trim();
1567
+ }
1568
+ function lockIsStale(lockPath, ttlMs) {
1569
+ try {
1570
+ if (Date.now() - fs.statSync(lockPath).mtimeMs > ttlMs) return true;
1571
+ const { pid } = JSON.parse(fs.readFileSync(lockPath, "utf8"));
1572
+ return typeof pid === "number" && !isPidAlive(pid);
1573
+ } catch {
1574
+ return false;
1575
+ }
1576
+ }
1577
+ async function withStartupLock(dataDir, waitMs, fn) {
1578
+ const lockPath = path.join(dataDir, ".pgserve.lock");
1579
+ const deadline = Date.now() + waitMs;
1580
+ for (; ; ) {
1581
+ try {
1582
+ fs.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), { flag: "wx" });
1583
+ break;
1584
+ } catch (err) {
1585
+ if (err.code !== "EEXIST") throw err;
1586
+ }
1587
+ if (lockIsStale(lockPath, waitMs)) {
1588
+ fs.rmSync(lockPath, { force: true });
1589
+ continue;
1590
+ }
1591
+ if (Date.now() >= deadline) {
1592
+ throw new Error(`Timed out waiting for another \`one\` process to start the embedded Postgres (lock: ${lockPath}).`);
1593
+ }
1594
+ await sleep(100);
1595
+ }
1596
+ try {
1597
+ return await fn();
1598
+ } finally {
1599
+ fs.rmSync(lockPath, { force: true });
1600
+ }
1601
+ }
1602
+ function readPostmasterPid(clusterDir) {
1603
+ try {
1604
+ const lines = fs.readFileSync(path.join(clusterDir, "postmaster.pid"), "utf8").split("\n");
1605
+ const pid = Number.parseInt(lines[0] ?? "", 10);
1606
+ const port = Number.parseInt(lines[3] ?? "", 10);
1607
+ return pid > 0 && port > 0 ? { pid, port } : null;
1608
+ } catch {
1609
+ return null;
1610
+ }
1611
+ }
1612
+ function pgserveOwner(postmasterPid, deps) {
1613
+ const ppid = deps.processInfo(postmasterPid)?.ppid;
1614
+ if (!ppid || ppid <= 1) return null;
1615
+ const command = deps.processInfo(ppid)?.command ?? "";
1616
+ return /pgserve[\\/]bin[\\/]postgres-server\.js/.test(command) ? ppid : null;
1617
+ }
1618
+ async function stopStaleCluster(clusterDir, deps, timeoutMs) {
1619
+ const holder = readPostmasterPid(clusterDir);
1620
+ if (!holder || !isPidAlive(holder.pid)) return;
1621
+ if (await deps.probe(holder.port, "postmaster") !== "ours") return;
1622
+ const owner = pgserveOwner(holder.pid, deps);
1623
+ if (owner !== null) {
1624
+ signal(owner, "SIGTERM");
1625
+ if (!await waitForExit(owner, timeoutMs)) signal(owner, "SIGKILL");
1626
+ }
1627
+ if (!isPidAlive(holder.pid)) return;
1628
+ signal(holder.pid, "SIGINT");
1629
+ if (!await waitForExit(holder.pid, timeoutMs)) {
1630
+ throw new Error(
1631
+ `The embedded Postgres (PID ${holder.pid}) is holding ${clusterDir} and did not stop. Stop it (kill ${holder.pid}) and retry.`
1632
+ );
1633
+ }
1634
+ }
1635
+ async function findHealthyDaemon(dataDir, deps) {
1636
+ const rec = readPidFile(dataDir);
1637
+ if (!rec || !isPidAlive(rec.pid)) return null;
1638
+ return await deps.probe(rec.port, "router") === "ours" ? rec.port : null;
1639
+ }
1640
+ function pgserveArgs(cfg, clusterDir, port) {
1641
+ const args = [
1642
+ "--data",
1643
+ clusterDir,
1644
+ "--port",
1645
+ String(port),
1646
+ "--host",
1647
+ cfg.host,
1648
+ "--log",
1649
+ cfg.logLevel,
1650
+ "--no-stats",
1651
+ // Single process. In cluster mode a worker that can't bind the port is
1652
+ // re-forked immediately, forever, each crash logging a full stack trace.
1653
+ "--no-cluster"
1654
+ ];
1655
+ if (cfg.pgvector) args.push("--pgvector");
1656
+ return args;
1657
+ }
1658
+ async function waitUntilServing(child, cfg, port, deps, log) {
1659
+ const deadline = Date.now() + cfg.startupTimeoutMs;
1660
+ const details = () => {
1661
+ const tail = logSince(log.path, log.offset);
1662
+ return `
1663
+ Log: ${log.path}${tail ? `
1664
+ ${tail}` : ""}`;
1665
+ };
1666
+ for (; ; ) {
1667
+ const exit = child.exitStatus();
1668
+ if (exit) throw new Error(`pgserve exited (${exit}) before it was ready.${details()}`);
1669
+ if (await deps.probe(port, "router") === "ours") return;
1670
+ if (Date.now() >= deadline) {
1671
+ throw new Error(
1672
+ `pgserve did not start serving on ${cfg.host}:${port} within ${Math.round(cfg.startupTimeoutMs / 1e3)}s.${details()}`
1673
+ );
1674
+ }
1675
+ await sleep(250);
1676
+ }
1677
+ }
1678
+ async function ensureRunning(cfg, deps) {
1679
+ const reused = await findHealthyDaemon(cfg.dataDir, deps);
1680
+ if (reused !== null) return reused;
1681
+ fs.mkdirSync(cfg.dataDir, { recursive: true });
1682
+ const stopTimeoutMs = deps.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS;
1683
+ return withStartupLock(cfg.dataDir, cfg.startupTimeoutMs + 3 * stopTimeoutMs, async () => {
1684
+ const raced = await findHealthyDaemon(cfg.dataDir, deps);
1685
+ if (raced !== null) return raced;
1686
+ const clusterDir = path.join(cfg.dataDir, "cluster");
1687
+ fs.mkdirSync(clusterDir, { recursive: true });
1688
+ await stopStaleCluster(clusterDir, deps, stopTimeoutMs);
1689
+ const port = await choosePort(cfg.host, cfg.port, cfg.portExplicit);
1690
+ const logPath = path.join(cfg.dataDir, "pgserve.log");
1691
+ trimLog(logPath);
1692
+ const log = { path: logPath, offset: fileSize(logPath) };
1693
+ const fd = fs.openSync(logPath, "a");
1694
+ let child;
1695
+ try {
1696
+ child = deps.spawnPgserve(pgserveArgs(cfg, clusterDir, port), fd);
1697
+ } finally {
1698
+ fs.closeSync(fd);
1699
+ }
1700
+ await waitUntilServing(child, cfg, port, deps, log);
1701
+ writePidFile(cfg.dataDir, {
1702
+ pid: child.pid,
1703
+ port,
1704
+ dataDir: cfg.dataDir,
1705
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
1706
+ });
1707
+ return port;
1708
+ });
1709
+ }
1710
+ function sameDir(a, b) {
1711
+ const norm = (p) => {
1712
+ let r = path.resolve(p);
1713
+ try {
1714
+ r = fs.realpathSync.native(r);
1715
+ } catch {
1716
+ }
1717
+ return process.platform === "win32" ? r.toLowerCase() : r;
1718
+ };
1719
+ return a !== "" && norm(a) === norm(b);
1720
+ }
1721
+ async function probeDataDirectory(Client, target, clusterDir) {
1722
+ const client = new Client({ ...target, connectionTimeoutMillis: 3e3, query_timeout: 3e3 });
1723
+ client.on("error", () => {
1724
+ });
1725
+ try {
1726
+ await client.connect();
1727
+ } catch (err) {
1728
+ return err.code === "ECONNREFUSED" ? "down" : "other";
1729
+ }
1730
+ try {
1731
+ const { rows } = await client.query("SHOW data_directory");
1732
+ return sameDir(String(rows[0]?.data_directory ?? ""), clusterDir) ? "ours" : "other";
1733
+ } catch {
1734
+ return "other";
1735
+ } finally {
1736
+ await client.end().catch(() => {
1737
+ });
1738
+ }
1739
+ }
1740
+ function resolvePgserveBin() {
1741
+ const pkg = requireFromHere.resolve("pgserve/package.json");
1742
+ return path.resolve(path.dirname(pkg), "bin/pgserve-wrapper.cjs");
1743
+ }
1744
+ function spawnPgserve(args, logFd, cwd) {
1745
+ const child = spawn(process.execPath, [resolvePgserveBin(), ...args], {
1746
+ detached: true,
1747
+ stdio: ["ignore", logFd, logFd],
1748
+ // pgserve resolves the bundled bun binary relative to its own package
1749
+ // location, not cwd, so cwd doesn't matter — but pin it for clarity.
1750
+ cwd
1751
+ });
1752
+ let status = null;
1753
+ child.once("error", (err) => {
1754
+ status = `failed to start: ${err.message}`;
1755
+ });
1756
+ child.once("exit", (code, sig) => {
1757
+ status = sig ? `signal ${sig}` : `code ${code}`;
1758
+ });
1759
+ child.unref();
1760
+ if (typeof child.pid !== "number") {
1761
+ throw new Error("Failed to spawn pgserve \u2014 no PID returned.");
1762
+ }
1763
+ return { pid: child.pid, exitStatus: () => status };
1764
+ }
1765
+ function processInfo(pid) {
1766
+ if (process.platform === "win32") return null;
1767
+ const res = spawnSync("ps", ["-o", "ppid=", "-o", "command=", "-p", String(pid)], { encoding: "utf8" });
1768
+ const match = /^(\d+)\s+(.*)$/.exec((res.stdout ?? "").trim());
1769
+ return match ? { ppid: Number(match[1]), command: match[2] } : null;
1770
+ }
1771
+ function nodeDaemonDeps(cfg, Client, router) {
1772
+ const clusterDir = path.join(cfg.dataDir, "cluster");
1773
+ const direct = { user: "postgres", password: "postgres", database: "postgres" };
1774
+ return {
1775
+ probe: (port, via) => probeDataDirectory(
1776
+ Client,
1777
+ via === "router" ? { host: cfg.host, port, ...router } : { host: "127.0.0.1", port, ...direct },
1778
+ clusterDir
1779
+ ),
1780
+ spawnPgserve: (args, logFd) => spawnPgserve(args, logFd, cfg.dataDir),
1781
+ processInfo
1782
+ };
1783
+ }
1784
+
1785
+ // src/lib/memory/plugins/embedded-postgres/index.ts
1465
1786
  var BASE_CAPABILITIES = {
1466
1787
  vectorSearch: true,
1467
1788
  fullTextSearch: true,
@@ -1477,13 +1798,14 @@ var DEFAULTS = {
1477
1798
  // `path.join(homeDir(), ...)` here would capture the home directory before
1478
1799
  // ONE_HOME could take effect. See lib/home.ts.
1479
1800
  get dataDir() {
1480
- return path.join(homeDir(), ".one", "pg");
1801
+ return path2.join(homeDir(), ".one", "pg");
1481
1802
  },
1482
1803
  database: "one_mem",
1483
1804
  schema: "public",
1484
1805
  pgvector: true,
1485
1806
  host: "127.0.0.1",
1486
1807
  port: 5434,
1808
+ portExplicit: false,
1487
1809
  // pgserve auto-provisions databases; the bundled superuser is `postgres`
1488
1810
  // with no password by default. We don't expose the password to the user
1489
1811
  // since the daemon only listens on 127.0.0.1.
@@ -1501,6 +1823,7 @@ function parseConfig2(raw) {
1501
1823
  pgvector: r.pgvector ?? DEFAULTS.pgvector,
1502
1824
  host: r.host ?? DEFAULTS.host,
1503
1825
  port: r.port ?? DEFAULTS.port,
1826
+ portExplicit: r.port !== void 0,
1504
1827
  user: r.user ?? DEFAULTS.user,
1505
1828
  password: r.password ?? DEFAULTS.password,
1506
1829
  logLevel: r.logLevel ?? DEFAULTS.logLevel,
@@ -1547,104 +1870,6 @@ function wrapClient2(pool) {
1547
1870
  }
1548
1871
  };
1549
1872
  }
1550
- function pidFilePath(dataDir) {
1551
- return path.join(dataDir, ".pgserve.json");
1552
- }
1553
- function readPidFile(dataDir) {
1554
- try {
1555
- const raw = fs.readFileSync(pidFilePath(dataDir), "utf8");
1556
- return JSON.parse(raw);
1557
- } catch {
1558
- return null;
1559
- }
1560
- }
1561
- function writePidFile(dataDir, rec) {
1562
- fs.mkdirSync(dataDir, { recursive: true });
1563
- fs.writeFileSync(pidFilePath(dataDir), JSON.stringify(rec, null, 2), { mode: 384 });
1564
- }
1565
- function isPidAlive(pid) {
1566
- try {
1567
- process.kill(pid, 0);
1568
- return true;
1569
- } catch {
1570
- return false;
1571
- }
1572
- }
1573
- async function isPortListening(host, port, timeoutMs = 1e3) {
1574
- return new Promise((resolve) => {
1575
- const socket = new net.Socket();
1576
- let resolved = false;
1577
- const finish = (ok) => {
1578
- if (resolved) return;
1579
- resolved = true;
1580
- socket.destroy();
1581
- resolve(ok);
1582
- };
1583
- socket.setTimeout(timeoutMs);
1584
- socket.once("connect", () => finish(true));
1585
- socket.once("timeout", () => finish(false));
1586
- socket.once("error", () => finish(false));
1587
- socket.connect(port, host);
1588
- });
1589
- }
1590
- async function waitForPort(host, port, deadlineAt) {
1591
- while (Date.now() < deadlineAt) {
1592
- if (await isPortListening(host, port)) return;
1593
- await new Promise((r) => setTimeout(r, 250));
1594
- }
1595
- throw new Error(
1596
- `pgserve did not start listening on ${host}:${port} within ${Math.round((deadlineAt - Date.now() + 3e4) / 1e3)}s. Check the data dir for the postgres log, or remove ${path.basename(pidFilePath("<dataDir>"))} and retry.`
1597
- );
1598
- }
1599
- function resolvePgserveBin() {
1600
- const pkg = requireFromHere.resolve("pgserve/package.json");
1601
- return path.resolve(path.dirname(pkg), "bin/pgserve-wrapper.cjs");
1602
- }
1603
- async function ensureRunning(cfg) {
1604
- const existing = readPidFile(cfg.dataDir);
1605
- if (existing && isPidAlive(existing.pid)) {
1606
- if (await isPortListening(cfg.host, existing.port)) {
1607
- cfg.port = existing.port;
1608
- return;
1609
- }
1610
- }
1611
- fs.mkdirSync(cfg.dataDir, { recursive: true });
1612
- const clusterDir = path.join(cfg.dataDir, "cluster");
1613
- fs.mkdirSync(clusterDir, { recursive: true });
1614
- const args = [
1615
- "--data",
1616
- clusterDir,
1617
- "--port",
1618
- String(cfg.port),
1619
- "--host",
1620
- cfg.host,
1621
- "--log",
1622
- cfg.logLevel,
1623
- "--no-stats"
1624
- ];
1625
- if (cfg.pgvector) args.push("--pgvector");
1626
- const bin = resolvePgserveBin();
1627
- const out = fs.openSync(path.join(cfg.dataDir, "pgserve.log"), "a");
1628
- const child = spawn(process.execPath, [bin, ...args], {
1629
- detached: true,
1630
- stdio: ["ignore", out, out],
1631
- // pgserve resolves the bundled bun binary relative to its own package
1632
- // location, not cwd, so cwd doesn't matter — but pin it for clarity.
1633
- cwd: cfg.dataDir
1634
- });
1635
- child.unref();
1636
- if (typeof child.pid !== "number") {
1637
- throw new Error("Failed to spawn pgserve \u2014 no PID returned.");
1638
- }
1639
- const deadlineAt = Date.now() + cfg.startupTimeoutMs;
1640
- await waitForPort(cfg.host, cfg.port, deadlineAt);
1641
- writePidFile(cfg.dataDir, {
1642
- pid: child.pid,
1643
- port: cfg.port,
1644
- dataDir: cfg.dataDir,
1645
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
1646
- });
1647
- }
1648
1873
  async function probePgvector(client) {
1649
1874
  try {
1650
1875
  await client.query("CREATE EXTENSION IF NOT EXISTS vector");
@@ -1662,12 +1887,20 @@ var LazyEmbeddedPostgresBackend = class {
1662
1887
  }
1663
1888
  async ensure() {
1664
1889
  if (this.backend) return this.backend;
1665
- await ensureRunning(this.config);
1666
1890
  const pgMod = await import("pg").catch((err) => {
1667
1891
  const msg = err instanceof Error ? err.message : String(err);
1668
1892
  throw new Error(`pg is not installed. Run \`npm i pg\` or pick a different backend. (${msg})`);
1669
1893
  });
1670
- const PoolCtor = pgMod.Pool ?? pgMod.default.Pool;
1894
+ const pgExports = pgMod.Pool ? pgMod : pgMod.default;
1895
+ const PoolCtor = pgExports.Pool;
1896
+ this.config.port = await ensureRunning(
1897
+ this.config,
1898
+ nodeDaemonDeps(this.config, pgExports.Client, {
1899
+ user: this.config.user,
1900
+ password: this.config.password,
1901
+ database: this.config.database
1902
+ })
1903
+ );
1671
1904
  const pool = new PoolCtor({
1672
1905
  host: this.config.host,
1673
1906
  port: this.config.port,
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  getBackend,
12
12
  upsertRecord
13
- } from "./chunk-VH46MAE4.js";
13
+ } from "./chunk-IMU2X3ZV.js";
14
14
 
15
15
  // src/commands/mem/migrate.ts
16
16
  import fs4 from "fs";
@@ -12,7 +12,7 @@ import {
12
12
  stripStepsAlias,
13
13
  summarizeFlowInputs,
14
14
  walkSteps
15
- } from "./chunk-QCXFSOTS.js";
15
+ } from "./chunk-6EQDEYVD.js";
16
16
  import "./chunk-44CV5IMX.js";
17
17
  import "./chunk-6UB623BG.js";
18
18
  export {