tina4-nodejs 3.13.140 → 3.13.142

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 (38) hide show
  1. package/CLAUDE.md +2 -2
  2. package/package.json +1 -1
  3. package/packages/cli/dist/bin.js +438 -453
  4. package/packages/cli/src/bin.ts +4 -14
  5. package/packages/cli/src/commands/build.ts +2 -18
  6. package/packages/cli/src/commands/generate.ts +2 -22
  7. package/packages/cli/src/commands/lint.ts +3 -13
  8. package/packages/cli/src/commands/migrate.ts +2 -6
  9. package/packages/cli/src/commands/migrateRollback.ts +2 -6
  10. package/packages/cli/src/commands/migrateStatus.ts +2 -6
  11. package/packages/cli/src/commands/queue.ts +2 -30
  12. package/packages/cli/src/util.ts +90 -0
  13. package/packages/core/dist/index.js +215 -184
  14. package/packages/core/gallery/queue/src/lib/queueDb.ts +11 -0
  15. package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +2 -5
  16. package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +2 -5
  17. package/packages/core/src/devAdmin.ts +3 -12
  18. package/packages/core/src/dispatchPipeline.ts +2 -18
  19. package/packages/core/src/etag.ts +24 -0
  20. package/packages/core/src/feedback.ts +2 -11
  21. package/packages/core/src/session.ts +22 -66
  22. package/packages/core/src/sessionHandlers/respSessionHandler.ts +94 -0
  23. package/packages/core/src/sessionHandlers/valkeyHandler.ts +22 -73
  24. package/packages/core/src/static.ts +1 -13
  25. package/packages/core/src/upstreamResponse.ts +31 -0
  26. package/packages/core/src/websocket.ts +48 -7
  27. package/packages/orm/dist/index.js +215 -184
  28. package/packages/orm/src/graph/adapters/bolt.ts +8 -29
  29. package/packages/orm/src/graph/adapters/rowMapping.ts +57 -0
  30. package/packages/orm/src/graph/adapters/ultipa.ts +8 -30
  31. package/types/cli/src/util.d.ts +28 -0
  32. package/types/core/src/etag.d.ts +9 -0
  33. package/types/core/src/session.d.ts +2 -19
  34. package/types/core/src/sessionHandlers/respSessionHandler.d.ts +52 -0
  35. package/types/core/src/sessionHandlers/valkeyHandler.d.ts +2 -26
  36. package/types/core/src/upstreamResponse.d.ts +10 -0
  37. package/types/core/src/websocket.d.ts +21 -0
  38. package/types/orm/src/graph/adapters/rowMapping.d.ts +26 -0
@@ -1221,8 +1221,8 @@ function hashPassword(password, salt, iterations = 26e4) {
1221
1221
  }
1222
1222
  function checkPassword(password, hash) {
1223
1223
  try {
1224
- const delimiter5 = hash.includes("$") ? "$" : ":";
1225
- const parts = hash.split(delimiter5);
1224
+ const delimiter4 = hash.includes("$") ? "$" : ":";
1225
+ const parts = hash.split(delimiter4);
1226
1226
  if (parts.length !== 4 || parts[0] !== "pbkdf2_sha256") return false;
1227
1227
  const iterations = parseInt(parts[1], 10);
1228
1228
  const salt = parts[2];
@@ -8980,7 +8980,7 @@ async function removeMigrationRecord(name) {
8980
8980
  [name]
8981
8981
  );
8982
8982
  }
8983
- async function rollback(migrationsDir, delimiter5) {
8983
+ async function rollback(migrationsDir, delimiter4) {
8984
8984
  if (migrationsDir instanceof Map) {
8985
8985
  const downFunctions = migrationsDir;
8986
8986
  const migrations2 = await getLastBatchMigrations();
@@ -8999,7 +8999,7 @@ async function rollback(migrationsDir, delimiter5) {
8999
8999
  return rolledBack2;
9000
9000
  }
9001
9001
  const dir = resolve4(migrationsDir ?? "migrations");
9002
- const delim = delimiter5 ?? ";";
9002
+ const delim = delimiter4 ?? ";";
9003
9003
  const db = getAdapter();
9004
9004
  const migrations = await getLastBatchMigrations();
9005
9005
  const rolledBack = [];
@@ -9115,13 +9115,13 @@ function consumeSqlDelimiter(state) {
9115
9115
  }
9116
9116
  return true;
9117
9117
  }
9118
- function splitStatements(sql, delimiter5 = ";") {
9118
+ function splitStatements(sql, delimiter4 = ";") {
9119
9119
  const state = {
9120
9120
  sql: normalizeQuotes(sql),
9121
9121
  statements: [],
9122
9122
  current: "",
9123
- delimiter: delimiter5,
9124
- dlen: delimiter5.length,
9123
+ delimiter: delimiter4,
9124
+ dlen: delimiter4.length,
9125
9125
  i: 0,
9126
9126
  inDollarBlock: false,
9127
9127
  inSlashBlock: false
@@ -9160,7 +9160,7 @@ function warnUnprefixedMigrations(files) {
9160
9160
  async function migrate(adapter, options) {
9161
9161
  const db = adapter ?? getAdapter();
9162
9162
  const dir = resolve4(options?.migrationsDir ?? "migrations");
9163
- const delimiter5 = options?.delimiter ?? ";";
9163
+ const delimiter4 = options?.delimiter ?? ";";
9164
9164
  const result = { applied: [], skipped: [], failed: [] };
9165
9165
  if (!existsSync5(dir)) {
9166
9166
  return result;
@@ -9203,7 +9203,7 @@ async function migrate(adapter, options) {
9203
9203
  result.skipped.push(file);
9204
9204
  continue;
9205
9205
  }
9206
- const statements = splitStatements(sqlContent, delimiter5);
9206
+ const statements = splitStatements(sqlContent, delimiter4);
9207
9207
  try {
9208
9208
  await adapterStartTransaction(db);
9209
9209
  for (const stmt of statements) {
@@ -13696,6 +13696,36 @@ var init_shapes = __esm({
13696
13696
  }
13697
13697
  });
13698
13698
 
13699
+ // ../orm/src/graph/adapters/rowMapping.ts
13700
+ function errorMessage(exc) {
13701
+ if (exc instanceof Error) return exc.message;
13702
+ return String(exc);
13703
+ }
13704
+ function nodeFromRow(row) {
13705
+ if (row === null || row === void 0) return null;
13706
+ return new GraphNode(
13707
+ String(row.id),
13708
+ row.labels ?? [],
13709
+ row.props ?? {}
13710
+ );
13711
+ }
13712
+ function edgeFromRow(row) {
13713
+ if (row === null || row === void 0) return null;
13714
+ return new GraphEdge(
13715
+ String(row.id),
13716
+ String(row.type),
13717
+ String(row.f),
13718
+ String(row.t),
13719
+ row.props ?? {}
13720
+ );
13721
+ }
13722
+ var init_rowMapping = __esm({
13723
+ "../orm/src/graph/adapters/rowMapping.ts"() {
13724
+ "use strict";
13725
+ init_shapes();
13726
+ }
13727
+ });
13728
+
13699
13729
  // ../orm/src/graph/connectTimeout.ts
13700
13730
  function resolveGraphConnectTimeout() {
13701
13731
  const raw = (process.env[GRAPH_CONNECT_TIMEOUT_VARIABLE] ?? "").trim();
@@ -13735,15 +13765,12 @@ function propClause(properties) {
13735
13765
  for (const key of keys) params[`p_${key}`] = props[key];
13736
13766
  return { clause: `{${pairs}}`, params };
13737
13767
  }
13738
- function errorMessage(exc) {
13739
- if (exc instanceof Error) return exc.message;
13740
- return String(exc);
13741
- }
13742
13768
  var DRIVER_PACKAGE, driver, UltipaClient, UltipaConnectError, UNBOUNDED_CONNECT_SECONDS, UltipaGraphAdapter;
13743
13769
  var init_ultipa = __esm({
13744
13770
  async "../orm/src/graph/adapters/ultipa.ts"() {
13745
13771
  "use strict";
13746
13772
  init_shapes();
13773
+ init_rowMapping();
13747
13774
  init_errors();
13748
13775
  init_connectTimeout2();
13749
13776
  DRIVER_PACKAGE = "tina4-ultipa";
@@ -13801,12 +13828,7 @@ var init_ultipa = __esm({
13801
13828
  }
13802
13829
  // -- portable node/edge/traverse core (GQL) ----------------------------
13803
13830
  nodeFromRow(row) {
13804
- if (row === null || row === void 0) return null;
13805
- return new GraphNode(
13806
- String(row.id),
13807
- row.labels ?? [],
13808
- row.props ?? {}
13809
- );
13831
+ return nodeFromRow(row);
13810
13832
  }
13811
13833
  async addNode(label, properties = null) {
13812
13834
  const { clause, params } = propClause(properties);
@@ -13820,15 +13842,7 @@ var init_ultipa = __esm({
13820
13842
  params.to_id = toId;
13821
13843
  const gql = `MATCH (a), (b) WHERE id(a) = $from_id AND id(b) = $to_id INSERT (a)-[e:\`${type2}\` ${clause}]->(b) RETURN id(e) AS id, type(e) AS type, id(a) AS f, id(b) AS t, properties(e) AS props`;
13822
13844
  const rows = (await this.run(gql, params, false)).dicts();
13823
- if (rows.length === 0) return null;
13824
- const row = rows[0];
13825
- return new GraphEdge(
13826
- String(row.id),
13827
- String(row.type),
13828
- String(row.f),
13829
- String(row.t),
13830
- row.props ?? {}
13831
- );
13845
+ return edgeFromRow(rows[0]);
13832
13846
  }
13833
13847
  async getNode(nodeId) {
13834
13848
  const gql = `MATCH (n) WHERE id(n) = $id RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
@@ -13893,10 +13907,6 @@ var bolt_exports = {};
13893
13907
  __export(bolt_exports, {
13894
13908
  BoltGraphAdapter: () => BoltGraphAdapter
13895
13909
  });
13896
- function errorMessage2(exc) {
13897
- if (exc instanceof Error) return exc.message;
13898
- return String(exc);
13899
- }
13900
13910
  function boltId(id) {
13901
13911
  const value = Number(id);
13902
13912
  return Number.isNaN(value) ? -1 : value;
@@ -13906,6 +13916,7 @@ var init_bolt = __esm({
13906
13916
  async "../orm/src/graph/adapters/bolt.ts"() {
13907
13917
  "use strict";
13908
13918
  init_shapes();
13919
+ init_rowMapping();
13909
13920
  init_errors();
13910
13921
  init_connectTimeout2();
13911
13922
  DRIVER_PACKAGE2 = "neo4j-driver";
@@ -13941,7 +13952,7 @@ var init_bolt = __esm({
13941
13952
  const result = await session.run(cypher, params ?? {});
13942
13953
  return result.records.map((record) => record.toObject());
13943
13954
  } catch (exc) {
13944
- this.lastError = errorMessage2(exc);
13955
+ this.lastError = errorMessage(exc);
13945
13956
  const code = exc?.code ?? "";
13946
13957
  const message = this.lastError.toLowerCase();
13947
13958
  if (code === "ServiceUnavailable" || code === neo4j.error?.SERVICE_UNAVAILABLE || message.includes("timed out") || message.includes("timeout")) {
@@ -13965,12 +13976,7 @@ var init_bolt = __esm({
13965
13976
  }
13966
13977
  // -- portable node/edge/traverse core (Cypher) -------------------------
13967
13978
  nodeFromRow(row) {
13968
- if (row === null || row === void 0) return null;
13969
- return new GraphNode(
13970
- String(row.id),
13971
- row.labels ?? [],
13972
- row.props ?? {}
13973
- );
13979
+ return nodeFromRow(row);
13974
13980
  }
13975
13981
  async addNode(label, properties = null) {
13976
13982
  const cypher = `CREATE (n:\`${label}\` $props) RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
@@ -13984,15 +13990,7 @@ var init_bolt = __esm({
13984
13990
  to_id: boltId(toId),
13985
13991
  props: properties ?? {}
13986
13992
  });
13987
- if (rows.length === 0) return null;
13988
- const row = rows[0];
13989
- return new GraphEdge(
13990
- String(row.id),
13991
- String(row.type),
13992
- String(row.f),
13993
- String(row.t),
13994
- row.props ?? {}
13995
- );
13993
+ return edgeFromRow(rows[0]);
13996
13994
  }
13997
13995
  async getNode(nodeId) {
13998
13996
  const cypher = `MATCH (n) WHERE id(n) = $id RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
@@ -14058,7 +14056,7 @@ function cleanProps(doc) {
14058
14056
  }
14059
14057
  return props;
14060
14058
  }
14061
- function errorMessage3(exc) {
14059
+ function errorMessage2(exc) {
14062
14060
  if (exc instanceof Error) return exc.message;
14063
14061
  return String(exc);
14064
14062
  }
@@ -14097,14 +14095,14 @@ var init_arango = __esm({
14097
14095
  this.db = new ArangoDatabase(config);
14098
14096
  }
14099
14097
  connectOrError(exc) {
14100
- const text = errorMessage3(exc).toLowerCase();
14098
+ const text = errorMessage2(exc).toLowerCase();
14101
14099
  if (text.includes("timed out") || text.includes("timeout") || text.includes("connection") || text.includes("econnrefused") || text.includes("etimedout") || text.includes("max retries")) {
14102
14100
  return new GraphConnectTimeout(
14103
14101
  `Graph connect to ${this.url.host}:${this.url.port} timed out (${GRAPH_CONNECT_TIMEOUT_VARIABLE}). Raise ${GRAPH_CONNECT_TIMEOUT_VARIABLE} if the server is simply slow, or set it to 0 to wait indefinitely.`,
14104
14102
  exc
14105
14103
  );
14106
14104
  }
14107
- return new GraphError(errorMessage3(exc), exc);
14105
+ return new GraphError(errorMessage2(exc), exc);
14108
14106
  }
14109
14107
  async ensureCollections() {
14110
14108
  if (this.ensured) return;
@@ -14115,7 +14113,7 @@ var init_arango = __esm({
14115
14113
  if (!await edges.exists()) await this.db.createEdgeCollection(EDGE_COLLECTION);
14116
14114
  this.ensured = true;
14117
14115
  } catch (exc) {
14118
- this.lastError = errorMessage3(exc);
14116
+ this.lastError = errorMessage2(exc);
14119
14117
  throw this.connectOrError(exc);
14120
14118
  }
14121
14119
  }
@@ -14125,7 +14123,7 @@ var init_arango = __esm({
14125
14123
  const cursor = await this.db.query({ query, bindVars: bind2 ?? {} });
14126
14124
  return await cursor.all();
14127
14125
  } catch (exc) {
14128
- this.lastError = errorMessage3(exc);
14126
+ this.lastError = errorMessage2(exc);
14129
14127
  throw this.connectOrError(exc);
14130
14128
  }
14131
14129
  }
@@ -21621,14 +21619,14 @@ function extractBoundary(contentType) {
21621
21619
  function parseMultipart(body, boundary) {
21622
21620
  const fields = {};
21623
21621
  const files = {};
21624
- const delimiter5 = Buffer.from(`--${boundary}`);
21622
+ const delimiter4 = Buffer.from(`--${boundary}`);
21625
21623
  const closeDelimiter = Buffer.from(`--${boundary}--`);
21626
21624
  const crlf = Buffer.from("\r\n");
21627
21625
  const doubleCrlf = Buffer.from("\r\n\r\n");
21628
21626
  let offset = 0;
21629
- const firstIdx = bufferIndexOf(body, delimiter5, offset);
21627
+ const firstIdx = bufferIndexOf(body, delimiter4, offset);
21630
21628
  if (firstIdx === -1) return { fields, files };
21631
- offset = firstIdx + delimiter5.length;
21629
+ offset = firstIdx + delimiter4.length;
21632
21630
  if (body[offset] === 13 && body[offset + 1] === 10) {
21633
21631
  offset += 2;
21634
21632
  }
@@ -21637,7 +21635,7 @@ function parseMultipart(body, boundary) {
21637
21635
  if (headersEnd === -1) break;
21638
21636
  const headersStr = body.subarray(offset, headersEnd).toString("utf-8");
21639
21637
  offset = headersEnd + doubleCrlf.length;
21640
- const nextDelimIdx = bufferIndexOf(body, delimiter5, offset);
21638
+ const nextDelimIdx = bufferIndexOf(body, delimiter4, offset);
21641
21639
  if (nextDelimIdx === -1) break;
21642
21640
  const contentEnd = nextDelimIdx - crlf.length;
21643
21641
  const content = body.subarray(offset, contentEnd);
@@ -21660,7 +21658,7 @@ function parseMultipart(body, boundary) {
21660
21658
  } else if (disposition.name) {
21661
21659
  fields[disposition.name] = content.toString("utf-8");
21662
21660
  }
21663
- offset = nextDelimIdx + delimiter5.length;
21661
+ offset = nextDelimIdx + delimiter4.length;
21664
21662
  if (body[offset] === 45 && body[offset + 1] === 45) {
21665
21663
  break;
21666
21664
  }
@@ -22408,6 +22406,21 @@ var init_portTakeover = __esm({
22408
22406
  }
22409
22407
  });
22410
22408
 
22409
+ // ../core/src/etag.ts
22410
+ function etagMatches(ifNoneMatch, etag) {
22411
+ const strip = (tag) => tag.trim().replace(/^W\//, "");
22412
+ const target = strip(etag);
22413
+ return ifNoneMatch.split(",").some((candidate) => {
22414
+ const trimmed = candidate.trim();
22415
+ return trimmed === "*" || strip(trimmed) === target;
22416
+ });
22417
+ }
22418
+ var init_etag = __esm({
22419
+ "../core/src/etag.ts"() {
22420
+ "use strict";
22421
+ }
22422
+ });
22423
+
22411
22424
  // ../core/src/sessionHandlers/syncBridge.ts
22412
22425
  import { Worker } from "node:worker_threads";
22413
22426
  function getBridge(key, workerSource, workerData) {
@@ -22689,42 +22702,35 @@ var init_respClient = __esm({
22689
22702
  }
22690
22703
  });
22691
22704
 
22692
- // ../core/src/sessionHandlers/valkeyHandler.ts
22693
- var ValkeySessionHandler;
22694
- var init_valkeyHandler = __esm({
22695
- "../core/src/sessionHandlers/valkeyHandler.ts"() {
22705
+ // ../core/src/sessionHandlers/respSessionHandler.ts
22706
+ var RespSessionHandler;
22707
+ var init_respSessionHandler = __esm({
22708
+ "../core/src/sessionHandlers/respSessionHandler.ts"() {
22696
22709
  "use strict";
22697
22710
  init_respClient();
22698
- ValkeySessionHandler = class {
22699
- host;
22700
- port;
22701
- password;
22702
- prefix;
22703
- db;
22704
- constructor(config) {
22705
- this.host = config?.host ?? process.env.TINA4_SESSION_VALKEY_HOST ?? "127.0.0.1";
22706
- this.port = config?.port ?? (process.env.TINA4_SESSION_VALKEY_PORT ? parseInt(process.env.TINA4_SESSION_VALKEY_PORT, 10) : 6379);
22707
- this.password = config?.password ?? process.env.TINA4_SESSION_VALKEY_PASSWORD ?? "";
22708
- this.prefix = config?.prefix ?? process.env.TINA4_SESSION_VALKEY_PREFIX ?? "tina4:session:";
22709
- this.db = config?.db ?? (process.env.TINA4_SESSION_VALKEY_DB ? parseInt(process.env.TINA4_SESSION_VALKEY_DB, 10) : 0);
22711
+ RespSessionHandler = class {
22712
+ constructor(conn, label) {
22713
+ this.conn = conn;
22714
+ this.label = label;
22710
22715
  }
22716
+ conn;
22717
+ label;
22711
22718
  /**
22712
- * Execute a RESP command synchronously against the live Valkey server.
22719
+ * Execute a RESP command synchronously against the live server.
22713
22720
  *
22714
- * Delegates to the shared {@link respCommandSync} transport: a genuine key miss
22715
- * yields `""`, and a transport/connection FAILURE (server unreachable, rejected
22716
- * AUTH, timeout) THROWS so the Session boundary can distinguish "not found"
22717
- * (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
22721
+ * Delegates to the shared {@link respCommandSync} transport: a genuine key
22722
+ * miss yields `""`, and a transport/connection FAILURE THROWS so the caller
22723
+ * can distinguish "not found" from "backend failed". Backend-failure parity.
22718
22724
  */
22719
22725
  execSync(args) {
22720
22726
  return respCommandSync(
22721
- { host: this.host, port: this.port, password: this.password, db: this.db },
22727
+ { host: this.conn.host, port: this.conn.port, password: this.conn.password, db: this.conn.db },
22722
22728
  args,
22723
- "Valkey"
22729
+ this.label
22724
22730
  );
22725
22731
  }
22726
22732
  key(sessionId) {
22727
- return `${this.prefix}${sessionId}`;
22733
+ return `${this.conn.prefix}${sessionId}`;
22728
22734
  }
22729
22735
  read(sessionId) {
22730
22736
  const raw = this.execSync(["GET", this.key(sessionId)]);
@@ -22735,7 +22741,7 @@ var init_valkeyHandler = __esm({
22735
22741
  return null;
22736
22742
  }
22737
22743
  }
22738
- write(sessionId, data, ttl) {
22744
+ write(sessionId, data, ttl = 0) {
22739
22745
  const json = JSON.stringify(data);
22740
22746
  if (ttl > 0) {
22741
22747
  this.execSync(["SETEX", this.key(sessionId), String(ttl), json]);
@@ -22750,6 +22756,29 @@ var init_valkeyHandler = __esm({
22750
22756
  }
22751
22757
  });
22752
22758
 
22759
+ // ../core/src/sessionHandlers/valkeyHandler.ts
22760
+ var ValkeySessionHandler;
22761
+ var init_valkeyHandler = __esm({
22762
+ "../core/src/sessionHandlers/valkeyHandler.ts"() {
22763
+ "use strict";
22764
+ init_respSessionHandler();
22765
+ ValkeySessionHandler = class extends RespSessionHandler {
22766
+ constructor(config) {
22767
+ super(
22768
+ {
22769
+ host: config?.host ?? process.env.TINA4_SESSION_VALKEY_HOST ?? "127.0.0.1",
22770
+ port: config?.port ?? (process.env.TINA4_SESSION_VALKEY_PORT ? parseInt(process.env.TINA4_SESSION_VALKEY_PORT, 10) : 6379),
22771
+ password: config?.password ?? process.env.TINA4_SESSION_VALKEY_PASSWORD ?? "",
22772
+ prefix: config?.prefix ?? process.env.TINA4_SESSION_VALKEY_PREFIX ?? "tina4:session:",
22773
+ db: config?.db ?? (process.env.TINA4_SESSION_VALKEY_DB ? parseInt(process.env.TINA4_SESSION_VALKEY_DB, 10) : 0)
22774
+ },
22775
+ "Valkey"
22776
+ );
22777
+ }
22778
+ };
22779
+ }
22780
+ });
22781
+
22753
22782
  // ../core/src/sessionHandlers/memcachedHandler.ts
22754
22783
  import { createHash as createHash4 } from "node:crypto";
22755
22784
  var MAX_KEY_BYTES, MAX_RELATIVE_EXPTIME, MemcachedSessionHandler;
@@ -23815,7 +23844,7 @@ var init_session = __esm({
23815
23844
  "use strict";
23816
23845
  init_logger();
23817
23846
  init_dotenv();
23818
- init_respClient();
23847
+ init_respSessionHandler();
23819
23848
  init_valkeyHandler();
23820
23849
  init_memcachedHandler();
23821
23850
  init_mongoHandler();
@@ -23941,57 +23970,19 @@ var init_session = __esm({
23941
23970
  }
23942
23971
  }
23943
23972
  };
23944
- RedisSessionHandler = class {
23945
- host;
23946
- port;
23947
- password;
23948
- prefix;
23949
- db;
23973
+ RedisSessionHandler = class extends RespSessionHandler {
23950
23974
  constructor(config) {
23951
- this.host = config?.redisHost ?? process.env.TINA4_SESSION_REDIS_HOST ?? "127.0.0.1";
23952
- this.port = config?.redisPort ?? (process.env.TINA4_SESSION_REDIS_PORT ? parseInt(process.env.TINA4_SESSION_REDIS_PORT, 10) : 6379);
23953
- this.password = config?.redisPassword ?? process.env.TINA4_SESSION_REDIS_PASSWORD ?? "";
23954
- this.prefix = config?.redisPrefix ?? process.env.TINA4_SESSION_REDIS_PREFIX ?? "tina4:session:";
23955
- this.db = config?.redisDb ?? (process.env.TINA4_SESSION_REDIS_DB ? parseInt(process.env.TINA4_SESSION_REDIS_DB, 10) : 0);
23956
- }
23957
- /**
23958
- * Execute a Redis command synchronously against the live server.
23959
- *
23960
- * Delegates to the shared {@link respCommandSync} transport: a genuine key miss
23961
- * yields `""`, and a transport/connection FAILURE (server unreachable, rejected
23962
- * AUTH, timeout) THROWS so the Session boundary can distinguish "not found"
23963
- * (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
23964
- */
23965
- execSync(args) {
23966
- return respCommandSync(
23967
- { host: this.host, port: this.port, password: this.password, db: this.db },
23968
- args,
23975
+ super(
23976
+ {
23977
+ host: config?.redisHost ?? process.env.TINA4_SESSION_REDIS_HOST ?? "127.0.0.1",
23978
+ port: config?.redisPort ?? (process.env.TINA4_SESSION_REDIS_PORT ? parseInt(process.env.TINA4_SESSION_REDIS_PORT, 10) : 6379),
23979
+ password: config?.redisPassword ?? process.env.TINA4_SESSION_REDIS_PASSWORD ?? "",
23980
+ prefix: config?.redisPrefix ?? process.env.TINA4_SESSION_REDIS_PREFIX ?? "tina4:session:",
23981
+ db: config?.redisDb ?? (process.env.TINA4_SESSION_REDIS_DB ? parseInt(process.env.TINA4_SESSION_REDIS_DB, 10) : 0)
23982
+ },
23969
23983
  "Redis"
23970
23984
  );
23971
23985
  }
23972
- key(sessionId) {
23973
- return `${this.prefix}${sessionId}`;
23974
- }
23975
- read(sessionId) {
23976
- const raw = this.execSync(["GET", this.key(sessionId)]);
23977
- if (!raw) return null;
23978
- try {
23979
- return JSON.parse(raw);
23980
- } catch {
23981
- return null;
23982
- }
23983
- }
23984
- write(sessionId, data, ttl = 0) {
23985
- const json = JSON.stringify(data);
23986
- if (ttl > 0) {
23987
- this.execSync(["SETEX", this.key(sessionId), String(ttl), json]);
23988
- } else {
23989
- this.execSync(["SET", this.key(sessionId), json]);
23990
- }
23991
- }
23992
- destroy(sessionId) {
23993
- this.execSync(["DEL", this.key(sessionId)]);
23994
- }
23995
23986
  };
23996
23987
  FLASH_PREFIX = "_flash_";
23997
23988
  Session = class {
@@ -24401,14 +24392,6 @@ function headStripIntercept(rawReq, rawRes) {
24401
24392
  function isCompressibleContentType(contentType) {
24402
24393
  return COMPRESSIBLE_PREFIXES.some((prefix) => contentType.includes(prefix));
24403
24394
  }
24404
- function etagMatchesInm(ifNoneMatch, etag) {
24405
- const strip = (tag) => tag.trim().replace(/^W\//, "");
24406
- const target = strip(etag);
24407
- return ifNoneMatch.split(",").some((candidate) => {
24408
- const trimmed = candidate.trim();
24409
- return trimmed === "*" || strip(trimmed) === target;
24410
- });
24411
- }
24412
24395
  function maybeCompressResponse(body, rawReq, rawRes) {
24413
24396
  const acceptEncoding = String(rawReq.headers["accept-encoding"] ?? "");
24414
24397
  const contentTypeHeader = rawRes.getHeader("content-type");
@@ -24424,7 +24407,7 @@ function maybeCompressResponse(body, rawReq, rawRes) {
24424
24407
  }
24425
24408
  function responseIsNotModified(rawReq, rawRes, etag) {
24426
24409
  const ifNoneMatch = String(rawReq.headers["if-none-match"] ?? "");
24427
- if (ifNoneMatch) return etagMatchesInm(ifNoneMatch, etag);
24410
+ if (ifNoneMatch) return etagMatches(ifNoneMatch, etag);
24428
24411
  const lastModifiedHeader = rawRes.getHeader("last-modified");
24429
24412
  if (typeof lastModifiedHeader !== "string") return false;
24430
24413
  const ifModifiedSince = String(rawReq.headers["if-modified-since"] ?? "");
@@ -24552,6 +24535,7 @@ var init_dispatchPipeline = __esm({
24552
24535
  "../core/src/dispatchPipeline.ts"() {
24553
24536
  "use strict";
24554
24537
  init_logger();
24538
+ init_etag();
24555
24539
  COMPRESSIBLE_PREFIXES = [
24556
24540
  "text/",
24557
24541
  "application/json",
@@ -24647,18 +24631,11 @@ function isNotModified(req2, etag, stat) {
24647
24631
  }
24648
24632
  return false;
24649
24633
  }
24650
- function etagMatches(ifNoneMatch, etag) {
24651
- const strip = (tag) => tag.trim().replace(/^W\//, "");
24652
- const target = strip(etag);
24653
- return ifNoneMatch.split(",").some((candidate) => {
24654
- const trimmed = candidate.trim();
24655
- return trimmed === "*" || strip(trimmed) === target;
24656
- });
24657
- }
24658
24634
  var COMPRESSIBLE_PREFIXES2, MIME_TYPES;
24659
24635
  var init_static = __esm({
24660
24636
  "../core/src/static.ts"() {
24661
24637
  "use strict";
24638
+ init_etag();
24662
24639
  COMPRESSIBLE_PREFIXES2 = [
24663
24640
  "text/",
24664
24641
  "application/json",
@@ -25235,6 +25212,25 @@ Generated by DevMailbox.seed() for development purposes.`,
25235
25212
  }
25236
25213
  });
25237
25214
 
25215
+ // ../core/src/upstreamResponse.ts
25216
+ async function relayUpstreamResponse(res, upstream) {
25217
+ const raw = await upstream.text();
25218
+ const status2 = upstream.status || 200;
25219
+ try {
25220
+ res.json(JSON.parse(raw), status2);
25221
+ } catch {
25222
+ res.raw.writeHead(status2, {
25223
+ "Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
25224
+ });
25225
+ res.raw.end(raw);
25226
+ }
25227
+ }
25228
+ var init_upstreamResponse = __esm({
25229
+ "../core/src/upstreamResponse.ts"() {
25230
+ "use strict";
25231
+ }
25232
+ });
25233
+
25238
25234
  // ../core/src/metrics.ts
25239
25235
  import * as fs3 from "node:fs";
25240
25236
  import * as path2 from "node:path";
@@ -25450,6 +25446,7 @@ var init_feedback = __esm({
25450
25446
  "use strict";
25451
25447
  init_auth();
25452
25448
  init_devAdmin();
25449
+ init_upstreamResponse();
25453
25450
  RATE_LIMIT_WINDOW_SEC = 3600;
25454
25451
  RATE_LIMIT_MAX = 5;
25455
25452
  _rateLimitHits = /* @__PURE__ */ new Map();
@@ -25496,16 +25493,7 @@ var init_feedback = __esm({
25496
25493
  return;
25497
25494
  }
25498
25495
  clearTimeout(timer);
25499
- const raw = await upstream.text();
25500
- const status2 = upstream.status || 200;
25501
- try {
25502
- res.json(JSON.parse(raw), status2);
25503
- } catch {
25504
- res.raw.writeHead(status2, {
25505
- "Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
25506
- });
25507
- res.raw.end(raw);
25508
- }
25496
+ await relayUpstreamResponse(res, upstream);
25509
25497
  };
25510
25498
  __feedbackDirname = dirname8(fileURLToPath3(import.meta.url));
25511
25499
  WIDGET_BUNDLE_PATH = resolve10(__feedbackDirname, "__feedback", "widget.js");
@@ -25555,6 +25543,61 @@ var init_version = __esm({
25555
25543
  }
25556
25544
  });
25557
25545
 
25546
+ // src/util.ts
25547
+ import { delimiter as delimiter2, join as join21 } from "node:path";
25548
+ import { statSync as statSync11 } from "node:fs";
25549
+ function parseFlags(args, booleanFlags) {
25550
+ const flags = {};
25551
+ const positional = [];
25552
+ let i = 0;
25553
+ while (i < args.length) {
25554
+ const arg = args[i];
25555
+ if (arg.startsWith("--")) {
25556
+ const key = arg.slice(2);
25557
+ if (booleanFlags?.has(key)) {
25558
+ flags[key] = true;
25559
+ i += 1;
25560
+ } else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
25561
+ flags[key] = args[i + 1];
25562
+ i += 2;
25563
+ } else {
25564
+ flags[key] = true;
25565
+ i += 1;
25566
+ }
25567
+ } else {
25568
+ positional.push(arg);
25569
+ i += 1;
25570
+ }
25571
+ }
25572
+ return { flags, positional };
25573
+ }
25574
+ function findOnPath(names) {
25575
+ for (const dir of (process.env.PATH ?? "").split(delimiter2)) {
25576
+ if (!dir) continue;
25577
+ for (const name of names) {
25578
+ const candidate = join21(dir, name);
25579
+ try {
25580
+ if (statSync11(candidate).isFile()) return candidate;
25581
+ } catch {
25582
+ }
25583
+ }
25584
+ }
25585
+ return null;
25586
+ }
25587
+ async function initDatabaseOrExit(initDatabase2) {
25588
+ try {
25589
+ await initDatabase2();
25590
+ } catch (err) {
25591
+ console.error(` Error initialising database: ${err instanceof Error ? err.message : String(err)}`);
25592
+ process.exit(1);
25593
+ }
25594
+ }
25595
+ var init_util = __esm({
25596
+ "src/util.ts"() {
25597
+ "use strict";
25598
+ }
25599
+ });
25600
+
25558
25601
  // src/commands/generate.ts
25559
25602
  var generate_exports = {};
25560
25603
  __export(generate_exports, {
@@ -25579,7 +25622,7 @@ __export(generate_exports, {
25579
25622
  toTableName: () => toTableName
25580
25623
  });
25581
25624
  import { existsSync as existsSync16, mkdirSync as mkdirSync13, writeFileSync as writeFileSync10 } from "node:fs";
25582
- import { join as join21, relative as relative2, resolve as resolve11, sep as sep5 } from "node:path";
25625
+ import { join as join22, relative as relative2, resolve as resolve11, sep as sep5 } from "node:path";
25583
25626
  function ensureDir(dir) {
25584
25627
  if (__resolution.dryRun) return;
25585
25628
  if (!existsSync16(dir)) {
@@ -25819,28 +25862,7 @@ function parseCliArgs(args) {
25819
25862
  "json",
25820
25863
  "dry-run"
25821
25864
  ]);
25822
- const flags = {};
25823
- const positional = [];
25824
- let i = 0;
25825
- while (i < args.length) {
25826
- if (args[i].startsWith("--")) {
25827
- const key = args[i].slice(2);
25828
- if (booleanFlags.has(key)) {
25829
- flags[key] = true;
25830
- i += 1;
25831
- } else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
25832
- flags[key] = args[i + 1];
25833
- i += 2;
25834
- } else {
25835
- flags[key] = true;
25836
- i += 1;
25837
- }
25838
- } else {
25839
- positional.push(args[i]);
25840
- i += 1;
25841
- }
25842
- }
25843
- return { flags, positional };
25865
+ return parseFlags(args, booleanFlags);
25844
25866
  }
25845
25867
  function parseEvery(every) {
25846
25868
  if (!every || every === true) return 60;
@@ -25939,7 +25961,7 @@ function generateModel(name, flags, emitTest = true) {
25939
25961
  const table2 = resolveTable(name, flags, { announce: true });
25940
25962
  const dir = resolve11("src/models");
25941
25963
  ensureDir(dir);
25942
- const path10 = join21(dir, `${name}.ts`);
25964
+ const path10 = join22(dir, `${name}.ts`);
25943
25965
  setResolutionField("class_name", name);
25944
25966
  setResolutionField("table_name", table2);
25945
25967
  setResolutionField("file_path", `src/models/${name}.ts`);
@@ -25979,7 +26001,7 @@ function generateRoute(name, flags, emitTest = true) {
25979
26001
  const model = flags.model;
25980
26002
  const isPublic = Boolean(flags.public);
25981
26003
  const base = resolve11("src/routes/api", routePath);
25982
- const idDir = join21(base, "[id]");
26004
+ const idDir = join22(base, "[id]");
25983
26005
  ensureDir(base);
25984
26006
  ensureDir(idDir);
25985
26007
  pushRoute(`/api/${routePath}`);
@@ -25995,7 +26017,7 @@ function generateRoute(name, flags, emitTest = true) {
25995
26017
  const writeDoc = isPublic ? "Public (--public): no token required." : "Secure by default: requires a Bearer token (use --public to open).";
25996
26018
  if (model) {
25997
26019
  writeFileSafe(
25998
- join21(base, "get.ts"),
26020
+ join22(base, "get.ts"),
25999
26021
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26000
26022
  ${modelImportBase}
26001
26023
  export const meta = { summary: "List all ${routePath}", tags: ["${routePath}"] };
@@ -26012,7 +26034,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
26012
26034
  );
26013
26035
  } else {
26014
26036
  writeFileSafe(
26015
- join21(base, "get.ts"),
26037
+ join22(base, "get.ts"),
26016
26038
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26017
26039
 
26018
26040
  export const meta = { summary: "List all ${routePath}", tags: ["${routePath}"] };
@@ -26031,7 +26053,7 @@ ${aiFill(`list_${routePath}`, {
26031
26053
  }
26032
26054
  if (model) {
26033
26055
  writeFileSafe(
26034
- join21(base, "post.ts"),
26056
+ join22(base, "post.ts"),
26035
26057
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26036
26058
  ${modelImportBase}${secureOptOut(isPublic)}export const meta = { summary: "Create a new ${singular}", tags: ["${routePath}"] };
26037
26059
 
@@ -26054,7 +26076,7 @@ ${extend(
26054
26076
  );
26055
26077
  } else {
26056
26078
  writeFileSafe(
26057
- join21(base, "post.ts"),
26079
+ join22(base, "post.ts"),
26058
26080
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26059
26081
 
26060
26082
  ${secureOptOut(isPublic)}export const meta = { summary: "Create a new ${singular}", tags: ["${routePath}"] };
@@ -26075,7 +26097,7 @@ ${aiFill(`create_${singular}`, {
26075
26097
  }
26076
26098
  if (model) {
26077
26099
  writeFileSafe(
26078
- join21(idDir, "get.ts"),
26100
+ join22(idDir, "get.ts"),
26079
26101
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26080
26102
  ${modelImportId}
26081
26103
  export const meta = { summary: "Get a ${singular} by ID", tags: ["${routePath}"] };
@@ -26093,7 +26115,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
26093
26115
  );
26094
26116
  } else {
26095
26117
  writeFileSafe(
26096
- join21(idDir, "get.ts"),
26118
+ join22(idDir, "get.ts"),
26097
26119
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26098
26120
 
26099
26121
  export const meta = { summary: "Get a ${singular} by ID", tags: ["${routePath}"] };
@@ -26112,7 +26134,7 @@ ${aiFill(`get_${singular}`, {
26112
26134
  }
26113
26135
  if (model) {
26114
26136
  writeFileSafe(
26115
- join21(idDir, "put.ts"),
26137
+ join22(idDir, "put.ts"),
26116
26138
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26117
26139
  ${modelImportId}${secureOptOut(isPublic)}export const meta = { summary: "Update a ${singular} by ID", tags: ["${routePath}"] };
26118
26140
 
@@ -26141,7 +26163,7 @@ ${extend(
26141
26163
  );
26142
26164
  } else {
26143
26165
  writeFileSafe(
26144
- join21(idDir, "put.ts"),
26166
+ join22(idDir, "put.ts"),
26145
26167
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26146
26168
 
26147
26169
  ${secureOptOut(isPublic)}export const meta = { summary: "Update a ${singular} by ID", tags: ["${routePath}"] };
@@ -26162,7 +26184,7 @@ ${aiFill(`update_${singular}`, {
26162
26184
  }
26163
26185
  if (model) {
26164
26186
  writeFileSafe(
26165
- join21(idDir, "delete.ts"),
26187
+ join22(idDir, "delete.ts"),
26166
26188
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26167
26189
  ${modelImportId}${secureOptOut(isPublic)}export const meta = { summary: "Delete a ${singular} by ID", tags: ["${routePath}"] };
26168
26190
 
@@ -26181,7 +26203,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
26181
26203
  );
26182
26204
  } else {
26183
26205
  writeFileSafe(
26184
- join21(idDir, "delete.ts"),
26206
+ join22(idDir, "delete.ts"),
26185
26207
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26186
26208
 
26187
26209
  ${secureOptOut(isPublic)}export const meta = { summary: "Delete a ${singular} by ID", tags: ["${routePath}"] };
@@ -26243,7 +26265,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
26243
26265
  const fields = fieldsOverride || parseFields(flags.fields || "");
26244
26266
  const isCreate = name.startsWith("create_") || fieldsOverride !== void 0;
26245
26267
  const fileName = `${ts}_${name}.sql`;
26246
- const path10 = join21(dir, fileName);
26268
+ const path10 = join22(dir, fileName);
26247
26269
  setResolutionField("migration_path", `migrations/${fileName}`);
26248
26270
  if (__resolution.target === "migration") {
26249
26271
  setResolutionField("file_path", `migrations/${fileName}`);
@@ -26282,7 +26304,7 @@ ${upSql}
26282
26304
  ${downSql}
26283
26305
  `;
26284
26306
  writeFileSafe(path10, content);
26285
- const downPath = join21(dir, `${ts}_${name}.down.sql`);
26307
+ const downPath = join22(dir, `${ts}_${name}.down.sql`);
26286
26308
  const downContent = `-- Rollback: ${name}
26287
26309
  -- Created: ${now}
26288
26310
 
@@ -26295,7 +26317,7 @@ function generateMiddleware(name, _flags) {
26295
26317
  const snake = toSnake(name);
26296
26318
  const dir = resolve11("src/middleware");
26297
26319
  ensureDir(dir);
26298
- const path10 = join21(dir, `${snake}.ts`);
26320
+ const path10 = join22(dir, `${snake}.ts`);
26299
26321
  if (__resolution.target === "middleware") {
26300
26322
  setResolutionField("class_name", name);
26301
26323
  setResolutionField("file_path", `src/middleware/${snake}.ts`);
@@ -26342,7 +26364,7 @@ function generateTest(name, flags) {
26342
26364
  const model = flags.model;
26343
26365
  const dir = resolve11("tests");
26344
26366
  ensureDir(dir);
26345
- const path10 = join21(dir, `${snake}.test.ts`);
26367
+ const path10 = join22(dir, `${snake}.test.ts`);
26346
26368
  if (model && flags["secure-writes"]) {
26347
26369
  const isPublic = Boolean(flags.public);
26348
26370
  const posture = isPublic ? "open (--public)" : "gated";
@@ -26480,7 +26502,7 @@ function generateForm(name, flags) {
26480
26502
  };
26481
26503
  const dir = resolve11("src/templates/forms");
26482
26504
  ensureDir(dir);
26483
- const path10 = join21(dir, `${table2}.twig`);
26505
+ const path10 = join22(dir, `${table2}.twig`);
26484
26506
  let fieldHtml = "";
26485
26507
  for (const [fname, ftype] of fields) {
26486
26508
  const itype = inputTypes[ftype] || "text";
@@ -26533,7 +26555,7 @@ function generateView(name, flags) {
26533
26555
  const cols = fields.map(([f]) => f);
26534
26556
  const dir = resolve11("src/templates/pages");
26535
26557
  ensureDir(dir);
26536
- const listPath = join21(dir, `${routeName}.twig`);
26558
+ const listPath = join22(dir, `${routeName}.twig`);
26537
26559
  const th = cols.map((c) => ` <th>${c.replace(/_/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase())}</th>`).join("\n");
26538
26560
  const td = cols.map((c) => ` <td>{{ item.${c} }}</td>`).join("\n");
26539
26561
  const listContent = `{% extends "base.twig" %}
@@ -26570,7 +26592,7 @@ ${td}
26570
26592
  {% endblock %}
26571
26593
  `;
26572
26594
  writeFileSafe(listPath, listContent);
26573
- const detailPath = join21(dir, `${table2}.twig`);
26595
+ const detailPath = join22(dir, `${table2}.twig`);
26574
26596
  const detailFields = cols.map((c) => ` <div class="mb-3"><strong>${c.replace(/_/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase())}:</strong> {{ item.${c} }}</div>`).join("\n");
26575
26597
  const detailContent = `{% extends "base.twig" %}
26576
26598
  {% block title %}${name} Detail{% endblock %}
@@ -26600,7 +26622,7 @@ function generateAuth(_flags) {
26600
26622
  ensureDir(loginDir);
26601
26623
  ensureDir(meDir);
26602
26624
  writeFileSafe(
26603
- join21(registerDir, "post.ts"),
26625
+ join22(registerDir, "post.ts"),
26604
26626
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26605
26627
  import { hashPassword } from "tina4-nodejs";
26606
26628
  import User from "../../../../models/User.js";
@@ -26632,7 +26654,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
26632
26654
  `
26633
26655
  );
26634
26656
  writeFileSafe(
26635
- join21(loginDir, "post.ts"),
26657
+ join22(loginDir, "post.ts"),
26636
26658
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26637
26659
  import { checkPassword, getToken } from "tina4-nodejs";
26638
26660
  import User from "../../../../models/User.js";
@@ -26665,7 +26687,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
26665
26687
  `
26666
26688
  );
26667
26689
  writeFileSafe(
26668
- join21(meDir, "get.ts"),
26690
+ join22(meDir, "get.ts"),
26669
26691
  `import type { Tina4Request, Tina4Response } from "tina4-nodejs";
26670
26692
  import { authenticateRequest } from "tina4-nodejs";
26671
26693
 
@@ -26684,7 +26706,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
26684
26706
  const formsDir = resolve11("src/templates/forms");
26685
26707
  ensureDir(formsDir);
26686
26708
  writeFileSafe(
26687
- join21(formsDir, "login.twig"),
26709
+ join22(formsDir, "login.twig"),
26688
26710
  `{% extends "base.twig" %}
26689
26711
  {% block title %}Login{% endblock %}
26690
26712
  {% block content %}
@@ -26708,7 +26730,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
26708
26730
  `
26709
26731
  );
26710
26732
  writeFileSafe(
26711
- join21(formsDir, "register.twig"),
26733
+ join22(formsDir, "register.twig"),
26712
26734
  `{% extends "base.twig" %}
26713
26735
  {% block title %}Register{% endblock %}
26714
26736
  {% block content %}
@@ -26746,7 +26768,7 @@ function generateService(name, flags) {
26746
26768
  const cron = flags.cron;
26747
26769
  const dir = resolve11("src/services");
26748
26770
  ensureDir(dir);
26749
- const path10 = join21(dir, `${snake}.ts`);
26771
+ const path10 = join22(dir, `${snake}.ts`);
26750
26772
  let scheduleField;
26751
26773
  let note;
26752
26774
  if (cron && cron !== true) {
@@ -26798,7 +26820,7 @@ function generateQueue(name, _flags) {
26798
26820
  const pascal = toPascal(topic) || "Topic";
26799
26821
  const dir = resolve11("src/services");
26800
26822
  ensureDir(dir);
26801
- const path10 = join21(dir, `${slug}_consumer.ts`);
26823
+ const path10 = join22(dir, `${slug}_consumer.ts`);
26802
26824
  const body = aiFill(`handle${pascal}`, {
26803
26825
  intent: `process ONE ${topic} job payload`,
26804
26826
  given: "payload -> the produced job data (job.payload)",
@@ -26859,7 +26881,7 @@ export default {
26859
26881
  function generateValidator(name, _flags) {
26860
26882
  const dir = resolve11("src/validators");
26861
26883
  ensureDir(dir);
26862
- const path10 = join21(dir, `${toSnake(name)}.ts`);
26884
+ const path10 = join22(dir, `${toSnake(name)}.ts`);
26863
26885
  const rules = extend(
26864
26886
  "add / adjust the validation rules for this payload",
26865
26887
  `e.g. .email("email").minLength("name", 2).integer("age"); ground: tina4_context("validate request body with Validator", "nodejs")`
@@ -26887,7 +26909,7 @@ function generateSeeder(name, flags) {
26887
26909
  const table2 = resolveTable(name, flags, { announce: false });
26888
26910
  const dir = resolve11("src/seeds");
26889
26911
  ensureDir(dir);
26890
- const path10 = join21(dir, `${table2}_seeder.ts`);
26912
+ const path10 = join22(dir, `${table2}_seeder.ts`);
26891
26913
  const overrides = extend(
26892
26914
  "override fields that need a specific shape (seedOrm auto-fills the rest)",
26893
26915
  `e.g. return { email: (f) => f.email(), status: "active" }; ground: tina4_context("seed ORM model with FakeData", "nodejs")`
@@ -26932,7 +26954,7 @@ function generateWebsocket(name, _flags) {
26932
26954
  const handlerName = `${toCamel(toPascal(base))}Ws`;
26933
26955
  const dir = resolve11("src/routes");
26934
26956
  ensureDir(dir);
26935
- const path10 = join21(dir, `ws_${base}.ts`);
26957
+ const path10 = join22(dir, `ws_${base}.ts`);
26936
26958
  const body = aiFill(handlerName, {
26937
26959
  intent: `handle an inbound "message" frame on ${wsPath}`,
26938
26960
  given: "data -> the message payload (string); connection -> WebSocketConnection",
@@ -26983,7 +27005,7 @@ function generateListener(name, _flags) {
26983
27005
  const handlerName = `on${toPascal(slug)}`;
26984
27006
  const dir = resolve11("src/listeners");
26985
27007
  ensureDir(dir);
26986
- const path10 = join21(dir, `${slug}.ts`);
27008
+ const path10 = join22(dir, `${slug}.ts`);
26987
27009
  const body = aiFill(handlerName, {
26988
27010
  intent: `react to the '${event}' event`,
26989
27011
  given: `args -> whatever Events.emit("${event}", ...args) passed`,
@@ -27015,7 +27037,7 @@ Events.on("${event}", ${handlerName});
27015
27037
  function writeTest(testName, content) {
27016
27038
  const dir = resolve11("tests");
27017
27039
  ensureDir(dir);
27018
- writeFileSafe(join21(dir, `${testName}.test.ts`), content);
27040
+ writeFileSafe(join22(dir, `${testName}.test.ts`), content);
27019
27041
  }
27020
27042
  function standaloneTest(doc, body) {
27021
27043
  return `${doc}
@@ -27401,6 +27423,7 @@ var FIELD_TYPE_MAP, SQL_RESERVED_TABLE_NAMES, RESOLUTION_ENVELOPE_VERSION, __res
27401
27423
  var init_generate = __esm({
27402
27424
  "src/commands/generate.ts"() {
27403
27425
  "use strict";
27426
+ init_util();
27404
27427
  FIELD_TYPE_MAP = {
27405
27428
  string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
27406
27429
  str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
@@ -30459,7 +30482,7 @@ __export(context_exports, {
30459
30482
  });
30460
30483
  import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
30461
30484
  import { existsSync as existsSync18, mkdirSync as mkdirSync15, readFileSync as readFileSync15, readdirSync as readdirSync10, realpathSync as realpathSync5 } from "node:fs";
30462
- import { basename as basename5, dirname as dirname11, extname as extname5, isAbsolute as isAbsolute6, join as join23, relative as relative4, resolve as resolve13 } from "node:path";
30485
+ import { basename as basename5, dirname as dirname11, extname as extname5, isAbsolute as isAbsolute6, join as join24, relative as relative4, resolve as resolve13 } from "node:path";
30463
30486
  function fts5Supported() {
30464
30487
  try {
30465
30488
  const conn = new DatabaseSync4(":memory:");
@@ -30482,13 +30505,13 @@ function realResolve(abs) {
30482
30505
  } catch {
30483
30506
  }
30484
30507
  try {
30485
- return join23(realpathSync5(dirname11(abs)), basename5(abs));
30508
+ return join24(realpathSync5(dirname11(abs)), basename5(abs));
30486
30509
  } catch {
30487
30510
  return abs;
30488
30511
  }
30489
30512
  }
30490
30513
  function dbKey(db) {
30491
- return resolve13(db ? String(db) : join23(process.cwd(), ".tina4", "context.db"));
30514
+ return resolve13(db ? String(db) : join24(process.cwd(), ".tina4", "context.db"));
30492
30515
  }
30493
30516
  function defaultContext(root, db) {
30494
30517
  const key = dbKey(db);
@@ -30661,11 +30684,11 @@ var init_context = __esm({
30661
30684
  files.sort();
30662
30685
  for (const fn of files) {
30663
30686
  if (!_Context.eligible(fn)) continue;
30664
- const full = join23(dir, fn);
30687
+ const full = join24(dir, fn);
30665
30688
  const rel = relative4(rootAbs, full);
30666
30689
  total += this.indexPath(full, rel);
30667
30690
  }
30668
- for (const d of subdirs) walk2(join23(dir, d));
30691
+ for (const d of subdirs) walk2(join24(dir, d));
30669
30692
  };
30670
30693
  walk2(rootAbs);
30671
30694
  return total;
@@ -30681,7 +30704,7 @@ var init_context = __esm({
30681
30704
  reindexFile(changedPath) {
30682
30705
  if (!this.available || this.root === null) return -1;
30683
30706
  const raw = String(changedPath);
30684
- const abs = isAbsolute6(raw) ? raw : join23(process.cwd(), raw);
30707
+ const abs = isAbsolute6(raw) ? raw : join24(process.cwd(), raw);
30685
30708
  const resolved = realResolve(resolve13(abs));
30686
30709
  const rel = relative4(this.root, resolved);
30687
30710
  if (rel === "" || rel.startsWith("..") || isAbsolute6(rel)) {
@@ -31019,6 +31042,7 @@ __export(websocket_exports, {
31019
31042
  CLOSE_NORMAL: () => CLOSE_NORMAL,
31020
31043
  CLOSE_POLICY_VIOLATION: () => CLOSE_POLICY_VIOLATION,
31021
31044
  CLOSE_PROTOCOL_ERROR: () => CLOSE_PROTOCOL_ERROR,
31045
+ MessageAssembler: () => MessageAssembler,
31022
31046
  OP_BINARY: () => OP_BINARY,
31023
31047
  OP_CLOSE: () => OP_CLOSE,
31024
31048
  OP_CONTINUATION: () => OP_CONTINUATION,
@@ -31276,21 +31300,24 @@ function serveWebSocketRoute(req2, socket, head) {
31276
31300
  state.closed = true;
31277
31301
  void Promise.resolve().then(() => conn._onClose ? conn._onClose() : handler(conn, "close", "")).catch((e) => Log.error(`WebSocket close handler error: ${e.message}`)).finally(() => wsRouteManager.remove(conn.id));
31278
31302
  };
31303
+ const assembler = new MessageAssembler();
31279
31304
  socket.on("data", (chunk) => {
31280
31305
  buffer = Buffer.concat([buffer, chunk]);
31281
31306
  while (buffer.length > 0) {
31282
31307
  const frame = parseFrame(buffer);
31283
31308
  if (!frame) break;
31284
31309
  buffer = buffer.subarray(frame.bytesConsumed);
31285
- switch (frame.opcode) {
31310
+ const whole = assembler.push(frame);
31311
+ if (!whole) continue;
31312
+ switch (whole.opcode) {
31286
31313
  case OP_TEXT: {
31287
- const text = frame.payload.toString("utf-8");
31314
+ const text = whole.payload.toString("utf-8");
31288
31315
  void Promise.resolve().then(() => conn._onMessage ? conn._onMessage(text) : handler(conn, "message", text)).catch((e) => Log.error(`WebSocket message handler error: ${e.message}`));
31289
31316
  break;
31290
31317
  }
31291
31318
  case OP_PING:
31292
31319
  try {
31293
- socket.write(buildFrame(OP_PONG, frame.payload));
31320
+ socket.write(buildFrame(OP_PONG, whole.payload));
31294
31321
  } catch {
31295
31322
  }
31296
31323
  break;
@@ -31309,7 +31336,7 @@ function serveWebSocketRoute(req2, socket, head) {
31309
31336
  socket.on("error", fireClose);
31310
31337
  return true;
31311
31338
  }
31312
- var MAGIC_STRING, OP_CONTINUATION, OP_TEXT, OP_BINARY, OP_CLOSE, OP_PING, OP_PONG, CLOSE_NORMAL, CLOSE_GOING_AWAY, CLOSE_PROTOCOL_ERROR, CLOSE_POLICY_VIOLATION, WebSocketServer, WsRouteManager, wsRouteManager, DevReloadWsManager, devReloadWs;
31339
+ var MAGIC_STRING, OP_CONTINUATION, OP_TEXT, OP_BINARY, OP_CLOSE, OP_PING, OP_PONG, CLOSE_NORMAL, CLOSE_GOING_AWAY, CLOSE_PROTOCOL_ERROR, CLOSE_POLICY_VIOLATION, MessageAssembler, WebSocketServer, WsRouteManager, wsRouteManager, DevReloadWsManager, devReloadWs;
31313
31340
  var init_websocket = __esm({
31314
31341
  "../core/src/websocket.ts"() {
31315
31342
  "use strict";
@@ -31328,6 +31355,30 @@ var init_websocket = __esm({
31328
31355
  CLOSE_GOING_AWAY = 1001;
31329
31356
  CLOSE_PROTOCOL_ERROR = 1002;
31330
31357
  CLOSE_POLICY_VIOLATION = 1008;
31358
+ MessageAssembler = class {
31359
+ parts = [];
31360
+ opcode = 0;
31361
+ push(frame) {
31362
+ if (frame.opcode >= 8) return { opcode: frame.opcode, payload: frame.payload };
31363
+ if (frame.opcode === OP_CONTINUATION) {
31364
+ if (!this.opcode) return null;
31365
+ this.parts.push(frame.payload);
31366
+ if (!frame.fin) return null;
31367
+ const whole = { opcode: this.opcode, payload: Buffer.concat(this.parts) };
31368
+ this.parts = [];
31369
+ this.opcode = 0;
31370
+ return whole;
31371
+ }
31372
+ if (frame.fin) {
31373
+ this.parts = [];
31374
+ this.opcode = 0;
31375
+ return { opcode: frame.opcode, payload: frame.payload };
31376
+ }
31377
+ this.opcode = frame.opcode;
31378
+ this.parts = [frame.payload];
31379
+ return null;
31380
+ }
31381
+ };
31331
31382
  WebSocketServer = class {
31332
31383
  port;
31333
31384
  server = null;
@@ -31770,6 +31821,15 @@ var init_websocket = __esm({
31770
31821
  this.emit("error", err, client);
31771
31822
  });
31772
31823
  }
31824
+ assemblers = /* @__PURE__ */ new WeakMap();
31825
+ assemblerFor(client) {
31826
+ let a = this.assemblers.get(client);
31827
+ if (!a) {
31828
+ a = new MessageAssembler();
31829
+ this.assemblers.set(client, a);
31830
+ }
31831
+ return a;
31832
+ }
31773
31833
  processBuffer(client, buffer, setBuffer) {
31774
31834
  let remaining = buffer;
31775
31835
  while (remaining.length > 0) {
@@ -31777,15 +31837,17 @@ var init_websocket = __esm({
31777
31837
  if (!frame) break;
31778
31838
  remaining = remaining.subarray(frame.bytesConsumed);
31779
31839
  client.lastActivity = Date.now();
31780
- switch (frame.opcode) {
31840
+ const whole = this.assemblerFor(client).push(frame);
31841
+ if (!whole) continue;
31842
+ switch (whole.opcode) {
31781
31843
  case OP_TEXT:
31782
- this.emit("message", client, frame.payload.toString("utf-8"));
31844
+ this.emit("message", client, whole.payload.toString("utf-8"));
31783
31845
  break;
31784
31846
  case OP_BINARY:
31785
- this.emit("message", client, frame.payload);
31847
+ this.emit("message", client, whole.payload);
31786
31848
  break;
31787
31849
  case OP_PING: {
31788
- const pongFrame = buildFrame(OP_PONG, frame.payload);
31850
+ const pongFrame = buildFrame(OP_PONG, whole.payload);
31789
31851
  try {
31790
31852
  client.socket.write(pongFrame);
31791
31853
  } catch {
@@ -32704,7 +32766,7 @@ var init_job = __esm({
32704
32766
 
32705
32767
  // ../core/src/queueBackends/liteBackend.ts
32706
32768
  import { mkdirSync as mkdirSync16, readdirSync as readdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync7, existsSync as existsSync19 } from "node:fs";
32707
- import { join as join24 } from "node:path";
32769
+ import { join as join25 } from "node:path";
32708
32770
  import { randomUUID as randomUUID6 } from "node:crypto";
32709
32771
  var LiteBackend;
32710
32772
  var init_liteBackend = __esm({
@@ -32728,22 +32790,22 @@ var init_liteBackend = __esm({
32728
32790
  this.visibilityTimeout = visibilityTimeout;
32729
32791
  }
32730
32792
  ensureDir(queue) {
32731
- const dir = join24(this.basePath, queue);
32793
+ const dir = join25(this.basePath, queue);
32732
32794
  mkdirSync16(dir, { recursive: true });
32733
32795
  return dir;
32734
32796
  }
32735
32797
  ensureFailedDir(queue) {
32736
- const dir = join24(this.basePath, queue, "failed");
32798
+ const dir = join25(this.basePath, queue, "failed");
32737
32799
  mkdirSync16(dir, { recursive: true });
32738
32800
  return dir;
32739
32801
  }
32740
32802
  ensureReservedDir(queue) {
32741
- const dir = join24(this.basePath, queue, "reserved");
32803
+ const dir = join25(this.basePath, queue, "reserved");
32742
32804
  mkdirSync16(dir, { recursive: true });
32743
32805
  return dir;
32744
32806
  }
32745
32807
  reservedPath(queue, jobId) {
32746
- return join24(this.ensureReservedDir(queue), `${jobId}.queue-data`);
32808
+ return join25(this.ensureReservedDir(queue), `${jobId}.queue-data`);
32747
32809
  }
32748
32810
  nowIso() {
32749
32811
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -32781,7 +32843,7 @@ var init_liteBackend = __esm({
32781
32843
  error: void 0
32782
32844
  };
32783
32845
  const prefix = this.nextPrefix();
32784
- writeFileSync12(join24(dir, `${prefix}_${id}.queue-data`), JSON.stringify(job, null, 2));
32846
+ writeFileSync12(join25(dir, `${prefix}_${id}.queue-data`), JSON.stringify(job, null, 2));
32785
32847
  return id;
32786
32848
  }
32787
32849
  /**
@@ -32800,7 +32862,7 @@ var init_liteBackend = __esm({
32800
32862
  }
32801
32863
  const candidates = [];
32802
32864
  for (const filename of filenames) {
32803
- const filePath = join24(dir, filename);
32865
+ const filePath = join25(dir, filename);
32804
32866
  let job;
32805
32867
  try {
32806
32868
  job = JSON.parse(readFileSync16(filePath, "utf-8"));
@@ -32864,7 +32926,7 @@ var init_liteBackend = __esm({
32864
32926
  return;
32865
32927
  }
32866
32928
  for (const filename of filenames) {
32867
- const filePath = join24(reservedDir, filename);
32929
+ const filePath = join25(reservedDir, filename);
32868
32930
  let record;
32869
32931
  try {
32870
32932
  record = JSON.parse(readFileSync16(filePath, "utf-8"));
@@ -32902,7 +32964,7 @@ var init_liteBackend = __esm({
32902
32964
  this.reclaimExpired(queue, bridge.getMaxRetries(), this.nowIso());
32903
32965
  const now = this.nowIso();
32904
32966
  for (const [filename, job] of this.availableCandidates(queue, now)) {
32905
- const filePath = join24(dir, filename);
32967
+ const filePath = join25(dir, filename);
32906
32968
  job.topic = queue;
32907
32969
  job.priority = job.priority ?? 0;
32908
32970
  this.writeReserved(queue, job);
@@ -32927,7 +32989,7 @@ var init_liteBackend = __esm({
32927
32989
  const results = [];
32928
32990
  for (const [filename, job] of this.availableCandidates(queue, now)) {
32929
32991
  if (results.length >= count) break;
32930
- const filePath = join24(dir, filename);
32992
+ const filePath = join25(dir, filename);
32931
32993
  job.topic = queue;
32932
32994
  job.priority = job.priority ?? 0;
32933
32995
  this.writeReserved(queue, job);
@@ -32980,7 +33042,7 @@ var init_liteBackend = __esm({
32980
33042
  let count = 0;
32981
33043
  for (const file of files) {
32982
33044
  try {
32983
- const job = JSON.parse(readFileSync16(join24(scanDir, file), "utf-8"));
33045
+ const job = JSON.parse(readFileSync16(join25(scanDir, file), "utf-8"));
32984
33046
  if (job.status === status2) count++;
32985
33047
  } catch {
32986
33048
  }
@@ -32993,28 +33055,28 @@ var init_liteBackend = __esm({
32993
33055
  try {
32994
33056
  const files = readdirSync11(dir).filter((f) => f.endsWith(".queue-data"));
32995
33057
  for (const file of files) {
32996
- unlinkSync7(join24(dir, file));
33058
+ unlinkSync7(join25(dir, file));
32997
33059
  count++;
32998
33060
  }
32999
33061
  } catch {
33000
33062
  }
33001
- const failedDir = join24(dir, "failed");
33063
+ const failedDir = join25(dir, "failed");
33002
33064
  try {
33003
33065
  if (existsSync19(failedDir)) {
33004
33066
  const files = readdirSync11(failedDir).filter((f) => f.endsWith(".queue-data"));
33005
33067
  for (const file of files) {
33006
- unlinkSync7(join24(failedDir, file));
33068
+ unlinkSync7(join25(failedDir, file));
33007
33069
  count++;
33008
33070
  }
33009
33071
  }
33010
33072
  } catch {
33011
33073
  }
33012
- const reservedDir = join24(dir, "reserved");
33074
+ const reservedDir = join25(dir, "reserved");
33013
33075
  try {
33014
33076
  if (existsSync19(reservedDir)) {
33015
33077
  const files = readdirSync11(reservedDir).filter((f) => f.endsWith(".queue-data"));
33016
33078
  for (const file of files) {
33017
- unlinkSync7(join24(reservedDir, file));
33079
+ unlinkSync7(join25(reservedDir, file));
33018
33080
  count++;
33019
33081
  }
33020
33082
  }
@@ -33037,7 +33099,7 @@ var init_liteBackend = __esm({
33037
33099
  const files = readdirSync11(dir).filter((f) => f.endsWith(".queue-data")).sort();
33038
33100
  for (const file of files) {
33039
33101
  try {
33040
- const job = JSON.parse(readFileSync16(join24(dir, file), "utf-8"));
33102
+ const job = JSON.parse(readFileSync16(join25(dir, file), "utf-8"));
33041
33103
  const attempts = job.attempts || 0;
33042
33104
  if (attempts > 0 && attempts < maxRetries) {
33043
33105
  results.push(job);
@@ -33060,8 +33122,8 @@ var init_liteBackend = __esm({
33060
33122
  try {
33061
33123
  const queues = readdirSync11(this.basePath);
33062
33124
  for (const q of queues) {
33063
- const failedDir = join24(this.basePath, q, "failed");
33064
- const filePath = join24(failedDir, `${jobId}.queue-data`);
33125
+ const failedDir = join25(this.basePath, q, "failed");
33126
+ const filePath = join25(failedDir, `${jobId}.queue-data`);
33065
33127
  if (existsSync19(filePath)) {
33066
33128
  const job = JSON.parse(readFileSync16(filePath, "utf-8"));
33067
33129
  job.status = "pending";
@@ -33070,8 +33132,8 @@ var init_liteBackend = __esm({
33070
33132
  job.createdAt = (/* @__PURE__ */ new Date()).toISOString();
33071
33133
  job.delayUntil = delaySeconds ? new Date(Date.now() + delaySeconds * 1e3).toISOString() : null;
33072
33134
  const prefix = this.nextPrefix();
33073
- const queueDir = join24(this.basePath, q);
33074
- writeFileSync12(join24(queueDir, `${prefix}_${jobId}.queue-data`), JSON.stringify(job, null, 2));
33135
+ const queueDir = join25(this.basePath, q);
33136
+ writeFileSync12(join25(queueDir, `${prefix}_${jobId}.queue-data`), JSON.stringify(job, null, 2));
33075
33137
  unlinkSync7(filePath);
33076
33138
  return true;
33077
33139
  }
@@ -33087,7 +33149,7 @@ var init_liteBackend = __esm({
33087
33149
  const files = readdirSync11(failedDir).filter((f) => f.endsWith(".queue-data")).sort();
33088
33150
  for (const file of files) {
33089
33151
  try {
33090
- const job = JSON.parse(readFileSync16(join24(failedDir, file), "utf-8"));
33152
+ const job = JSON.parse(readFileSync16(join25(failedDir, file), "utf-8"));
33091
33153
  if ((job.attempts || 0) >= maxRetries) {
33092
33154
  job.status = "dead";
33093
33155
  results.push(job);
@@ -33108,7 +33170,7 @@ var init_liteBackend = __esm({
33108
33170
  const files = readdirSync11(failedDir).filter((f) => f.endsWith(".queue-data"));
33109
33171
  for (const file of files) {
33110
33172
  try {
33111
- unlinkSync7(join24(failedDir, file));
33173
+ unlinkSync7(join25(failedDir, file));
33112
33174
  count++;
33113
33175
  } catch {
33114
33176
  }
@@ -33121,9 +33183,9 @@ var init_liteBackend = __esm({
33121
33183
  const files = readdirSync11(dir).filter((f) => f.endsWith(".queue-data"));
33122
33184
  for (const file of files) {
33123
33185
  try {
33124
- const job = JSON.parse(readFileSync16(join24(dir, file), "utf-8"));
33186
+ const job = JSON.parse(readFileSync16(join25(dir, file), "utf-8"));
33125
33187
  if (job.status === status2) {
33126
- unlinkSync7(join24(dir, file));
33188
+ unlinkSync7(join25(dir, file));
33127
33189
  count++;
33128
33190
  }
33129
33191
  } catch {
@@ -33147,7 +33209,7 @@ var init_liteBackend = __esm({
33147
33209
  const files = readdirSync11(failedDir).filter((f) => f.endsWith(".queue-data"));
33148
33210
  for (const file of files) {
33149
33211
  try {
33150
- const filePath = join24(failedDir, file);
33212
+ const filePath = join25(failedDir, file);
33151
33213
  const job = JSON.parse(readFileSync16(filePath, "utf-8"));
33152
33214
  if ((job.attempts || 0) >= maxRetries) {
33153
33215
  continue;
@@ -33157,7 +33219,7 @@ var init_liteBackend = __esm({
33157
33219
  job.createdAt = (/* @__PURE__ */ new Date()).toISOString();
33158
33220
  job.delayUntil = null;
33159
33221
  const prefix = this.nextPrefix();
33160
- writeFileSync12(join24(queueDir, `${prefix}_${job.id}.queue-data`), JSON.stringify(job, null, 2));
33222
+ writeFileSync12(join25(queueDir, `${prefix}_${job.id}.queue-data`), JSON.stringify(job, null, 2));
33161
33223
  unlinkSync7(filePath);
33162
33224
  count++;
33163
33225
  } catch {
@@ -33177,7 +33239,7 @@ var init_liteBackend = __esm({
33177
33239
  }
33178
33240
  for (const file of files) {
33179
33241
  if (!file.includes(id)) continue;
33180
- const filePath = join24(dir, file);
33242
+ const filePath = join25(dir, file);
33181
33243
  let job;
33182
33244
  try {
33183
33245
  job = JSON.parse(readFileSync16(filePath, "utf-8"));
@@ -33225,7 +33287,7 @@ var init_liteBackend = __esm({
33225
33287
  error: error2
33226
33288
  };
33227
33289
  const prefix = this.nextPrefix();
33228
- writeFileSync12(join24(dir, `${prefix}_${job.id}.queue-data`), JSON.stringify(jobData, null, 2));
33290
+ writeFileSync12(join25(dir, `${prefix}_${job.id}.queue-data`), JSON.stringify(jobData, null, 2));
33229
33291
  }
33230
33292
  /**
33231
33293
  * Move the job to the dead-letter (failed/) directory. Terminal until a
@@ -33245,7 +33307,7 @@ var init_liteBackend = __esm({
33245
33307
  error: error2,
33246
33308
  failedAt: (/* @__PURE__ */ new Date()).toISOString()
33247
33309
  };
33248
- writeFileSync12(join24(failedDir, `${job.id}.queue-data`), JSON.stringify(jobData, null, 2));
33310
+ writeFileSync12(join25(failedDir, `${job.id}.queue-data`), JSON.stringify(jobData, null, 2));
33249
33311
  }
33250
33312
  /**
33251
33313
  * Record a failed attempt.
@@ -33295,7 +33357,7 @@ var init_liteBackend = __esm({
33295
33357
  retryJob(queue, job, delaySeconds) {
33296
33358
  this.clearReservation(queue, job.id);
33297
33359
  try {
33298
- unlinkSync7(join24(this.ensureFailedDir(queue), `${job.id}.queue-data`));
33360
+ unlinkSync7(join25(this.ensureFailedDir(queue), `${job.id}.queue-data`));
33299
33361
  } catch {
33300
33362
  }
33301
33363
  job.attempts = (job.attempts || 0) + 1;
@@ -35915,8 +35977,8 @@ ${end}
35915
35977
 
35916
35978
  // ../core/src/devAdmin.ts
35917
35979
  import { cpus as osCpus } from "node:os";
35918
- import { readFileSync as readFileSync20, writeFileSync as writeFileSync16, existsSync as existsSync23, readdirSync as readdirSync15, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, statSync as statSync17, openSync as openSync4, closeSync as closeSync4, fstatSync as fstatSync2, fchmodSync as fchmodSync2, ftruncateSync, constants as constants3, realpathSync as realpathSync6, lstatSync } from "node:fs";
35919
- import { join as join28, dirname as dirname13, resolve as resolve17, relative as relative9, sep as sep8, basename as basename6 } from "node:path";
35980
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync16, existsSync as existsSync23, readdirSync as readdirSync15, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, statSync as statSync18, openSync as openSync4, closeSync as closeSync4, fstatSync as fstatSync2, fchmodSync as fchmodSync2, ftruncateSync, constants as constants3, realpathSync as realpathSync6, lstatSync } from "node:fs";
35981
+ import { join as join29, dirname as dirname13, resolve as resolve17, relative as relative9, sep as sep8, basename as basename6 } from "node:path";
35920
35982
  import { fileURLToPath as fileURLToPath6 } from "node:url";
35921
35983
  import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
35922
35984
  function escapeHtml(value) {
@@ -36060,7 +36122,7 @@ function readQueueDir(dir, topic, status2) {
36060
36122
  for (const filename of readdirSync15(dir).sort()) {
36061
36123
  if (!filename.endsWith(".queue-data")) continue;
36062
36124
  try {
36063
- jobs.push(mapQueueJob(JSON.parse(readFileSync20(join28(dir, filename), "utf-8")), topic, status2));
36125
+ jobs.push(mapQueueJob(JSON.parse(readFileSync20(join29(dir, filename), "utf-8")), topic, status2));
36064
36126
  } catch {
36065
36127
  }
36066
36128
  }
@@ -36163,21 +36225,12 @@ async function proxyToSupervisor(req2, res, downstreamPath) {
36163
36225
  return;
36164
36226
  }
36165
36227
  clearTimeout(timer);
36166
- const raw = await upstream.text();
36167
- const status2 = upstream.status || 200;
36168
- try {
36169
- res.json(JSON.parse(raw), status2);
36170
- } catch {
36171
- res.raw.writeHead(status2, {
36172
- "Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
36173
- });
36174
- res.raw.end(raw);
36175
- }
36228
+ await relayUpstreamResponse(res, upstream);
36176
36229
  }
36177
36230
  function resolveDevEnvVar(key) {
36178
36231
  const live = process.env[key];
36179
36232
  if (live !== void 0 && live !== "") return live;
36180
- const envPath = join28(process.cwd(), ".env");
36233
+ const envPath = join29(process.cwd(), ".env");
36181
36234
  if (!existsSync23(envPath)) return "";
36182
36235
  for (const line of readFileSync20(envPath, "utf-8").split("\n")) {
36183
36236
  const t = line.trim();
@@ -36188,7 +36241,7 @@ function resolveDevEnvVar(key) {
36188
36241
  return "";
36189
36242
  }
36190
36243
  function upsertDevEnvVar(key, value) {
36191
- const envPath = join28(process.cwd(), ".env");
36244
+ const envPath = join29(process.cwd(), ".env");
36192
36245
  const lines = existsSync23(envPath) ? readFileSync20(envPath, "utf-8").split("\n") : [];
36193
36246
  let found = false;
36194
36247
  const out = [];
@@ -36219,7 +36272,7 @@ function formatUptime(seconds) {
36219
36272
  return parts.join(" ");
36220
36273
  }
36221
36274
  function parseEnvFile() {
36222
- const envPath = join28(process.cwd(), ".env");
36275
+ const envPath = join29(process.cwd(), ".env");
36223
36276
  const result = {};
36224
36277
  if (!existsSync23(envPath)) return result;
36225
36278
  const lines = readFileSync20(envPath, "utf-8").split("\n");
@@ -36235,8 +36288,8 @@ function walkDirRecursive(dir) {
36235
36288
  const results = [];
36236
36289
  if (!existsSync23(dir)) return results;
36237
36290
  for (const entry of readdirSync15(dir)) {
36238
- const full = join28(dir, entry);
36239
- if (statSync17(full).isDirectory()) {
36291
+ const full = join29(dir, entry);
36292
+ if (statSync18(full).isDirectory()) {
36240
36293
  results.push(...walkDirRecursive(full));
36241
36294
  } else {
36242
36295
  results.push(full);
@@ -36253,7 +36306,7 @@ function handleGalleryDeploy(router) {
36253
36306
  return;
36254
36307
  }
36255
36308
  const galleryDir = resolve17(__devAdminDirname, "..", "gallery");
36256
- const gallerySrc = join28(galleryDir, name, "src");
36309
+ const gallerySrc = join29(galleryDir, name, "src");
36257
36310
  if (!existsSync23(gallerySrc)) {
36258
36311
  res.json({ error: `Gallery item '${name}' not found` }, 404);
36259
36312
  return;
@@ -36263,7 +36316,7 @@ function handleGalleryDeploy(router) {
36263
36316
  const allFiles = walkDirRecursive(gallerySrc);
36264
36317
  for (const srcFile of allFiles) {
36265
36318
  const rel = relative9(gallerySrc, srcFile);
36266
- const dest = join28(projectSrc, rel);
36319
+ const dest = join29(projectSrc, rel);
36267
36320
  mkdirSync19(dirname13(dest), { recursive: true });
36268
36321
  copyFileSync2(srcFile, dest);
36269
36322
  copied.push(rel);
@@ -36299,7 +36352,7 @@ function realPathOf(target) {
36299
36352
  tail.unshift(basename6(existing));
36300
36353
  existing = dirname13(existing);
36301
36354
  }
36302
- return join28(realpathSync6(existing), ...tail);
36355
+ return join29(realpathSync6(existing), ...tail);
36303
36356
  }
36304
36357
  function safeJoin(projectRoot3, rel, extraRoots = []) {
36305
36358
  try {
@@ -36614,6 +36667,7 @@ var init_devAdmin = __esm({
36614
36667
  "../core/src/devAdmin.ts"() {
36615
36668
  "use strict";
36616
36669
  init_devMailbox();
36670
+ init_upstreamResponse();
36617
36671
  init_dotenv();
36618
36672
  init_metrics();
36619
36673
  init_feedback();
@@ -36981,7 +37035,7 @@ var init_devAdmin = __esm({
36981
37035
  const p = (url.searchParams.get("path") || "").toString();
36982
37036
  const scanRoot = metricsScanRoot();
36983
37037
  let target = p ? safeJoin(process.cwd(), p, [scanRoot]) : "";
36984
- if (target === null && scanRoot) target = safeJoin(process.cwd(), join28(scanRoot, p), [scanRoot]);
37038
+ if (target === null && scanRoot) target = safeJoin(process.cwd(), join29(scanRoot, p), [scanRoot]);
36985
37039
  if (target === null) {
36986
37040
  res.status(403).json({ error: "Path outside project" });
36987
37041
  return;
@@ -37296,16 +37350,16 @@ var init_devAdmin = __esm({
37296
37350
  failed: queue.size("failed"),
37297
37351
  reserved: queue.size("reserved")
37298
37352
  };
37299
- const topicDir = join28(queueBasePath2(), topic);
37353
+ const topicDir = join29(queueBasePath2(), topic);
37300
37354
  const jobs = [];
37301
37355
  if (!statusFilter || statusFilter === "pending") {
37302
37356
  jobs.push(...readQueueDir(topicDir, topic, "pending"));
37303
37357
  }
37304
37358
  if (!statusFilter || statusFilter === "reserved") {
37305
- jobs.push(...readQueueDir(join28(topicDir, "reserved"), topic, "reserved"));
37359
+ jobs.push(...readQueueDir(join29(topicDir, "reserved"), topic, "reserved"));
37306
37360
  }
37307
37361
  if (!statusFilter || statusFilter === "failed" || statusFilter === "dead") {
37308
- jobs.push(...readQueueDir(join28(topicDir, "failed"), topic, "dead_letter"));
37362
+ jobs.push(...readQueueDir(join29(topicDir, "failed"), topic, "dead_letter"));
37309
37363
  }
37310
37364
  res.json({ stats, jobs });
37311
37365
  } catch (e) {
@@ -37324,7 +37378,7 @@ var init_devAdmin = __esm({
37324
37378
  if (existsSync23(queueDir)) {
37325
37379
  topics = readdirSync15(queueDir).filter((d) => {
37326
37380
  try {
37327
- return statSync17(join28(queueDir, d)).isDirectory();
37381
+ return statSync18(join29(queueDir, d)).isDirectory();
37328
37382
  } catch {
37329
37383
  return false;
37330
37384
  }
@@ -37782,7 +37836,7 @@ var init_devAdmin = __esm({
37782
37836
  return;
37783
37837
  }
37784
37838
  try {
37785
- const envPath = join28(process.cwd(), ".env");
37839
+ const envPath = join29(process.cwd(), ".env");
37786
37840
  const fd = openSync4(envPath, constants3.O_RDWR | constants3.O_APPEND | constants3.O_CREAT | (constants3.O_NOFOLLOW ?? 0) | (constants3.O_NONBLOCK ?? 0), 384);
37787
37841
  try {
37788
37842
  const info = fstatSync2(fd);
@@ -37834,13 +37888,13 @@ var init_devAdmin = __esm({
37834
37888
  if (existsSync23(galleryDir)) {
37835
37889
  const entries = readdirSync15(galleryDir).sort();
37836
37890
  for (const entry of entries) {
37837
- const entryPath = join28(galleryDir, entry);
37838
- const metaFile = join28(entryPath, "meta.json");
37839
- if (statSync17(entryPath).isDirectory() && existsSync23(metaFile)) {
37891
+ const entryPath = join29(galleryDir, entry);
37892
+ const metaFile = join29(entryPath, "meta.json");
37893
+ if (statSync18(entryPath).isDirectory() && existsSync23(metaFile)) {
37840
37894
  try {
37841
37895
  const meta = JSON.parse(readFileSync20(metaFile, "utf-8"));
37842
37896
  meta.id = entry;
37843
- const srcDir = join28(entryPath, "src");
37897
+ const srcDir = join29(entryPath, "src");
37844
37898
  if (existsSync23(srcDir)) {
37845
37899
  const allFiles = walkDirRecursive(srcDir);
37846
37900
  meta.files = allFiles.map((f) => relative9(srcDir, f));
@@ -37848,7 +37902,7 @@ var init_devAdmin = __esm({
37848
37902
  const projectSrc = resolve17(process.cwd(), "src");
37849
37903
  if (existsSync23(srcDir) && meta.files) {
37850
37904
  meta.deployed = meta.files.every(
37851
- (f) => existsSync23(join28(projectSrc, f))
37905
+ (f) => existsSync23(join29(projectSrc, f))
37852
37906
  );
37853
37907
  } else {
37854
37908
  meta.deployed = false;
@@ -37925,7 +37979,7 @@ var init_devAdmin = __esm({
37925
37979
  const root = resolve17(process.cwd());
37926
37980
  const target = safeJoin(root, rel);
37927
37981
  const { branch, gitRoot, status: gitStatus } = await devGitInfo(root);
37928
- if (!target || !existsSync23(target) || !statSync17(target).isDirectory()) {
37982
+ if (!target || !existsSync23(target) || !statSync18(target).isDirectory()) {
37929
37983
  res.json({ path: rel, branch, entries: [], error: "not a directory" });
37930
37984
  return;
37931
37985
  }
@@ -37938,13 +37992,13 @@ var init_devAdmin = __esm({
37938
37992
  const entries = [];
37939
37993
  for (const name of readdirSync15(target).sort()) {
37940
37994
  if (devFilesHidden(name)) continue;
37941
- const full = join28(target, name);
37995
+ const full = join29(target, name);
37942
37996
  const entryRel = relative9(root, full).replace(/\\/g, "/");
37943
37997
  if (isSecretPath(entryRel)) continue;
37944
37998
  let isDir = false;
37945
37999
  let size = null;
37946
38000
  try {
37947
- const st = statSync17(full);
38001
+ const st = statSync18(full);
37948
38002
  isDir = st.isDirectory();
37949
38003
  if (!isDir) size = st.size;
37950
38004
  } catch {
@@ -38489,13 +38543,13 @@ var init_devAdmin = __esm({
38489
38543
  };
38490
38544
  handleDevAdminJs = async (_req, res) => {
38491
38545
  const { readFileSync: readFileSync30, existsSync: existsSync37 } = await import("node:fs");
38492
- const { dirname: dirname18, join: join40, resolve: resolve29 } = await import("node:path");
38546
+ const { dirname: dirname18, join: join41, resolve: resolve29 } = await import("node:path");
38493
38547
  const { fileURLToPath: fileURLToPath10 } = await import("node:url");
38494
38548
  const dir = dirname18(fileURLToPath10(import.meta.url));
38495
38549
  const candidates = [
38496
- join40(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
38550
+ join41(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
38497
38551
  // src/../public/js/
38498
- join40(dir, "..", "..", "public", "js", "tina4-dev-admin.min.js"),
38552
+ join41(dir, "..", "..", "public", "js", "tina4-dev-admin.min.js"),
38499
38553
  // deeper nesting
38500
38554
  resolve29(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin.min.js"),
38501
38555
  resolve29(process.cwd(), "public", "js", "tina4-dev-admin.min.js")
@@ -38528,7 +38582,7 @@ var init_devAdmin = __esm({
38528
38582
 
38529
38583
  // ../core/src/i18n.ts
38530
38584
  import { readFileSync as readFileSync21, readdirSync as readdirSync16, existsSync as existsSync24 } from "node:fs";
38531
- import { join as join29, resolve as resolve18 } from "node:path";
38585
+ import { join as join30, resolve as resolve18 } from "node:path";
38532
38586
  var I18n;
38533
38587
  var init_i18n = __esm({
38534
38588
  "../core/src/i18n.ts"() {
@@ -38621,7 +38675,7 @@ var init_i18n = __esm({
38621
38675
  if (this._translations.has(locale)) {
38622
38676
  return;
38623
38677
  }
38624
- const filePath = join29(this._localeDir, `${locale}.json`);
38678
+ const filePath = join30(this._localeDir, `${locale}.json`);
38625
38679
  if (existsSync24(filePath)) {
38626
38680
  try {
38627
38681
  const raw = readFileSync21(filePath, "utf-8");
@@ -38634,7 +38688,7 @@ var init_i18n = __esm({
38634
38688
  }
38635
38689
  }
38636
38690
  for (const ext of [".yml", ".yaml"]) {
38637
- const yamlPath = join29(this._localeDir, `${locale}${ext}`);
38691
+ const yamlPath = join30(this._localeDir, `${locale}${ext}`);
38638
38692
  if (existsSync24(yamlPath)) {
38639
38693
  try {
38640
38694
  const raw = readFileSync21(yamlPath, "utf-8");
@@ -39865,8 +39919,8 @@ var init_docsAutoDiscovery = __esm({
39865
39919
 
39866
39920
  // ../core/src/server.ts
39867
39921
  import { randomBytes as randomBytes8 } from "node:crypto";
39868
- import { resolve as resolve20, dirname as dirname14, join as join31, relative as relative10 } from "node:path";
39869
- import { existsSync as existsSync26, readdirSync as readdirSync17, readFileSync as readFileSync23, statSync as statSync18 } from "node:fs";
39922
+ import { resolve as resolve20, dirname as dirname14, join as join32, relative as relative10 } from "node:path";
39923
+ import { existsSync as existsSync26, readdirSync as readdirSync17, readFileSync as readFileSync23, statSync as statSync19 } from "node:fs";
39870
39924
  import { isatty } from "node:tty";
39871
39925
  import { fileURLToPath as fileURLToPath7 } from "node:url";
39872
39926
  import { execFileSync as execFileSync3, exec } from "node:child_process";
@@ -40054,11 +40108,11 @@ async function renderErrorPage(code, data, templatesDir) {
40054
40108
  }
40055
40109
  return instance;
40056
40110
  };
40057
- const userTemplatePath = join31(templatesDir, templateFile);
40111
+ const userTemplatePath = join32(templatesDir, templateFile);
40058
40112
  if (existsSync26(userTemplatePath)) {
40059
40113
  return getCachedFrond(templatesDir).render(templateFile, data);
40060
40114
  }
40061
- const builtinTemplatePath = join31(BUILTIN_ERROR_TEMPLATES_DIR, templateFile);
40115
+ const builtinTemplatePath = join32(BUILTIN_ERROR_TEMPLATES_DIR, templateFile);
40062
40116
  if (existsSync26(builtinTemplatePath)) {
40063
40117
  return getCachedFrond(BUILTIN_ERROR_TEMPLATES_DIR).render(templateFile, data);
40064
40118
  }
@@ -40078,8 +40132,8 @@ function walkGalleryFiles(dir) {
40078
40132
  const results = [];
40079
40133
  if (!existsSync26(dir)) return results;
40080
40134
  for (const f of readdirSync17(dir)) {
40081
- const full = join31(dir, f);
40082
- if (statSync18(full).isDirectory()) results.push(...walkGalleryFiles(full));
40135
+ const full = join32(dir, f);
40136
+ if (statSync19(full).isDirectory()) results.push(...walkGalleryFiles(full));
40083
40137
  else results.push(full);
40084
40138
  }
40085
40139
  return results;
@@ -40091,14 +40145,14 @@ function getGalleryDeployedState() {
40091
40145
  try {
40092
40146
  const entries = readdirSync17(galleryDir).sort();
40093
40147
  for (const entry of entries) {
40094
- const entryPath = join31(galleryDir, entry);
40095
- const metaFile = join31(entryPath, "meta.json");
40096
- if (statSync18(entryPath).isDirectory() && existsSync26(metaFile)) {
40097
- const srcDir = join31(entryPath, "src");
40148
+ const entryPath = join32(galleryDir, entry);
40149
+ const metaFile = join32(entryPath, "meta.json");
40150
+ if (statSync19(entryPath).isDirectory() && existsSync26(metaFile)) {
40151
+ const srcDir = join32(entryPath, "src");
40098
40152
  if (existsSync26(srcDir)) {
40099
40153
  const files = walkGalleryFiles(srcDir);
40100
40154
  const projectSrc = resolve20(process.cwd(), "src");
40101
- state[entry] = files.every((f) => existsSync26(join31(projectSrc, relative10(srcDir, f))));
40155
+ state[entry] = files.every((f) => existsSync26(join32(projectSrc, relative10(srcDir, f))));
40102
40156
  } else {
40103
40157
  state[entry] = false;
40104
40158
  }
@@ -42147,8 +42201,8 @@ var init_mqtt = __esm({
42147
42201
  });
42148
42202
 
42149
42203
  // ../core/src/service.ts
42150
- import { readdirSync as readdirSync18, statSync as statSync19, watchFile, unwatchFile } from "node:fs";
42151
- import { join as join32, extname as extname8 } from "node:path";
42204
+ import { readdirSync as readdirSync18, statSync as statSync20, watchFile, unwatchFile } from "node:fs";
42205
+ import { join as join33, extname as extname8 } from "node:path";
42152
42206
  import { pathToFileURL } from "node:url";
42153
42207
  function matchCronField(field, value) {
42154
42208
  if (field === "*") return true;
@@ -42322,8 +42376,8 @@ var init_service = __esm({
42322
42376
  for (const entry of entries) {
42323
42377
  const ext = extname8(entry);
42324
42378
  if (ext !== ".ts" && ext !== ".js") continue;
42325
- const fullPath = join32(dir, entry);
42326
- const stat = statSync19(fullPath);
42379
+ const fullPath = join33(dir, entry);
42380
+ const stat = statSync20(fullPath);
42327
42381
  if (!stat.isFile()) continue;
42328
42382
  try {
42329
42383
  const fileUrl = pathToFileURL(fullPath).href;
@@ -42449,7 +42503,7 @@ var init_service = __esm({
42449
42503
  for (const entry of entries) {
42450
42504
  const ext = extname8(entry);
42451
42505
  if (ext !== ".ts" && ext !== ".js") continue;
42452
- const fullPath = join32(dir, entry);
42506
+ const fullPath = join33(dir, entry);
42453
42507
  if (watchedFiles.has(fullPath)) continue;
42454
42508
  watchedFiles.add(fullPath);
42455
42509
  watchFile(fullPath, { interval: 1e3 }, async () => {
@@ -42709,23 +42763,23 @@ function guessContentType(filename) {
42709
42763
  }
42710
42764
  function buildMultipartBody(boundary, fieldName, filename, fileContent, contentType, extraFields) {
42711
42765
  const crlf = "\r\n";
42712
- const delimiter5 = `--${boundary}`;
42766
+ const delimiter4 = `--${boundary}`;
42713
42767
  const parts = [];
42714
42768
  if (extraFields) {
42715
42769
  for (const [key, value] of Object.entries(extraFields)) {
42716
- parts.push(Buffer.from(delimiter5 + crlf, "utf-8"));
42770
+ parts.push(Buffer.from(delimiter4 + crlf, "utf-8"));
42717
42771
  parts.push(Buffer.from(`Content-Disposition: form-data; name="${key}"` + crlf + crlf, "utf-8"));
42718
42772
  parts.push(Buffer.from(String(value) + crlf, "utf-8"));
42719
42773
  }
42720
42774
  }
42721
- parts.push(Buffer.from(delimiter5 + crlf, "utf-8"));
42775
+ parts.push(Buffer.from(delimiter4 + crlf, "utf-8"));
42722
42776
  parts.push(
42723
42777
  Buffer.from(`Content-Disposition: form-data; name="${fieldName}"; filename="${filename}"` + crlf, "utf-8")
42724
42778
  );
42725
42779
  parts.push(Buffer.from(`Content-Type: ${contentType}` + crlf + crlf, "utf-8"));
42726
42780
  parts.push(fileContent);
42727
42781
  parts.push(Buffer.from(crlf, "utf-8"));
42728
- parts.push(Buffer.from(delimiter5 + "--" + crlf, "utf-8"));
42782
+ parts.push(Buffer.from(delimiter4 + "--" + crlf, "utf-8"));
42729
42783
  return Buffer.concat(parts);
42730
42784
  }
42731
42785
  async function* parseLineStream(chunks) {
@@ -45203,7 +45257,7 @@ __export(ai_exports, {
45203
45257
  });
45204
45258
  import { existsSync as existsSync28, mkdirSync as mkdirSync22, writeFileSync as writeFileSync19, readFileSync as readFileSync26 } from "node:fs";
45205
45259
  import { homedir } from "node:os";
45206
- import { join as join33, resolve as resolve21, relative as relative11, dirname as dirname15 } from "node:path";
45260
+ import { join as join34, resolve as resolve21, relative as relative11, dirname as dirname15 } from "node:path";
45207
45261
  import { fileURLToPath as fileURLToPath8 } from "node:url";
45208
45262
  import { execSync as execSync2, execFileSync as execFileSync4 } from "node:child_process";
45209
45263
  import { createInterface } from "node:readline";
@@ -45274,8 +45328,8 @@ function downloadSkillsSync(jobs) {
45274
45328
  function installSkills(root = ".", targets) {
45275
45329
  const ref = skillsRef();
45276
45330
  const dests = targets ?? [
45277
- join33(resolve21(root), ".claude", "skills"),
45278
- join33(homedir(), ".claude", "skills")
45331
+ join34(resolve21(root), ".claude", "skills"),
45332
+ join34(homedir(), ".claude", "skills")
45279
45333
  ];
45280
45334
  const jobs = [];
45281
45335
  const index = /* @__PURE__ */ new Map();
@@ -45293,9 +45347,9 @@ function installSkills(root = ".", targets) {
45293
45347
  const base = `https://raw.githubusercontent.com/tina4stack/${spec.repo}/${ref}/.claude/skills/${skill}`;
45294
45348
  skillMdUrl[skill] = `${base}/SKILL.md`;
45295
45349
  for (const dest of dests) {
45296
- add(`${base}/SKILL.md`, join33(dest, skill, "SKILL.md"));
45350
+ add(`${base}/SKILL.md`, join34(dest, skill, "SKILL.md"));
45297
45351
  for (const r of spec.references) {
45298
- add(`${base}/references/${r}`, join33(dest, skill, "references", r));
45352
+ add(`${base}/references/${r}`, join34(dest, skill, "references", r));
45299
45353
  }
45300
45354
  }
45301
45355
  }
@@ -45307,7 +45361,7 @@ function installSkills(root = ".", targets) {
45307
45361
  return installed;
45308
45362
  }
45309
45363
  function isInstalled(root, tool) {
45310
- return existsSync28(join33(resolve21(root), tool.contextFile));
45364
+ return existsSync28(join34(resolve21(root), tool.contextFile));
45311
45365
  }
45312
45366
  function showMenu(root = ".") {
45313
45367
  const r = resolve21(root);
@@ -45451,9 +45505,9 @@ function writeOrMerge(contextPath, contextFile, frameworkGuide) {
45451
45505
  }
45452
45506
  function installForTool(root, tool, context) {
45453
45507
  const created = [];
45454
- const contextPath = join33(root, tool.contextFile);
45508
+ const contextPath = join34(root, tool.contextFile);
45455
45509
  if (tool.configDir) {
45456
- mkdirSync22(join33(root, tool.configDir), { recursive: true });
45510
+ mkdirSync22(join34(root, tool.configDir), { recursive: true });
45457
45511
  }
45458
45512
  const parentDir = dirname15(contextPath);
45459
45513
  mkdirSync22(parentDir, { recursive: true });
@@ -45491,7 +45545,7 @@ function installTina4Ai() {
45491
45545
  function installClaudeSkills(root) {
45492
45546
  const created = [];
45493
45547
  for (const skill of installSkills(root)) {
45494
- created.push(join33(".claude", "skills", skill));
45548
+ created.push(join34(".claude", "skills", skill));
45495
45549
  console.log(` ${GREEN2}\u2713${RESET2} Installed .claude/skills/${skill} (project + global)`);
45496
45550
  }
45497
45551
  return created;
@@ -45834,7 +45888,7 @@ function generateClaudeCodeContext() {
45834
45888
  try {
45835
45889
  const thisDir = dirname15(fileURLToPath8(import.meta.url));
45836
45890
  const repoRoot = resolve21(thisDir, "..", "..", "..");
45837
- const claudeMdPath = join33(repoRoot, "CLAUDE.md");
45891
+ const claudeMdPath = join34(repoRoot, "CLAUDE.md");
45838
45892
  if (existsSync28(claudeMdPath)) {
45839
45893
  return readFileSync26(claudeMdPath, "utf-8");
45840
45894
  }
@@ -49358,6 +49412,7 @@ async function serveProject(options) {
49358
49412
 
49359
49413
  // src/commands/migrate.ts
49360
49414
  init_dotenv();
49415
+ init_util();
49361
49416
  import { existsSync as existsSync30 } from "node:fs";
49362
49417
  import { resolve as resolve23 } from "node:path";
49363
49418
  async function runMigrations(migrationDir) {
@@ -49377,12 +49432,7 @@ async function runMigrations(migrationDir) {
49377
49432
  console.error(" Error: @tina4/orm is required to run migrations.");
49378
49433
  process.exit(1);
49379
49434
  }
49380
- try {
49381
- await initDatabase2();
49382
- } catch (err) {
49383
- console.error(` Error initialising database: ${err instanceof Error ? err.message : String(err)}`);
49384
- process.exit(1);
49385
- }
49435
+ await initDatabaseOrExit(initDatabase2);
49386
49436
  const result = await migrate2(void 0, { migrationsDir: dir });
49387
49437
  if (result.applied.length === 0 && result.failed.length === 0) {
49388
49438
  console.log(" Nothing to migrate \u2014 all migrations already applied.");
@@ -49421,6 +49471,7 @@ async function createMigration2(args = []) {
49421
49471
 
49422
49472
  // src/commands/migrateStatus.ts
49423
49473
  init_dotenv();
49474
+ init_util();
49424
49475
  import { resolve as resolve24 } from "node:path";
49425
49476
  async function migrateStatus(migrationDir) {
49426
49477
  loadEnv();
@@ -49437,12 +49488,7 @@ async function migrateStatus(migrationDir) {
49437
49488
  console.error(" Error: @tina4/orm is required to check migration status.");
49438
49489
  process.exit(1);
49439
49490
  }
49440
- try {
49441
- await initDatabase2();
49442
- } catch (err) {
49443
- console.error(` Error initialising database: ${err instanceof Error ? err.message : String(err)}`);
49444
- process.exit(1);
49445
- }
49491
+ await initDatabaseOrExit(initDatabase2);
49446
49492
  ensureMigrationTable2();
49447
49493
  const result = await statusFn(void 0, { migrationsDir: dir });
49448
49494
  console.log("");
@@ -49469,6 +49515,7 @@ async function migrateStatus(migrationDir) {
49469
49515
 
49470
49516
  // src/commands/migrateRollback.ts
49471
49517
  init_dotenv();
49518
+ init_util();
49472
49519
  import { resolve as resolve25 } from "node:path";
49473
49520
  async function migrateRollback(migrationDir) {
49474
49521
  loadEnv();
@@ -49487,12 +49534,7 @@ async function migrateRollback(migrationDir) {
49487
49534
  console.error(" Error: @tina4/orm is required to rollback migrations.");
49488
49535
  process.exit(1);
49489
49536
  }
49490
- try {
49491
- await initDatabase2();
49492
- } catch (err) {
49493
- console.error(` Error initialising database: ${err instanceof Error ? err.message : String(err)}`);
49494
- process.exit(1);
49495
- }
49537
+ await initDatabaseOrExit(initDatabase2);
49496
49538
  await ensureMigrationTable2();
49497
49539
  const lastBatch = await getLastBatchMigrations2();
49498
49540
  if (lastBatch.length === 0) {
@@ -49562,8 +49604,8 @@ async function listRoutes() {
49562
49604
  }
49563
49605
 
49564
49606
  // src/commands/test.ts
49565
- import { existsSync as existsSync32, readdirSync as readdirSync19, readFileSync as readFileSync27, statSync as statSync20 } from "node:fs";
49566
- import { resolve as resolve27, join as join34 } from "node:path";
49607
+ import { existsSync as existsSync32, readdirSync as readdirSync19, readFileSync as readFileSync27, statSync as statSync21 } from "node:fs";
49608
+ import { resolve as resolve27, join as join35 } from "node:path";
49567
49609
  import { pathToFileURL as pathToFileURL2 } from "node:url";
49568
49610
  import { execSync as execSync3 } from "node:child_process";
49569
49611
  function walkSource(dir) {
@@ -49575,10 +49617,10 @@ function walkSource(dir) {
49575
49617
  return out;
49576
49618
  }
49577
49619
  for (const name of entries) {
49578
- const full = join34(dir, name);
49620
+ const full = join35(dir, name);
49579
49621
  let st;
49580
49622
  try {
49581
- st = statSync20(full);
49623
+ st = statSync21(full);
49582
49624
  } catch {
49583
49625
  continue;
49584
49626
  }
@@ -49646,7 +49688,7 @@ async function runTests(testPath) {
49646
49688
  break;
49647
49689
  }
49648
49690
  try {
49649
- const files = readdirSync19(fullPath).filter((f) => f.endsWith(".ts")).map((f) => join34(fullPath, f));
49691
+ const files = readdirSync19(fullPath).filter((f) => f.endsWith(".ts")).map((f) => join35(fullPath, f));
49650
49692
  testFiles.push(...files);
49651
49693
  } catch {
49652
49694
  }
@@ -49675,9 +49717,10 @@ async function runTests(testPath) {
49675
49717
  }
49676
49718
 
49677
49719
  // src/commands/lint.ts
49678
- import { existsSync as existsSync33, readdirSync as readdirSync20, readFileSync as readFileSync28, statSync as statSync21, writeFileSync as writeFileSync20 } from "node:fs";
49720
+ init_util();
49721
+ import { existsSync as existsSync33, readdirSync as readdirSync20, readFileSync as readFileSync28, statSync as statSync22, writeFileSync as writeFileSync20 } from "node:fs";
49679
49722
  import { createRequire as createRequire9 } from "node:module";
49680
- import { delimiter as delimiter2, dirname as dirname16, join as join35, relative as relative12 } from "node:path";
49723
+ import { dirname as dirname16, join as join36, relative as relative12 } from "node:path";
49681
49724
  import { spawnSync as spawnSync3 } from "node:child_process";
49682
49725
  var LINT_EXTENSIONS = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
49683
49726
  var JS_EXTENSIONS = [".js", ".mjs", ".cjs"];
@@ -49695,10 +49738,10 @@ function walkSource2(dir, out) {
49695
49738
  }
49696
49739
  for (const name of entries) {
49697
49740
  if (SKIP_DIRS3.has(name)) continue;
49698
- const full = join35(dir, name);
49741
+ const full = join36(dir, name);
49699
49742
  let st;
49700
49743
  try {
49701
- st = statSync21(full);
49744
+ st = statSync22(full);
49702
49745
  } catch {
49703
49746
  continue;
49704
49747
  }
@@ -49711,15 +49754,15 @@ function walkSource2(dir, out) {
49711
49754
  }
49712
49755
  function collectAppFiles(cwd) {
49713
49756
  const files = [];
49714
- const srcDir = join35(cwd, "src");
49757
+ const srcDir = join36(cwd, "src");
49715
49758
  try {
49716
- if (statSync21(srcDir).isDirectory()) walkSource2(srcDir, files);
49759
+ if (statSync22(srcDir).isDirectory()) walkSource2(srcDir, files);
49717
49760
  } catch {
49718
49761
  }
49719
49762
  for (const entry of ["app.ts", "app.mts", "app.js", "app.mjs"]) {
49720
- const p = join35(cwd, entry);
49763
+ const p = join36(cwd, entry);
49721
49764
  try {
49722
- if (statSync21(p).isFile()) files.push(p);
49765
+ if (statSync22(p).isFile()) files.push(p);
49723
49766
  } catch {
49724
49767
  }
49725
49768
  }
@@ -49740,15 +49783,15 @@ export default [js.configs.recommended, ...tseslint.configs.recommended];
49740
49783
  `;
49741
49784
  function findEslintConfig(cwd) {
49742
49785
  for (const name of ESLINT_FLAT_CONFIGS) {
49743
- const p = join35(cwd, name);
49786
+ const p = join36(cwd, name);
49744
49787
  if (existsSync33(p)) return p;
49745
49788
  }
49746
49789
  return null;
49747
49790
  }
49748
49791
  function resolvePackageBin(cwd, pkg, binRelative) {
49749
- const direct = join35(cwd, "node_modules", pkg, binRelative);
49792
+ const direct = join36(cwd, "node_modules", pkg, binRelative);
49750
49793
  if (existsSync33(direct)) return direct;
49751
- const require3 = createRequire9(join35(cwd, "package.json"));
49794
+ const require3 = createRequire9(join36(cwd, "package.json"));
49752
49795
  let entry;
49753
49796
  try {
49754
49797
  entry = require3.resolve(pkg);
@@ -49757,11 +49800,11 @@ function resolvePackageBin(cwd, pkg, binRelative) {
49757
49800
  }
49758
49801
  let dir = dirname16(entry);
49759
49802
  for (let i = 0; i < 8; i++) {
49760
- const pkgJson = join35(dir, "package.json");
49803
+ const pkgJson = join36(dir, "package.json");
49761
49804
  if (existsSync33(pkgJson)) {
49762
49805
  try {
49763
49806
  if (JSON.parse(readFileSync28(pkgJson, "utf-8")).name === pkg) {
49764
- const bin = join35(dir, binRelative);
49807
+ const bin = join36(dir, binRelative);
49765
49808
  return existsSync33(bin) ? bin : null;
49766
49809
  }
49767
49810
  } catch {
@@ -49777,22 +49820,12 @@ function resolveEslintBin(cwd) {
49777
49820
  return resolvePackageBin(cwd, "eslint", "bin/eslint.js");
49778
49821
  }
49779
49822
  function hasPackage(cwd, pkg) {
49780
- return existsSync33(join35(cwd, "node_modules", ...pkg.split("/"), "package.json"));
49823
+ return existsSync33(join36(cwd, "node_modules", ...pkg.split("/"), "package.json"));
49781
49824
  }
49782
49825
  function resolveNpm() {
49783
49826
  const windows = process.platform === "win32";
49784
49827
  const names = windows ? ["npm.cmd", "npm.exe", "npm"] : ["npm"];
49785
- for (const dir of (process.env.PATH ?? "").split(delimiter2)) {
49786
- if (!dir) continue;
49787
- for (const name of names) {
49788
- const candidate = join35(dir, name);
49789
- try {
49790
- if (statSync21(candidate).isFile()) return candidate;
49791
- } catch {
49792
- }
49793
- }
49794
- }
49795
- return null;
49828
+ return findOnPath(names);
49796
49829
  }
49797
49830
  function bootstrapEslint(cwd, noInstall) {
49798
49831
  let bin = resolveEslintBin(cwd);
@@ -49810,7 +49843,7 @@ function bootstrapEslint(cwd, noInstall) {
49810
49843
  else console.log(" \xB7 could not install eslint \u2014 using the zero-dependency baseline.");
49811
49844
  }
49812
49845
  if (bin && !config && hasPackage(cwd, "@eslint/js") && hasPackage(cwd, "typescript-eslint")) {
49813
- const scaffold = join35(cwd, ESLINT_SCAFFOLD_FILE);
49846
+ const scaffold = join36(cwd, ESLINT_SCAFFOLD_FILE);
49814
49847
  try {
49815
49848
  writeFileSync20(scaffold, ESLINT_SCAFFOLD, "utf-8");
49816
49849
  config = scaffold;
@@ -49879,7 +49912,7 @@ function runLint(args) {
49879
49912
  if (fix) {
49880
49913
  console.log(" \xB7 --fix needs eslint \u2014 the baseline check has no autofix.");
49881
49914
  }
49882
- if (existsSync33(join35(cwd, "tsconfig.json")) && runTypescript(cwd, files)) return;
49915
+ if (existsSync33(join36(cwd, "tsconfig.json")) && runTypescript(cwd, files)) return;
49883
49916
  runJavaScript(cwd, files);
49884
49917
  }
49885
49918
 
@@ -49888,7 +49921,7 @@ init_generate();
49888
49921
 
49889
49922
  // src/commands/seed.ts
49890
49923
  import { existsSync as existsSync34, readdirSync as readdirSync21 } from "node:fs";
49891
- import { resolve as resolve28, join as join36 } from "node:path";
49924
+ import { resolve as resolve28, join as join37 } from "node:path";
49892
49925
  import { execSync as execSync4 } from "node:child_process";
49893
49926
  async function runSeeds(seedPath) {
49894
49927
  const cwd = process.cwd();
@@ -49915,7 +49948,7 @@ async function runSeeds(seedPath) {
49915
49948
  }
49916
49949
  let seedFiles;
49917
49950
  try {
49918
- seedFiles = readdirSync21(seedDir).filter((f) => f.endsWith(".ts")).sort().map((f) => join36(seedDir, f));
49951
+ seedFiles = readdirSync21(seedDir).filter((f) => f.endsWith(".ts")).sort().map((f) => join37(seedDir, f));
49919
49952
  } catch {
49920
49953
  console.log(" Could not read src/seeds/ directory.");
49921
49954
  return;
@@ -49946,39 +49979,16 @@ async function runSeeds(seedPath) {
49946
49979
  // src/commands/queue.ts
49947
49980
  init_dotenv();
49948
49981
  init_queue();
49949
- import { readdirSync as readdirSync22, statSync as statSync22 } from "node:fs";
49950
- import { extname as extname9, join as join37 } from "node:path";
49982
+ init_util();
49983
+ import { readdirSync as readdirSync22, statSync as statSync23 } from "node:fs";
49984
+ import { extname as extname9, join as join38 } from "node:path";
49951
49985
  import { pathToFileURL as pathToFileURL3 } from "node:url";
49952
49986
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["once", "json"]);
49953
- function parseFlags(args) {
49954
- const flags = {};
49955
- const positional = [];
49956
- let i = 0;
49957
- while (i < args.length) {
49958
- const arg = args[i];
49959
- if (arg.startsWith("--")) {
49960
- const key = arg.slice(2);
49961
- if (BOOLEAN_FLAGS.has(key)) {
49962
- flags[key] = true;
49963
- i += 1;
49964
- } else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
49965
- flags[key] = args[i + 1];
49966
- i += 2;
49967
- } else {
49968
- flags[key] = true;
49969
- i += 1;
49970
- }
49971
- } else {
49972
- positional.push(arg);
49973
- i += 1;
49974
- }
49975
- }
49976
- return { flags, positional };
49977
- }
49987
+ var parseFlags2 = (args) => parseFlags(args, BOOLEAN_FLAGS);
49978
49988
  async function resolveQueueHandler(servicesDir, topic) {
49979
49989
  let entries;
49980
49990
  try {
49981
- if (!statSync22(servicesDir).isDirectory()) return null;
49991
+ if (!statSync23(servicesDir).isDirectory()) return null;
49982
49992
  entries = readdirSync22(servicesDir);
49983
49993
  } catch {
49984
49994
  return null;
@@ -49987,9 +49997,9 @@ async function resolveQueueHandler(servicesDir, topic) {
49987
49997
  if (entry.startsWith("_")) continue;
49988
49998
  const ext = extname9(entry);
49989
49999
  if (ext !== ".ts" && ext !== ".js") continue;
49990
- const fullPath = join37(servicesDir, entry);
50000
+ const fullPath = join38(servicesDir, entry);
49991
50001
  try {
49992
- if (!statSync22(fullPath).isFile()) continue;
50002
+ if (!statSync23(fullPath).isFile()) continue;
49993
50003
  const mod = await import(pathToFileURL3(fullPath).href);
49994
50004
  const config = mod.default ?? mod;
49995
50005
  if (config && typeof config === "object" && config.topic === topic && typeof config.handle === "function") {
@@ -50006,7 +50016,7 @@ function firstJob(yielded) {
50006
50016
  }
50007
50017
  async function queueWork(args) {
50008
50018
  loadEnv();
50009
- const { flags, positional } = parseFlags(args);
50019
+ const { flags, positional } = parseFlags2(args);
50010
50020
  const topic = positional[0] ?? "default";
50011
50021
  const once2 = Boolean(flags.once);
50012
50022
  let pollSeconds;
@@ -50058,7 +50068,7 @@ async function queueWork(args) {
50058
50068
  }
50059
50069
  async function queueStats(args) {
50060
50070
  loadEnv();
50061
- const { flags, positional } = parseFlags(args);
50071
+ const { flags, positional } = parseFlags2(args);
50062
50072
  const topic = positional[0] ?? "default";
50063
50073
  const queue = new Queue({ topic });
50064
50074
  const stats = {
@@ -50089,7 +50099,7 @@ async function queueStats(args) {
50089
50099
  }
50090
50100
  async function queueRetry(args) {
50091
50101
  loadEnv();
50092
- const { positional } = parseFlags(args);
50102
+ const { positional } = parseFlags2(args);
50093
50103
  const topic = positional[0] ?? "default";
50094
50104
  const queue = new Queue({ topic });
50095
50105
  const dead = queue.deadLetters(0);
@@ -50105,7 +50115,7 @@ async function queueRetry(args) {
50105
50115
  }
50106
50116
  async function queueClear(args) {
50107
50117
  loadEnv();
50108
- const { positional } = parseFlags(args);
50118
+ const { positional } = parseFlags2(args);
50109
50119
  const status2 = positional[0] ?? "completed";
50110
50120
  const topic = positional[1] ?? "default";
50111
50121
  const queue = new Queue({ topic });
@@ -50139,28 +50149,12 @@ async function queueCommand(args = []) {
50139
50149
  }
50140
50150
 
50141
50151
  // src/commands/build.ts
50142
- import { accessSync as accessSync2, constants as constants4, existsSync as existsSync35, statSync as statSync23 } from "node:fs";
50143
- import { basename as basename9, delimiter as delimiter3, join as join38 } from "node:path";
50152
+ init_util();
50153
+ import { accessSync as accessSync2, constants as constants4, existsSync as existsSync35, statSync as statSync24 } from "node:fs";
50154
+ import { basename as basename9, delimiter as delimiter3, join as join39 } from "node:path";
50144
50155
  import { spawnSync as spawnSync4 } from "node:child_process";
50145
- function parseFlags2(args) {
50146
- const flags = {};
50147
- let i = 0;
50148
- while (i < args.length) {
50149
- const arg = args[i];
50150
- if (arg.startsWith("--")) {
50151
- const key = arg.slice(2);
50152
- if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
50153
- flags[key] = args[i + 1];
50154
- i += 2;
50155
- } else {
50156
- flags[key] = true;
50157
- i += 1;
50158
- }
50159
- } else {
50160
- i += 1;
50161
- }
50162
- }
50163
- return flags;
50156
+ function parseFlags3(args) {
50157
+ return parseFlags(args).flags;
50164
50158
  }
50165
50159
  function whichDocker() {
50166
50160
  const pathValue = process.env.PATH || process.env.Path || "";
@@ -50169,10 +50163,10 @@ function whichDocker() {
50169
50163
  for (const dir of pathValue.split(delimiter3)) {
50170
50164
  if (!dir) continue;
50171
50165
  for (const ext of exts) {
50172
- const candidate = join38(dir, `docker${ext}`);
50166
+ const candidate = join39(dir, `docker${ext}`);
50173
50167
  try {
50174
50168
  accessSync2(candidate, constants4.X_OK);
50175
- if (statSync23(candidate).isFile()) return candidate;
50169
+ if (statSync24(candidate).isFile()) return candidate;
50176
50170
  } catch {
50177
50171
  }
50178
50172
  }
@@ -50180,14 +50174,14 @@ function whichDocker() {
50180
50174
  return null;
50181
50175
  }
50182
50176
  function buildImage(args) {
50183
- const flags = parseFlags2(args);
50177
+ const flags = parseFlags3(args);
50184
50178
  let tag = typeof flags.tag === "string" ? flags.tag : "";
50185
50179
  if (!tag) {
50186
50180
  const dirName = basename9(process.cwd()).toLowerCase();
50187
50181
  tag = `${dirName || "tina4app"}:latest`;
50188
50182
  }
50189
50183
  const dockerfile = typeof flags.file === "string" && flags.file ? flags.file : "Dockerfile";
50190
- if (!existsSync35(dockerfile) || !statSync23(dockerfile).isFile()) {
50184
+ if (!existsSync35(dockerfile) || !statSync24(dockerfile).isFile()) {
50191
50185
  console.log(` \u2717 No ${dockerfile} found.`);
50192
50186
  console.log(" A Tina4 app deploys as a container. Scaffold a Dockerfile first:");
50193
50187
  console.log(" tina4 deploy docker (or: tina4nodejs init)");
@@ -50214,14 +50208,15 @@ function buildImage(args) {
50214
50208
  }
50215
50209
 
50216
50210
  // src/bin.ts
50211
+ init_util();
50217
50212
  import { spawnSync as spawnSync5 } from "node:child_process";
50218
- import { existsSync as existsSync36, readFileSync as readFileSync29, statSync as statSync24 } from "node:fs";
50219
- import { delimiter as delimiter4, dirname as dirname17, join as join39 } from "node:path";
50213
+ import { existsSync as existsSync36, readFileSync as readFileSync29 } from "node:fs";
50214
+ import { dirname as dirname17, join as join40 } from "node:path";
50220
50215
  import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL4 } from "node:url";
50221
50216
  function readCliVersion() {
50222
50217
  let dir = dirname17(fileURLToPath9(import.meta.url));
50223
50218
  for (let i = 0; i < 6; i++) {
50224
- const pkgPath = join39(dir, "package.json");
50219
+ const pkgPath = join40(dir, "package.json");
50225
50220
  if (existsSync36(pkgPath)) {
50226
50221
  try {
50227
50222
  const pkg = JSON.parse(readFileSync29(pkgPath, "utf-8"));
@@ -50526,17 +50521,7 @@ var CLIENT_INSTALL_HINT = " Install it: curl -fsSL https://tina4.com/install.s
50526
50521
  function findClient() {
50527
50522
  const windows = process.platform === "win32";
50528
50523
  const names = windows ? [`${CLIENT_BINARY}.exe`, `${CLIENT_BINARY}.cmd`, `${CLIENT_BINARY}.bat`] : [CLIENT_BINARY];
50529
- for (const dir of (process.env.PATH ?? "").split(delimiter4)) {
50530
- if (!dir) continue;
50531
- for (const name of names) {
50532
- const candidate = join39(dir, name);
50533
- try {
50534
- if (statSync24(candidate).isFile()) return candidate;
50535
- } catch {
50536
- }
50537
- }
50538
- }
50539
- return null;
50524
+ return findOnPath(names);
50540
50525
  }
50541
50526
  function delegateToClient(command, args) {
50542
50527
  if (process.env[DELEGATION_GUARD_ENV] === command) {