planche 0.7.8 → 0.7.10

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.
Files changed (54) hide show
  1. package/README.md +17 -1
  2. package/app/assets/{AboutDialog-8g4nv7oI.js → AboutDialog-CNjQdT5u.js} +1 -1
  3. package/app/assets/ConnectDialog-JmGFmUJv.js +1 -0
  4. package/app/assets/{CopyDatabaseDialog-DFHhTGzA.js → CopyDatabaseDialog-BeIaHJEE.js} +1 -1
  5. package/app/assets/{CopyTableDialog-Ai65hTaM.js → CopyTableDialog-RTJBBuHd.js} +1 -1
  6. package/app/assets/{CreateDatabaseDialog-BaddFu5_.js → CreateDatabaseDialog-EQk84TRf.js} +1 -1
  7. package/app/assets/DataDiffDialog-Bsk7fEYT.js +1 -0
  8. package/app/assets/{DownloadToCSVDialog-A3GD6diN.js → DownloadToCSVDialog-fZOrfJCY.js} +2 -2
  9. package/app/assets/{EditSchemaDialog-CfYXbsMs.js → EditSchemaDialog-C8sd3QZp.js} +1 -1
  10. package/app/assets/{FindReplaceDialog-DsafIyXL.js → FindReplaceDialog-Q-kOCd30.js} +1 -1
  11. package/app/assets/{FlushDialog-BJ7t14hx.js → FlushDialog-DuPXbGV8.js} +1 -1
  12. package/app/assets/LinkColumnDialog-Vfx3YnVz.js +1 -0
  13. package/app/assets/{LocalHistoryDialog-DnRAW0Yc.js → LocalHistoryDialog-D7PbCc1T.js} +1 -1
  14. package/app/assets/ProcessListDialog-BcrS--N5.js +1 -0
  15. package/app/assets/QuickCommandPalette-DgmR-AEB.js +1 -0
  16. package/app/assets/RowFormDialog-BeY3wZoM.js +2 -0
  17. package/app/assets/{SafetySettingsDialog-CSJgC2S8.js → SafetySettingsDialog-COTjlwgg.js} +1 -1
  18. package/app/assets/SchemaDiffDialog-DShgozcV.js +7 -0
  19. package/app/assets/{SchemaToHTMLDialog-De2prqOE.js → SchemaToHTMLDialog-6ZXb_qWR.js} +1 -1
  20. package/app/assets/{StatusDialog-DwZ3fiLH.js → StatusDialog-DYnmWcrB.js} +1 -1
  21. package/app/assets/{UserAddDialog-6SxCG4hP.js → UserAddDialog-WJfSy3UY.js} +1 -1
  22. package/app/assets/UserManagerDialog-Bp3YK2PA.js +1 -0
  23. package/app/assets/{VariablesDialog-C9tOMsRa.js → VariablesDialog-CofNmdTr.js} +1 -1
  24. package/app/assets/database-C07SXivf.js +1 -0
  25. package/app/assets/de-BHXb0E0M.js +2 -0
  26. package/app/assets/fr-vqo4FVRK.js +2 -0
  27. package/app/assets/{i18n-BBZ4HyyF.js → i18n-ByO9kvGG.js} +2 -2
  28. package/app/assets/ja-BosgFbQC.js +2 -0
  29. package/app/assets/ko-CvSx-uAq.js +2 -0
  30. package/app/assets/main-BKsXOMU1.css +2 -0
  31. package/app/assets/{main-DwGQ4Tb3.js → main-DKTBYrT9.js} +184 -184
  32. package/app/assets/{query-execution-DNBwLTK8.js → query-execution-Bqix04tS.js} +3 -3
  33. package/app/assets/{select-CYih1UpS.js → select-teWP1vRl.js} +1 -1
  34. package/app/assets/table-copy-NGGO-qDb.js +1 -0
  35. package/app/assets/zh-B0EkNPpR.js +2 -0
  36. package/app/index.html +7 -6
  37. package/dist/index.js +366 -34
  38. package/package.json +2 -2
  39. package/app/assets/ConnectDialog-DaEC7cZs.js +0 -1
  40. package/app/assets/DataDiffDialog-DHLDDTLx.js +0 -1
  41. package/app/assets/LinkColumnDialog-CcIHoa0p.js +0 -1
  42. package/app/assets/ProcessListDialog-D4kPbazL.js +0 -1
  43. package/app/assets/QuickCommandPalette-BPYRYAsk.js +0 -1
  44. package/app/assets/RowFormDialog-C2OsE9vc.js +0 -2
  45. package/app/assets/SchemaDiffDialog-C8v-22Oj.js +0 -7
  46. package/app/assets/UserManagerDialog-o0TErGmM.js +0 -1
  47. package/app/assets/database-DT05D7dO.js +0 -1
  48. package/app/assets/de-CC5Txb37.js +0 -2
  49. package/app/assets/fr-DuwjnnAw.js +0 -2
  50. package/app/assets/ja-B8MYtGo3.js +0 -2
  51. package/app/assets/ko-lJaJ15rE.js +0 -2
  52. package/app/assets/main-DCJnT21X.css +0 -2
  53. package/app/assets/table-copy-CDK_Gn8v.js +0 -1
  54. package/app/assets/zh-DXC_ymYM.js +0 -2
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { randomUUID as randomUUID2 } from "crypto";
5
- import { createRequire } from "module";
5
+ import { createRequire as createRequire2 } from "module";
6
6
  import { spawn } from "child_process";
7
7
  import { parseArgs } from "util";
8
8
 
@@ -344,7 +344,7 @@ var postgresDriverFactory = {
344
344
  };
345
345
 
346
346
  // src/drivers/sqlite.ts
347
- import Database from "better-sqlite3";
347
+ import { createRequire } from "module";
348
348
  import { existsSync, realpathSync } from "fs";
349
349
  import { homedir } from "os";
350
350
  import { basename, dirname, join, relative, resolve, sep } from "path";
@@ -403,13 +403,18 @@ function resolveFile(file, forCreate = false) {
403
403
  }
404
404
  return resolved;
405
405
  }
406
+ var ctor = null;
407
+ function sqliteCtor() {
408
+ ctor ??= createRequire(import.meta.url)("better-sqlite3");
409
+ return ctor;
410
+ }
406
411
  function openDatabase(config) {
407
412
  const file = resolveFile(config.file, config.createIfMissing === true);
408
413
  const isMemory = file === ":memory:";
409
414
  if (!isMemory && !config.createIfMissing && !existsSync(file)) {
410
415
  throw new Error(`SQLite file not found: ${config.file}`);
411
416
  }
412
- const db = new Database(file, {
417
+ const db = new (sqliteCtor())(file, {
413
418
  readonly: config.readonly ?? false,
414
419
  fileMustExist: !isMemory && !config.createIfMissing
415
420
  });
@@ -614,8 +619,8 @@ var MssqlDriver = class {
614
619
  * decide whether it is editable and address rows by primary key.
615
620
  */
616
621
  async describe(text) {
617
- const cached = this.sources.get(text);
618
- if (cached !== void 0) return cached;
622
+ const cached2 = this.sources.get(text);
623
+ if (cached2 !== void 0) return cached2;
619
624
  let result = null;
620
625
  try {
621
626
  const req = this.pool.request();
@@ -760,8 +765,8 @@ var OracleDriver = class {
760
765
  }
761
766
  async tableColumns(schema, table) {
762
767
  const key = `${schema}.${table}`;
763
- const cached = this.columns.get(key);
764
- if (cached !== void 0) return cached;
768
+ const cached2 = this.columns.get(key);
769
+ if (cached2 !== void 0) return cached2;
765
770
  let out = null;
766
771
  try {
767
772
  const res = await this.conn.execute(
@@ -929,8 +934,8 @@ var D1Driver = class {
929
934
  return result;
930
935
  }
931
936
  async tableColumns(table) {
932
- const cached = this.columns.get(table);
933
- if (cached !== void 0) return cached;
937
+ const cached2 = this.columns.get(table);
938
+ if (cached2 !== void 0) return cached2;
934
939
  let out = null;
935
940
  try {
936
941
  const info = await this.raw(`PRAGMA table_info("${table.replaceAll('"', '""')}")`);
@@ -1369,8 +1374,8 @@ async function openSshTunnel(requested, target) {
1369
1374
  verified(false);
1370
1375
  });
1371
1376
  };
1372
- await new Promise((resolve4, reject) => {
1373
- client.once("ready", resolve4);
1377
+ await new Promise((resolve5, reject) => {
1378
+ client.once("ready", resolve5);
1374
1379
  client.once("error", (err) => reject(verifyError ?? describeSshError(err, ssh)));
1375
1380
  try {
1376
1381
  client.connect(connectConfig);
@@ -1400,22 +1405,22 @@ async function openSshTunnel(requested, target) {
1400
1405
  socket.once("close", () => stream.destroy());
1401
1406
  });
1402
1407
  });
1403
- await new Promise((resolve4, reject) => {
1408
+ await new Promise((resolve5, reject) => {
1404
1409
  server.once("error", reject);
1405
- server.listen(0, "127.0.0.1", () => resolve4());
1410
+ server.listen(0, "127.0.0.1", () => resolve5());
1406
1411
  }).catch((err) => {
1407
1412
  client.end();
1408
1413
  throw new TunnelError(`Could not open local tunnel port: ${err.message}`, 400);
1409
1414
  });
1410
1415
  const { port } = server.address();
1411
1416
  let closed = false;
1412
- const close = () => new Promise((resolve4) => {
1413
- if (closed) return resolve4();
1417
+ const close = () => new Promise((resolve5) => {
1418
+ if (closed) return resolve5();
1414
1419
  closed = true;
1415
1420
  for (const s of sockets) s.destroy();
1416
1421
  server.close(() => {
1417
1422
  client.end();
1418
- resolve4();
1423
+ resolve5();
1419
1424
  });
1420
1425
  });
1421
1426
  client.once("close", () => void close());
@@ -1429,6 +1434,116 @@ async function openSshTunnel(requested, target) {
1429
1434
  };
1430
1435
  }
1431
1436
 
1437
+ // src/usage.ts
1438
+ import { mkdir as mkdir2, readFile as readFile3, rename, writeFile } from "fs/promises";
1439
+ import { homedir as homedir4 } from "os";
1440
+ import { join as join4 } from "path";
1441
+
1442
+ // ../shared/contract.ts
1443
+ var PROTOCOL_VERSION = 2;
1444
+ var PRO_FEATURES = [
1445
+ "ssh_tunnel",
1446
+ "compare_schemas",
1447
+ "compare_data",
1448
+ "copy_table",
1449
+ "federated",
1450
+ "scratchpad",
1451
+ "column_link"
1452
+ ];
1453
+
1454
+ // src/usage.ts
1455
+ var PLANCHE_DIR2 = join4(homedir4(), ".planche");
1456
+ var USAGE_FILE = join4(PLANCHE_DIR2, "usage.json");
1457
+ var WRITE_DEBOUNCE_MS = 2e3;
1458
+ var KNOWN = new Set(PRO_FEATURES);
1459
+ var state = null;
1460
+ var loading = null;
1461
+ var timer = null;
1462
+ var writes = Promise.resolve();
1463
+ function isProFeature(value) {
1464
+ return typeof value === "string" && KNOWN.has(value);
1465
+ }
1466
+ var isoNow = () => (/* @__PURE__ */ new Date()).toISOString();
1467
+ function empty() {
1468
+ return { version: 1, since: isoNow(), features: {} };
1469
+ }
1470
+ function parse(raw) {
1471
+ const data = JSON.parse(raw);
1472
+ if (!data || typeof data !== "object") return empty();
1473
+ const features = {};
1474
+ for (const [key, value] of Object.entries(data.features ?? {})) {
1475
+ if (!isProFeature(key) || !value || typeof value !== "object") continue;
1476
+ const entry = value;
1477
+ const count = Number(entry.count);
1478
+ if (!Number.isFinite(count) || count < 0) continue;
1479
+ features[key] = {
1480
+ count: Math.floor(count),
1481
+ first: typeof entry.first === "string" ? entry.first : isoNow(),
1482
+ last: typeof entry.last === "string" ? entry.last : isoNow()
1483
+ };
1484
+ }
1485
+ return { version: 1, since: typeof data.since === "string" ? data.since : isoNow(), features };
1486
+ }
1487
+ async function load() {
1488
+ if (state) return state;
1489
+ loading ??= (async () => {
1490
+ try {
1491
+ state = parse(await readFile3(USAGE_FILE, "utf8"));
1492
+ } catch {
1493
+ state = empty();
1494
+ }
1495
+ return state;
1496
+ })();
1497
+ return loading;
1498
+ }
1499
+ async function persist() {
1500
+ if (!state) return;
1501
+ const body = `${JSON.stringify(state, null, 2)}
1502
+ `;
1503
+ await mkdir2(PLANCHE_DIR2, { recursive: true, mode: 448 });
1504
+ const tmp = `${USAGE_FILE}.${process.pid}.tmp`;
1505
+ await writeFile(tmp, body, { mode: 384 });
1506
+ await rename(tmp, USAGE_FILE);
1507
+ }
1508
+ function schedule() {
1509
+ if (timer) return;
1510
+ timer = setTimeout(() => {
1511
+ timer = null;
1512
+ void flushUsage();
1513
+ }, WRITE_DEBOUNCE_MS);
1514
+ timer.unref?.();
1515
+ }
1516
+ function recordUsage(feature) {
1517
+ void (async () => {
1518
+ try {
1519
+ const file = await load();
1520
+ const now = isoNow();
1521
+ const entry = file.features[feature];
1522
+ if (entry) {
1523
+ entry.count += 1;
1524
+ entry.last = now;
1525
+ } else {
1526
+ file.features[feature] = { count: 1, first: now, last: now };
1527
+ }
1528
+ schedule();
1529
+ } catch {
1530
+ }
1531
+ })();
1532
+ }
1533
+ function flushUsage() {
1534
+ if (timer) {
1535
+ clearTimeout(timer);
1536
+ timer = null;
1537
+ }
1538
+ writes = writes.then(persist).catch(() => {
1539
+ });
1540
+ return writes;
1541
+ }
1542
+ async function usageSnapshot() {
1543
+ const file = await load().catch(empty);
1544
+ return { success: true, since: file.since, features: { ...file.features } };
1545
+ }
1546
+
1432
1547
  // src/drivers/index.ts
1433
1548
  var factories = {
1434
1549
  mysql: mysqlDriverFactory,
@@ -1454,6 +1569,7 @@ function parseDriverConfig(body) {
1454
1569
  async function withTunnel(config, run) {
1455
1570
  if (config.driver === "sqlite" || config.driver === "d1" || !config.ssh) return run(config, void 0);
1456
1571
  const tunnel = await openSshTunnel(config.ssh, { host: config.host, port: config.port });
1572
+ recordUsage("ssh_tunnel");
1457
1573
  try {
1458
1574
  return await run({ ...config, host: tunnel.host, port: tunnel.port, ssh: void 0 }, tunnel);
1459
1575
  } catch (err) {
@@ -1565,17 +1681,207 @@ function describeProfile(entry) {
1565
1681
  }
1566
1682
 
1567
1683
  // src/index.ts
1568
- import { existsSync as existsSync3 } from "fs";
1569
- import { join as join5, resolve as resolve3 } from "path";
1684
+ import { existsSync as existsSync4 } from "fs";
1685
+ import { join as join7, resolve as resolve4 } from "path";
1686
+ import { fileURLToPath as fileURLToPath2 } from "url";
1687
+
1688
+ // src/install.ts
1689
+ import { spawnSync } from "child_process";
1690
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
1691
+ import { homedir as homedir5 } from "os";
1692
+ import { createInterface } from "readline/promises";
1693
+ import { dirname as dirname3, join as join5, resolve as resolve3 } from "path";
1570
1694
  import { fileURLToPath } from "url";
1695
+ var MANIFEST = "install.json";
1696
+ var PLANCHE_DIR3 = join5(homedir5(), ".planche");
1697
+ var UPDATE_CACHE = join5(PLANCHE_DIR3, "update-check.json");
1698
+ var REGISTRY = "https://registry.npmjs.org/planche/latest";
1699
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1700
+ var cached = null;
1701
+ function locate() {
1702
+ if (cached) return cached;
1703
+ const candidates = [];
1704
+ if (process.env.PLANCHE_INSTALL_ROOT) candidates.push(resolve3(process.env.PLANCHE_INSTALL_ROOT));
1705
+ let dir = dirname3(fileURLToPath(import.meta.url));
1706
+ for (let i = 0; i < 6; i += 1) {
1707
+ candidates.push(dir);
1708
+ const parent = dirname3(dir);
1709
+ if (parent === dir) break;
1710
+ dir = parent;
1711
+ }
1712
+ for (const root of candidates) {
1713
+ try {
1714
+ const raw = readFileSync2(join5(root, MANIFEST), "utf8");
1715
+ const manifest = JSON.parse(raw);
1716
+ if (manifest && manifest.version === 1) {
1717
+ cached = { root, manifest };
1718
+ return cached;
1719
+ }
1720
+ } catch {
1721
+ }
1722
+ }
1723
+ cached = { root: null, manifest: null };
1724
+ return cached;
1725
+ }
1726
+ function installKind() {
1727
+ return locate().manifest ? "standalone" : "npx";
1728
+ }
1729
+ function abiWarning() {
1730
+ const { manifest } = locate();
1731
+ if (!manifest?.modules || manifest.modules === process.versions.modules) return null;
1732
+ return `This install was built for Node ABI ${manifest.modules}, but it is running on ${process.version} (ABI ${process.versions.modules}).
1733
+ SQLite connections will fail until the native bindings are rebuilt \u2014 run: planche upgrade`;
1734
+ }
1735
+ function npmCommand(manifest) {
1736
+ const node = manifest.node && existsSync2(manifest.node) ? manifest.node : process.execPath;
1737
+ if (manifest.npmCli && existsSync2(manifest.npmCli)) return { cmd: node, args: [manifest.npmCli], shell: false };
1738
+ if (manifest.npm && existsSync2(manifest.npm)) {
1739
+ return { cmd: manifest.npm, args: [], shell: process.platform === "win32" };
1740
+ }
1741
+ return null;
1742
+ }
1743
+ var VERSION_SPEC = /^(latest|next|\d+\.\d+\.\d+(-[0-9A-Za-z.]+)?)$/;
1744
+ function runUpgrade(target) {
1745
+ if (!VERSION_SPEC.test(target)) {
1746
+ console.error(`Not a version: ${target}. Use \`planche upgrade\` or \`planche upgrade 1.2.3\`.`);
1747
+ return 2;
1748
+ }
1749
+ const { root, manifest } = locate();
1750
+ if (!root || !manifest) {
1751
+ console.error("This copy of planche was not installed by planche.dev/install.sh.");
1752
+ console.error("Run the latest version with: npx planche@latest");
1753
+ return 1;
1754
+ }
1755
+ const npm = npmCommand(manifest);
1756
+ if (!npm) {
1757
+ console.error(`Cannot find npm for this install (see ${join5(root, MANIFEST)}).`);
1758
+ console.error("Re-run the installer: curl -fsSL https://planche.dev/install.sh | sh");
1759
+ return 1;
1760
+ }
1761
+ const prefix = join5(root, "pkg");
1762
+ console.log(`Installing planche@${target} into ${prefix} ...`);
1763
+ const result = spawnSync(
1764
+ npm.cmd,
1765
+ [...npm.args, "install", "--prefix", prefix, "--omit=dev", "--no-audit", "--no-fund", "--loglevel=error", `planche@${target}`],
1766
+ { stdio: "inherit", shell: npm.shell }
1767
+ );
1768
+ if (result.status !== 0) {
1769
+ console.error("Upgrade failed. Re-run the installer to repair the install:");
1770
+ console.error(" curl -fsSL https://planche.dev/install.sh | sh");
1771
+ return result.status ?? 1;
1772
+ }
1773
+ let installed = target;
1774
+ try {
1775
+ const pkg = JSON.parse(readFileSync2(join5(prefix, "node_modules", "planche", "package.json"), "utf8"));
1776
+ installed = pkg.version;
1777
+ } catch {
1778
+ }
1779
+ try {
1780
+ writeFileSync(join5(root, MANIFEST), `${JSON.stringify({ ...manifest, modules: process.versions.modules, installedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
1781
+ `);
1782
+ } catch {
1783
+ }
1784
+ cached = null;
1785
+ clearUpdateCache();
1786
+ console.log(`planche ${installed} installed. Start it with: planche`);
1787
+ return 0;
1788
+ }
1789
+ async function runUninstall(assumeYes) {
1790
+ const { root, manifest } = locate();
1791
+ if (!root || !manifest) {
1792
+ console.error("This copy of planche was not installed by planche.dev/install.sh; there is nothing to uninstall.");
1793
+ console.error("An `npx planche` download lives in the npm cache and is cleared with: npm cache clean --force");
1794
+ return 1;
1795
+ }
1796
+ const targets = [root, ...manifest.bin && existsSync2(manifest.bin) ? [manifest.bin] : []];
1797
+ console.log("This will remove:");
1798
+ for (const t of targets) console.log(` ${t}`);
1799
+ console.log(`
1800
+ Your settings stay: ${PLANCHE_DIR3} (known_hosts, usage counts) is left untouched.`);
1801
+ if (!assumeYes) {
1802
+ if (!process.stdin.isTTY) {
1803
+ console.error("\nRefusing to delete without a confirmation. Re-run with: planche uninstall --yes");
1804
+ return 1;
1805
+ }
1806
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1807
+ const answer = (await rl.question("\nRemove them? [y/N] ")).trim().toLowerCase();
1808
+ rl.close();
1809
+ if (answer !== "y" && answer !== "yes") {
1810
+ console.log("Nothing was removed.");
1811
+ return 1;
1812
+ }
1813
+ }
1814
+ for (const t of targets) {
1815
+ try {
1816
+ rmSync(t, { recursive: true, force: true });
1817
+ } catch (err) {
1818
+ console.error(`Could not remove ${t}: ${err instanceof Error ? err.message : String(err)}`);
1819
+ return 1;
1820
+ }
1821
+ }
1822
+ console.log("planche has been uninstalled.");
1823
+ return 0;
1824
+ }
1825
+ function readUpdateCache() {
1826
+ try {
1827
+ const data = JSON.parse(readFileSync2(UPDATE_CACHE, "utf8"));
1828
+ return typeof data.checkedAt === "number" && typeof data.latest === "string" ? data : null;
1829
+ } catch {
1830
+ return null;
1831
+ }
1832
+ }
1833
+ function writeUpdateCache(latest) {
1834
+ try {
1835
+ mkdirSync(PLANCHE_DIR3, { recursive: true, mode: 448 });
1836
+ writeFileSync(UPDATE_CACHE, `${JSON.stringify({ checkedAt: Date.now(), latest })}
1837
+ `, { mode: 384 });
1838
+ } catch {
1839
+ }
1840
+ }
1841
+ function clearUpdateCache() {
1842
+ try {
1843
+ rmSync(UPDATE_CACHE, { force: true });
1844
+ } catch {
1845
+ }
1846
+ }
1847
+ function isNewer(candidate, current) {
1848
+ const parse2 = (v) => /^\d+\.\d+\.\d+$/.test(v) ? v.split(".").map(Number) : null;
1849
+ const a = parse2(candidate);
1850
+ const b = parse2(current);
1851
+ if (!a || !b) return false;
1852
+ for (let i = 0; i < 3; i += 1) {
1853
+ if (a[i] !== b[i]) return a[i] > b[i];
1854
+ }
1855
+ return false;
1856
+ }
1857
+ async function updateNotice(current) {
1858
+ if (installKind() !== "standalone") return null;
1859
+ if (process.env.PLANCHE_NO_UPDATE_CHECK) return null;
1860
+ const cache = readUpdateCache();
1861
+ let latest = cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS ? cache.latest : null;
1862
+ if (!latest) {
1863
+ const controller = new AbortController();
1864
+ const timer2 = setTimeout(() => controller.abort(), 2500);
1865
+ try {
1866
+ const response = await fetch(REGISTRY, { signal: controller.signal, headers: { accept: "application/json" } });
1867
+ if (!response.ok) return null;
1868
+ const data = await response.json();
1869
+ if (typeof data.version !== "string" || !/^[\w.+-]{1,32}$/.test(data.version)) return null;
1870
+ latest = data.version;
1871
+ writeUpdateCache(latest);
1872
+ } catch {
1873
+ return null;
1874
+ } finally {
1875
+ clearTimeout(timer2);
1876
+ }
1877
+ }
1878
+ return isNewer(latest, current) ? `planche ${latest} is available (you have ${current}) \u2014 update with: planche upgrade` : null;
1879
+ }
1571
1880
 
1572
1881
  // src/server.ts
1573
1882
  import express from "express";
1574
1883
  import { createHash as createHash2, timingSafeEqual as timingSafeEqual2 } from "crypto";
1575
1884
 
1576
- // ../shared/contract.ts
1577
- var PROTOCOL_VERSION = 2;
1578
-
1579
1885
  // src/tunneling.ts
1580
1886
  import { randomUUID } from "crypto";
1581
1887
 
@@ -1742,8 +2048,8 @@ var ConnectionManager = class {
1742
2048
  /** Connections opened for a read-only gateway profile: writes are refused before reaching the driver. */
1743
2049
  readOnly = /* @__PURE__ */ new Set();
1744
2050
  constructor() {
1745
- const timer = setInterval(() => void this.closeIdle(), 6e4);
1746
- timer.unref();
2051
+ const timer2 = setInterval(() => void this.closeIdle(), 6e4);
2052
+ timer2.unref();
1747
2053
  }
1748
2054
  /** Close connections idle for longer than `IDLE_TIMEOUT_MS`. */
1749
2055
  async closeIdle() {
@@ -1852,8 +2158,8 @@ var ConnectionManager = class {
1852
2158
  };
1853
2159
 
1854
2160
  // src/server.ts
1855
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1856
- import { join as join4 } from "path";
2161
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
2162
+ import { join as join6 } from "path";
1857
2163
  function errorStatus(err) {
1858
2164
  const status = err?.status;
1859
2165
  return typeof status === "number" && status >= 400 && status < 600 ? status : 400;
@@ -1985,6 +2291,7 @@ function createAgent(options) {
1985
2291
  activeConnections: manager.activeConnections,
1986
2292
  uptime: process.uptime()
1987
2293
  };
2294
+ if (options.install) body.install = options.install;
1988
2295
  if (options.gateway) body.gateway = { name: options.gateway.name, profiles: options.gateway.profiles.length };
1989
2296
  if (req.headers.authorization !== void 0) {
1990
2297
  if (throttle.locked(peerOf(req))) {
@@ -2084,6 +2391,15 @@ function createAgent(options) {
2084
2391
  fail(res, 422, message);
2085
2392
  }
2086
2393
  });
2394
+ app.post("/usage", (req, res) => {
2395
+ const feature = (req.body ?? {}).feature;
2396
+ if (isProFeature(feature)) recordUsage(feature);
2397
+ res.json({ success: true });
2398
+ });
2399
+ app.get("/usage", async (_req, res) => {
2400
+ await flushUsage();
2401
+ res.json(await usageSnapshot());
2402
+ });
2087
2403
  app.post("/disconnect", async (req, res) => {
2088
2404
  try {
2089
2405
  const { connectionId } = req.body ?? {};
@@ -2138,8 +2454,8 @@ function serveApp(app, appDir) {
2138
2454
  "form-action 'none'",
2139
2455
  "frame-ancestors 'none'"
2140
2456
  ].join("; ");
2141
- const indexFile = join4(appDir, "index.html");
2142
- const indexHtml = existsSync2(indexFile) ? readFileSync2(indexFile, "utf8").replace("<head>", '<head>\n <meta name="planche-agent" content="same-origin" />') : null;
2457
+ const indexFile = join6(appDir, "index.html");
2458
+ const indexHtml = existsSync3(indexFile) ? readFileSync3(indexFile, "utf8").replace("<head>", '<head>\n <meta name="planche-agent" content="same-origin" />') : null;
2143
2459
  const headers = (res) => {
2144
2460
  res.setHeader("Content-Security-Policy", csp);
2145
2461
  res.setHeader("X-Frame-Options", "DENY");
@@ -2164,7 +2480,7 @@ function serveApp(app, appDir) {
2164
2480
  }
2165
2481
 
2166
2482
  // src/index.ts
2167
- var { version: VERSION } = createRequire(import.meta.url)("../package.json");
2483
+ var { version: VERSION } = createRequire2(import.meta.url)("../package.json");
2168
2484
  var DEFAULT_APP_URL = "https://planche.dev";
2169
2485
  var DEV_ORIGINS = [
2170
2486
  "http://localhost:5173",
@@ -2173,8 +2489,8 @@ var DEV_ORIGINS = [
2173
2489
  "http://127.0.0.1:4173"
2174
2490
  ];
2175
2491
  function bundledAppDir() {
2176
- const dir = fileURLToPath(new URL("../app/", import.meta.url));
2177
- return existsSync3(join5(dir, "index.html")) ? dir : null;
2492
+ const dir = fileURLToPath2(new URL("../app/", import.meta.url));
2493
+ return existsSync4(join7(dir, "index.html")) ? dir : null;
2178
2494
  }
2179
2495
  function isLoopbackUrl(url) {
2180
2496
  try {
@@ -2185,19 +2501,19 @@ function isLoopbackUrl(url) {
2185
2501
  }
2186
2502
  async function appIsReachable(url, timeoutMs = 2500) {
2187
2503
  const controller = new AbortController();
2188
- const timer = setTimeout(() => controller.abort(), timeoutMs);
2504
+ const timer2 = setTimeout(() => controller.abort(), timeoutMs);
2189
2505
  try {
2190
2506
  const response = await fetch(url, { signal: controller.signal, redirect: "follow", headers: { "cache-control": "no-store" } });
2191
2507
  return response.ok;
2192
2508
  } catch {
2193
2509
  return false;
2194
2510
  } finally {
2195
- clearTimeout(timer);
2511
+ clearTimeout(timer2);
2196
2512
  }
2197
2513
  }
2198
2514
  function resolveAppDir(explicit) {
2199
- const dir = explicit ? resolve3(explicit) : fileURLToPath(new URL("../app/", import.meta.url));
2200
- if (!existsSync3(join5(dir, "index.html"))) {
2515
+ const dir = explicit ? resolve4(explicit) : fileURLToPath2(new URL("../app/", import.meta.url));
2516
+ if (!existsSync4(join7(dir, "index.html"))) {
2201
2517
  console.error(explicit ? `--app-dir has no index.html: ${dir}` : `This build does not include the web app (${dir}). Install the published package, or pass --app-dir <dir>.`);
2202
2518
  process.exit(2);
2203
2519
  }
@@ -2207,6 +2523,8 @@ function usage() {
2207
2523
  return `planche ${VERSION} \u2014 local agent for the Planche SQL client
2208
2524
 
2209
2525
  Usage: planche [options]
2526
+ planche upgrade [version] Update a standalone install in place
2527
+ planche uninstall [--yes] Remove it again (settings in ~/.planche are kept)
2210
2528
 
2211
2529
  Options:
2212
2530
  -p, --port <n> Port to listen on (default: 8888, env PLANCHE_PORT)
@@ -2254,12 +2572,21 @@ function openInBrowser(url) {
2254
2572
  }
2255
2573
  }
2256
2574
  async function main() {
2575
+ const [subcommand] = process.argv.slice(2);
2576
+ if (subcommand === "upgrade") {
2577
+ process.exit(runUpgrade(process.argv[3] ?? "latest"));
2578
+ }
2579
+ if (subcommand === "uninstall") {
2580
+ process.exit(await runUninstall(process.argv.includes("--yes") || process.argv.includes("-y")));
2581
+ }
2257
2582
  const major = Number(process.versions.node.split(".")[0]);
2258
2583
  if (major < 22) {
2259
2584
  console.error(`planche needs Node.js 22 or newer (you are running ${process.version}).`);
2260
2585
  console.error("Try `nvm use 22` or `npx -p node@22 planche`.");
2261
2586
  process.exit(1);
2262
2587
  }
2588
+ const abi = abiWarning();
2589
+ if (abi) console.warn(`WARNING: ${abi}`);
2263
2590
  let values;
2264
2591
  try {
2265
2592
  values = parseArgs({
@@ -2360,6 +2687,7 @@ async function main() {
2360
2687
  bodyLimit,
2361
2688
  version: VERSION,
2362
2689
  logRequests: flags.verbose ?? false,
2690
+ install: installKind(),
2363
2691
  // Browsers omit a default port from `Host`, so the bare names are allowed too.
2364
2692
  allowedHosts: loopback ? ["127.0.0.1", "localhost", "[::1]", `127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`] : void 0,
2365
2693
  pairingCode,
@@ -2387,6 +2715,9 @@ async function main() {
2387
2715
  }
2388
2716
  console.log(line);
2389
2717
  if (shouldOpen) openInBrowser(pairUrl);
2718
+ void updateNotice(VERSION).then((notice) => {
2719
+ if (notice) console.log(notice);
2720
+ });
2390
2721
  });
2391
2722
  server.on("error", (err) => {
2392
2723
  if (err.code === "EADDRINUSE") {
@@ -2399,6 +2730,7 @@ async function main() {
2399
2730
  const shutdown = async () => {
2400
2731
  console.log("\nShutting down...");
2401
2732
  await manager.disconnectAll();
2733
+ await flushUsage();
2402
2734
  process.exit(0);
2403
2735
  };
2404
2736
  process.on("SIGINT", shutdown);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "planche",
3
- "version": "0.7.8",
4
- "description": "Local agent for Planche — lets the browser-based Planche SQL client reach MySQL/MariaDB/PostgreSQL/SQLite/SQL Server/Oracle/Neon/Supabase/Cloudflare D1 databases from your machine.",
3
+ "version": "0.7.10",
4
+ "description": "Local agent for Planche \u2014 lets the browser-based Planche SQL client reach MySQL/MariaDB/PostgreSQL/SQLite/SQL Server/Oracle/Neon/Supabase/Cloudflare D1 databases from your machine.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://planche.dev",
7
7
  "repository": {
@@ -1 +0,0 @@
1
- import{_ as e,m as t,n,t as r}from"./ui-store-VGM_km-l.js";import{C as i,K as a,M as o,N as s,S as c,a as l,b as u,c as d,d as f,i as p,j as m,l as h,n as g,o as _,r as v,s as y,u as b,w as x,x as S,y as C}from"./main-DwGQ4Tb3.js";import{l as w,m as T,t as E}from"./button-BRD_vJ8s.js";import{a as D,i as O,n as k,r as A,t as j}from"./select-CYih1UpS.js";import{t as M}from"./connection-store-Cn79Ey1H.js";import{n as ee}from"./query-store-Cu277RoH.js";import{O as N,S as te,T as ne,b as re,j as P,k as F,l as I,m as ie,n as ae,s as L,t as oe,w as se}from"./database-DT05D7dO.js";import{n as R,t as z}from"./dbms-Bsjvyvsp.js";import{a as ce,i as le,n as ue,o as de,t as fe}from"./dialog-1MXGAorC.js";var B=e(t(),1),V=new Set([`postgres:`,`postgresql:`]);function H(e){let t=e.trim().replace(/^psql\s+(['"])?/,``).replace(/['"]$/,``);if(!/^postgres(ql)?:\/\//i.test(t))return null;let n;try{n=new URL(t)}catch{return null}if(!V.has(n.protocol.toLowerCase())||!n.hostname)return null;let r=e=>{try{return decodeURIComponent(e)}catch{return e}},i={host:n.hostname};n.port&&(i.port=Number(n.port)),n.username&&(i.user=r(n.username)),n.password&&(i.pass=r(n.password));let a=r(n.pathname.replace(/^\//,``));return a&&(i.database=a),i}var U=w(),W=[`utf8`,`utf8mb4`,`latin1`,`ascii`,`euckr`,`sjis`,`big5`],G={enabled:!1,host:``,port:22,user:``,auth:`key`};function K({label:e,htmlFor:t,children:n}){return(0,U.jsxs)(`div`,{className:`grid grid-cols-[120px_minmax(0,1fr)] items-center gap-2`,children:[(0,U.jsx)(h,{htmlFor:t,children:e}),n]})}function pe({value:e,onChange:t}){let{t:n}=T();function r(n,r){t({...e,[n]:r})}let i=e.dbms===`sqlite`,a=e.transport??`browser`,c=i&&a===`browser`,l=F.has(e.dbms),u=e.dbms===`d1`,d=z.find(t=>t.id===e.dbms)?.capabilities,p=e.ssh??G,[m,h]=(0,B.useState)(``),g=m.trim()===``?`empty`:H(m)?`applied`:`invalid`,v=e.dbms===`supabase`&&e.port===6543;function x(n){h(n);let r=H(n);r&&t({...e,host:r.host??e.host,port:r.port??z.find(t=>t.id===e.dbms)?.defaultPort??e.port,user:r.user??e.user,pass:r.pass??e.pass,database:r.database??e.database})}function S(n){t({...e,ssh:{...p,...n}})}async function C(e){e&&S({privateKey:await e.text(),privateKeyName:e.name})}return(0,U.jsxs)(`div`,{className:`flex flex-col gap-3 p-4`,children:[(0,U.jsx)(K,{label:n(`connection.form.hostName`),htmlFor:`conn-hostName`,children:(0,U.jsx)(b,{id:`conn-hostName`,placeholder:n(`connection.form.hostNamePlaceholder`),value:e.hostName,onChange:e=>r(`hostName`,e.target.value)})}),(0,U.jsx)(K,{label:n(`connection.form.dataSource`),children:(0,U.jsxs)(j,{value:e.dbms,onValueChange:n=>{let r=z.find(e=>e.id===n);if(!r)return;let i=r.id,a=F.has(i)||i===`d1`;h(``),t({...e,dbms:i,transactionMode:r.capabilities.transactions?e.transactionMode:`auto`,port:r.defaultPort??e.port,host:a&&e.host===`localhost`?``:!a&&e.host===``?`localhost`:e.host,user:a||l||u?y(i):e.user,database:e.database&&!a&&!l&&!u?e.database:_(i)??e.database})},children:[(0,U.jsx)(O,{id:`conn-dbms`,className:`w-full`,children:(0,U.jsx)(D,{})}),(0,U.jsx)(k,{children:z.map(e=>(0,U.jsx)(A,{value:e.id,children:(0,U.jsxs)(`span`,{className:`flex items-center gap-2`,children:[(0,U.jsx)(f,{dbms:e.id}),e.label]})},e.id))})]})}),l&&(0,U.jsx)(K,{label:n(`connection.form.connectionString`),htmlFor:`conn-uri`,children:(0,U.jsxs)(`div`,{className:`min-w-0`,children:[(0,U.jsx)(b,{id:`conn-uri`,type:`password`,autoComplete:`off`,spellCheck:!1,className:`font-mono text-xs`,placeholder:e.dbms===`neon`?`postgresql://neondb_owner:npg_…@ep-….neon.tech/neondb?sslmode=require`:`postgresql://postgres.[ref]:[password]@aws-0-….pooler.supabase.com:5432/postgres`,value:m,onChange:e=>x(e.target.value)}),(0,U.jsx)(`p`,{className:`mt-1 text-xs leading-relaxed ${g===`invalid`?`text-destructive`:`text-muted-foreground`}`,children:n(g===`invalid`?`connection.form.connectionStringInvalid`:g===`applied`?`connection.form.connectionStringApplied`:e.dbms===`neon`?`connection.form.neonHint`:`connection.form.supabaseHint`)})]})}),i&&(0,U.jsxs)(K,{label:n(`connection.form.openVia`),children:[(0,U.jsxs)(`div`,{className:`flex items-center gap-4`,children:[(0,U.jsxs)(`label`,{className:`flex items-center gap-1.5 text-sm cursor-pointer`,children:[(0,U.jsx)(`input`,{type:`radio`,name:`transport`,value:`browser`,checked:a===`browser`,onChange:()=>r(`transport`,`browser`),className:`accent-primary`}),n(`connection.form.viaBrowser`)]}),(0,U.jsxs)(`label`,{className:`flex items-center gap-1.5 text-sm cursor-pointer`,children:[(0,U.jsx)(`input`,{type:`radio`,name:`transport`,value:`tunnel`,checked:a===`tunnel`,onChange:()=>r(`transport`,`tunnel`),className:`accent-primary`}),n(`connection.form.viaAgent`)]})]}),(0,U.jsx)(`p`,{className:`col-start-2 text-xs text-muted-foreground leading-relaxed`,children:n(a===`browser`?`connection.form.viaBrowserHint`:`connection.form.viaAgentHint`)})]}),c?(0,U.jsx)(K,{label:n(`connection.form.databaseFile`),children:(0,U.jsxs)(`div`,{className:`min-w-0 text-xs text-muted-foreground leading-relaxed`,children:[e.file?(0,U.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-3 gap-y-1`,children:[(0,U.jsx)(`span`,{className:`min-w-0 break-all text-foreground`,title:e.file,children:e.file}),(0,U.jsx)(E,{type:`button`,variant:`outline`,size:`sm`,className:`h-6 shrink-0 px-2 text-xs`,onClick:()=>{L(e.id),r(`file`,``)},children:n(`connection.form.changeFile`)})]}):(0,U.jsx)(`p`,{children:n(`connection.form.filePickerHint`)}),(0,U.jsx)(`p`,{className:`mt-1`,children:n(`connection.form.fileRememberedHint`)})]})}):null,!c&&(0,U.jsx)(K,{label:n(`connection.form.authToken`),htmlFor:`conn-authToken`,children:(0,U.jsx)(b,{id:`conn-authToken`,type:`password`,placeholder:n(`connection.form.authTokenPlaceholder`),value:e.authToken,onChange:e=>r(`authToken`,e.target.value.trim())})}),c?null:u?(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(K,{label:n(`connection.form.d1AccountId`),htmlFor:`conn-d1-account`,children:(0,U.jsx)(b,{id:`conn-d1-account`,className:`font-mono text-xs`,spellCheck:!1,placeholder:`023e105f4ecef8ad9ca31a8372d0c353`,value:e.accountId??``,onChange:e=>r(`accountId`,e.target.value.trim())})}),(0,U.jsx)(K,{label:n(`connection.form.d1DatabaseId`),htmlFor:`conn-d1-database`,children:(0,U.jsx)(b,{id:`conn-d1-database`,className:`font-mono text-xs`,spellCheck:!1,placeholder:`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`,value:e.databaseId??``,onChange:e=>r(`databaseId`,e.target.value.trim())})}),(0,U.jsx)(K,{label:n(`connection.form.d1ApiToken`),htmlFor:`conn-pass`,children:(0,U.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,U.jsx)(b,{id:`conn-pass`,type:`password`,value:e.pass,onChange:e=>r(`pass`,e.target.value.trim())}),(0,U.jsxs)(`label`,{className:`flex items-center gap-1.5 text-xs whitespace-nowrap cursor-pointer`,title:n(`connection.form.savePasswordHint`),children:[(0,U.jsx)(`input`,{type:`checkbox`,checked:e.savePassword??!0,onChange:e=>r(`savePassword`,e.target.checked)}),n(`connection.form.savePassword`)]})]})}),(0,U.jsx)(`p`,{className:`col-start-2 text-xs text-muted-foreground leading-relaxed`,children:n(`connection.form.d1Hint`)}),(0,U.jsx)(`p`,{className:`col-start-2 text-xs text-muted-foreground leading-relaxed`,children:n(`connection.form.d1LimitsHint`)})]}):i?(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(K,{label:n(`connection.form.databaseFile`),htmlFor:`conn-file`,children:(0,U.jsx)(b,{id:`conn-file`,placeholder:n(`connection.form.filePathPlaceholder`),value:e.file??``,onChange:e=>r(`file`,e.target.value)})}),(0,U.jsx)(K,{label:n(`connection.form.createIfMissing`),htmlFor:`conn-create`,children:(0,U.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,U.jsx)(`input`,{id:`conn-create`,type:`checkbox`,checked:e.createIfMissing??!1,onChange:e=>r(`createIfMissing`,e.target.checked),className:`size-4 accent-primary cursor-pointer`}),(0,U.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:n(`connection.form.createIfMissingHint`)})]})})]}):(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(K,{label:n(`connection.form.host`),htmlFor:`conn-host`,children:(0,U.jsx)(b,{id:`conn-host`,placeholder:e.dbms===`neon`?`ep-….aws.neon.tech`:e.dbms===`supabase`?`aws-0-….pooler.supabase.com`:`localhost`,value:e.host,onChange:e=>r(`host`,e.target.value)})}),(0,U.jsx)(K,{label:n(`connection.form.port`),htmlFor:`conn-port`,children:(0,U.jsx)(b,{id:`conn-port`,type:`number`,value:e.port,onChange:t=>r(`port`,Number(t.target.value)||z.find(t=>t.id===e.dbms)?.defaultPort||3306)})}),v&&(0,U.jsx)(`p`,{className:`col-start-2 rounded-md border border-yellow-500/40 bg-yellow-500/10 px-2 py-1.5 text-xs leading-relaxed text-foreground`,children:n(`connection.form.supabasePoolerWarning`)}),(P(e.dbms)||e.dbms===`mssql`||e.dbms===`oracle`)&&(0,U.jsx)(K,{label:e.dbms===`oracle`?n(`connection.form.serviceName`):n(`connection.form.database`),htmlFor:`conn-database`,children:(0,U.jsx)(b,{id:`conn-database`,placeholder:_(e.dbms),value:e.database??``,onChange:e=>r(`database`,e.target.value.trim())})}),(0,U.jsx)(K,{label:n(`connection.form.user`),htmlFor:`conn-user`,children:(0,U.jsx)(b,{id:`conn-user`,placeholder:y(e.dbms),value:e.user,onChange:e=>r(`user`,e.target.value)})}),(0,U.jsx)(K,{label:n(`connection.form.password`),htmlFor:`conn-pass`,children:(0,U.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,U.jsx)(b,{id:`conn-pass`,type:`password`,value:e.pass,onChange:e=>r(`pass`,e.target.value)}),(0,U.jsxs)(`label`,{className:`flex items-center gap-1.5 text-xs whitespace-nowrap cursor-pointer`,title:n(`connection.form.savePasswordHint`),children:[(0,U.jsx)(`input`,{type:`checkbox`,checked:e.savePassword??!0,onChange:e=>r(`savePassword`,e.target.checked)}),n(`connection.form.savePassword`)]})]})}),l&&(0,U.jsx)(`p`,{className:`col-start-2 text-xs text-muted-foreground leading-relaxed`,children:n(`connection.form.managedTlsHint`)}),!l&&e.dbms!==`oracle`&&(0,U.jsx)(K,{label:n(`connection.form.tls`),children:(0,U.jsxs)(`div`,{className:`flex flex-wrap items-center gap-4`,children:[(0,U.jsxs)(`label`,{className:`flex items-center gap-1.5 text-xs cursor-pointer`,children:[(0,U.jsx)(`input`,{type:`checkbox`,checked:e.ssl??!1,onChange:e=>r(`ssl`,e.target.checked),className:`size-4 accent-primary`}),n(`connection.form.tlsEnable`)]}),(0,U.jsxs)(`label`,{className:`flex items-center gap-1.5 text-xs cursor-pointer ${e.ssl?``:`opacity-50`}`,title:n(`connection.form.tlsVerifyHint`),children:[(0,U.jsx)(`input`,{type:`checkbox`,disabled:!e.ssl,checked:e.sslVerify??!1,onChange:e=>r(`sslVerify`,e.target.checked),className:`size-4 accent-primary`}),n(`connection.form.tlsVerify`)]})]})}),!l&&(0,U.jsx)(K,{label:n(`connection.form.charset`),children:(0,U.jsxs)(j,{value:e.charset,onValueChange:e=>r(`charset`,e),children:[(0,U.jsx)(O,{className:`w-full`,children:(0,U.jsx)(D,{})}),(0,U.jsx)(k,{children:W.map(e=>(0,U.jsx)(A,{value:e,children:e},e))})]})})]}),!i&&!l&&!u&&(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(`div`,{className:`mt-1 border-t pt-3 text-xs font-medium text-muted-foreground`,children:n(`connection.form.ssh`)}),(0,U.jsx)(K,{label:n(`connection.form.sshTunnel`),htmlFor:`conn-ssh`,children:(0,U.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,U.jsx)(`input`,{id:`conn-ssh`,type:`checkbox`,checked:p.enabled,onChange:e=>S({enabled:e.target.checked}),className:`size-4 accent-primary cursor-pointer`}),(0,U.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:n(`connection.form.sshTunnelHint`)})]})}),p.enabled&&(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(K,{label:n(`connection.form.sshHost`),htmlFor:`conn-ssh-host`,children:(0,U.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,U.jsx)(b,{id:`conn-ssh-host`,placeholder:n(`connection.form.sshHostPlaceholder`),value:p.host,onChange:e=>S({host:e.target.value.trim()})}),(0,U.jsx)(b,{"aria-label":n(`connection.form.sshPort`),type:`number`,className:`w-24 shrink-0`,value:p.port,onChange:e=>S({port:Number(e.target.value)||22})})]})}),(0,U.jsx)(K,{label:n(`connection.form.sshUser`),htmlFor:`conn-ssh-user`,children:(0,U.jsx)(b,{id:`conn-ssh-user`,placeholder:n(`connection.form.sshUserPlaceholder`),value:p.user,onChange:e=>S({user:e.target.value.trim()})})}),(0,U.jsx)(K,{label:n(`connection.form.sshAuth`),children:(0,U.jsxs)(j,{value:p.auth,onValueChange:e=>S({auth:e}),children:[(0,U.jsx)(O,{className:`w-full`,children:(0,U.jsx)(D,{})}),(0,U.jsxs)(k,{children:[(0,U.jsx)(A,{value:`key`,children:n(`connection.form.sshAuthKey`)}),(0,U.jsx)(A,{value:`password`,children:n(`connection.form.sshAuthPassword`)}),(0,U.jsx)(A,{value:`agent`,children:n(`connection.form.sshAuthAgent`)})]})]})}),p.auth===`password`&&(0,U.jsx)(K,{label:n(`connection.form.sshPassword`),htmlFor:`conn-ssh-pass`,children:(0,U.jsx)(b,{id:`conn-ssh-pass`,type:`password`,value:p.password??``,onChange:e=>S({password:e.target.value})})}),p.auth===`key`&&(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(K,{label:n(`connection.form.sshKey`),htmlFor:`conn-ssh-key`,children:(0,U.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2`,children:[(0,U.jsx)(`input`,{id:`conn-ssh-key`,type:`file`,className:`hidden`,onChange:e=>{C(e.target.files?.[0]),e.target.value=``}}),(0,U.jsx)(E,{type:`button`,variant:`outline`,size:`sm`,className:`h-7 shrink-0 px-2 text-xs`,onClick:()=>document.getElementById(`conn-ssh-key`)?.click(),children:p.privateKey?n(`connection.form.changeFile`):n(`connection.form.sshKeyPick`)}),(0,U.jsx)(`span`,{className:`min-w-0 truncate text-xs text-muted-foreground`,title:p.privateKeyName,children:p.privateKey?p.privateKeyName||n(`connection.form.sshKeyLoaded`):n(`connection.form.sshKeyHint`)})]})}),(0,U.jsx)(K,{label:n(`connection.form.sshPassphrase`),htmlFor:`conn-ssh-passphrase`,children:(0,U.jsx)(b,{id:`conn-ssh-passphrase`,type:`password`,placeholder:n(`connection.form.sshPassphrasePlaceholder`),value:p.passphrase??``,onChange:e=>S({passphrase:e.target.value})})})]}),p.auth===`agent`&&(0,U.jsx)(`p`,{className:`col-start-2 text-xs text-muted-foreground leading-relaxed`,children:n(`connection.form.sshAuthAgentHint`)}),(0,U.jsx)(`p`,{className:`text-xs text-muted-foreground leading-relaxed`,children:n(`connection.form.sshTargetHint`)}),(0,U.jsx)(`p`,{className:`text-xs text-muted-foreground leading-relaxed`,children:n(`connection.form.sshSecretsHint`)})]})]}),(0,U.jsx)(K,{label:n(`connection.form.autoConnection`),htmlFor:`conn-auto`,children:(0,U.jsx)(`div`,{className:`flex items-center`,children:(0,U.jsx)(`input`,{id:`conn-auto`,type:`checkbox`,checked:e.autoConnection,onChange:e=>r(`autoConnection`,e.target.checked),className:`size-4 accent-primary cursor-pointer`})})}),(0,U.jsx)(`div`,{className:`mt-1 border-t pt-3 text-xs font-medium text-muted-foreground`,children:n(`connection.form.safety`)}),(0,U.jsx)(K,{label:n(`connection.form.environment`),children:(0,U.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[o.map(t=>{let n=(e.environmentColor??`none`)===t.id;return(0,U.jsx)(`button`,{type:`button`,title:s(t.id),"aria-label":s(t.id),"aria-pressed":n,onClick:()=>r(`environmentColor`,t.id),className:`size-6 rounded-full border-2 transition-transform ${n?`scale-110 border-foreground`:`border-transparent hover:scale-105`}`,style:{background:t.id===`none`?`repeating-linear-gradient(45deg, transparent, transparent 3px, var(--muted-foreground) 3px, var(--muted-foreground) 4px)`:t.hex}},t.id)}),(0,U.jsx)(`span`,{className:`ml-1 text-xs text-muted-foreground`,children:n(`connection.form.environmentHint`)})]})}),(0,U.jsx)(K,{label:n(`connection.form.readOnly`),htmlFor:`conn-readonly`,children:(0,U.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,U.jsx)(`input`,{id:`conn-readonly`,type:`checkbox`,checked:e.readOnly??!1,onChange:e=>r(`readOnly`,e.target.checked),className:`size-4 accent-primary cursor-pointer`}),(0,U.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:n(`connection.form.readOnlyHint`)})]})}),d?.transactions!==!1&&(0,U.jsx)(K,{label:n(`connection.form.transactions`),children:(0,U.jsxs)(j,{value:e.transactionMode??`auto`,onValueChange:e=>r(`transactionMode`,e),children:[(0,U.jsx)(O,{className:`w-full`,children:(0,U.jsx)(D,{})}),(0,U.jsxs)(k,{children:[(0,U.jsx)(A,{value:`auto`,children:n(`connection.form.txAuto`)}),(0,U.jsx)(A,{value:`manual`,children:n(`connection.form.txManual`)})]})]})})]})}function q(){let{t:e}=T(),{activeDialog:t,closeDialog:o,showMessage:s}=n(),[h,_]=(0,B.useState)(!1),{savedConnections:y,addSavedConnection:b,updateSavedConnection:w,deleteSavedConnection:D,openConnection:O}=M(),k=r(`connect`)?.profileId,[A,j]=(0,B.useState)(()=>k??null),[P,F]=(0,B.useState)(()=>{let e=k?y.find(e=>e.id===k):void 0;return e?{...e}:null}),L=t===`connect`,[z,V]=(0,B.useState)(null),[H,W]=(0,B.useState)(null),G=d(e=>e.url),K=d(e=>e.token);(0,B.useEffect)(()=>{if(!L)return;let e=!1;return(async()=>{try{let t=await te();if(e||t.state!==`online`||!t.gateway){e||V(null);return}let n=await se();e||(V({name:n.gateway,profiles:n.profiles}),W(null))}catch(t){e||W(t instanceof Error?t.message:String(t))}})(),()=>{e=!0}},[L,G,K]);function q(t){let r=y.find(e=>e.gatewayProfile===t.name);if(r){Y(r);return}let i=v(t);b(i),Y(i),n.getState().setFooterMessage(e(`connection.dialog.gatewayAdded`,{name:t.name}))}function J(e){e||(o(),j(null),F(null))}function Y(e){j(e.id),F({...e})}function me(e){F(e),w(e.id,e)}function he(){let e=l();b(e),j(e.id),F(e)}function ge(){A&&(D(A),j(null),F(null))}let[_e,X]=(0,B.useState)(!1),ve=ee(e=>e.addQueryTab);function Z(t){let n=M.getState().activeConnections.some(e=>e.id===t.id);if(O(t),n){re.getState().removeTree(t.id),o(),j(null),F(null);return}ve(t.id,{id:`tab-${Date.now()}`,title:e(`connection.dialog.firstQueryTab`),query:``,database:``,isExecuting:!1,results:[],error:null,executionTime:null}),o(),j(null),F(null)}async function ye(t){let r=await oe(t.id,{interactive:!0});if(!r)return;let i={...t,file:r.fileName,serverConnectionId:r.connectionId};w(t.id,{file:r.fileName}),Z(i),M.getState().setFileDetached(t.id,r.fileDetached),r.restoredUnsaved?n.getState().setFooterMessage(e(r.diskChanged?`connection.dialog.restoredUnsavedDiskChanged`:`connection.dialog.restoredUnsaved`,{file:r.fileName})):r.wal&&!r.walApplied?n.getState().setFooterMessage(e(`connection.dialog.walPending`,{file:r.fileName})):r.walApplied&&n.getState().setFooterMessage(e(`connection.dialog.walApplied`,{file:r.fileName}))}async function Q(t,r){try{return await r(t)}catch(i){let a=ne(i);if(!a||!(`ssh`in t)||!t.ssh||!await n.getState().requestDestructiveConfirm({title:e(`connection.dialog.hostKeyTitle`),description:e(`connection.dialog.hostKeyDescription`,a),confirmLabel:e(`connection.dialog.hostKeyAccept`)}))throw i;return r({...t,ssh:{...t.ssh,acceptHostKey:a.fingerprint}})}}async function $(){if(P){X(!0);try{if(I(P)===`browser`){await ye(P);return}let t=await Q(N(P),e=>ae(e,P.authToken||void 0));t.success?Z({...P,serverConnectionId:t.connectionId}):s({tone:`error`,title:e(`connection.dialog.connectFailedTitle`),description:t.message??e(`connection.dialog.unknownError`)})}catch(t){s({tone:`error`,title:e(`connection.dialog.connectFailedTitle`),description:t instanceof Error?t.message:String(t)})}finally{X(!1)}}}async function be(){if(P){if(I(P)===`browser`){s({tone:`info`,title:e(`connection.dialog.testConnection`),description:e(`connection.dialog.browserTestHint`)});return}_(!0);try{let t=await Q(N(P),e=>ie(e,P.authToken||void 0));t.success?s({tone:`success`,title:e(`connection.dialog.testSuccess`),description:e(`connection.dialog.testSuccessDescription`)}):s({tone:`error`,title:e(`connection.dialog.testFailedTitle`),description:t.message??e(`connection.dialog.unknownError`)})}catch(t){s({tone:`error`,title:e(`connection.dialog.testFailedTitle`),description:t instanceof Error?t.message:String(t)})}finally{_(!1)}}}return(0,U.jsx)(fe,{open:L,onOpenChange:J,children:(0,U.jsxs)(ue,{className:`sm:max-w-[1100px] h-[85vh] max-h-[85vh] flex flex-col`,children:[(0,U.jsx)(ce,{children:(0,U.jsx)(de,{children:e(`connection.dialog.title`)})}),(0,U.jsxs)(`p`,{className:`flex items-start gap-2 rounded-md border border-catppuccin-green/40 bg-catppuccin-green/10 px-3 py-2 text-xs leading-relaxed text-foreground`,children:[(0,U.jsx)(a,{className:`mt-0.5 size-4 shrink-0 text-catppuccin-green`,"aria-hidden":!0}),(0,U.jsx)(`span`,{children:e(`connection.dialog.privacy`)})]}),(0,U.jsxs)(`div`,{className:`flex flex-1 gap-4 overflow-hidden min-h-0`,children:[(0,U.jsxs)(`div`,{className:`w-[460px] shrink-0 border rounded-md overflow-hidden flex flex-col`,children:[(z||H)&&(0,U.jsxs)(`div`,{className:`border-b p-2 text-xs space-y-1 max-h-40 overflow-auto`,children:[(0,U.jsxs)(`p`,{className:`font-medium`,children:[e(`connection.dialog.gatewayTitle`),z?` — ${z.name}`:``]}),H&&(0,U.jsx)(`p`,{className:`text-destructive`,children:e(`connection.dialog.gatewayLoadFailed`,{error:H})}),z?.profiles.map(t=>(0,U.jsxs)(`div`,{className:`flex items-center justify-between gap-2`,children:[(0,U.jsxs)(`span`,{className:`flex items-center gap-1.5 truncate`,children:[(0,U.jsx)(f,{dbms:g[t.driver],className:`size-3.5`}),(0,U.jsx)(`span`,{className:`truncate`,children:t.name}),(0,U.jsxs)(`span`,{className:`text-muted-foreground`,children:[`(`,t.readOnly?e(`connection.dialog.gatewayReadOnly`):e(`connection.dialog.gatewayWritable`),`)`]})]}),(0,U.jsx)(E,{size:`sm`,variant:`outline`,className:`h-6 px-2 text-xs`,onClick:()=>q(t),children:e(`connection.dialog.gatewayAdd`)})]},t.name))]}),(0,U.jsx)(m,{className:`h-full`,children:(0,U.jsxs)(C,{className:`table-fixed`,children:[(0,U.jsx)(i,{children:(0,U.jsxs)(x,{children:[(0,U.jsx)(c,{className:`w-[32%]`,children:e(`connection.dialog.colHostName`)}),(0,U.jsx)(c,{className:`w-[40%]`,children:e(`connection.dialog.colTarget`)}),(0,U.jsx)(c,{className:`w-[28%]`,children:e(`connection.dialog.colDbms`)})]})}),(0,U.jsx)(u,{children:y.length===0?(0,U.jsx)(x,{children:(0,U.jsx)(S,{colSpan:3,className:`text-center text-muted-foreground`,children:e(`connection.dialog.noSaved`)})}):y.map(t=>(0,U.jsxs)(x,{"data-state":A===t.id?`selected`:void 0,className:`cursor-pointer`,onClick:()=>Y(t),onDoubleClick:()=>{Y(t),$()},children:[(0,U.jsx)(S,{className:`truncate font-medium`,title:t.hostName,children:t.hostName||e(`connection.dialog.unnamed`)}),(0,U.jsx)(S,{className:`truncate`,title:t.dbms===`sqlite`?t.file:t.dbms===`d1`?t.databaseId:`${t.user}@${t.host}:${t.port}`,children:t.dbms===`sqlite`?t.file||(I(t)===`browser`?e(`connection.dialog.pickOnConnect`):e(`connection.dialog.noFile`)):t.dbms===`d1`?t.databaseId||`—`:`${t.user}@${t.host}:${t.port}`}),(0,U.jsx)(S,{className:`truncate`,title:R(t.dbms).label,children:(0,U.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,U.jsx)(f,{dbms:t.dbms,className:`size-3.5`}),(0,U.jsx)(`span`,{className:`truncate`,children:R(t.dbms).label})]})})]},t.id))})]})})]}),(0,U.jsx)(`div`,{className:`flex-1 border rounded-md overflow-auto`,children:P?(0,U.jsxs)(U.Fragment,{children:[I(P)!==`browser`&&(0,U.jsx)(`div`,{className:`p-4 pb-0`,children:(0,U.jsx)(p,{})}),P.gatewayProfile?(0,U.jsxs)(`div`,{className:`p-4 space-y-2 text-sm`,children:[(0,U.jsxs)(`p`,{className:`font-medium flex items-center gap-2`,children:[(0,U.jsx)(f,{dbms:P.dbms,className:`size-4`}),P.hostName]}),(0,U.jsx)(`p`,{className:`text-muted-foreground`,children:e(`connection.dialog.gatewayProfileHint`,{name:P.gatewayProfile})}),(0,U.jsxs)(`p`,{className:`text-xs text-muted-foreground`,children:[P.readOnly?e(`connection.dialog.gatewayReadOnly`):e(`connection.dialog.gatewayWritable`),P.database?` · ${P.database}`:``]})]}):(0,U.jsx)(pe,{value:P,onChange:me})]}):(0,U.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground text-sm`,children:e(`connection.dialog.selectPrompt`)})})]}),(0,U.jsxs)(le,{className:`sm:justify-between`,children:[(0,U.jsxs)(`div`,{className:`flex gap-2`,children:[(0,U.jsx)(E,{variant:`outline`,onClick:he,children:e(`connection.dialog.addNew`)}),(0,U.jsx)(E,{variant:`destructive`,onClick:ge,disabled:!A,children:e(`connection.dialog.delete`)})]}),(0,U.jsxs)(`div`,{className:`flex gap-2`,children:[(0,U.jsx)(E,{variant:`outline`,onClick:be,disabled:!P||h,children:e(`connection.dialog.testConnection`)}),(0,U.jsx)(E,{onClick:$,disabled:!P||_e,children:e(`connection.dialog.connect`)}),(0,U.jsx)(E,{variant:`outline`,onClick:()=>J(!1),children:e(`connection.dialog.close`)})]})]})]})})}export{q as ConnectDialog};