@pgplex/pgconsole 0.0.3 → 0.1.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.
package/dist/server.mjs CHANGED
@@ -41058,7 +41058,7 @@ async function loadConfig(configPath) {
41058
41058
  const path5 = configPath || "pgconsole.toml";
41059
41059
  if (!existsSync(path5)) {
41060
41060
  throw new Error(`Config file not found: ${path5}
41061
- See https://docs.pgconsole.com/configuration/toml-config`);
41061
+ See https://docs.pgconsole.com/configuration/config`);
41062
41062
  }
41063
41063
  const content = readFileSync(path5, "utf-8");
41064
41064
  const parsed = parse2(content);
@@ -41271,57 +41271,36 @@ See https://docs.pgconsole.com/configuration/toml-config`);
41271
41271
  if (a.jwt_secret.length < 32) {
41272
41272
  throw new Error("Auth jwt_secret must be at least 32 characters");
41273
41273
  }
41274
- let providers = void 0;
41274
+ const providers = [];
41275
41275
  const rawProviders = a.providers;
41276
- if (rawProviders) {
41277
- providers = {};
41278
- for (const key of Object.keys(rawProviders)) {
41279
- const raw = rawProviders[key];
41280
- if (key === "google") {
41281
- if (!raw.client_id || typeof raw.client_id !== "string") {
41282
- throw new Error("Google provider missing required field: client_id");
41283
- }
41284
- if (!raw.client_secret || typeof raw.client_secret !== "string") {
41285
- throw new Error("Google provider missing required field: client_secret");
41286
- }
41287
- providers.google = {
41288
- client_id: raw.client_id,
41289
- client_secret: raw.client_secret
41290
- };
41291
- } else if (key === "keycloak") {
41292
- if (!raw.issuer_url || typeof raw.issuer_url !== "string") {
41293
- throw new Error("Keycloak provider missing required field: issuer_url");
41294
- }
41295
- if (!raw.client_id || typeof raw.client_id !== "string") {
41296
- throw new Error("Keycloak provider missing required field: client_id");
41297
- }
41298
- if (!raw.client_secret || typeof raw.client_secret !== "string") {
41299
- throw new Error("Keycloak provider missing required field: client_secret");
41300
- }
41301
- providers.keycloak = {
41302
- issuer_url: raw.issuer_url.replace(/\/+$/, ""),
41303
- client_id: raw.client_id,
41304
- client_secret: raw.client_secret
41305
- };
41306
- } else if (key === "okta") {
41307
- if (!raw.issuer_url || typeof raw.issuer_url !== "string") {
41308
- throw new Error("Okta provider missing required field: issuer_url");
41309
- }
41310
- if (!raw.client_id || typeof raw.client_id !== "string") {
41311
- throw new Error("Okta provider missing required field: client_id");
41312
- }
41313
- if (!raw.client_secret || typeof raw.client_secret !== "string") {
41314
- throw new Error("Okta provider missing required field: client_secret");
41315
- }
41316
- providers.okta = {
41317
- issuer_url: raw.issuer_url.replace(/\/+$/, ""),
41318
- client_id: raw.client_id,
41319
- client_secret: raw.client_secret
41320
- };
41321
- }
41276
+ const validProviderTypes = ["google", "keycloak", "okta"];
41277
+ if (rawProviders && Array.isArray(rawProviders)) {
41278
+ for (const entry of rawProviders) {
41279
+ const raw = entry;
41280
+ if (!raw.type || typeof raw.type !== "string") {
41281
+ throw new Error("Auth provider missing required field: type");
41282
+ }
41283
+ if (!validProviderTypes.includes(raw.type)) {
41284
+ throw new Error(`Auth provider has invalid type: ${raw.type}. Must be one of: ${validProviderTypes.join(", ")}`);
41285
+ }
41286
+ if (!raw.client_id || typeof raw.client_id !== "string") {
41287
+ throw new Error(`${raw.type} provider missing required field: client_id`);
41288
+ }
41289
+ if (!raw.client_secret || typeof raw.client_secret !== "string") {
41290
+ throw new Error(`${raw.type} provider missing required field: client_secret`);
41291
+ }
41292
+ if ((raw.type === "keycloak" || raw.type === "okta") && (!raw.issuer_url || typeof raw.issuer_url !== "string")) {
41293
+ throw new Error(`${raw.type} provider missing required field: issuer_url`);
41294
+ }
41295
+ providers.push({
41296
+ type: raw.type,
41297
+ client_id: raw.client_id,
41298
+ client_secret: raw.client_secret,
41299
+ issuer_url: typeof raw.issuer_url === "string" ? raw.issuer_url.replace(/\/+$/, "") : void 0
41300
+ });
41322
41301
  }
41323
41302
  }
41324
- const hasOAuthProvider = providers?.google || providers?.keycloak || providers?.okta;
41303
+ const hasOAuthProvider = providers.length > 0;
41325
41304
  if (hasOAuthProvider && !external_url) {
41326
41305
  throw new Error("[general] external_url is required when OAuth providers are enabled");
41327
41306
  }
@@ -41375,7 +41354,7 @@ See https://docs.pgconsole.com/configuration/toml-config`);
41375
41354
  }
41376
41355
  }
41377
41356
  const iam = [];
41378
- const validPermissions = ["read", "write", "ddl", "admin"];
41357
+ const validPermissions = ["read", "write", "ddl", "admin", "explain", "execute", "export"];
41379
41358
  const rawIAM = parsed.iam || [];
41380
41359
  for (let i2 = 0; i2 < rawIAM.length; i2++) {
41381
41360
  const rule = rawIAM[i2];
@@ -41395,10 +41374,13 @@ See https://docs.pgconsole.com/configuration/toml-config`);
41395
41374
  if (typeof perm !== "string") {
41396
41375
  throw new Error(`IAM rule ${ruleNum} has invalid permission: must be string`);
41397
41376
  }
41398
- if (!validPermissions.includes(perm)) {
41399
- throw new Error(`IAM rule ${ruleNum} has invalid permission: ${perm}. Must be one of: ${validPermissions.join(", ")}`);
41377
+ if (perm === "*") {
41378
+ permissions.push(...validPermissions);
41379
+ } else if (!validPermissions.includes(perm)) {
41380
+ throw new Error(`IAM rule ${ruleNum} has invalid permission: ${perm}. Must be one of: ${validPermissions.join(", ")}, *`);
41381
+ } else {
41382
+ permissions.push(perm);
41400
41383
  }
41401
- permissions.push(perm);
41402
41384
  }
41403
41385
  if (!Array.isArray(rule.members) || rule.members.length === 0) {
41404
41386
  throw new Error(`IAM rule ${ruleNum} missing required field: members (must be non-empty array)`);
@@ -41409,7 +41391,7 @@ See https://docs.pgconsole.com/configuration/toml-config`);
41409
41391
  throw new Error(`IAM rule ${ruleNum} has invalid member: must be non-empty string`);
41410
41392
  }
41411
41393
  const m2 = member.trim();
41412
- if (m2 === "allAuthenticatedUsers") {
41394
+ if (m2 === "*") {
41413
41395
  members.push(m2);
41414
41396
  } else if (m2.startsWith("user:")) {
41415
41397
  const email = m2.slice(5);
@@ -41427,7 +41409,7 @@ See https://docs.pgconsole.com/configuration/toml-config`);
41427
41409
  }
41428
41410
  members.push(m2);
41429
41411
  } else {
41430
- throw new Error(`IAM rule ${ruleNum} has invalid member format: ${m2}. Must be "allAuthenticatedUsers", "user:xxx", or "group:xxx"`);
41412
+ throw new Error(`IAM rule ${ruleNum} has invalid member format: ${m2}. Must be "*", "user:xxx", or "group:xxx"`);
41431
41413
  }
41432
41414
  }
41433
41415
  iam.push({ connection, permissions, members });
@@ -41452,13 +41434,6 @@ See https://docs.pgconsole.com/configuration/toml-config`);
41452
41434
  throw new Error(`Too many [[users]] entries: ${users.length} configured but current license only allows ${limit2}. Remove users or upgrade your license.`);
41453
41435
  }
41454
41436
  }
41455
- console.log(`Loaded ${users.length} user(s), ${groups.length} group(s), ${labels.length} label(s) and ${connections.length} connection(s) from ${path5}`);
41456
- if (ai) {
41457
- console.log(`AI configured with ${ai.providers.length} provider(s)`);
41458
- }
41459
- if (iam.length > 0) {
41460
- console.log(`IAM configured with ${iam.length} rule(s)`);
41461
- }
41462
41437
  }
41463
41438
  function getLabels() {
41464
41439
  return loadedConfig.labels;
@@ -41476,6 +41451,9 @@ function getConnectionById(id) {
41476
41451
  function getAuthConfig() {
41477
41452
  return loadedConfig.auth;
41478
41453
  }
41454
+ function getAuthProvider(type) {
41455
+ return loadedConfig.auth?.providers.find((p) => p.type === type);
41456
+ }
41479
41457
  function getUsers() {
41480
41458
  return loadedConfig.users;
41481
41459
  }
@@ -41638,11 +41616,24 @@ function auditSQL(actor, connection, database, sql, success, duration_ms, row_co
41638
41616
  if (error) event.error = error;
41639
41617
  emit(event);
41640
41618
  }
41619
+ function auditExport(actor, connection, database, sql, row_count, format) {
41620
+ emit({
41621
+ type: "audit",
41622
+ ts: now(),
41623
+ action: "data.export",
41624
+ actor,
41625
+ connection,
41626
+ database,
41627
+ sql,
41628
+ row_count,
41629
+ format
41630
+ });
41631
+ }
41641
41632
 
41642
41633
  // server/lib/oauth/google.ts
41643
41634
  function registerGoogleOAuth(router3, opts) {
41644
41635
  router3.get("/google", (_req, res) => {
41645
- const google = getAuthConfig()?.providers?.google;
41636
+ const google = getAuthProvider("google");
41646
41637
  if (!google) {
41647
41638
  return res.status(400).json({ error: "Google OAuth not configured" });
41648
41639
  }
@@ -41659,7 +41650,7 @@ function registerGoogleOAuth(router3, opts) {
41659
41650
  return res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params}`);
41660
41651
  });
41661
41652
  router3.get("/google/callback", async (req, res) => {
41662
- const google = getAuthConfig()?.providers?.google;
41653
+ const google = getAuthProvider("google");
41663
41654
  const externalUrl = getExternalUrl();
41664
41655
  if (!google) {
41665
41656
  return res.redirect(`${externalUrl}/signin?error=not_configured`);
@@ -41729,7 +41720,7 @@ function registerGoogleOAuth(router3, opts) {
41729
41720
  // server/lib/oauth/keycloak.ts
41730
41721
  function registerKeycloakOAuth(router3, opts) {
41731
41722
  router3.get("/keycloak", (_req, res) => {
41732
- const keycloak = getAuthConfig()?.providers?.keycloak;
41723
+ const keycloak = getAuthProvider("keycloak");
41733
41724
  if (!keycloak) {
41734
41725
  return res.status(400).json({ error: "Keycloak not configured" });
41735
41726
  }
@@ -41746,7 +41737,7 @@ function registerKeycloakOAuth(router3, opts) {
41746
41737
  return res.redirect(`${keycloak.issuer_url}/protocol/openid-connect/auth?${params}`);
41747
41738
  });
41748
41739
  router3.get("/keycloak/callback", async (req, res) => {
41749
- const keycloak = getAuthConfig()?.providers?.keycloak;
41740
+ const keycloak = getAuthProvider("keycloak");
41750
41741
  const externalUrl = getExternalUrl();
41751
41742
  if (!keycloak) {
41752
41743
  return res.redirect(`${externalUrl}/signin?error=not_configured`);
@@ -41813,7 +41804,7 @@ function registerKeycloakOAuth(router3, opts) {
41813
41804
  // server/lib/oauth/okta.ts
41814
41805
  function registerOktaOAuth(router3, opts) {
41815
41806
  router3.get("/okta", (_req, res) => {
41816
- const okta = getAuthConfig()?.providers?.okta;
41807
+ const okta = getAuthProvider("okta");
41817
41808
  if (!okta) {
41818
41809
  return res.status(400).json({ error: "Okta not configured" });
41819
41810
  }
@@ -41830,7 +41821,7 @@ function registerOktaOAuth(router3, opts) {
41830
41821
  return res.redirect(`${okta.issuer_url}/v1/authorize?${params}`);
41831
41822
  });
41832
41823
  router3.get("/okta/callback", async (req, res) => {
41833
- const okta = getAuthConfig()?.providers?.okta;
41824
+ const okta = getAuthProvider("okta");
41834
41825
  const externalUrl = getExternalUrl();
41835
41826
  if (!okta) {
41836
41827
  return res.redirect(`${externalUrl}/signin?error=not_configured`);
@@ -42018,14 +42009,12 @@ router.get("/providers", (_req, res) => {
42018
42009
  const plan = getPlan();
42019
42010
  const providers = [];
42020
42011
  if (getUsers().some((u) => u.password)) providers.push({ name: "basic" });
42021
- if (config?.providers) {
42022
- for (const key of Object.keys(config.providers)) {
42023
- const feat = SSO_FEATURE[key];
42024
- if (feat && !feature(feat, plan)) {
42025
- providers.push({ name: key, requiredPlan: requiredPlan(feat) });
42026
- } else {
42027
- providers.push({ name: key });
42028
- }
42012
+ for (const provider of config?.providers ?? []) {
42013
+ const feat = SSO_FEATURE[provider.type];
42014
+ if (feat && !feature(feat, plan)) {
42015
+ providers.push({ name: provider.type, requiredPlan: requiredPlan(feat) });
42016
+ } else {
42017
+ providers.push({ name: provider.type });
42029
42018
  }
42030
42019
  }
42031
42020
  return res.json({ providers });
@@ -56349,6 +56338,93 @@ var TerminateSessionResponse = class _TerminateSessionResponse extends Message {
56349
56338
  return proto3.util.equals(_TerminateSessionResponse, a, b);
56350
56339
  }
56351
56340
  };
56341
+ var AuditExportRequest = class _AuditExportRequest extends Message {
56342
+ /**
56343
+ * @generated from field: string connection_id = 1;
56344
+ */
56345
+ connectionId = "";
56346
+ /**
56347
+ * @generated from field: string sql = 2;
56348
+ */
56349
+ sql = "";
56350
+ /**
56351
+ * @generated from field: int32 row_count = 3;
56352
+ */
56353
+ rowCount = 0;
56354
+ /**
56355
+ * @generated from field: string format = 4;
56356
+ */
56357
+ format = "";
56358
+ constructor(data) {
56359
+ super();
56360
+ proto3.util.initPartial(data, this);
56361
+ }
56362
+ static runtime = proto3;
56363
+ static typeName = "query.v1.AuditExportRequest";
56364
+ static fields = proto3.util.newFieldList(() => [
56365
+ {
56366
+ no: 1,
56367
+ name: "connection_id",
56368
+ kind: "scalar",
56369
+ T: 9
56370
+ /* ScalarType.STRING */
56371
+ },
56372
+ {
56373
+ no: 2,
56374
+ name: "sql",
56375
+ kind: "scalar",
56376
+ T: 9
56377
+ /* ScalarType.STRING */
56378
+ },
56379
+ {
56380
+ no: 3,
56381
+ name: "row_count",
56382
+ kind: "scalar",
56383
+ T: 5
56384
+ /* ScalarType.INT32 */
56385
+ },
56386
+ {
56387
+ no: 4,
56388
+ name: "format",
56389
+ kind: "scalar",
56390
+ T: 9
56391
+ /* ScalarType.STRING */
56392
+ }
56393
+ ]);
56394
+ static fromBinary(bytes, options) {
56395
+ return new _AuditExportRequest().fromBinary(bytes, options);
56396
+ }
56397
+ static fromJson(jsonValue, options) {
56398
+ return new _AuditExportRequest().fromJson(jsonValue, options);
56399
+ }
56400
+ static fromJsonString(jsonString, options) {
56401
+ return new _AuditExportRequest().fromJsonString(jsonString, options);
56402
+ }
56403
+ static equals(a, b) {
56404
+ return proto3.util.equals(_AuditExportRequest, a, b);
56405
+ }
56406
+ };
56407
+ var AuditExportResponse = class _AuditExportResponse extends Message {
56408
+ constructor(data) {
56409
+ super();
56410
+ proto3.util.initPartial(data, this);
56411
+ }
56412
+ static runtime = proto3;
56413
+ static typeName = "query.v1.AuditExportResponse";
56414
+ static fields = proto3.util.newFieldList(() => []);
56415
+ static fromBinary(bytes, options) {
56416
+ return new _AuditExportResponse().fromBinary(bytes, options);
56417
+ }
56418
+ static fromJson(jsonValue, options) {
56419
+ return new _AuditExportResponse().fromJson(jsonValue, options);
56420
+ }
56421
+ static fromJsonString(jsonString, options) {
56422
+ return new _AuditExportResponse().fromJsonString(jsonString, options);
56423
+ }
56424
+ static equals(a, b) {
56425
+ return proto3.util.equals(_AuditExportResponse, a, b);
56426
+ }
56427
+ };
56352
56428
 
56353
56429
  // src/gen/query_connect.ts
56354
56430
  var QueryService = {
@@ -56515,6 +56591,15 @@ var QueryService = {
56515
56591
  I: TerminateSessionRequest,
56516
56592
  O: TerminateSessionResponse,
56517
56593
  kind: MethodKind.Unary
56594
+ },
56595
+ /**
56596
+ * @generated from rpc query.v1.QueryService.AuditExport
56597
+ */
56598
+ auditExport: {
56599
+ name: "AuditExport",
56600
+ I: AuditExportRequest,
56601
+ O: AuditExportResponse,
56602
+ kind: MethodKind.Unary
56518
56603
  }
56519
56604
  }
56520
56605
  };
@@ -57488,16 +57573,21 @@ function setConnectionVersion(connectionId, version) {
57488
57573
  const info = { version };
57489
57574
  connectionCache.set(connectionId, info);
57490
57575
  }
57491
- function extractPostgresVersion(versionString) {
57576
+ async function testAndCacheConnection(client, connectionId) {
57577
+ await client`SELECT 1`;
57578
+ const versionResult = await client`SELECT version()`;
57579
+ const versionString = versionResult[0]?.version || "";
57492
57580
  const match = versionString.match(/PostgreSQL (\d+)/);
57493
57581
  if (!match?.[1]) {
57494
57582
  throw new Error(`Failed to extract PostgreSQL version from: ${versionString}`);
57495
57583
  }
57496
- return match[1];
57584
+ const version = match[1];
57585
+ setConnectionVersion(connectionId, version);
57586
+ return version;
57497
57587
  }
57498
57588
 
57499
57589
  // server/lib/iam.ts
57500
- var ALL_PERMISSIONS = ["read", "write", "ddl", "admin"];
57590
+ var ALL_PERMISSIONS = ["read", "write", "ddl", "admin", "explain", "execute", "export"];
57501
57591
  function requirePermission(user, connectionId, permission, action) {
57502
57592
  if (!user) {
57503
57593
  throw new ConnectError("Authentication required", Code.Unauthenticated);
@@ -57542,7 +57632,7 @@ function getUserPermissions(email, connectionId) {
57542
57632
  continue;
57543
57633
  }
57544
57634
  const matches = rule.members.some((member) => {
57545
- if (member === "allAuthenticatedUsers") {
57635
+ if (member === "*") {
57546
57636
  return true;
57547
57637
  }
57548
57638
  if (member.startsWith("user:")) {
@@ -57644,11 +57734,7 @@ var connectionServiceHandlers = {
57644
57734
  sslMode: conn.ssl_mode || "prefer"
57645
57735
  }, user?.email);
57646
57736
  try {
57647
- await client`SELECT 1`;
57648
- const versionResult = await client`SELECT version()`;
57649
- const versionString = versionResult[0]?.version || "";
57650
- const version = extractPostgresVersion(versionString);
57651
- setConnectionVersion(req.id, version);
57737
+ await testAndCacheConnection(client, req.id);
57652
57738
  const latencyMs = Date.now() - start2;
57653
57739
  return { success: true, error: "", latencyMs };
57654
57740
  } catch (err) {
@@ -57757,6 +57843,7 @@ function transformStatement(node, source) {
57757
57843
  if ("AlterPublicationStmt" in obj) return { kind: "alter_publication", source };
57758
57844
  if ("ReassignOwnedStmt" in obj) return { kind: "reassign_owned", source };
57759
57845
  if ("DropOwnedStmt" in obj) return { kind: "drop_owned", source };
57846
+ if ("CallStmt" in obj) return { kind: "call", source };
57760
57847
  return { kind: "unknown", raw: node, source };
57761
57848
  }
57762
57849
  function transformSelect(raw) {
@@ -62078,9 +62165,14 @@ function getRequiredPermission(kind) {
62078
62165
  switch (kind) {
62079
62166
  // Read-only
62080
62167
  case "select":
62081
- case "explain":
62082
62168
  case "show":
62083
62169
  return "read";
62170
+ // Explain
62171
+ case "explain":
62172
+ return "explain";
62173
+ // Execute (stored procedures)
62174
+ case "call":
62175
+ return "execute";
62084
62176
  // DML (data modification)
62085
62177
  case "insert":
62086
62178
  case "update":
@@ -62105,7 +62197,7 @@ function getRequiredPermission(kind) {
62105
62197
  case "revoke":
62106
62198
  case "refresh_matview":
62107
62199
  return "ddl";
62108
- // COPY depends on direction
62200
+ // COPY (data import/export via server filesystem)
62109
62201
  case "copy":
62110
62202
  return "write";
62111
62203
  // Session/transaction control - safe operations
@@ -63195,6 +63287,21 @@ var queryServiceHandlers = {
63195
63287
  error: err instanceof Error ? err.message : "Failed to terminate session"
63196
63288
  };
63197
63289
  }
63290
+ },
63291
+ async auditExport(req, context) {
63292
+ if (!req.connectionId) {
63293
+ throw new ConnectError("connection_id is required", Code.InvalidArgument);
63294
+ }
63295
+ const user = await getUserFromContext(context.values);
63296
+ if (!user) {
63297
+ throw new ConnectError("Authentication required", Code.Unauthenticated);
63298
+ }
63299
+ const conn = getConnectionById(req.connectionId);
63300
+ if (!conn) {
63301
+ throw new ConnectError("Connection not found", Code.NotFound);
63302
+ }
63303
+ auditExport(user.email, req.connectionId, conn.database, req.sql, req.rowCount, req.format);
63304
+ return {};
63198
63305
  }
63199
63306
  };
63200
63307
 
@@ -92448,11 +92555,7 @@ async function testAllConnections() {
92448
92555
  sslMode: conn.ssl_mode || "prefer"
92449
92556
  });
92450
92557
  try {
92451
- await client`SELECT 1`;
92452
- const versionResult = await client`SELECT version()`;
92453
- const versionString = versionResult[0]?.version || "";
92454
- const version = extractPostgresVersion(versionString);
92455
- setConnectionVersion(conn.id, version);
92558
+ const version = await testAndCacheConnection(client, conn.id);
92456
92559
  console.log(` \u2713 ${conn.name} (PostgreSQL ${version})`);
92457
92560
  } finally {
92458
92561
  await client.end();
@@ -92527,8 +92630,10 @@ app.use((req, res, next) => {
92527
92630
  async function start() {
92528
92631
  const args = parseArgs();
92529
92632
  const port = args.port || process.env.PORT || 9876;
92633
+ const configPath = args.config || "pgconsole.toml";
92530
92634
  try {
92531
- await loadConfig(args.config);
92635
+ await loadConfig(configPath);
92636
+ console.log(`\u2713 Loaded config from: ${path4.resolve(configPath)}`);
92532
92637
  } catch (error) {
92533
92638
  console.error("Failed to load config:", error instanceof Error ? error.message : error);
92534
92639
  process.exit(1);
@@ -92546,9 +92651,40 @@ async function start() {
92546
92651
  console.error("\nConnection test failed:", error instanceof Error ? error.message : error);
92547
92652
  process.exit(1);
92548
92653
  }
92549
- app.listen(port, () => {
92654
+ const server = app.listen(port, () => {
92655
+ console.log(`
92656
+ ___
92657
+ /\\_ \\
92658
+ _____ __ ___ ___ ___ ____ ___\\//\\ \\ __
92659
+ /\\ '__\`\\ /'_ \`\\ /'___\\ / __\`\\ /' _ \`\\ /',__\\ / __\`\\\\ \\ \\ /'__\`\\
92660
+ \\ \\ \\L\\ \\/\\ \\L\\ \\/\\ \\__//\\ \\L\\ \\/\\ \\/\\ \\/\\__, \`\\/\\ \\L\\ \\\\_\\ \\_/\\ __/
92661
+ \\ \\ ,__/\\ \\____ \\ \\____\\ \\____/\\ \\_\\ \\_\\/\\____/\\ \\____//\\____\\ \\____\\
92662
+ \\ \\ \\/ \\/___L\\ \\/____/\\/___/ \\/_/\\/_/\\/___/ \\/___/ \\/____/\\/____/
92663
+ \\ \\_\\ /\\____/
92664
+ \\/_/ \\_/__/
92665
+
92666
+ Version ${"0.1.0"}
92667
+ `);
92550
92668
  console.log(`Server running on http://localhost:${port}`);
92669
+ const browserUrl = false ? `http://localhost:5173` : getExternalUrl() || `http://localhost:${port}`;
92670
+ console.log(`Open in browser: ${browserUrl}`);
92551
92671
  });
92672
+ server.on("error", (err) => {
92673
+ if (err.code === "EADDRINUSE") {
92674
+ console.error(`Error: Port ${port} is already in use.`);
92675
+ } else {
92676
+ console.error(`Error starting server: ${err.message}`);
92677
+ }
92678
+ process.exit(1);
92679
+ });
92680
+ const shutdown = () => {
92681
+ console.log("\nShutting down...");
92682
+ server.close(() => {
92683
+ process.exit(0);
92684
+ });
92685
+ };
92686
+ process.on("SIGTERM", shutdown);
92687
+ process.on("SIGINT", shutdown);
92552
92688
  }
92553
92689
  start();
92554
92690
  /*! Bundled license information: