tina4-nodejs 3.13.99 → 3.13.101

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.
@@ -2620,7 +2620,10 @@ var init_trustedProxy = __esm({
2620
2620
  var engine_exports = {};
2621
2621
  __export(engine_exports, {
2622
2622
  Frond: () => Frond,
2623
+ MEMO_CACHE_MAX: () => MEMO_CACHE_MAX,
2623
2624
  TEMPLATE_CACHE_MAX: () => TEMPLATE_CACHE_MAX,
2625
+ filterChainCache: () => filterChainCache,
2626
+ pathParseCache: () => pathParseCache,
2624
2627
  setFormTokenSessionId: () => setFormTokenSessionId
2625
2628
  });
2626
2629
  import { createHash as createHash2, createHmac as createHmac2, randomBytes as randomBytes2 } from "node:crypto";
@@ -2724,6 +2727,12 @@ function capCache(cache, maxEntries) {
2724
2727
  if (--drop <= 0) break;
2725
2728
  }
2726
2729
  }
2730
+ function sweepExpiredCache(cache) {
2731
+ const now = Date.now();
2732
+ for (const [key, [, expiresAt]] of cache) {
2733
+ if (expiresAt <= now) cache.delete(key);
2734
+ }
2735
+ }
2727
2736
  function tokenize(source) {
2728
2737
  const rawBlocks = [];
2729
2738
  source = source.replace(RAW_BLOCK_RE, (_match, content) => {
@@ -2787,6 +2796,16 @@ function stripTag(raw) {
2787
2796
  }
2788
2797
  return [inner.trim(), stripBefore, stripAfter];
2789
2798
  }
2799
+ function extendsTarget(source) {
2800
+ const matches = source.match(EXTENDS_RE_GLOBAL);
2801
+ if (matches && matches.length > 1) {
2802
+ throw new Error(
2803
+ `Frond: template has ${matches.length} "{% extends %}" tags -- a template can extend only one parent`
2804
+ );
2805
+ }
2806
+ const match = source.match(EXTENDS_RE);
2807
+ return match ? match[1] : "";
2808
+ }
2790
2809
  function resolveVar(expr, context) {
2791
2810
  expr = expr.trim();
2792
2811
  if (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'")) {
@@ -2867,6 +2886,7 @@ function resolveVar(expr, context) {
2867
2886
  fromBracket.push(false);
2868
2887
  }
2869
2888
  }
2889
+ capCache(pathParseCache, MEMO_CACHE_MAX);
2870
2890
  pathParseCache.set(expr, [parts, fromBracket]);
2871
2891
  }
2872
2892
  let value = context;
@@ -3454,6 +3474,7 @@ function parseFilterChain(expr) {
3454
3474
  }
3455
3475
  }
3456
3476
  const result = [variable, filters];
3477
+ capCache(filterChainCache, MEMO_CACHE_MAX);
3457
3478
  filterChainCache.set(expr, result);
3458
3479
  return result;
3459
3480
  }
@@ -3633,7 +3654,7 @@ function _generateFormToken(descriptor = "") {
3633
3654
  function _generateFormTokenValue(descriptor = "") {
3634
3655
  return new SafeString(_buildFormTokenJwt(descriptor));
3635
3656
  }
3636
- var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, VarRef, BUILTIN_FILTERS, _formTokenSessionId, Frond;
3657
+ var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, VarRef, BUILTIN_FILTERS, _formTokenSessionId, Frond;
3637
3658
  var init_engine = __esm({
3638
3659
  "../frond/src/engine.ts"() {
3639
3660
  "use strict";
@@ -3727,9 +3748,12 @@ var init_engine = __esm({
3727
3748
  LIVE_RE = /^live\s+["']([^"']+)["']([\s\S]*)$/;
3728
3749
  LIVE_WS_RE = /ws\s+["']([^"']+)["']/;
3729
3750
  LIVE_SRC_RE = /src\s+["']([^"']+)["']/;
3751
+ EXTENDS_RE = /\{%[-\s]*extends\s+["'](.+?)["']\s*[-]?%\}/;
3752
+ EXTENDS_RE_GLOBAL = /\{%[-\s]*extends\s+["'](.+?)["']\s*[-]?%\}/g;
3730
3753
  filterChainCache = /* @__PURE__ */ new Map();
3731
3754
  pathParseCache = /* @__PURE__ */ new Map();
3732
3755
  TEMPLATE_CACHE_MAX = 256;
3756
+ MEMO_CACHE_MAX = 1024;
3733
3757
  TOKEN_RE = /(\{%-?\s*[\s\S]*?\s*-?%\})|(\{\{-?\s*[\s\S]*?\s*-?\}\})|(\{#[\s\S]*?#\})/g;
3734
3758
  RAW_BLOCK_RE = /\{%-?\s*raw\s*-?%\}([\s\S]*?)\{%-?\s*endraw\s*-?%\}/g;
3735
3759
  VarRef = class {
@@ -4082,29 +4106,22 @@ var init_engine = __esm({
4082
4106
  return this;
4083
4107
  }
4084
4108
  /**
4085
- * Register a custom filter. The filter is persisted at class level
4086
- * so new instances created by hot-reload inherit it automatically;
4087
- * the live instance's local filter map also receives the addition
4088
- * immediately. Mirrors Python's _ClassOrInstanceMethod dual-call.
4109
+ * Register a custom filter on this instance only. Use the static method
4110
+ * for process-global registration. tina4: ADR-0052.
4089
4111
  */
4090
4112
  addFilter(name, fn) {
4091
- _Frond.classFilters.set(name, fn);
4092
4113
  this.filters[name] = fn;
4093
4114
  }
4094
4115
  /**
4095
- * Register a global variable available in all templates. Persisted
4096
- * at class level — see ``addFilter`` for the dual-call semantics.
4116
+ * Register a global variable on this instance only.
4097
4117
  */
4098
4118
  addGlobal(name, value) {
4099
- _Frond.classGlobals.set(name, value);
4100
4119
  this.globals[name] = value;
4101
4120
  }
4102
4121
  /**
4103
- * Register a custom test. Persisted at class level — see
4104
- * ``addFilter`` for the dual-call semantics.
4122
+ * Register a custom test on this instance only.
4105
4123
  */
4106
4124
  addTest(name, fn) {
4107
- _Frond.classTests.set(name, fn);
4108
4125
  this.tests[name] = fn;
4109
4126
  }
4110
4127
  /**
@@ -4219,9 +4236,8 @@ var init_engine = __esm({
4219
4236
  if (Object.keys(this.tests).length > 0) {
4220
4237
  context.__frond_tests__ = this.tests;
4221
4238
  }
4222
- const extendsMatch = source.match(/\{%[-\s]*extends\s+["'](.+?)["']\s*[-]?%\}/);
4223
- if (extendsMatch) {
4224
- const parentName = extendsMatch[1];
4239
+ const parentName = extendsTarget(source);
4240
+ if (parentName) {
4225
4241
  const parentSource = this.load(parentName);
4226
4242
  const childBlocks = this.extractBlocks(source);
4227
4243
  return this.renderWithBlocks(parentSource, context, childBlocks);
@@ -4232,9 +4248,8 @@ var init_engine = __esm({
4232
4248
  if (Object.keys(this.tests).length > 0) {
4233
4249
  context.__frond_tests__ = this.tests;
4234
4250
  }
4235
- const extendsMatch = source.match(/\{%[-\s]*extends\s+["'](.+?)["']\s*[-]?%\}/);
4236
- if (extendsMatch) {
4237
- const parentName = extendsMatch[1];
4251
+ const parentName = extendsTarget(source);
4252
+ if (parentName) {
4238
4253
  const parentSource = this.load(parentName);
4239
4254
  const childBlocks = this.extractBlocks(source);
4240
4255
  return this.renderWithBlocks(parentSource, context, childBlocks);
@@ -4279,10 +4294,93 @@ var init_engine = __esm({
4279
4294
  }
4280
4295
  return blocks;
4281
4296
  }
4297
+ /**
4298
+ * Depth-aware block substitution against `source` (typically the
4299
+ * fully-resolved root template).
4300
+ *
4301
+ * A single regex `.replace()` pass (the flat `pattern` this replaces in
4302
+ * renderWithBlocks) pairs an OUTER block's open tag with the FIRST
4303
+ * `{% endblock %}` found -- which, when the outer block wraps a NESTED
4304
+ * `{% block %}`, is the nested block's own close tag, not the outer's.
4305
+ * That silently truncates the outer block's captured content and drops
4306
+ * everything after the inner endblock (the root-nested-block
4307
+ * content-loss bug). This scans with an open/close depth counter
4308
+ * instead (mirroring extractBlocks), so an outer block always captures
4309
+ * its FULL body, nested child blocks included.
4310
+ *
4311
+ * The content chosen for each block -- the child override in `blocks`
4312
+ * if present, else the block's own default body -- is then recursively
4313
+ * substituted against the SAME `blocks` map before being tokenized and
4314
+ * rendered, so a block nested inside another block resolves correctly
4315
+ * regardless of which template in the inheritance chain declared the
4316
+ * nesting (the root, an intermediate, however many levels deep).
4317
+ *
4318
+ * `{{ parent() }}` / `{{ super() }}` inside a block still render that
4319
+ * block's OWN default content at this level (lazy, on first call).
4320
+ */
4321
+ substituteBlocks(source, blocks, context) {
4322
+ const blockOpen = /\{%[-\s]*block\s+(\w+)\s*[-]?%\}/g;
4323
+ const blockClose = /\{%[-\s]*endblock\s*[-]?%\}/g;
4324
+ const engine = this;
4325
+ const pieces = [];
4326
+ let pos = 0;
4327
+ while (pos < source.length) {
4328
+ blockOpen.lastIndex = pos;
4329
+ const mOpen = blockOpen.exec(source);
4330
+ if (!mOpen) {
4331
+ pieces.push(source.slice(pos));
4332
+ break;
4333
+ }
4334
+ pieces.push(source.slice(pos, mOpen.index));
4335
+ const name = mOpen[1];
4336
+ const contentStart = mOpen.index + mOpen[0].length;
4337
+ let depth = 1;
4338
+ let scan = contentStart;
4339
+ let closeMatch = null;
4340
+ while (depth > 0 && scan < source.length) {
4341
+ blockOpen.lastIndex = scan;
4342
+ blockClose.lastIndex = scan;
4343
+ const nextOpen = blockOpen.exec(source);
4344
+ const nextClose = blockClose.exec(source);
4345
+ if (!nextClose) break;
4346
+ if (nextOpen && nextOpen.index < nextClose.index) {
4347
+ depth++;
4348
+ scan = nextOpen.index + nextOpen[0].length;
4349
+ } else {
4350
+ depth--;
4351
+ if (depth === 0) {
4352
+ closeMatch = nextClose;
4353
+ } else {
4354
+ scan = nextClose.index + nextClose[0].length;
4355
+ }
4356
+ }
4357
+ }
4358
+ if (!closeMatch) {
4359
+ pieces.push(source.slice(mOpen.index));
4360
+ pos = source.length;
4361
+ break;
4362
+ }
4363
+ const parentContent = source.slice(contentStart, closeMatch.index);
4364
+ const blockSource = blocks[name] ?? parentContent;
4365
+ const resolvedSource = engine.substituteBlocks(blockSource, blocks, context);
4366
+ let renderedParent = null;
4367
+ const getParent = () => {
4368
+ if (renderedParent === null) {
4369
+ renderedParent = new SafeString(
4370
+ engine.renderTokens(tokenize(parentContent), context)
4371
+ );
4372
+ }
4373
+ return renderedParent;
4374
+ };
4375
+ const blockCtx = { ...context, parent: getParent, super: getParent };
4376
+ pieces.push(engine.renderTokens(tokenize(resolvedSource), blockCtx));
4377
+ pos = closeMatch.index + closeMatch[0].length;
4378
+ }
4379
+ return pieces.join("");
4380
+ }
4282
4381
  renderWithBlocks(parentSource, context, childBlocks) {
4283
- const extendsMatch = parentSource.trimStart().match(/\{%[-\s]*extends\s+["'](.+?)["']\s*[-]?%\}/);
4284
- if (extendsMatch) {
4285
- const grandparentName = extendsMatch[1];
4382
+ const grandparentName = extendsTarget(parentSource);
4383
+ if (grandparentName) {
4286
4384
  const grandparentSource = this.load(grandparentName);
4287
4385
  const parentBlocks = this.extractBlocks(parentSource);
4288
4386
  const mergedBlocks = { ...parentBlocks, ...childBlocks };
@@ -4302,22 +4400,7 @@ var init_engine = __esm({
4302
4400
  }
4303
4401
  return this.renderWithBlocks(grandparentSource, context, mergedBlocks);
4304
4402
  }
4305
- const pattern = /\{%[-\s]*block\s+(\w+)\s*[-]?%\}([\s\S]*?)\{%[-\s]*endblock\s*[-]?%\}/g;
4306
- const engine = this;
4307
- const result = parentSource.replace(pattern, (_match, name, parentContent) => {
4308
- const blockSource = childBlocks[name] ?? parentContent;
4309
- let renderedParent = null;
4310
- const getParent = () => {
4311
- if (renderedParent === null) {
4312
- renderedParent = new SafeString(
4313
- engine.renderTokens(tokenize(parentContent), context)
4314
- );
4315
- }
4316
- return renderedParent;
4317
- };
4318
- const blockCtx = { ...context, parent: getParent, super: getParent };
4319
- return this.renderTokens(tokenize(blockSource), blockCtx);
4320
- });
4403
+ const result = this.substituteBlocks(parentSource, childBlocks, context);
4321
4404
  return this.renderTokens(tokenize(result), context);
4322
4405
  }
4323
4406
  renderTokens(tokens, context) {
@@ -5140,6 +5223,7 @@ var init_engine = __esm({
5140
5223
  const m = content.match(/^cache\s+["'](.+?)["']\s*(\d+)?/);
5141
5224
  const cacheKey = m ? m[1] : "default";
5142
5225
  const ttl = m && m[2] ? parseInt(m[2], 10) : 60;
5226
+ sweepExpiredCache(this.fragmentCache);
5143
5227
  const cached = this.fragmentCache.get(cacheKey);
5144
5228
  if (cached) {
5145
5229
  const [htmlContent, expiresAt] = cached;
@@ -5187,6 +5271,7 @@ var init_engine = __esm({
5187
5271
  i++;
5188
5272
  }
5189
5273
  const rendered = this.renderTokens([...bodyTokens], context);
5274
+ capCache(this.fragmentCache, TEMPLATE_CACHE_MAX);
5190
5275
  this.fragmentCache.set(cacheKey, [rendered, Date.now() + ttl * 1e3]);
5191
5276
  return [rendered, i];
5192
5277
  }
@@ -8734,14 +8819,14 @@ async function discoverRoutes(routesDir) {
8734
8819
  const currentMtime = statSync5(filePath).mtimeMs;
8735
8820
  if (_seenFiles.has(filePath) && _seenMtimes.get(filePath) === currentMtime) continue;
8736
8821
  const method = name.toUpperCase();
8737
- const relativePath3 = relative(routesDir, filePath);
8738
- const pattern = filePathToPattern(relativePath3);
8822
+ const relativePath2 = relative(routesDir, filePath);
8823
+ const pattern = filePathToPattern(relativePath2);
8739
8824
  try {
8740
8825
  const moduleUrl = `file://${filePath}?t=${currentMtime}`;
8741
8826
  const mod = await import(moduleUrl);
8742
8827
  const handler = mod.default ?? mod.handler;
8743
8828
  if (typeof handler !== "function") {
8744
- console.warn(` Warning: ${relativePath3} does not export a handler function, skipping`);
8829
+ console.warn(` Warning: ${relativePath2} does not export a handler function, skipping`);
8745
8830
  continue;
8746
8831
  }
8747
8832
  const meta = mod.meta;
@@ -8753,7 +8838,7 @@ async function discoverRoutes(routesDir) {
8753
8838
  _seenMtimes.set(filePath, currentMtime);
8754
8839
  registeredFromThisScan++;
8755
8840
  } catch (err) {
8756
- console.error(` Error loading route ${relativePath3}:`, err);
8841
+ console.error(` Error loading route ${relativePath2}:`, err);
8757
8842
  recordBrokenImport(filePath, err);
8758
8843
  }
8759
8844
  }
@@ -8787,8 +8872,8 @@ function recordBrokenImport(filePath, error) {
8787
8872
  } catch {
8788
8873
  }
8789
8874
  }
8790
- function filePathToPattern(relativePath3) {
8791
- const parts = relativePath3.replace(/\\/g, "/").split("/").slice(0, -1);
8875
+ function filePathToPattern(relativePath2) {
8876
+ const parts = relativePath2.replace(/\\/g, "/").split("/").slice(0, -1);
8792
8877
  const urlParts = parts.map((part) => {
8793
8878
  if (part.startsWith("[...") && part.endsWith("]")) {
8794
8879
  const name = part.slice(4, -1);
@@ -12025,489 +12110,127 @@ import * as fs3 from "node:fs";
12025
12110
  import * as path2 from "node:path";
12026
12111
  import { spawnSync } from "node:child_process";
12027
12112
  import { fileURLToPath } from "node:url";
12028
- function walkFiles(dir, extensions, exclude = ["node_modules", ".git", "dist", "build"]) {
12029
- const results = [];
12030
- if (!fs3.existsSync(dir)) return results;
12031
- const entries = fs3.readdirSync(dir, { withFileTypes: true });
12032
- for (const entry of entries) {
12033
- const fullPath = path2.join(dir, entry.name);
12034
- if (entry.isDirectory()) {
12035
- if (!exclude.includes(entry.name)) {
12036
- results.push(...walkFiles(fullPath, extensions, exclude));
12037
- }
12038
- } else if (entry.isFile()) {
12039
- const ext = path2.extname(entry.name);
12040
- if (extensions.includes(ext) && !entry.name.endsWith(".d.ts")) {
12041
- results.push(fullPath);
12042
- }
12043
- }
12044
- }
12045
- return results;
12046
- }
12047
- function readFileSafe(filePath) {
12048
- try {
12049
- return fs3.readFileSync(filePath, "utf-8");
12050
- } catch {
12051
- return null;
12052
- }
12053
- }
12054
- function relativePath(filePath, root = ".") {
12055
- return path2.relative(root, filePath);
12056
- }
12057
- function countLines(source) {
12058
- const lines = source.split("\n");
12059
- let loc = 0;
12060
- let blank = 0;
12061
- let comment = 0;
12062
- let inBlockComment = false;
12063
- for (const line of lines) {
12064
- const stripped = line.trim();
12065
- if (!stripped) {
12066
- blank++;
12067
- continue;
12068
- }
12069
- if (inBlockComment) {
12070
- comment++;
12071
- if (stripped.includes("*/")) {
12072
- inBlockComment = false;
12073
- }
12074
- continue;
12075
- }
12076
- if (stripped.startsWith("/*")) {
12077
- comment++;
12078
- if (!stripped.includes("*/") || stripped.endsWith("/*")) {
12079
- inBlockComment = true;
12080
- }
12081
- continue;
12082
- }
12083
- if (stripped.startsWith("//")) {
12084
- comment++;
12085
- continue;
12086
- }
12087
- loc++;
12088
- }
12089
- return { loc, blank, comment };
12090
- }
12091
- function stripLiterals(source) {
12092
- const out = [];
12093
- const n = source.length;
12094
- let i = 0;
12095
- let prevSignificant = "";
12096
- let prevWord = "";
12097
- const regexKeywords = /* @__PURE__ */ new Set([
12098
- "return",
12099
- "typeof",
12100
- "instanceof",
12101
- "in",
12102
- "of",
12103
- "new",
12104
- "delete",
12105
- "void",
12106
- "throw",
12107
- "case",
12108
- "do",
12109
- "else",
12110
- "yield",
12111
- "await"
12112
- ]);
12113
- function prevEndsExpression() {
12114
- if (prevSignificant === "") return false;
12115
- if (/[A-Za-z0-9_$]/.test(prevSignificant)) {
12116
- return !regexKeywords.has(prevWord);
12117
- }
12118
- if (prevSignificant === ")" || prevSignificant === "]") return true;
12119
- if (prevSignificant === ".") return true;
12120
- return false;
12121
- }
12122
- while (i < n) {
12123
- const ch = source[i];
12124
- const next = i + 1 < n ? source[i + 1] : "";
12125
- if (ch === "/" && next === "/") {
12126
- out.push("//");
12127
- i += 2;
12128
- while (i < n && source[i] !== "\n") {
12129
- out.push(" ");
12130
- i++;
12131
- }
12132
- continue;
12133
- }
12134
- if (ch === "/" && next === "*") {
12135
- out.push("/*");
12136
- i += 2;
12137
- while (i < n && !(source[i] === "*" && source[i + 1] === "/")) {
12138
- out.push(source[i] === "\n" ? "\n" : " ");
12139
- i++;
12140
- }
12141
- if (i < n) {
12142
- out.push("*/");
12143
- i += 2;
12144
- }
12145
- continue;
12146
- }
12147
- if (ch === '"' || ch === "'") {
12148
- const quote = ch;
12149
- out.push(quote);
12150
- i++;
12151
- while (i < n && source[i] !== quote) {
12152
- if (source[i] === "\\" && i + 1 < n) {
12153
- out.push(" ");
12154
- i += 2;
12155
- continue;
12156
- }
12157
- if (source[i] === "\n") {
12158
- out.push("\n");
12159
- i++;
12160
- break;
12161
- }
12162
- out.push(" ");
12163
- i++;
12164
- }
12165
- if (i < n && source[i] === quote) {
12166
- out.push(quote);
12167
- i++;
12168
- }
12169
- prevSignificant = quote;
12170
- prevWord = "";
12171
- continue;
12172
- }
12173
- if (ch === "`") {
12174
- out.push("`");
12175
- i++;
12176
- while (i < n && source[i] !== "`") {
12177
- if (source[i] === "\\" && i + 1 < n) {
12178
- out.push(source[i + 1] === "\n" ? " \n" : " ");
12179
- i += 2;
12180
- continue;
12181
- }
12182
- if (source[i] === "$" && source[i + 1] === "{") {
12183
- out.push("${");
12184
- i += 2;
12185
- let depth = 1;
12186
- const exprStart = i;
12187
- while (i < n && depth > 0) {
12188
- if (source[i] === "{") depth++;
12189
- else if (source[i] === "}") depth--;
12190
- if (depth === 0) break;
12191
- i++;
12192
- }
12193
- out.push(stripLiterals(source.slice(exprStart, i)));
12194
- if (i < n && source[i] === "}") {
12195
- out.push("}");
12196
- i++;
12197
- }
12198
- continue;
12199
- }
12200
- out.push(source[i] === "\n" ? "\n" : " ");
12201
- i++;
12202
- }
12203
- if (i < n && source[i] === "`") {
12204
- out.push("`");
12205
- i++;
12206
- }
12207
- prevSignificant = "`";
12208
- prevWord = "";
12209
- continue;
12210
- }
12211
- if (ch === "/" && !prevEndsExpression()) {
12212
- let j = i + 1;
12213
- let ok = false;
12214
- let inClass = false;
12215
- while (j < n) {
12216
- const c = source[j];
12217
- if (c === "\\") {
12218
- j += 2;
12219
- continue;
12220
- }
12221
- if (c === "\n") break;
12222
- if (c === "[") inClass = true;
12223
- else if (c === "]") inClass = false;
12224
- else if (c === "/" && !inClass) {
12225
- ok = true;
12226
- break;
12227
- }
12228
- j++;
12229
- }
12230
- if (ok) {
12231
- out.push("/");
12232
- for (let k = i + 1; k < j; k++) out.push(" ");
12233
- out.push("/");
12234
- i = j + 1;
12235
- while (i < n && /[a-z]/i.test(source[i])) {
12236
- out.push(source[i]);
12237
- i++;
12238
- }
12239
- prevSignificant = "/";
12240
- prevWord = "";
12241
- continue;
12242
- }
12243
- }
12244
- out.push(ch);
12245
- if (!/\s/.test(ch)) {
12246
- prevSignificant = ch;
12247
- if (/[A-Za-z0-9_$]/.test(ch)) {
12248
- prevWord = /[A-Za-z0-9_$]/.test(source[i - 1] ?? "") ? prevWord + ch : ch;
12249
- } else {
12250
- prevWord = "";
12251
- }
12252
- }
12253
- i++;
12113
+ function containsTypeScript(directory) {
12114
+ if (!fs3.existsSync(directory) || !fs3.statSync(directory).isDirectory()) return false;
12115
+ for (const entry of fs3.readdirSync(directory, { withFileTypes: true })) {
12116
+ if (["node_modules", ".git", "dist", "build"].includes(entry.name)) continue;
12117
+ const target = path2.join(directory, entry.name);
12118
+ if (entry.isDirectory() ? containsTypeScript(target) : /\.[cm]?[jt]sx?$/.test(entry.name)) return true;
12254
12119
  }
12255
- return out.join("");
12256
- }
12257
- function countClassesQuick(source) {
12258
- const matches = source.match(
12259
- /(?:^|\n)\s*(?:export\s+)?(?:abstract\s+)?class\s+\w+/g
12260
- );
12261
- return matches ? matches.length : 0;
12262
- }
12263
- function countFunctionsQuick(source) {
12264
- const clean = stripLiterals(source);
12265
- let count = 0;
12266
- const funcDecls = clean.match(
12267
- /(?:^|\n)\s*(?:export\s+)?(?:async\s+)?function\s+\w+\s*\(/g
12268
- );
12269
- if (funcDecls) count += funcDecls.length;
12270
- const methods = clean.match(
12271
- /(?:^|\n)\s*(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*\([^)]*\)\s*(?::\s*\S+)?\s*\{/g
12272
- );
12273
- if (methods) count += methods.length;
12274
- const arrows = clean.match(
12275
- /(?:^|\n)\s*(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*(?:async\s+)?\(/g
12276
- );
12277
- if (arrows) count += arrows.length;
12278
- return count;
12279
- }
12280
- function resolveRoot(root = "src") {
12281
- const rootPath = path2.resolve(root);
12282
- if (fs3.existsSync(rootPath) && walkFiles(rootPath, [".ts", ".js"]).length > 0) {
12283
- _lastScanRoot = rootPath;
12284
- return root;
12285
- }
12286
- const fwDir = path2.resolve(path2.dirname(new URL(import.meta.url).pathname));
12287
- _lastScanRoot = fwDir;
12288
- return fwDir;
12289
- }
12290
- function quickMetrics(root = "src") {
12291
- root = resolveRoot(root);
12292
- const rootPath = path2.resolve(root);
12293
- if (!fs3.existsSync(rootPath)) {
12294
- return { error: `Directory not found: ${root}` };
12295
- }
12296
- const tsFiles = walkFiles(rootPath, [".ts", ".js"]);
12297
- const twigFiles = walkFiles(rootPath, [".twig", ".html"]);
12298
- const migrationsDir = path2.resolve("migrations");
12299
- const migrationFiles = [
12300
- ...walkFiles(migrationsDir, [".sql"]),
12301
- ...walkFiles(migrationsDir, [".ts"])
12302
- ];
12303
- const scssFiles = walkFiles(rootPath, [".scss", ".css"]);
12304
- let totalLoc = 0;
12305
- let totalBlank = 0;
12306
- let totalComment = 0;
12307
- let totalClasses = 0;
12308
- let totalFunctions = 0;
12309
- const fileDetails = [];
12310
- for (const f of tsFiles) {
12311
- const source = readFileSafe(f);
12312
- if (source === null) continue;
12313
- const counts = countLines(source);
12314
- const classes = countClassesQuick(source);
12315
- const functions = countFunctionsQuick(source);
12316
- totalLoc += counts.loc;
12317
- totalBlank += counts.blank;
12318
- totalComment += counts.comment;
12319
- totalClasses += classes;
12320
- totalFunctions += functions;
12321
- fileDetails.push({
12322
- path: relativePath(f, rootPath),
12323
- loc: counts.loc,
12324
- blank: counts.blank,
12325
- comment: counts.comment,
12326
- classes,
12327
- functions
12328
- });
12329
- }
12330
- fileDetails.sort((a, b) => b.loc - a.loc);
12331
- let routeCount = 0;
12332
- let ormCount = 0;
12333
- for (const f of tsFiles) {
12334
- const source = readFileSafe(f);
12335
- if (source === null) continue;
12336
- const routes = source.match(
12337
- /(?:router\s*\.\s*(?:get|post|put|delete|patch|any)\s*\(|@(?:get|post|put|delete|patch)\s*\()/g
12338
- );
12339
- if (routes) routeCount += routes.length;
12340
- const orms = source.match(
12341
- /class\s+\w+\s+extends\s+(?:ORM|Model)\b/g
12342
- );
12343
- if (orms) ormCount += orms.length;
12344
- }
12345
- const breakdown = {
12346
- typescript: tsFiles.filter((f) => f.endsWith(".ts")).length,
12347
- javascript: tsFiles.filter((f) => f.endsWith(".js")).length,
12348
- templates: twigFiles.length,
12349
- migrations: migrationFiles.length,
12350
- stylesheets: scssFiles.length
12351
- };
12352
- return {
12353
- file_count: tsFiles.length,
12354
- total_loc: totalLoc,
12355
- total_blank: totalBlank,
12356
- total_comment: totalComment,
12357
- lloc: totalLoc,
12358
- classes: totalClasses,
12359
- functions: totalFunctions,
12360
- route_count: routeCount,
12361
- orm_count: ormCount,
12362
- template_count: twigFiles.length,
12363
- migration_count: migrationFiles.length,
12364
- avg_file_size: tsFiles.length > 0 ? Math.round(totalLoc / tsFiles.length * 10) / 10 : 0,
12365
- largest_files: fileDetails.slice(0, 10),
12366
- breakdown
12367
- };
12120
+ return false;
12368
12121
  }
12369
- function resolveScanTarget(root = "src") {
12370
- const resolved = resolveRoot(root);
12371
- const frameworkDir = path2.dirname(fileURLToPath(import.meta.url));
12372
- const real = path2.resolve(resolved);
12373
- const scanningFramework = real === frameworkDir || real.startsWith(frameworkDir);
12374
- return [resolved, scanningFramework ? "framework" : "project"];
12122
+ function resolveTarget(root = "src") {
12123
+ const resolved = containsTypeScript(root) ? path2.resolve(root) : path2.dirname(fileURLToPath(import.meta.url));
12124
+ const mode = containsTypeScript(root) ? "project" : "framework";
12125
+ lastScanRoot = resolved;
12126
+ return [resolved, mode];
12375
12127
  }
12376
12128
  function enginePath() {
12377
- const names = process.platform === "win32" ? ["tina4.exe", "tina4.cmd", "tina4"] : ["tina4"];
12378
- for (const dir of (process.env.PATH || "").split(path2.delimiter)) {
12379
- if (!dir) continue;
12129
+ const names = process.platform === "win32" ? ["tina4.exe", "tina4"] : ["tina4"];
12130
+ for (const directory of (process.env.PATH || "").split(path2.delimiter)) {
12380
12131
  for (const name of names) {
12381
- const candidate = path2.join(dir, name);
12132
+ const candidate = path2.join(directory, name);
12382
12133
  try {
12383
- if (!fs3.statSync(candidate).isFile()) continue;
12384
12134
  fs3.accessSync(candidate, fs3.constants.X_OK);
12135
+ if (!fs3.statSync(candidate).isFile()) continue;
12136
+ const descriptor = fs3.openSync(candidate, "r");
12137
+ const header = Buffer.alloc(2);
12138
+ fs3.readSync(descriptor, header, 0, 2, 0);
12139
+ fs3.closeSync(descriptor);
12140
+ if (header.toString("latin1") !== "#!") return candidate;
12385
12141
  } catch {
12386
12142
  continue;
12387
12143
  }
12388
- try {
12389
- const fd = fs3.openSync(candidate, "r");
12390
- const buf = Buffer.alloc(2);
12391
- fs3.readSync(fd, buf, 0, 2, 0);
12392
- fs3.closeSync(fd);
12393
- if (buf.toString("latin1") === "#!") continue;
12394
- } catch {
12395
- }
12396
- return candidate;
12397
12144
  }
12398
12145
  }
12399
12146
  return null;
12400
12147
  }
12401
12148
  function runEngine(target) {
12402
12149
  const binary = enginePath();
12403
- if (binary === null) {
12404
- throw new MetricsEngineError(`tina4 not found on PATH - ${INSTALL_HINT}`);
12405
- }
12406
- const proc = spawnSync(binary, ["metrics", "--path", target, "--json"], {
12150
+ if (!binary) throw new MetricsEngineError(`tina4 not found on PATH - ${INSTALL_HINT}`);
12151
+ const processResult = spawnSync(binary, ["metrics", "--path", target, "--json"], {
12407
12152
  encoding: "utf8",
12408
- timeout: TIMEOUT_MS,
12153
+ timeout: 6e4,
12409
12154
  maxBuffer: 64 * 1024 * 1024
12410
12155
  });
12411
- if (proc.error) {
12412
- const err = proc.error;
12413
- if (err.code === "ETIMEDOUT") {
12414
- throw new MetricsEngineError(`tina4 metrics timed out after ${TIMEOUT_MS / 1e3}s on ${target}`);
12415
- }
12416
- throw new MetricsEngineError(`could not run ${binary}: ${err.message}`);
12156
+ if (processResult.error) {
12157
+ throw new MetricsEngineError(`could not run ${binary}: ${processResult.error.message}`);
12417
12158
  }
12418
- if (proc.status !== 0) {
12419
- const detail = (proc.stderr || proc.stdout || "").trim().split("\n")[0];
12420
- throw new MetricsEngineError(
12421
- `tina4 metrics failed on ${target}: ${detail || `exit code ${proc.status}`}`
12422
- );
12423
- }
12424
- if (!proc.stdout || !proc.stdout.trim()) {
12425
- throw new MetricsEngineError(`tina4 metrics produced no output for ${target}`);
12159
+ if (processResult.status !== 0) {
12160
+ const detail = (processResult.stderr || processResult.stdout || "").trim().split("\n")[0];
12161
+ throw new MetricsEngineError(`tina4 metrics failed on ${target}: ${detail || processResult.status}`);
12426
12162
  }
12427
- let payload;
12428
12163
  try {
12429
- payload = JSON.parse(proc.stdout);
12430
- } catch (e) {
12431
- throw new MetricsEngineError(`tina4 metrics returned unreadable JSON: ${e.message}`);
12432
- }
12433
- if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
12434
- throw new MetricsEngineError("tina4 metrics returned a non-object payload");
12164
+ const payload = JSON.parse(processResult.stdout);
12165
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
12166
+ throw new Error("non-object payload");
12167
+ }
12168
+ return payload;
12169
+ } catch (error) {
12170
+ throw new MetricsEngineError(`tina4 metrics returned unreadable JSON: ${error.message}`);
12435
12171
  }
12436
- return payload;
12437
12172
  }
12438
- function requireKey(payload, key, isArray) {
12439
- const value = payload[key];
12440
- const ok = isArray ? Array.isArray(value) : value !== null && typeof value === "object" && !Array.isArray(value);
12441
- if (!ok) {
12442
- throw new MetricsEngineError(
12443
- `engine payload has no usable '${key}' - the installed tina4 CLI predates a field the dashboard renders. Update it: ${INSTALL_HINT}`
12444
- );
12173
+ function requireArray(payload, key) {
12174
+ if (!Array.isArray(payload[key])) {
12175
+ throw new MetricsEngineError(`engine payload has no usable '${key}' - ${INSTALL_HINT}`);
12445
12176
  }
12446
- return value;
12177
+ return payload[key];
12447
12178
  }
12448
12179
  function fullAnalysis(root = "src") {
12449
- const [resolved, scanMode] = resolveScanTarget(root);
12180
+ const [resolved, scanMode] = resolveTarget(root);
12450
12181
  const payload = runEngine(resolved);
12451
- const summary = requireKey(payload, "summary", false);
12452
- const fileMetrics = requireKey(payload, "file_metrics", true);
12453
- const functions = requireKey(payload, "most_complex_functions", true);
12454
- const missing = SUMMARY_KEYS.filter((k) => !(k in summary));
12455
- if (missing.length) {
12456
- throw new MetricsEngineError(
12457
- `engine summary is missing ${missing.join(", ")} - update the CLI: ${INSTALL_HINT}`
12458
- );
12459
- }
12460
- if (fileMetrics.length) {
12461
- const absent = FILE_KEYS.filter((k) => !(k in fileMetrics[0]));
12462
- if (absent.length) throw new MetricsEngineError(`engine file_metrics is missing ${absent.join(", ")}`);
12463
- }
12464
- if (functions.length) {
12465
- const absent = FUNCTION_KEYS.filter((k) => !(k in functions[0]));
12466
- if (absent.length) throw new MetricsEngineError(`engine function metrics are missing ${absent.join(", ")}`);
12182
+ const summary = payload.summary;
12183
+ if (!summary || typeof summary !== "object" || Array.isArray(summary)) {
12184
+ throw new MetricsEngineError(`engine payload has no usable 'summary' - ${INSTALL_HINT}`);
12185
+ }
12186
+ const fileMetrics = requireArray(payload, "file_metrics");
12187
+ const functions = requireArray(payload, "most_complex_functions");
12188
+ const missingSummary = SUMMARY_KEYS.filter((key) => !(key in summary));
12189
+ if (missingSummary.length) throw new MetricsEngineError(`engine summary is missing ${missingSummary.join(", ")}`);
12190
+ const missingFile = fileMetrics.length ? FILE_KEYS.filter((key) => !(key in fileMetrics[0])) : [];
12191
+ if (missingFile.length) throw new MetricsEngineError(`engine file_metrics is missing ${missingFile.join(", ")}`);
12192
+ const missingFunction = functions.length ? FUNCTION_KEYS.filter((key) => !(key in functions[0])) : [];
12193
+ if (missingFunction.length) {
12194
+ throw new MetricsEngineError(`engine function metrics are missing ${missingFunction.join(", ")}`);
12467
12195
  }
12468
- const result = {};
12469
- for (const key of SUMMARY_KEYS) result[key] = summary[key];
12470
- result.file_metrics = fileMetrics;
12471
- result.most_complex_functions = functions.slice(0, 15);
12472
- result.dependency_graph = payload.dependency_graph || {};
12473
- result.scan_mode = scanMode;
12474
- result.scan_root = path2.resolve(resolved);
12475
- result.engine = "tina4-cli";
12476
- return result;
12196
+ return {
12197
+ ...Object.fromEntries(SUMMARY_KEYS.map((key) => [key, summary[key]])),
12198
+ file_metrics: fileMetrics,
12199
+ most_complex_functions: functions.slice(0, 15),
12200
+ dependency_graph: payload.dependency_graph || {},
12201
+ scan_mode: scanMode,
12202
+ scan_root: resolved,
12203
+ engine: "tina4-cli"
12204
+ };
12477
12205
  }
12478
12206
  function fileDetail(filePath) {
12479
12207
  if (!filePath) throw new MetricsEngineError("fileDetail needs a path");
12480
12208
  let target = filePath;
12481
- if (!fs3.existsSync(target) && _lastScanRoot) {
12482
- const candidate = path2.join(_lastScanRoot, filePath);
12483
- if (fs3.existsSync(candidate)) target = candidate;
12484
- }
12209
+ if (!fs3.existsSync(target) && lastScanRoot) target = path2.join(lastScanRoot, filePath);
12485
12210
  if (!fs3.existsSync(target)) throw new MetricsEngineError(`no such file: ${filePath}`);
12486
12211
  if (fs3.statSync(target).isDirectory()) throw new MetricsEngineError(`not a file: ${filePath}`);
12487
12212
  const payload = runEngine(target);
12488
- const fileMetrics = requireKey(payload, "file_metrics", true);
12489
- if (!fileMetrics.length) {
12490
- throw new MetricsEngineError(`engine reported no metrics for ${filePath}`);
12491
- }
12492
- return { ...fileMetrics[0], engine: "tina4-cli" };
12213
+ const files = requireArray(payload, "file_metrics");
12214
+ if (!files.length) throw new MetricsEngineError(`engine reported no metrics for ${filePath}`);
12215
+ return {
12216
+ ...files[0],
12217
+ function_count: files[0].functions || 0,
12218
+ functions: requireArray(payload, "most_complex_functions"),
12219
+ engine: "tina4-cli"
12220
+ };
12493
12221
  }
12494
- var _lastScanRoot, MetricsEngineError, TIMEOUT_MS, INSTALL_HINT, SUMMARY_KEYS, FILE_KEYS, FUNCTION_KEYS;
12222
+ var lastScanRoot, MetricsEngineError, INSTALL_HINT, SUMMARY_KEYS, FILE_KEYS, FUNCTION_KEYS;
12495
12223
  var init_metrics = __esm({
12496
12224
  "../core/src/metrics.ts"() {
12497
12225
  "use strict";
12498
- _lastScanRoot = "";
12226
+ lastScanRoot = "";
12499
12227
  MetricsEngineError = class extends Error {
12500
12228
  constructor(message) {
12501
12229
  super(message);
12502
12230
  this.name = "MetricsEngineError";
12503
12231
  }
12504
12232
  };
12505
- TIMEOUT_MS = 6e4;
12506
- INSTALL_HINT = [
12507
- "the tina4 CLI provides the metrics engine (ADR-0002). Install it with",
12508
- " curl -fsSL https://tina4.com/install.sh | sh",
12509
- "or see https://tina4.com/cli"
12510
- ].join("\n");
12233
+ INSTALL_HINT = "update the native tina4 CLI: https://tina4.com/cli";
12511
12234
  SUMMARY_KEYS = ["files_analyzed", "total_functions", "avg_complexity", "avg_maintainability"];
12512
12235
  FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "has_tests"];
12513
12236
  FUNCTION_KEYS = ["name", "file", "line", "complexity", "loc"];
@@ -12515,7 +12238,7 @@ var init_metrics = __esm({
12515
12238
  });
12516
12239
 
12517
12240
  // ../core/src/feedback.ts
12518
- import { readFileSync as readFileSync11, existsSync as existsSync11 } from "node:fs";
12241
+ import { readFileSync as readFileSync10, existsSync as existsSync11 } from "node:fs";
12519
12242
  import { dirname as dirname5, join as join13, resolve as resolve5 } from "node:path";
12520
12243
  import { fileURLToPath as fileURLToPath2 } from "node:url";
12521
12244
  function feedbackEnabled() {
@@ -12656,7 +12379,7 @@ var init_feedback = __esm({
12656
12379
  handleFeedbackWidgetJs = (_req, res) => {
12657
12380
  let body;
12658
12381
  if (existsSync11(WIDGET_BUNDLE_PATH)) {
12659
- body = readFileSync11(WIDGET_BUNDLE_PATH);
12382
+ body = readFileSync10(WIDGET_BUNDLE_PATH);
12660
12383
  } else {
12661
12384
  body = "console.warn('tina4-feedback-widget bundle not built yet');";
12662
12385
  }
@@ -12671,7 +12394,7 @@ var init_feedback = __esm({
12671
12394
  });
12672
12395
 
12673
12396
  // ../core/src/version.ts
12674
- import { existsSync as existsSync12, readFileSync as readFileSync12 } from "node:fs";
12397
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
12675
12398
  import { dirname as dirname6, join as join14 } from "node:path";
12676
12399
  import { fileURLToPath as fileURLToPath3 } from "node:url";
12677
12400
  function resolveFrameworkVersion() {
@@ -12680,7 +12403,7 @@ function resolveFrameworkVersion() {
12680
12403
  const pkgPath = join14(dir, "package.json");
12681
12404
  if (existsSync12(pkgPath)) {
12682
12405
  try {
12683
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
12406
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
12684
12407
  if (typeof pkg.version === "string" && pkg.version) return pkg.version;
12685
12408
  } catch {
12686
12409
  }
@@ -15468,8 +15191,8 @@ __export(context_exports, {
15468
15191
  fts5Supported: () => fts5Supported
15469
15192
  });
15470
15193
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
15471
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync14, readdirSync as readdirSync7, realpathSync as realpathSync5 } from "node:fs";
15472
- import { basename as basename4, dirname as dirname8, extname as extname5, isAbsolute as isAbsolute4, join as join16, relative as relative4, resolve as resolve7 } from "node:path";
15194
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync13, readdirSync as readdirSync7, realpathSync as realpathSync5 } from "node:fs";
15195
+ import { basename as basename4, dirname as dirname8, extname as extname4, isAbsolute as isAbsolute4, join as join16, relative as relative3, resolve as resolve7 } from "node:path";
15473
15196
  function fts5Supported() {
15474
15197
  try {
15475
15198
  const conn = new DatabaseSync2(":memory:");
@@ -15604,7 +15327,7 @@ var init_context = __esm({
15604
15327
  }
15605
15328
  // ── indexing ───────────────────────────────────────────────
15606
15329
  static chunksFor(label, text) {
15607
- const ext = extname5(label).toLowerCase();
15330
+ const ext = extname4(label).toLowerCase();
15608
15331
  const special = SPECIAL_FILES.has(basename4(label).toLowerCase());
15609
15332
  if (CODE_EXTS.has(ext) || CONFIG_EXTS.has(ext) || special) {
15610
15333
  return chunkCode(text, label);
@@ -15622,7 +15345,7 @@ var init_context = __esm({
15622
15345
  const stored = label != null ? String(label) : String(file);
15623
15346
  let text;
15624
15347
  try {
15625
- text = readFileSync14(file, "utf-8");
15348
+ text = readFileSync13(file, "utf-8");
15626
15349
  } catch {
15627
15350
  return 0;
15628
15351
  }
@@ -15643,7 +15366,7 @@ var init_context = __esm({
15643
15366
  static eligible(filename) {
15644
15367
  const fn = filename.toLowerCase();
15645
15368
  if (fn.endsWith(".min.js")) return false;
15646
- const ext = extname5(fn);
15369
+ const ext = extname4(fn);
15647
15370
  return CODE_EXTS.has(ext) || DOC_EXTS.has(ext) || CONFIG_EXTS.has(ext) || SPECIAL_FILES.has(fn);
15648
15371
  }
15649
15372
  /**
@@ -15672,7 +15395,7 @@ var init_context = __esm({
15672
15395
  for (const fn of files) {
15673
15396
  if (!_Context.eligible(fn)) continue;
15674
15397
  const full = join16(dir, fn);
15675
- const rel = relative4(rootAbs, full);
15398
+ const rel = relative3(rootAbs, full);
15676
15399
  total += this.indexPath(full, rel);
15677
15400
  }
15678
15401
  for (const d of subdirs) walk2(join16(dir, d));
@@ -15693,7 +15416,7 @@ var init_context = __esm({
15693
15416
  const raw = String(changedPath);
15694
15417
  const abs = isAbsolute4(raw) ? raw : join16(process.cwd(), raw);
15695
15418
  const resolved = realResolve(resolve7(abs));
15696
- const rel = relative4(this.root, resolved);
15419
+ const rel = relative3(this.root, resolved);
15697
15420
  if (rel === "" || rel.startsWith("..") || isAbsolute4(rel)) {
15698
15421
  return -1;
15699
15422
  }
@@ -17615,7 +17338,7 @@ var init_job = __esm({
17615
17338
  });
17616
17339
 
17617
17340
  // ../core/src/queueBackends/liteBackend.ts
17618
- import { mkdirSync as mkdirSync10, readdirSync as readdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync8, unlinkSync as unlinkSync6, existsSync as existsSync15 } from "node:fs";
17341
+ import { mkdirSync as mkdirSync10, readdirSync as readdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync8, unlinkSync as unlinkSync6, existsSync as existsSync15 } from "node:fs";
17619
17342
  import { join as join17 } from "node:path";
17620
17343
  import { randomUUID as randomUUID5 } from "node:crypto";
17621
17344
  var LiteBackend;
@@ -17715,7 +17438,7 @@ var init_liteBackend = __esm({
17715
17438
  const filePath = join17(dir, filename);
17716
17439
  let job;
17717
17440
  try {
17718
- job = JSON.parse(readFileSync15(filePath, "utf-8"));
17441
+ job = JSON.parse(readFileSync14(filePath, "utf-8"));
17719
17442
  } catch {
17720
17443
  continue;
17721
17444
  }
@@ -17779,7 +17502,7 @@ var init_liteBackend = __esm({
17779
17502
  const filePath = join17(reservedDir, filename);
17780
17503
  let record;
17781
17504
  try {
17782
- record = JSON.parse(readFileSync15(filePath, "utf-8"));
17505
+ record = JSON.parse(readFileSync14(filePath, "utf-8"));
17783
17506
  } catch {
17784
17507
  continue;
17785
17508
  }
@@ -17892,7 +17615,7 @@ var init_liteBackend = __esm({
17892
17615
  let count = 0;
17893
17616
  for (const file of files) {
17894
17617
  try {
17895
- const job = JSON.parse(readFileSync15(join17(scanDir, file), "utf-8"));
17618
+ const job = JSON.parse(readFileSync14(join17(scanDir, file), "utf-8"));
17896
17619
  if (job.status === status2) count++;
17897
17620
  } catch {
17898
17621
  }
@@ -17949,7 +17672,7 @@ var init_liteBackend = __esm({
17949
17672
  const files = readdirSync8(dir).filter((f) => f.endsWith(".queue-data")).sort();
17950
17673
  for (const file of files) {
17951
17674
  try {
17952
- const job = JSON.parse(readFileSync15(join17(dir, file), "utf-8"));
17675
+ const job = JSON.parse(readFileSync14(join17(dir, file), "utf-8"));
17953
17676
  const attempts = job.attempts || 0;
17954
17677
  if (attempts > 0 && attempts < maxRetries) {
17955
17678
  results.push(job);
@@ -17975,7 +17698,7 @@ var init_liteBackend = __esm({
17975
17698
  const failedDir = join17(this.basePath, q, "failed");
17976
17699
  const filePath = join17(failedDir, `${jobId}.queue-data`);
17977
17700
  if (existsSync15(filePath)) {
17978
- const job = JSON.parse(readFileSync15(filePath, "utf-8"));
17701
+ const job = JSON.parse(readFileSync14(filePath, "utf-8"));
17979
17702
  job.status = "pending";
17980
17703
  job.attempts = (job.attempts || 0) + 1;
17981
17704
  job.error = void 0;
@@ -17999,7 +17722,7 @@ var init_liteBackend = __esm({
17999
17722
  const files = readdirSync8(failedDir).filter((f) => f.endsWith(".queue-data")).sort();
18000
17723
  for (const file of files) {
18001
17724
  try {
18002
- const job = JSON.parse(readFileSync15(join17(failedDir, file), "utf-8"));
17725
+ const job = JSON.parse(readFileSync14(join17(failedDir, file), "utf-8"));
18003
17726
  if ((job.attempts || 0) >= maxRetries) {
18004
17727
  job.status = "dead";
18005
17728
  results.push(job);
@@ -18033,7 +17756,7 @@ var init_liteBackend = __esm({
18033
17756
  const files = readdirSync8(dir).filter((f) => f.endsWith(".queue-data"));
18034
17757
  for (const file of files) {
18035
17758
  try {
18036
- const job = JSON.parse(readFileSync15(join17(dir, file), "utf-8"));
17759
+ const job = JSON.parse(readFileSync14(join17(dir, file), "utf-8"));
18037
17760
  if (job.status === status2) {
18038
17761
  unlinkSync6(join17(dir, file));
18039
17762
  count++;
@@ -18060,7 +17783,7 @@ var init_liteBackend = __esm({
18060
17783
  for (const file of files) {
18061
17784
  try {
18062
17785
  const filePath = join17(failedDir, file);
18063
- const job = JSON.parse(readFileSync15(filePath, "utf-8"));
17786
+ const job = JSON.parse(readFileSync14(filePath, "utf-8"));
18064
17787
  if ((job.attempts || 0) >= maxRetries) {
18065
17788
  continue;
18066
17789
  }
@@ -18092,7 +17815,7 @@ var init_liteBackend = __esm({
18092
17815
  const filePath = join17(dir, file);
18093
17816
  let job;
18094
17817
  try {
18095
- job = JSON.parse(readFileSync15(filePath, "utf-8"));
17818
+ job = JSON.parse(readFileSync14(filePath, "utf-8"));
18096
17819
  } catch {
18097
17820
  continue;
18098
17821
  }
@@ -19555,7 +19278,7 @@ function detectVersion(projectRoot3) {
19555
19278
  }
19556
19279
  return "0.0.0";
19557
19280
  }
19558
- function relativePath2(absPath, projectRoot3, frameworkRoots) {
19281
+ function relativePath(absPath, projectRoot3, frameworkRoots) {
19559
19282
  const norm = path6.resolve(absPath);
19560
19283
  for (const fw of frameworkRoots) {
19561
19284
  const parent = path6.dirname(fw);
@@ -20020,7 +19743,7 @@ function buildEntriesForFile(absPath, source, fwRoots, projectRoot3, version, ou
20020
19743
  } catch {
20021
19744
  return;
20022
19745
  }
20023
- const rel = relativePath2(absPath, projectRoot3, fwRoots);
19746
+ const rel = relativePath(absPath, projectRoot3, fwRoots);
20024
19747
  for (const cls of parsed.classes) {
20025
19748
  if (!cls.exported && source === "framework") {
20026
19749
  continue;
@@ -20656,8 +20379,8 @@ ${end}
20656
20379
 
20657
20380
  // ../core/src/devAdmin.ts
20658
20381
  import { cpus as osCpus } from "node:os";
20659
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync12, existsSync as existsSync19, readdirSync as readdirSync12, mkdirSync as mkdirSync13, copyFileSync, statSync as statSync14 } from "node:fs";
20660
- import { join as join21, dirname as dirname10, resolve as resolve11, relative as relative8 } from "node:path";
20382
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as existsSync19, readdirSync as readdirSync12, mkdirSync as mkdirSync13, copyFileSync, statSync as statSync14 } from "node:fs";
20383
+ import { join as join21, dirname as dirname10, resolve as resolve11, relative as relative7 } from "node:path";
20661
20384
  import { fileURLToPath as fileURLToPath5 } from "node:url";
20662
20385
  import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
20663
20386
  function escapeHtml(value) {
@@ -20776,7 +20499,7 @@ function readQueueDir(dir, topic, status2) {
20776
20499
  for (const filename of readdirSync12(dir).sort()) {
20777
20500
  if (!filename.endsWith(".queue-data")) continue;
20778
20501
  try {
20779
- jobs.push(mapQueueJob(JSON.parse(readFileSync19(join21(dir, filename), "utf-8")), topic, status2));
20502
+ jobs.push(mapQueueJob(JSON.parse(readFileSync18(join21(dir, filename), "utf-8")), topic, status2));
20780
20503
  } catch {
20781
20504
  }
20782
20505
  }
@@ -20895,7 +20618,7 @@ function resolveDevEnvVar(key) {
20895
20618
  if (live !== void 0 && live !== "") return live;
20896
20619
  const envPath = join21(process.cwd(), ".env");
20897
20620
  if (!existsSync19(envPath)) return "";
20898
- for (const line of readFileSync19(envPath, "utf-8").split("\n")) {
20621
+ for (const line of readFileSync18(envPath, "utf-8").split("\n")) {
20899
20622
  const t = line.trim();
20900
20623
  if (!t || t.startsWith("#") || !t.includes("=")) continue;
20901
20624
  const eq = t.indexOf("=");
@@ -20905,7 +20628,7 @@ function resolveDevEnvVar(key) {
20905
20628
  }
20906
20629
  function upsertDevEnvVar(key, value) {
20907
20630
  const envPath = join21(process.cwd(), ".env");
20908
- const lines = existsSync19(envPath) ? readFileSync19(envPath, "utf-8").split("\n") : [];
20631
+ const lines = existsSync19(envPath) ? readFileSync18(envPath, "utf-8").split("\n") : [];
20909
20632
  let found = false;
20910
20633
  const out = [];
20911
20634
  for (const line of lines) {
@@ -20938,7 +20661,7 @@ function parseEnvFile() {
20938
20661
  const envPath = join21(process.cwd(), ".env");
20939
20662
  const result = {};
20940
20663
  if (!existsSync19(envPath)) return result;
20941
- const lines = readFileSync19(envPath, "utf-8").split("\n");
20664
+ const lines = readFileSync18(envPath, "utf-8").split("\n");
20942
20665
  for (const line of lines) {
20943
20666
  const trimmed = line.trim();
20944
20667
  if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;
@@ -20978,7 +20701,7 @@ function handleGalleryDeploy(router) {
20978
20701
  const copied = [];
20979
20702
  const allFiles = walkDirRecursive(gallerySrc);
20980
20703
  for (const srcFile of allFiles) {
20981
- const rel = relative8(gallerySrc, srcFile);
20704
+ const rel = relative7(gallerySrc, srcFile);
20982
20705
  const dest = join21(projectSrc, rel);
20983
20706
  mkdirSync13(dirname10(dest), { recursive: true });
20984
20707
  copyFileSync(srcFile, dest);
@@ -21640,9 +21363,6 @@ var init_devAdmin = __esm({
21640
21363
  { method: "GET", pattern: "/__dev/api/gallery", handler: handleGalleryList },
21641
21364
  { method: "POST", pattern: "/__dev/api/gallery/deploy", handler: handleGalleryDeploy(router) },
21642
21365
  // Metrics
21643
- { method: "GET", pattern: "/__dev/api/metrics", handler: (_req, res) => {
21644
- res.json(quickMetrics());
21645
- } },
21646
21366
  // No fallback (ADR-0002): a missing or stale CLI is a 503 naming the
21647
21367
  // install command, never zeros that read as a healthy codebase.
21648
21368
  { method: "GET", pattern: "/__dev/api/metrics/full", handler: (_req, res) => {
@@ -22451,7 +22171,7 @@ var init_devAdmin = __esm({
22451
22171
  }
22452
22172
  try {
22453
22173
  const envPath = join21(process.cwd(), ".env");
22454
- const lines = existsSync19(envPath) ? readFileSync19(envPath, "utf-8").split("\n") : [];
22174
+ const lines = existsSync19(envPath) ? readFileSync18(envPath, "utf-8").split("\n") : [];
22455
22175
  const keysFound = { TINA4_DATABASE_URL: false, TINA4_DATABASE_USERNAME: false, TINA4_DATABASE_PASSWORD: false };
22456
22176
  const newLines = [];
22457
22177
  for (const line of lines) {
@@ -22497,12 +22217,12 @@ var init_devAdmin = __esm({
22497
22217
  const metaFile = join21(entryPath, "meta.json");
22498
22218
  if (statSync14(entryPath).isDirectory() && existsSync19(metaFile)) {
22499
22219
  try {
22500
- const meta = JSON.parse(readFileSync19(metaFile, "utf-8"));
22220
+ const meta = JSON.parse(readFileSync18(metaFile, "utf-8"));
22501
22221
  meta.id = entry;
22502
22222
  const srcDir = join21(entryPath, "src");
22503
22223
  if (existsSync19(srcDir)) {
22504
22224
  const allFiles = walkDirRecursive(srcDir);
22505
- meta.files = allFiles.map((f) => relative8(srcDir, f));
22225
+ meta.files = allFiles.map((f) => relative7(srcDir, f));
22506
22226
  }
22507
22227
  const projectSrc = resolve11(process.cwd(), "src");
22508
22228
  if (existsSync19(srcDir) && meta.files) {
@@ -22620,7 +22340,7 @@ var init_devAdmin = __esm({
22620
22340
  for (const name of readdirSync12(target).sort()) {
22621
22341
  if (devFilesHidden(name)) continue;
22622
22342
  const full = join21(target, name);
22623
- const entryRel = relative8(root, full).replace(/\\/g, "/");
22343
+ const entryRel = relative7(root, full).replace(/\\/g, "/");
22624
22344
  if (isSecretPath(entryRel)) continue;
22625
22345
  let isDir = false;
22626
22346
  let size = null;
@@ -22665,7 +22385,7 @@ var init_devAdmin = __esm({
22665
22385
  size
22666
22386
  });
22667
22387
  }
22668
- res.json({ path: relative8(root, target).replace(/\\/g, "/") || ".", branch, entries });
22388
+ res.json({ path: relative7(root, target).replace(/\\/g, "/") || ".", branch, entries });
22669
22389
  };
22670
22390
  DEV_ADMIN_LANG_MAP = {
22671
22391
  ".py": "python",
@@ -22717,8 +22437,8 @@ var init_devAdmin = __esm({
22717
22437
  return;
22718
22438
  }
22719
22439
  try {
22720
- const content = readFileSync19(target, "utf-8");
22721
- const path8 = relative8(root, target);
22440
+ const content = readFileSync18(target, "utf-8");
22441
+ const path8 = relative7(root, target);
22722
22442
  res.json({ path: path8, content, language: devAdminLanguage(path8), bytes: Buffer.byteLength(content, "utf-8") });
22723
22443
  } catch (e) {
22724
22444
  res.json({ error: e.message }, 500);
@@ -22740,10 +22460,10 @@ var init_devAdmin = __esm({
22740
22460
  writeFileSync12(target, content, "utf-8");
22741
22461
  try {
22742
22462
  const { Plan: Plan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
22743
- Plan2.recordAction(existed ? "patched" : "created", relative8(root, target));
22463
+ Plan2.recordAction(existed ? "patched" : "created", relative7(root, target));
22744
22464
  } catch {
22745
22465
  }
22746
- res.json({ ok: true, path: relative8(root, target), bytes: Buffer.byteLength(content, "utf-8") });
22466
+ res.json({ ok: true, path: relative7(root, target), bytes: Buffer.byteLength(content, "utf-8") });
22747
22467
  } catch (e) {
22748
22468
  res.json({ error: e.message }, 500);
22749
22469
  }
@@ -22763,7 +22483,7 @@ var init_devAdmin = __esm({
22763
22483
  return;
22764
22484
  }
22765
22485
  try {
22766
- const buf = readFileSync19(target);
22486
+ const buf = readFileSync18(target);
22767
22487
  const ext = target.slice(target.lastIndexOf(".") + 1).toLowerCase();
22768
22488
  const mime = {
22769
22489
  js: "application/javascript",
@@ -22805,7 +22525,7 @@ var init_devAdmin = __esm({
22805
22525
  const { renameSync: renameSync3 } = await import("node:fs");
22806
22526
  mkdirSync13(dirname10(dst), { recursive: true });
22807
22527
  renameSync3(src, dst);
22808
- res.json({ ok: true, from: relative8(root, src), to: relative8(root, dst) });
22528
+ res.json({ ok: true, from: relative7(root, src), to: relative7(root, dst) });
22809
22529
  } catch (e) {
22810
22530
  res.json({ error: e.message }, 500);
22811
22531
  }
@@ -22826,7 +22546,7 @@ var init_devAdmin = __esm({
22826
22546
  try {
22827
22547
  const { rmSync } = await import("node:fs");
22828
22548
  rmSync(target, { recursive: true, force: true });
22829
- res.json({ ok: true, deleted: relative8(root, target) });
22549
+ res.json({ ok: true, deleted: relative7(root, target) });
22830
22550
  } catch (e) {
22831
22551
  res.json({ error: e.message }, 500);
22832
22552
  }
@@ -23160,7 +22880,7 @@ var init_devAdmin = __esm({
23160
22880
  });
23161
22881
  };
23162
22882
  handleDevAdminJs = async (_req, res) => {
23163
- const { readFileSync: readFileSync28, existsSync: existsSync27 } = await import("node:fs");
22883
+ const { readFileSync: readFileSync27, existsSync: existsSync27 } = await import("node:fs");
23164
22884
  const { dirname: dirname15, join: join32, resolve: resolve20 } = await import("node:path");
23165
22885
  const { fileURLToPath: fileURLToPath8 } = await import("node:url");
23166
22886
  const dir = dirname15(fileURLToPath8(import.meta.url));
@@ -23176,7 +22896,7 @@ var init_devAdmin = __esm({
23176
22896
  for (const jsPath of candidates) {
23177
22897
  if (existsSync27(jsPath)) {
23178
22898
  try {
23179
- const content = readFileSync28(jsPath, "utf-8");
22899
+ const content = readFileSync27(jsPath, "utf-8");
23180
22900
  res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
23181
22901
  res.raw.end(content);
23182
22902
  return;
@@ -23191,7 +22911,7 @@ var init_devAdmin = __esm({
23191
22911
  });
23192
22912
 
23193
22913
  // ../core/src/i18n.ts
23194
- import { readFileSync as readFileSync20, readdirSync as readdirSync13, existsSync as existsSync20 } from "node:fs";
22914
+ import { readFileSync as readFileSync19, readdirSync as readdirSync13, existsSync as existsSync20 } from "node:fs";
23195
22915
  import { join as join22, resolve as resolve12 } from "node:path";
23196
22916
  var I18n;
23197
22917
  var init_i18n = __esm({
@@ -23288,7 +23008,7 @@ var init_i18n = __esm({
23288
23008
  const filePath = join22(this._localeDir, `${locale}.json`);
23289
23009
  if (existsSync20(filePath)) {
23290
23010
  try {
23291
- const raw = readFileSync20(filePath, "utf-8");
23011
+ const raw = readFileSync19(filePath, "utf-8");
23292
23012
  const data = JSON.parse(raw);
23293
23013
  this._translations.set(locale, _I18n._flatten(data));
23294
23014
  return;
@@ -23301,7 +23021,7 @@ var init_i18n = __esm({
23301
23021
  const yamlPath = join22(this._localeDir, `${locale}${ext}`);
23302
23022
  if (existsSync20(yamlPath)) {
23303
23023
  try {
23304
- const raw = readFileSync20(yamlPath, "utf-8");
23024
+ const raw = readFileSync19(yamlPath, "utf-8");
23305
23025
  const data = _I18n._parseSimpleYaml(raw);
23306
23026
  this._translations.set(locale, _I18n._flatten(data));
23307
23027
  return;
@@ -24164,8 +23884,8 @@ var init_docsAutoDiscovery = __esm({
24164
23884
  // ../core/src/server.ts
24165
23885
  import { createServer as createServer2 } from "node:http";
24166
23886
  import { randomBytes as randomBytes5 } from "node:crypto";
24167
- import { resolve as resolve14, dirname as dirname11, join as join24, relative as relative9 } from "node:path";
24168
- import { existsSync as existsSync22, readdirSync as readdirSync14, readFileSync as readFileSync22, statSync as statSync15 } from "node:fs";
23887
+ import { resolve as resolve14, dirname as dirname11, join as join24, relative as relative8 } from "node:path";
23888
+ import { existsSync as existsSync22, readdirSync as readdirSync14, readFileSync as readFileSync21, statSync as statSync15 } from "node:fs";
24169
23889
  import { isatty } from "node:tty";
24170
23890
  import { fileURLToPath as fileURLToPath6 } from "node:url";
24171
23891
  import { execFileSync as execFileSync3, exec } from "node:child_process";
@@ -24389,7 +24109,7 @@ function getGalleryDeployedState() {
24389
24109
  if (existsSync22(srcDir)) {
24390
24110
  const files = walkGalleryFiles(srcDir);
24391
24111
  const projectSrc = resolve14(process.cwd(), "src");
24392
- state[entry] = files.every((f) => existsSync22(join24(projectSrc, relative9(srcDir, f))));
24112
+ state[entry] = files.every((f) => existsSync22(join24(projectSrc, relative8(srcDir, f))));
24393
24113
  } else {
24394
24114
  state[entry] = false;
24395
24115
  }
@@ -24826,7 +24546,7 @@ function serveTemplateFallback(ctx) {
24826
24546
  if ((ctx.req.method ?? "GET") !== "GET") return false;
24827
24547
  const tplFile = resolveTemplate(ctx.pathname, ctx.templatesDir);
24828
24548
  if (!tplFile) return false;
24829
- const html = ctx.frondEngine ? ctx.frondEngine.render(tplFile, {}) : readFileSync22(resolve14(ctx.templatesDir, tplFile), "utf-8");
24549
+ const html = ctx.frondEngine ? ctx.frondEngine.render(tplFile, {}) : readFileSync21(resolve14(ctx.templatesDir, tplFile), "utf-8");
24830
24550
  ctx.res.raw.writeHead(200, void 0, { "Content-Type": "text/html; charset=utf-8" });
24831
24551
  ctx.res.raw.end(html);
24832
24552
  return true;
@@ -26121,7 +25841,7 @@ var init_mqttMessage = __esm({
26121
25841
  import net2 from "node:net";
26122
25842
  import tls from "node:tls";
26123
25843
  import { randomBytes as randomBytes6 } from "node:crypto";
26124
- import { existsSync as existsSync24, readFileSync as readFileSync23 } from "node:fs";
25844
+ import { existsSync as existsSync24, readFileSync as readFileSync22 } from "node:fs";
26125
25845
  var MqttError, MqttTimeoutError, CONNECT, CONNACK, PUBLISH, PUBACK, SUBSCRIBE, SUBACK, PINGREQ, PINGRESP, DISCONNECT, PROTOCOL_LEVEL, DEFAULT_PORT, DEFAULT_TLS_PORT, DEFAULT_URL, DEFAULT_KEEPALIVE, SUBSCRIPTION_REFUSED, MAX_REMAINING_LENGTH, QOS2_REFUSED_MESSAGE, CONNACK_RETURN_CODES, Mqtt;
26126
25846
  var init_mqtt = __esm({
26127
25847
  "../core/src/mqtt.ts"() {
@@ -26586,7 +26306,7 @@ var init_mqtt = __esm({
26586
26306
  servername: this.host,
26587
26307
  rejectUnauthorized: this.tlsVerify
26588
26308
  };
26589
- if (this.tlsVerify && this.caFile) opts.ca = readFileSync23(this.caFile);
26309
+ if (this.tlsVerify && this.caFile) opts.ca = readFileSync22(this.caFile);
26590
26310
  sock = tls.connect(opts, () => settle(() => resolve20(sock)));
26591
26311
  } else {
26592
26312
  sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve20(sock)));
@@ -26807,7 +26527,7 @@ var init_mqtt = __esm({
26807
26527
 
26808
26528
  // ../core/src/service.ts
26809
26529
  import { readdirSync as readdirSync16, statSync as statSync16, watchFile, unwatchFile } from "node:fs";
26810
- import { join as join26, extname as extname7 } from "node:path";
26530
+ import { join as join26, extname as extname6 } from "node:path";
26811
26531
  import { pathToFileURL } from "node:url";
26812
26532
  function matchCronField(field, value) {
26813
26533
  if (field === "*") return true;
@@ -26978,7 +26698,7 @@ var init_service = __esm({
26978
26698
  return discovered;
26979
26699
  }
26980
26700
  for (const entry of entries) {
26981
- const ext = extname7(entry);
26701
+ const ext = extname6(entry);
26982
26702
  if (ext !== ".ts" && ext !== ".js") continue;
26983
26703
  const fullPath = join26(dir, entry);
26984
26704
  const stat = statSync16(fullPath);
@@ -27094,7 +26814,7 @@ var init_service = __esm({
27094
26814
  return;
27095
26815
  }
27096
26816
  for (const entry of entries) {
27097
- const ext = extname7(entry);
26817
+ const ext = extname6(entry);
27098
26818
  if (ext !== ".ts" && ext !== ".js") continue;
27099
26819
  const fullPath = join26(dir, entry);
27100
26820
  if (watchedFiles.has(fullPath)) continue;
@@ -27797,7 +27517,7 @@ var init_api = __esm({
27797
27517
  // ../core/src/messenger.ts
27798
27518
  import net3 from "node:net";
27799
27519
  import tls2 from "node:tls";
27800
- import { readFileSync as readFileSync24 } from "node:fs";
27520
+ import { readFileSync as readFileSync23 } from "node:fs";
27801
27521
  import { basename as basename6 } from "node:path";
27802
27522
  import { randomUUID as randomUUID7 } from "node:crypto";
27803
27523
  function tlsRejectUnauthorized() {
@@ -27894,7 +27614,7 @@ function buildMimeMessage(options) {
27894
27614
  }
27895
27615
  for (const filePath of options.attachments) {
27896
27616
  const fileName = basename6(filePath);
27897
- const fileData = readFileSync24(filePath);
27617
+ const fileData = readFileSync23(filePath);
27898
27618
  const base64Data = fileData.toString("base64");
27899
27619
  lines.push("");
27900
27620
  lines.push(`--${boundary}`);
@@ -29357,9 +29077,9 @@ var init_htmlElement = __esm({
29357
29077
  });
29358
29078
 
29359
29079
  // ../core/src/ai.ts
29360
- import { existsSync as existsSync25, mkdirSync as mkdirSync16, writeFileSync as writeFileSync15, readFileSync as readFileSync25 } from "node:fs";
29080
+ import { existsSync as existsSync25, mkdirSync as mkdirSync16, writeFileSync as writeFileSync15, readFileSync as readFileSync24 } from "node:fs";
29361
29081
  import { homedir } from "node:os";
29362
- import { join as join27, resolve as resolve16, relative as relative10, dirname as dirname12 } from "node:path";
29082
+ import { join as join27, resolve as resolve16, relative as relative9, dirname as dirname12 } from "node:path";
29363
29083
  import { fileURLToPath as fileURLToPath7 } from "node:url";
29364
29084
  import { execSync, execFileSync as execFileSync4 } from "node:child_process";
29365
29085
  import { createInterface } from "node:readline";
@@ -29367,7 +29087,7 @@ function readVersion() {
29367
29087
  try {
29368
29088
  const thisDir = dirname12(fileURLToPath7(import.meta.url));
29369
29089
  const rootPkg = resolve16(thisDir, "..", "..", "..", "package.json");
29370
- const pkg = JSON.parse(readFileSync25(rootPkg, "utf-8"));
29090
+ const pkg = JSON.parse(readFileSync24(rootPkg, "utf-8"));
29371
29091
  return pkg.version ?? "0.0.0";
29372
29092
  } catch {
29373
29093
  return "0.0.0";
@@ -29385,14 +29105,16 @@ function downloadSkillsSync(jobs) {
29385
29105
  const jobs = JSON.parse(process.argv[1]);
29386
29106
  const fs = require("node:fs");
29387
29107
  const path = require("node:path");
29108
+ const transientStatuses = new Set([429, 500, 502, 503, 504]);
29388
29109
  async function fetchOne(job) {
29389
29110
  const resp = await fetch(job.url, { signal: AbortSignal.timeout(15000) });
29390
- if (!resp.ok) throw new Error("HTTP " + resp.status);
29111
+ if (!resp.ok) return { ok: false, retry: transientStatuses.has(resp.status) };
29391
29112
  const buf = Buffer.from(await resp.arrayBuffer());
29392
29113
  for (const dest of job.dests) {
29393
29114
  fs.mkdirSync(path.dirname(dest), { recursive: true });
29394
29115
  fs.writeFileSync(dest, buf);
29395
29116
  }
29117
+ return { ok: true, retry: false };
29396
29118
  }
29397
29119
  (async () => {
29398
29120
  const ok = [];
@@ -29401,9 +29123,11 @@ function downloadSkillsSync(jobs) {
29401
29123
  const failed = [];
29402
29124
  await Promise.all(pending.map(async (job) => {
29403
29125
  try {
29404
- await fetchOne(job);
29405
- ok.push(job.url);
29126
+ const result = await fetchOne(job);
29127
+ if (result.ok) ok.push(job.url);
29128
+ else if (result.retry) failed.push(job);
29406
29129
  } catch {
29130
+ // DNS, TLS, timeout and connection failures are transient.
29407
29131
  failed.push(job);
29408
29132
  }
29409
29133
  }));
@@ -29586,7 +29310,7 @@ function writeOrMerge(contextPath, contextFile, frameworkGuide) {
29586
29310
  writeFileSync15(contextPath, frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
29587
29311
  return "Installed";
29588
29312
  }
29589
- const existing = readFileSync25(contextPath, "utf-8");
29313
+ const existing = readFileSync24(contextPath, "utf-8");
29590
29314
  if (hasMarkers(existing, start2, end)) {
29591
29315
  writeFileSync15(contextPath, replaceMarkerBlock(existing, block, start2, end), "utf-8");
29592
29316
  return "Refreshed skill block in";
@@ -29610,7 +29334,7 @@ function installForTool(root, tool, context) {
29610
29334
  const parentDir = dirname12(contextPath);
29611
29335
  mkdirSync16(parentDir, { recursive: true });
29612
29336
  const action = writeOrMerge(contextPath, tool.contextFile, context);
29613
- const rel = relative10(root, contextPath);
29337
+ const rel = relative9(root, contextPath);
29614
29338
  created.push(rel);
29615
29339
  console.log(` ${GREEN2}\u2713${RESET2} ${action} ${rel}`);
29616
29340
  if (tool.name === "claude-code") {
@@ -29988,7 +29712,7 @@ function generateClaudeCodeContext() {
29988
29712
  const repoRoot = resolve16(thisDir, "..", "..", "..");
29989
29713
  const claudeMdPath = join27(repoRoot, "CLAUDE.md");
29990
29714
  if (existsSync25(claudeMdPath)) {
29991
- return readFileSync25(claudeMdPath, "utf-8");
29715
+ return readFileSync24(claudeMdPath, "utf-8");
29992
29716
  }
29993
29717
  } catch {
29994
29718
  }
@@ -30182,6 +29906,292 @@ export default class User {
30182
29906
  }
30183
29907
  });
30184
29908
 
29909
+ // ../core/src/aiClient.ts
29910
+ import http2 from "node:http";
29911
+ import https2 from "node:https";
29912
+ var AiError, AiConfigError, AiTimeoutError, AiParseError, AiHTTPError, Ai;
29913
+ var init_aiClient = __esm({
29914
+ "../core/src/aiClient.ts"() {
29915
+ "use strict";
29916
+ AiError = class extends Error {
29917
+ };
29918
+ AiConfigError = class extends AiError {
29919
+ };
29920
+ AiTimeoutError = class extends AiError {
29921
+ };
29922
+ AiParseError = class extends AiError {
29923
+ };
29924
+ AiHTTPError = class extends AiError {
29925
+ constructor(message, status2 = null) {
29926
+ super(message);
29927
+ this.status = status2;
29928
+ }
29929
+ };
29930
+ Ai = class {
29931
+ static chat(messages, options = {}) {
29932
+ this.validateMessages(messages);
29933
+ const config = this.config("chat", options);
29934
+ const body = this.chatBody(config, messages, options);
29935
+ const headers = this.headers(config);
29936
+ return options.stream ? this.streamRequest(config, headers, body) : this.chatResponse(config, headers, body);
29937
+ }
29938
+ static async complete(prompt, options = {}) {
29939
+ if (typeof prompt !== "string") throw new AiConfigError("AI prompt must be a string");
29940
+ return (await this.chat([{ role: "user", content: prompt }], { ...options, stream: false })).text;
29941
+ }
29942
+ static async embed(textOrTexts, options = {}) {
29943
+ const single = typeof textOrTexts === "string";
29944
+ if (!single && (!Array.isArray(textOrTexts) || textOrTexts.length === 0 || !textOrTexts.every((item) => typeof item === "string"))) {
29945
+ throw new AiConfigError("AI embedding input must be a string or a non-empty list of strings");
29946
+ }
29947
+ const config = this.config("embed", options);
29948
+ if (config.provider === "anthropic") throw new AiConfigError("Anthropic does not provide the embedding endpoint in this contract");
29949
+ const raw = await this.requestJson(config, this.headers(config), { model: config.model, input: textOrTexts });
29950
+ try {
29951
+ const data = raw.data.sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
29952
+ const vectors = data.map((item) => item.embedding);
29953
+ const expected = single ? 1 : textOrTexts.length;
29954
+ if (vectors.length !== expected || !vectors.every((vector) => Array.isArray(vector) && vector.length > 0 && vector.every((value) => typeof value === "number" && Number.isFinite(value)))) throw new Error();
29955
+ return single ? vectors[0] : vectors;
29956
+ } catch {
29957
+ throw new AiParseError("AI provider returned a malformed embedding response");
29958
+ }
29959
+ }
29960
+ static validateMessages(messages) {
29961
+ if (!Array.isArray(messages) || messages.length === 0 || !messages.every((message) => message && ["system", "user", "assistant"].includes(message.role) && typeof message.content === "string")) {
29962
+ throw new AiConfigError("AI messages must contain supported roles and string content");
29963
+ }
29964
+ }
29965
+ static number(name, fallback, minimum) {
29966
+ const value = process.env[name] === void 0 ? fallback : Number(process.env[name]);
29967
+ if (!Number.isFinite(value) || value < minimum) throw new AiConfigError(`${name} must be numeric and at least ${minimum}`);
29968
+ return value;
29969
+ }
29970
+ static config(capability, options) {
29971
+ const provider = (options.provider ?? process.env.TINA4_AI_PROVIDER ?? "local").trim().toLowerCase();
29972
+ if (provider !== "local" && provider !== "openai" && provider !== "anthropic") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, or anthropic");
29973
+ const key = process.env.TINA4_AI_KEY || null;
29974
+ if ((provider === "openai" || provider === "anthropic") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
29975
+ const defaults = {
29976
+ local: ["http://localhost:11437", "llama3.2"],
29977
+ openai: ["https://api.openai.com/v1", "gpt-4o-mini"],
29978
+ anthropic: ["https://api.anthropic.com/v1", "claude-3-5-haiku-latest"]
29979
+ };
29980
+ const rawUrl = capability === "embed" && process.env.TINA4_EMBED_URL ? process.env.TINA4_EMBED_URL : process.env.TINA4_AI_URL ?? defaults[provider][0];
29981
+ const model = (options.model ?? process.env.TINA4_AI_MODEL ?? defaults[provider][1]).trim();
29982
+ if (!model) throw new AiConfigError("AI model must be a non-empty string");
29983
+ const totalTimeout = options.timeout === void 0 ? this.number("TINA4_AI_TIMEOUT", 60, 1e-3) : Number(options.timeout);
29984
+ if (!Number.isFinite(totalTimeout) || totalTimeout <= 0) throw new AiConfigError("AI timeout must be greater than zero");
29985
+ return { provider, url: this.endpoint(rawUrl, capability, provider), model, key, totalTimeout, connectTimeout: this.number("TINA4_AI_CONNECT_TIMEOUT", 10, 1e-3), maxRetries: Math.trunc(this.number("TINA4_AI_MAX_RETRIES", 2, 0)) };
29986
+ }
29987
+ static endpoint(value, capability, provider) {
29988
+ let url;
29989
+ try {
29990
+ url = new URL(value);
29991
+ } catch {
29992
+ throw new AiConfigError("AI URL must be an http or https URL");
29993
+ }
29994
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new AiConfigError("AI URL must be an http or https URL");
29995
+ const path8 = url.pathname.replace(/\/+$/, "");
29996
+ if (path8 === "" || path8 === "/v1" || path8 === "/api") {
29997
+ const suffix = provider === "anthropic" ? "/messages" : capability === "embed" ? "/embeddings" : "/chat/completions";
29998
+ url.pathname = (path8 || "/v1") + suffix;
29999
+ }
30000
+ return url.toString();
30001
+ }
30002
+ static headers(config) {
30003
+ const headers = { "content-type": "application/json", accept: "application/json" };
30004
+ if (config.provider === "openai") headers.authorization = `Bearer ${config.key}`;
30005
+ if (config.provider === "anthropic") {
30006
+ headers["x-api-key"] = config.key;
30007
+ headers["anthropic-version"] = "2023-06-01";
30008
+ }
30009
+ return headers;
30010
+ }
30011
+ static chatBody(config, messages, options) {
30012
+ const body = { model: config.model, messages, stream: options.stream ?? false };
30013
+ if (options.temperature !== void 0) body.temperature = options.temperature;
30014
+ if (options.maxTokens !== void 0) body.max_tokens = options.maxTokens;
30015
+ if (config.provider === "anthropic") {
30016
+ const system = messages.filter((message) => message.role === "system").map((message) => message.content);
30017
+ body.messages = messages.filter((message) => message.role !== "system");
30018
+ body.max_tokens = options.maxTokens ?? 1024;
30019
+ if (system.length) body.system = system.join("\n\n");
30020
+ }
30021
+ return body;
30022
+ }
30023
+ static open(config, deadline, headers, body) {
30024
+ const remainingMs = deadline - performance.now();
30025
+ if (remainingMs <= 0) return Promise.reject(new AiTimeoutError("AI total request timeout expired"));
30026
+ const url = new URL(config.url);
30027
+ const payload = JSON.stringify(body);
30028
+ const controller = new AbortController();
30029
+ const totalTimer = setTimeout(() => controller.abort(new AiTimeoutError("AI total request timeout expired")), remainingMs);
30030
+ return new Promise((resolve20, reject) => {
30031
+ const client = url.protocol === "https:" ? https2 : http2;
30032
+ const request = client.request(url, { method: "POST", headers: { ...headers, "content-length": Buffer.byteLength(payload) }, signal: controller.signal }, (response) => {
30033
+ clearTimeout(connectTimer);
30034
+ resolve20({ response, cleanup: () => {
30035
+ clearTimeout(totalTimer);
30036
+ clearTimeout(connectTimer);
30037
+ } });
30038
+ });
30039
+ const connectTimer = setTimeout(() => request.destroy(new AiTimeoutError("AI connection timeout expired")), Math.min(config.connectTimeout * 1e3, remainingMs));
30040
+ request.on("socket", (socket) => {
30041
+ if (!socket.connecting) clearTimeout(connectTimer);
30042
+ socket.once(url.protocol === "https:" ? "secureConnect" : "connect", () => clearTimeout(connectTimer));
30043
+ });
30044
+ request.once("error", (error) => {
30045
+ clearTimeout(totalTimer);
30046
+ clearTimeout(connectTimer);
30047
+ if (error instanceof AiError) reject(error);
30048
+ else if (controller.signal.aborted) reject(new AiTimeoutError("AI total request timeout expired"));
30049
+ else reject(new AiHTTPError(`AI transport failed (${error.name})`));
30050
+ });
30051
+ request.end(payload);
30052
+ });
30053
+ }
30054
+ static async readBody(response) {
30055
+ const chunks = [];
30056
+ for await (const chunk of response) chunks.push(Buffer.from(chunk));
30057
+ return Buffer.concat(chunks).toString("utf8");
30058
+ }
30059
+ static retryDelay(headers, deadline) {
30060
+ const value = Array.isArray(headers["retry-after"]) ? headers["retry-after"][0] : headers["retry-after"];
30061
+ const requested = value !== void 0 && Number.isFinite(Number(value)) ? Math.max(0, Number(value) * 1e3) : 100;
30062
+ const delay = Math.min(requested, Math.max(0, deadline - performance.now()));
30063
+ return new Promise((resolve20) => setTimeout(resolve20, delay));
30064
+ }
30065
+ static async requestJson(config, headers, body) {
30066
+ const deadline = performance.now() + config.totalTimeout * 1e3;
30067
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
30068
+ let opened = null;
30069
+ try {
30070
+ opened = await this.open(config, deadline, headers, body);
30071
+ const status2 = opened.response.statusCode ?? 0;
30072
+ const responseHeaders = opened.response.headers;
30073
+ const raw = await this.readBody(opened.response);
30074
+ opened.cleanup();
30075
+ opened = null;
30076
+ if (status2 < 200 || status2 >= 300) {
30077
+ if ((status2 === 429 || status2 >= 500) && attempt < config.maxRetries) {
30078
+ await this.retryDelay(responseHeaders, deadline);
30079
+ continue;
30080
+ }
30081
+ throw new AiHTTPError(`AI provider returned HTTP ${status2}`, status2);
30082
+ }
30083
+ let parsed;
30084
+ try {
30085
+ parsed = JSON.parse(raw);
30086
+ } catch {
30087
+ throw new AiParseError("AI provider returned malformed JSON");
30088
+ }
30089
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new AiParseError("AI provider returned a non-object JSON response");
30090
+ return parsed;
30091
+ } catch (error) {
30092
+ opened?.cleanup();
30093
+ if (error instanceof AiParseError || error instanceof AiHTTPError && error.status !== null) throw error;
30094
+ if (attempt >= config.maxRetries) throw error;
30095
+ }
30096
+ }
30097
+ throw new AiHTTPError("AI request failed");
30098
+ }
30099
+ static normalizeChat(provider, raw) {
30100
+ try {
30101
+ if (provider === "anthropic") {
30102
+ const content = raw.content;
30103
+ const parts = content.filter((item) => (item.type ?? "text") === "text").map((item) => item.text);
30104
+ if (!parts.length || !parts.every((part) => typeof part === "string")) throw new Error();
30105
+ const usage2 = raw.usage ?? {};
30106
+ const promptTokens = Number(usage2.input_tokens ?? 0);
30107
+ const completionTokens = Number(usage2.output_tokens ?? 0);
30108
+ return { text: parts.join(""), model: String(raw.model ?? ""), usage: { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens }, finishReason: raw.stop_reason == null ? null : String(raw.stop_reason), raw };
30109
+ }
30110
+ const choice = raw.choices[0];
30111
+ const text = choice.message.content;
30112
+ if (typeof text !== "string") throw new Error();
30113
+ const usage = raw.usage ?? {};
30114
+ return { text, model: String(raw.model ?? ""), usage: { promptTokens: Number(usage.prompt_tokens ?? 0), completionTokens: Number(usage.completion_tokens ?? 0), totalTokens: Number(usage.total_tokens ?? 0) }, finishReason: choice.finish_reason == null ? null : String(choice.finish_reason), raw };
30115
+ } catch {
30116
+ throw new AiParseError("AI provider returned a malformed chat response");
30117
+ }
30118
+ }
30119
+ static async chatResponse(config, headers, body) {
30120
+ return this.normalizeChat(config.provider, await this.requestJson(config, headers, body));
30121
+ }
30122
+ static streamDelta(provider, data) {
30123
+ if (data === "[DONE]") return { completed: true };
30124
+ let event;
30125
+ try {
30126
+ event = JSON.parse(data);
30127
+ } catch {
30128
+ throw new AiParseError("AI provider returned malformed stream data");
30129
+ }
30130
+ const text = provider === "anthropic" ? event.type === "content_block_delta" ? event.delta?.text : void 0 : event.choices?.[0]?.delta?.content;
30131
+ if (text !== void 0 && text !== null && typeof text !== "string") throw new AiParseError("AI provider returned malformed stream data");
30132
+ return { completed: false, text };
30133
+ }
30134
+ static async *streamData(response) {
30135
+ let buffer = "";
30136
+ for await (const chunk of response) {
30137
+ buffer += Buffer.from(chunk).toString("utf8");
30138
+ let newline;
30139
+ while ((newline = buffer.indexOf("\n")) >= 0) {
30140
+ const line = buffer.slice(0, newline).trim();
30141
+ buffer = buffer.slice(newline + 1);
30142
+ if (line.startsWith("data:")) yield line.slice(5).trim();
30143
+ }
30144
+ }
30145
+ }
30146
+ static streamError(error) {
30147
+ if (error instanceof AiError) return error;
30148
+ if (error instanceof Error && error.name === "AbortError") return new AiTimeoutError("AI total request timeout expired");
30149
+ return new AiHTTPError(`AI transport failed (${error instanceof Error ? error.name : "Error"})`);
30150
+ }
30151
+ static async *streamRequest(config, headers, body) {
30152
+ const deadline = performance.now() + config.totalTimeout * 1e3;
30153
+ let yielded = false;
30154
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
30155
+ let opened = null;
30156
+ try {
30157
+ opened = await this.open(config, deadline, { ...headers, accept: "text/event-stream" }, body);
30158
+ const status2 = opened.response.statusCode ?? 0;
30159
+ if (status2 < 200 || status2 >= 300) {
30160
+ await this.readBody(opened.response);
30161
+ if ((status2 === 429 || status2 >= 500) && attempt < config.maxRetries) {
30162
+ await this.retryDelay(opened.response.headers, deadline);
30163
+ opened.cleanup();
30164
+ opened = null;
30165
+ continue;
30166
+ }
30167
+ throw new AiHTTPError(`AI provider returned HTTP ${status2}`, status2);
30168
+ }
30169
+ let completed = false;
30170
+ for await (const data of this.streamData(opened.response)) {
30171
+ const delta = this.streamDelta(config.provider, data);
30172
+ if (delta.completed) {
30173
+ completed = true;
30174
+ break;
30175
+ }
30176
+ if (delta.text === void 0) continue;
30177
+ yielded = true;
30178
+ yield delta.text;
30179
+ }
30180
+ opened.cleanup();
30181
+ opened = null;
30182
+ if (completed) return;
30183
+ throw new AiParseError("AI provider stream ended before [DONE]");
30184
+ } catch (error) {
30185
+ opened?.cleanup();
30186
+ const failure = this.streamError(error);
30187
+ if (failure instanceof AiParseError || failure instanceof AiHTTPError && failure.status !== null || yielded || attempt >= config.maxRetries) throw failure;
30188
+ }
30189
+ }
30190
+ }
30191
+ };
30192
+ }
30193
+ });
30194
+
30185
30195
  // ../core/src/queueBackends/rabbitmqBackend.ts
30186
30196
  import { execFileSync as execFileSync5 } from "node:child_process";
30187
30197
  import { randomUUID as randomUUID8 } from "node:crypto";
@@ -31885,6 +31895,12 @@ __export(src_exports2, {
31885
31895
  APPLICATION_JSON: () => APPLICATION_JSON,
31886
31896
  APPLICATION_OCTET: () => APPLICATION_OCTET,
31887
31897
  APPLICATION_XML: () => APPLICATION_XML,
31898
+ Ai: () => Ai,
31899
+ AiConfigError: () => AiConfigError,
31900
+ AiError: () => AiError,
31901
+ AiHTTPError: () => AiHTTPError,
31902
+ AiParseError: () => AiParseError,
31903
+ AiTimeoutError: () => AiTimeoutError,
31888
31904
  Api: () => Api,
31889
31905
  Auth: () => Auth,
31890
31906
  CANONICAL_SESSION_BACKENDS: () => CANONICAL_SESSION_BACKENDS,
@@ -32212,6 +32228,7 @@ var init_src2 = __esm({
32212
32228
  init_htmlElement();
32213
32229
  init_errorOverlay();
32214
32230
  init_ai();
32231
+ init_aiClient();
32215
32232
  init_liteBackend();
32216
32233
  init_rabbitmqBackend();
32217
32234
  init_kafkaBackend();
@@ -37650,7 +37667,7 @@ var init_database = __esm({
37650
37667
 
37651
37668
  // src/model.ts
37652
37669
  import { readdirSync as readdirSync17, statSync as statSync17 } from "node:fs";
37653
- import { join as join29, extname as extname8 } from "node:path";
37670
+ import { join as join29, extname as extname7 } from "node:path";
37654
37671
  async function discoverModels(modelsDir) {
37655
37672
  const models = [];
37656
37673
  let files;
@@ -37663,7 +37680,7 @@ async function discoverModels(modelsDir) {
37663
37680
  const filePath = join29(modelsDir, file);
37664
37681
  const stat = statSync17(filePath);
37665
37682
  if (!stat.isFile()) continue;
37666
- const ext = extname8(file);
37683
+ const ext = extname7(file);
37667
37684
  if (ext !== ".ts" && ext !== ".js") continue;
37668
37685
  try {
37669
37686
  const moduleUrl = `file://${filePath}?t=${Date.now()}`;
@@ -37702,7 +37719,7 @@ var init_model = __esm({
37702
37719
  });
37703
37720
 
37704
37721
  // src/migration.ts
37705
- import { existsSync as existsSync26, readdirSync as readdirSync18, readFileSync as readFileSync26, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "node:fs";
37722
+ import { existsSync as existsSync26, readdirSync as readdirSync18, readFileSync as readFileSync25, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "node:fs";
37706
37723
  import { join as join30, resolve as resolve18 } from "node:path";
37707
37724
  function unwrapAdapter(db) {
37708
37725
  let cur = db;
@@ -38030,7 +38047,7 @@ async function rollback(migrationsDir, delimiter2) {
38030
38047
  `Cannot rollback ${migration.migration_name}: no .down.sql file found`
38031
38048
  );
38032
38049
  }
38033
- const sqlContent = readFileSync26(downPath, "utf-8").trim();
38050
+ const sqlContent = readFileSync25(downPath, "utf-8").trim();
38034
38051
  if (sqlContent) {
38035
38052
  const statements = splitStatements(sqlContent, delim);
38036
38053
  try {
@@ -38228,7 +38245,7 @@ async function migrate(adapter, options) {
38228
38245
  result.skipped.push(file);
38229
38246
  continue;
38230
38247
  }
38231
- const sqlContent = readFileSync26(join30(dir, file), "utf-8").trim();
38248
+ const sqlContent = readFileSync25(join30(dir, file), "utf-8").trim();
38232
38249
  if (!sqlContent) {
38233
38250
  result.skipped.push(file);
38234
38251
  continue;
@@ -41919,7 +41936,7 @@ var init_attachment = __esm({
41919
41936
 
41920
41937
  // src/realtime/storage.ts
41921
41938
  import { randomBytes as randomBytes9 } from "node:crypto";
41922
- import { mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync17, unlinkSync as unlinkSync8, statSync as statSync18 } from "node:fs";
41939
+ import { mkdirSync as mkdirSync20, readFileSync as readFileSync26, writeFileSync as writeFileSync17, unlinkSync as unlinkSync8, statSync as statSync18 } from "node:fs";
41923
41940
  import { resolve as resolve19, sep as sep5 } from "node:path";
41924
41941
  import { createRequire as createRequire8 } from "node:module";
41925
41942
  function storageKey(filename = "") {
@@ -41972,7 +41989,7 @@ var init_storage = __esm({
41972
41989
  }
41973
41990
  get(key) {
41974
41991
  try {
41975
- return readFileSync27(this.pathFor(key));
41992
+ return readFileSync26(this.pathFor(key));
41976
41993
  } catch {
41977
41994
  return null;
41978
41995
  }