tina4-nodejs 3.13.100 → 3.13.103

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.
@@ -1463,6 +1463,7 @@ __export(engine_exports, {
1463
1463
  Frond: () => Frond,
1464
1464
  MEMO_CACHE_MAX: () => MEMO_CACHE_MAX,
1465
1465
  TEMPLATE_CACHE_MAX: () => TEMPLATE_CACHE_MAX,
1466
+ expressionFormCache: () => expressionFormCache,
1466
1467
  filterChainCache: () => filterChainCache,
1467
1468
  pathParseCache: () => pathParseCache,
1468
1469
  setFormTokenSessionId: () => setFormTokenSessionId
@@ -1871,62 +1872,58 @@ function splitOutsideQuotes(expr, sep6) {
1871
1872
  parts.push(expr.slice(currentStart));
1872
1873
  return parts;
1873
1874
  }
1874
- function evalExpr(expr, context) {
1875
- expr = expr.trim();
1876
- if (expr.length >= 2) {
1877
- const q = expr[0];
1878
- if ((q === '"' || q === "'") && expr.endsWith(q) && !expr.slice(1, -1).includes(q)) {
1879
- return expr.slice(1, -1);
1880
- }
1881
- }
1882
- if (expr.length >= 2 && expr[0] === "(" && expr.endsWith(")")) {
1883
- let depth = 0;
1884
- let matched = true;
1885
- for (let pi = 0; pi < expr.length; pi++) {
1886
- if (expr[pi] === "(") depth++;
1887
- else if (expr[pi] === ")") depth--;
1888
- if (depth === 0 && pi < expr.length - 1) {
1889
- matched = false;
1890
- break;
1891
- }
1892
- }
1893
- if (matched) {
1894
- return evalExpr(expr.slice(1, -1), context);
1895
- }
1875
+ function parenthesizedInner(expr) {
1876
+ if (expr.length < 2 || expr[0] !== "(" || !expr.endsWith(")")) return null;
1877
+ let depth = 0;
1878
+ for (let index = 0; index < expr.length; index++) {
1879
+ if (expr[index] === "(") depth++;
1880
+ else if (expr[index] === ")") depth--;
1881
+ if (depth === 0 && index < expr.length - 1) return null;
1896
1882
  }
1897
- const ternaryIdx = findTernary(expr);
1898
- if (ternaryIdx !== -1) {
1899
- const condPart = expr.slice(0, ternaryIdx).trim();
1900
- const rest = expr.slice(ternaryIdx + 1);
1901
- const colonIdx = findColon(rest);
1902
- if (colonIdx !== -1) {
1903
- const truePart = rest.slice(0, colonIdx).trim();
1904
- const falsePart = rest.slice(colonIdx + 1).trim();
1905
- const cond = evalExpr(condPart, context);
1906
- return cond ? evalExpr(truePart, context) : evalExpr(falsePart, context);
1907
- }
1883
+ return expr.slice(1, -1);
1884
+ }
1885
+ function evalPrimary(expr, context) {
1886
+ const quote = expr[0];
1887
+ if (expr.length >= 2 && (quote === '"' || quote === "'") && expr.endsWith(quote) && !expr.slice(1, -1).includes(quote)) {
1888
+ return expr.slice(1, -1);
1908
1889
  }
1890
+ const inner = parenthesizedInner(expr);
1891
+ if (inner !== null) return evalExpr(inner, context);
1892
+ return EXPR_NOT_MATCHED;
1893
+ }
1894
+ function evalTernaryExpression(expr, context) {
1895
+ const ternaryIdx = findTernary(expr);
1896
+ if (ternaryIdx === -1) return EXPR_NOT_MATCHED;
1897
+ const rest = expr.slice(ternaryIdx + 1);
1898
+ const colonIdx = findColon(rest);
1899
+ if (colonIdx === -1) return EXPR_NOT_MATCHED;
1900
+ const condition = evalExpr(expr.slice(0, ternaryIdx).trim(), context);
1901
+ const branch = condition ? rest.slice(0, colonIdx) : rest.slice(colonIdx + 1);
1902
+ return evalExpr(branch.trim(), context);
1903
+ }
1904
+ function evalInlineIfExpression(expr, context) {
1909
1905
  const ifIdx = findOutsideQuotes(expr, " if ");
1910
- if (ifIdx >= 0) {
1911
- const elseIdx = findOutsideQuotes(expr, " else ");
1912
- if (elseIdx >= 0 && elseIdx > ifIdx) {
1913
- const valuePart = expr.slice(0, ifIdx).trim();
1914
- const condPart = expr.slice(ifIdx + 4, elseIdx).trim();
1915
- const elsePart = expr.slice(elseIdx + 6).trim();
1916
- const cond = evalExpr(condPart, context);
1917
- return cond ? evalExpr(valuePart, context) : evalExpr(elsePart, context);
1918
- }
1919
- }
1906
+ if (ifIdx < 0) return EXPR_NOT_MATCHED;
1907
+ const elseIdx = findOutsideQuotes(expr, " else ");
1908
+ if (elseIdx < 0 || elseIdx <= ifIdx) return EXPR_NOT_MATCHED;
1909
+ const condition = evalExpr(expr.slice(ifIdx + 4, elseIdx).trim(), context);
1910
+ const branch = condition ? expr.slice(0, ifIdx) : expr.slice(elseIdx + 6);
1911
+ return evalExpr(branch.trim(), context);
1912
+ }
1913
+ function evalCoalesceExpression(expr, context) {
1920
1914
  const qqIdx = findOutsideQuotes(expr, "??");
1921
- if (qqIdx !== -1) {
1922
- const left = expr.slice(0, qqIdx).trim();
1923
- const right = expr.slice(qqIdx + 2).trim();
1924
- const val = evalExpr(left, context);
1925
- if (val === null || val === void 0) {
1926
- return evalExpr(right, context);
1927
- }
1928
- return val;
1915
+ if (qqIdx === -1) return EXPR_NOT_MATCHED;
1916
+ const value = evalExpr(expr.slice(0, qqIdx).trim(), context);
1917
+ return value === null || value === void 0 ? evalExpr(expr.slice(qqIdx + 2).trim(), context) : value;
1918
+ }
1919
+ function evalConditional(expr, context) {
1920
+ for (const evaluator of [evalTernaryExpression, evalInlineIfExpression, evalCoalesceExpression]) {
1921
+ const result = evaluator(expr, context);
1922
+ if (result !== EXPR_NOT_MATCHED) return result;
1929
1923
  }
1924
+ return EXPR_NOT_MATCHED;
1925
+ }
1926
+ function evalConcatOrComparison(expr, context) {
1930
1927
  if (findOutsideQuotes(expr, "~") >= 0) {
1931
1928
  const parts = splitOutsideQuotes(expr, "~");
1932
1929
  if (parts.length > 1) {
@@ -1944,6 +1941,9 @@ function evalExpr(expr, context) {
1944
1941
  return evalComparison(expr, context);
1945
1942
  }
1946
1943
  }
1944
+ return EXPR_NOT_MATCHED;
1945
+ }
1946
+ function evalArithmeticExpression(expr, context) {
1947
1947
  for (const op of [" + ", " - ", " * ", " // ", " / ", " % ", " ** "]) {
1948
1948
  const pos = findOutsideQuotes(expr, op);
1949
1949
  if (pos >= 0) {
@@ -1956,40 +1956,15 @@ function evalExpr(expr, context) {
1956
1956
  let rNum = rVal != null ? Number(rVal) : 0;
1957
1957
  if (isNaN(lNum)) lNum = 0;
1958
1958
  if (isNaN(rNum)) rNum = 0;
1959
- const opS = op.trim();
1960
- const bothInt = Number.isInteger(lNum) && Number.isInteger(rNum) && opS !== "/";
1961
- let result;
1962
- switch (opS) {
1963
- case "+":
1964
- result = lNum + rNum;
1965
- break;
1966
- case "-":
1967
- result = lNum - rNum;
1968
- break;
1969
- case "*":
1970
- result = lNum * rNum;
1971
- break;
1972
- case "//":
1973
- result = rNum !== 0 ? Math.floor(lNum / rNum) : 0;
1974
- break;
1975
- case "/":
1976
- result = rNum !== 0 ? lNum / rNum : 0;
1977
- break;
1978
- case "%":
1979
- result = rNum !== 0 ? lNum % rNum : 0;
1980
- break;
1981
- case "**":
1982
- result = lNum ** rNum;
1983
- break;
1984
- default:
1985
- result = 0;
1986
- }
1987
- return bothInt && Number.isInteger(result) ? result : result;
1959
+ return ARITHMETIC_OPERATIONS[op.trim()](lNum, rNum);
1988
1960
  } catch {
1989
1961
  return null;
1990
1962
  }
1991
1963
  }
1992
1964
  }
1965
+ return EXPR_NOT_MATCHED;
1966
+ }
1967
+ function evalFilterExpression(expr, context) {
1993
1968
  if (findOutsideQuotes(expr, "|") >= 0) {
1994
1969
  const [baseExpr, filters] = parseFilterChain(expr);
1995
1970
  if (filters.length > 0) {
@@ -2008,38 +1983,49 @@ function evalExpr(expr, context) {
2008
1983
  return value;
2009
1984
  }
2010
1985
  }
2011
- const fnMatch = expr.match(FN_CALL_RE);
2012
- if (fnMatch) {
2013
- const fnName = fnMatch[1];
2014
- const rawArgs = fnMatch[2] || "";
2015
- if (fnName.includes(".")) {
2016
- const lastDot = fnName.lastIndexOf(".");
2017
- const objPath = fnName.slice(0, lastDot);
2018
- const methodName = fnName.slice(lastDot + 1);
2019
- const obj = resolveVar(objPath, context);
2020
- if (obj && typeof obj === "object" && methodName in obj) {
2021
- const method = obj[methodName];
2022
- if (typeof method === "function") {
2023
- if (rawArgs.trim()) {
2024
- const parts = splitArgs(rawArgs);
2025
- const evalArgs = parts.map((a) => evalExpr(a.trim(), context));
2026
- return method.apply(obj, evalArgs);
2027
- }
2028
- return method.call(obj);
2029
- }
2030
- }
2031
- } else {
2032
- const fn = context[fnName] ?? resolveVar(fnName, context);
2033
- if (typeof fn === "function") {
2034
- if (rawArgs.trim()) {
2035
- const parts = splitArgs(rawArgs);
2036
- const evalArgs = parts.map((a) => evalExpr(a.trim(), context));
2037
- return fn(...evalArgs);
2038
- }
2039
- return fn();
2040
- }
1986
+ return EXPR_NOT_MATCHED;
1987
+ }
1988
+ function evaluateCallArgs(rawArgs, context) {
1989
+ return rawArgs.trim() ? splitArgs(rawArgs).map((arg) => evalExpr(arg.trim(), context)) : [];
1990
+ }
1991
+ function evalDottedFunction(name, rawArgs, context) {
1992
+ const lastDot = name.lastIndexOf(".");
1993
+ const owner = resolveVar(name.slice(0, lastDot), context);
1994
+ const member = name.slice(lastDot + 1);
1995
+ if (!owner || typeof owner !== "object" || !(member in owner)) {
1996
+ return EXPR_NOT_MATCHED;
1997
+ }
1998
+ const method = owner[member];
1999
+ return typeof method === "function" ? method.apply(owner, evaluateCallArgs(rawArgs, context)) : EXPR_NOT_MATCHED;
2000
+ }
2001
+ function evalFunctionExpression(expr, context) {
2002
+ const match = expr.match(FN_CALL_RE);
2003
+ if (!match) return EXPR_NOT_MATCHED;
2004
+ const name = match[1];
2005
+ const rawArgs = match[2] || "";
2006
+ if (name.includes(".")) return evalDottedFunction(name, rawArgs, context);
2007
+ const fn = context[name] ?? resolveVar(name, context);
2008
+ if (typeof fn === "function") return fn(...evaluateCallArgs(rawArgs, context));
2009
+ return EXPR_NOT_MATCHED;
2010
+ }
2011
+ function evalExpr(expr, context) {
2012
+ expr = expr.trim();
2013
+ const cachedForm = expressionFormCache.get(expr);
2014
+ if (cachedForm !== void 0) {
2015
+ if (cachedForm === -1) return resolveVar(expr, context);
2016
+ const result = EXPR_EVALUATORS[cachedForm](expr, context);
2017
+ return result === EXPR_NOT_MATCHED ? resolveVar(expr, context) : result;
2018
+ }
2019
+ for (let index = 0; index < EXPR_EVALUATORS.length; index++) {
2020
+ const result = EXPR_EVALUATORS[index](expr, context);
2021
+ if (result !== EXPR_NOT_MATCHED) {
2022
+ capCache(expressionFormCache, MEMO_CACHE_MAX);
2023
+ expressionFormCache.set(expr, index);
2024
+ return result;
2041
2025
  }
2042
2026
  }
2027
+ capCache(expressionFormCache, MEMO_CACHE_MAX);
2028
+ expressionFormCache.set(expr, FN_CALL_RE.test(expr) ? EXPR_EVALUATORS.length - 1 : -1);
2043
2029
  return resolveVar(expr, context);
2044
2030
  }
2045
2031
  function findTernary(expr) {
@@ -2495,7 +2481,7 @@ function _generateFormToken(descriptor = "") {
2495
2481
  function _generateFormTokenValue(descriptor = "") {
2496
2482
  return new SafeString(_buildFormTokenJwt(descriptor));
2497
2483
  }
2498
- 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;
2484
+ 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, EXPR_NOT_MATCHED, ARITHMETIC_OPERATIONS, EXPR_EVALUATORS, expressionFormCache, VarRef, BUILTIN_FILTERS, _formTokenSessionId, Frond;
2499
2485
  var init_engine = __esm({
2500
2486
  "../frond/src/engine.ts"() {
2501
2487
  "use strict";
@@ -2597,6 +2583,25 @@ var init_engine = __esm({
2597
2583
  MEMO_CACHE_MAX = 1024;
2598
2584
  TOKEN_RE = /(\{%-?\s*[\s\S]*?\s*-?%\})|(\{\{-?\s*[\s\S]*?\s*-?\}\})|(\{#[\s\S]*?#\})/g;
2599
2585
  RAW_BLOCK_RE = /\{%-?\s*raw\s*-?%\}([\s\S]*?)\{%-?\s*endraw\s*-?%\}/g;
2586
+ EXPR_NOT_MATCHED = Symbol("frond-expression-not-matched");
2587
+ ARITHMETIC_OPERATIONS = {
2588
+ "+": (left, right) => left + right,
2589
+ "-": (left, right) => left - right,
2590
+ "*": (left, right) => left * right,
2591
+ "//": (left, right) => right !== 0 ? Math.floor(left / right) : 0,
2592
+ "/": (left, right) => right !== 0 ? left / right : 0,
2593
+ "%": (left, right) => right !== 0 ? left % right : 0,
2594
+ "**": (left, right) => left ** right
2595
+ };
2596
+ EXPR_EVALUATORS = [
2597
+ evalPrimary,
2598
+ evalConditional,
2599
+ evalConcatOrComparison,
2600
+ evalArithmeticExpression,
2601
+ evalFilterExpression,
2602
+ evalFunctionExpression
2603
+ ];
2604
+ expressionFormCache = /* @__PURE__ */ new Map();
2600
2605
  VarRef = class {
2601
2606
  constructor(name) {
2602
2607
  this.name = name;
@@ -19567,14 +19572,14 @@ async function discoverRoutes(routesDir) {
19567
19572
  const currentMtime = statSync7(filePath).mtimeMs;
19568
19573
  if (_seenFiles.has(filePath) && _seenMtimes.get(filePath) === currentMtime) continue;
19569
19574
  const method = name.toUpperCase();
19570
- const relativePath3 = relative(routesDir, filePath);
19571
- const pattern = filePathToPattern(relativePath3);
19575
+ const relativePath2 = relative(routesDir, filePath);
19576
+ const pattern = filePathToPattern(relativePath2);
19572
19577
  try {
19573
19578
  const moduleUrl = `file://${filePath}?t=${currentMtime}`;
19574
19579
  const mod = await import(moduleUrl);
19575
19580
  const handler = mod.default ?? mod.handler;
19576
19581
  if (typeof handler !== "function") {
19577
- console.warn(` Warning: ${relativePath3} does not export a handler function, skipping`);
19582
+ console.warn(` Warning: ${relativePath2} does not export a handler function, skipping`);
19578
19583
  continue;
19579
19584
  }
19580
19585
  const meta = mod.meta;
@@ -19586,7 +19591,7 @@ async function discoverRoutes(routesDir) {
19586
19591
  _seenMtimes.set(filePath, currentMtime);
19587
19592
  registeredFromThisScan++;
19588
19593
  } catch (err) {
19589
- console.error(` Error loading route ${relativePath3}:`, err);
19594
+ console.error(` Error loading route ${relativePath2}:`, err);
19590
19595
  recordBrokenImport(filePath, err);
19591
19596
  }
19592
19597
  }
@@ -19620,8 +19625,8 @@ function recordBrokenImport(filePath, error) {
19620
19625
  } catch {
19621
19626
  }
19622
19627
  }
19623
- function filePathToPattern(relativePath3) {
19624
- const parts = relativePath3.replace(/\\/g, "/").split("/").slice(0, -1);
19628
+ function filePathToPattern(relativePath2) {
19629
+ const parts = relativePath2.replace(/\\/g, "/").split("/").slice(0, -1);
19625
19630
  const urlParts = parts.map((part) => {
19626
19631
  if (part.startsWith("[...") && part.endsWith("]")) {
19627
19632
  const name = part.slice(4, -1);
@@ -22878,508 +22883,135 @@ import * as fs3 from "node:fs";
22878
22883
  import * as path2 from "node:path";
22879
22884
  import { spawnSync } from "node:child_process";
22880
22885
  import { fileURLToPath as fileURLToPath2 } from "node:url";
22881
- function walkFiles(dir, extensions, exclude = ["node_modules", ".git", "dist", "build"]) {
22882
- const results = [];
22883
- if (!fs3.existsSync(dir)) return results;
22884
- const entries = fs3.readdirSync(dir, { withFileTypes: true });
22885
- for (const entry of entries) {
22886
- const fullPath = path2.join(dir, entry.name);
22887
- if (entry.isDirectory()) {
22888
- if (!exclude.includes(entry.name)) {
22889
- results.push(...walkFiles(fullPath, extensions, exclude));
22890
- }
22891
- } else if (entry.isFile()) {
22892
- const ext = path2.extname(entry.name);
22893
- if (extensions.includes(ext) && !entry.name.endsWith(".d.ts")) {
22894
- results.push(fullPath);
22895
- }
22896
- }
22897
- }
22898
- return results;
22899
- }
22900
- function readFileSafe(filePath) {
22901
- try {
22902
- return fs3.readFileSync(filePath, "utf-8");
22903
- } catch {
22904
- return null;
22905
- }
22906
- }
22907
- function relativePath(filePath, root = ".") {
22908
- return path2.relative(root, filePath);
22909
- }
22910
- function countLines(source) {
22911
- const lines = source.split("\n");
22912
- let loc = 0;
22913
- let blank = 0;
22914
- let comment = 0;
22915
- let inBlockComment = false;
22916
- for (const line of lines) {
22917
- const stripped = line.trim();
22918
- if (!stripped) {
22919
- blank++;
22920
- continue;
22921
- }
22922
- if (inBlockComment) {
22923
- comment++;
22924
- if (stripped.includes("*/")) {
22925
- inBlockComment = false;
22926
- }
22927
- continue;
22928
- }
22929
- if (stripped.startsWith("/*")) {
22930
- comment++;
22931
- if (!stripped.includes("*/") || stripped.endsWith("/*")) {
22932
- inBlockComment = true;
22933
- }
22934
- continue;
22935
- }
22936
- if (stripped.startsWith("//")) {
22937
- comment++;
22938
- continue;
22939
- }
22940
- loc++;
22941
- }
22942
- return { loc, blank, comment };
22943
- }
22944
- function stripLiterals(source) {
22945
- const out = [];
22946
- const n = source.length;
22947
- let i = 0;
22948
- let prevSignificant = "";
22949
- let prevWord = "";
22950
- const regexKeywords = /* @__PURE__ */ new Set([
22951
- "return",
22952
- "typeof",
22953
- "instanceof",
22954
- "in",
22955
- "of",
22956
- "new",
22957
- "delete",
22958
- "void",
22959
- "throw",
22960
- "case",
22961
- "do",
22962
- "else",
22963
- "yield",
22964
- "await"
22965
- ]);
22966
- function prevEndsExpression() {
22967
- if (prevSignificant === "") return false;
22968
- if (/[A-Za-z0-9_$]/.test(prevSignificant)) {
22969
- return !regexKeywords.has(prevWord);
22970
- }
22971
- if (prevSignificant === ")" || prevSignificant === "]") return true;
22972
- if (prevSignificant === ".") return true;
22973
- return false;
22974
- }
22975
- while (i < n) {
22976
- const ch = source[i];
22977
- const next = i + 1 < n ? source[i + 1] : "";
22978
- if (ch === "/" && next === "/") {
22979
- out.push("//");
22980
- i += 2;
22981
- while (i < n && source[i] !== "\n") {
22982
- out.push(" ");
22983
- i++;
22984
- }
22985
- continue;
22986
- }
22987
- if (ch === "/" && next === "*") {
22988
- out.push("/*");
22989
- i += 2;
22990
- while (i < n && !(source[i] === "*" && source[i + 1] === "/")) {
22991
- out.push(source[i] === "\n" ? "\n" : " ");
22992
- i++;
22993
- }
22994
- if (i < n) {
22995
- out.push("*/");
22996
- i += 2;
22997
- }
22998
- continue;
22999
- }
23000
- if (ch === '"' || ch === "'") {
23001
- const quote = ch;
23002
- out.push(quote);
23003
- i++;
23004
- while (i < n && source[i] !== quote) {
23005
- if (source[i] === "\\" && i + 1 < n) {
23006
- out.push(" ");
23007
- i += 2;
23008
- continue;
23009
- }
23010
- if (source[i] === "\n") {
23011
- out.push("\n");
23012
- i++;
23013
- break;
23014
- }
23015
- out.push(" ");
23016
- i++;
23017
- }
23018
- if (i < n && source[i] === quote) {
23019
- out.push(quote);
23020
- i++;
23021
- }
23022
- prevSignificant = quote;
23023
- prevWord = "";
23024
- continue;
23025
- }
23026
- if (ch === "`") {
23027
- out.push("`");
23028
- i++;
23029
- while (i < n && source[i] !== "`") {
23030
- if (source[i] === "\\" && i + 1 < n) {
23031
- out.push(source[i + 1] === "\n" ? " \n" : " ");
23032
- i += 2;
23033
- continue;
23034
- }
23035
- if (source[i] === "$" && source[i + 1] === "{") {
23036
- out.push("${");
23037
- i += 2;
23038
- let depth = 1;
23039
- const exprStart = i;
23040
- while (i < n && depth > 0) {
23041
- if (source[i] === "{") depth++;
23042
- else if (source[i] === "}") depth--;
23043
- if (depth === 0) break;
23044
- i++;
23045
- }
23046
- out.push(stripLiterals(source.slice(exprStart, i)));
23047
- if (i < n && source[i] === "}") {
23048
- out.push("}");
23049
- i++;
23050
- }
23051
- continue;
23052
- }
23053
- out.push(source[i] === "\n" ? "\n" : " ");
23054
- i++;
23055
- }
23056
- if (i < n && source[i] === "`") {
23057
- out.push("`");
23058
- i++;
23059
- }
23060
- prevSignificant = "`";
23061
- prevWord = "";
23062
- continue;
23063
- }
23064
- if (ch === "/" && !prevEndsExpression()) {
23065
- let j = i + 1;
23066
- let ok = false;
23067
- let inClass = false;
23068
- while (j < n) {
23069
- const c = source[j];
23070
- if (c === "\\") {
23071
- j += 2;
23072
- continue;
23073
- }
23074
- if (c === "\n") break;
23075
- if (c === "[") inClass = true;
23076
- else if (c === "]") inClass = false;
23077
- else if (c === "/" && !inClass) {
23078
- ok = true;
23079
- break;
23080
- }
23081
- j++;
23082
- }
23083
- if (ok) {
23084
- out.push("/");
23085
- for (let k = i + 1; k < j; k++) out.push(" ");
23086
- out.push("/");
23087
- i = j + 1;
23088
- while (i < n && /[a-z]/i.test(source[i])) {
23089
- out.push(source[i]);
23090
- i++;
23091
- }
23092
- prevSignificant = "/";
23093
- prevWord = "";
23094
- continue;
23095
- }
23096
- }
23097
- out.push(ch);
23098
- if (!/\s/.test(ch)) {
23099
- prevSignificant = ch;
23100
- if (/[A-Za-z0-9_$]/.test(ch)) {
23101
- prevWord = /[A-Za-z0-9_$]/.test(source[i - 1] ?? "") ? prevWord + ch : ch;
23102
- } else {
23103
- prevWord = "";
23104
- }
23105
- }
23106
- i++;
22886
+ function containsTypeScript(directory) {
22887
+ if (!fs3.existsSync(directory) || !fs3.statSync(directory).isDirectory()) return false;
22888
+ for (const entry of fs3.readdirSync(directory, { withFileTypes: true })) {
22889
+ if (["node_modules", ".git", "dist", "build"].includes(entry.name)) continue;
22890
+ const target = path2.join(directory, entry.name);
22891
+ if (entry.isDirectory() ? containsTypeScript(target) : /\.[cm]?[jt]sx?$/.test(entry.name)) return true;
23107
22892
  }
23108
- return out.join("");
23109
- }
23110
- function countClassesQuick(source) {
23111
- const matches = source.match(
23112
- /(?:^|\n)\s*(?:export\s+)?(?:abstract\s+)?class\s+\w+/g
23113
- );
23114
- return matches ? matches.length : 0;
23115
- }
23116
- function countFunctionsQuick(source) {
23117
- const clean = stripLiterals(source);
23118
- let count = 0;
23119
- const funcDecls = clean.match(
23120
- /(?:^|\n)\s*(?:export\s+)?(?:async\s+)?function\s+\w+\s*\(/g
23121
- );
23122
- if (funcDecls) count += funcDecls.length;
23123
- const methods = clean.match(
23124
- /(?:^|\n)\s*(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*\([^)]*\)\s*(?::\s*\S+)?\s*\{/g
23125
- );
23126
- if (methods) count += methods.length;
23127
- const arrows = clean.match(
23128
- /(?:^|\n)\s*(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*(?:async\s+)?\(/g
23129
- );
23130
- if (arrows) count += arrows.length;
23131
- return count;
23132
- }
23133
- function resolveRoot(root = "src") {
23134
- const rootPath = path2.resolve(root);
23135
- if (fs3.existsSync(rootPath) && walkFiles(rootPath, [".ts", ".js"]).length > 0) {
23136
- _lastScanRoot = rootPath;
23137
- return root;
23138
- }
23139
- const fwDir = path2.resolve(path2.dirname(new URL(import.meta.url).pathname));
23140
- _lastScanRoot = fwDir;
23141
- return fwDir;
23142
- }
23143
- function quickMetrics(root = "src") {
23144
- root = resolveRoot(root);
23145
- const rootPath = path2.resolve(root);
23146
- if (!fs3.existsSync(rootPath)) {
23147
- return { error: `Directory not found: ${root}` };
23148
- }
23149
- const tsFiles = walkFiles(rootPath, [".ts", ".js"]);
23150
- const twigFiles = walkFiles(rootPath, [".twig", ".html"]);
23151
- const migrationsDir = path2.resolve("migrations");
23152
- const migrationFiles = [
23153
- ...walkFiles(migrationsDir, [".sql"]),
23154
- ...walkFiles(migrationsDir, [".ts"])
23155
- ];
23156
- const scssFiles = walkFiles(rootPath, [".scss", ".css"]);
23157
- let totalLoc = 0;
23158
- let totalBlank = 0;
23159
- let totalComment = 0;
23160
- let totalClasses = 0;
23161
- let totalFunctions = 0;
23162
- const fileDetails = [];
23163
- for (const f of tsFiles) {
23164
- const source = readFileSafe(f);
23165
- if (source === null) continue;
23166
- const counts = countLines(source);
23167
- const classes = countClassesQuick(source);
23168
- const functions = countFunctionsQuick(source);
23169
- totalLoc += counts.loc;
23170
- totalBlank += counts.blank;
23171
- totalComment += counts.comment;
23172
- totalClasses += classes;
23173
- totalFunctions += functions;
23174
- fileDetails.push({
23175
- path: relativePath(f, rootPath),
23176
- loc: counts.loc,
23177
- blank: counts.blank,
23178
- comment: counts.comment,
23179
- classes,
23180
- functions
23181
- });
23182
- }
23183
- fileDetails.sort((a, b) => b.loc - a.loc);
23184
- let routeCount = 0;
23185
- let ormCount = 0;
23186
- for (const f of tsFiles) {
23187
- const source = readFileSafe(f);
23188
- if (source === null) continue;
23189
- const routes = source.match(
23190
- /(?:router\s*\.\s*(?:get|post|put|delete|patch|any)\s*\(|@(?:get|post|put|delete|patch)\s*\()/g
23191
- );
23192
- if (routes) routeCount += routes.length;
23193
- const orms = source.match(
23194
- /class\s+\w+\s+extends\s+(?:ORM|Model)\b/g
23195
- );
23196
- if (orms) ormCount += orms.length;
23197
- }
23198
- const breakdown = {
23199
- typescript: tsFiles.filter((f) => f.endsWith(".ts")).length,
23200
- javascript: tsFiles.filter((f) => f.endsWith(".js")).length,
23201
- templates: twigFiles.length,
23202
- migrations: migrationFiles.length,
23203
- stylesheets: scssFiles.length
23204
- };
23205
- return {
23206
- file_count: tsFiles.length,
23207
- total_loc: totalLoc,
23208
- total_blank: totalBlank,
23209
- total_comment: totalComment,
23210
- lloc: totalLoc,
23211
- classes: totalClasses,
23212
- functions: totalFunctions,
23213
- route_count: routeCount,
23214
- orm_count: ormCount,
23215
- template_count: twigFiles.length,
23216
- migration_count: migrationFiles.length,
23217
- avg_file_size: tsFiles.length > 0 ? Math.round(totalLoc / tsFiles.length * 10) / 10 : 0,
23218
- largest_files: fileDetails.slice(0, 10),
23219
- breakdown
23220
- };
22893
+ return false;
23221
22894
  }
23222
- function resolveScanTarget(root = "src") {
23223
- const resolved = resolveRoot(root);
23224
- const frameworkDir = path2.dirname(fileURLToPath2(import.meta.url));
23225
- const real = path2.resolve(resolved);
23226
- const scanningFramework = real === frameworkDir || real.startsWith(frameworkDir);
23227
- return [resolved, scanningFramework ? "framework" : "project"];
22895
+ function resolveTarget(root = "src") {
22896
+ const resolved = containsTypeScript(root) ? path2.resolve(root) : path2.dirname(fileURLToPath2(import.meta.url));
22897
+ const mode = containsTypeScript(root) ? "project" : "framework";
22898
+ lastScanRoot = resolved;
22899
+ return [resolved, mode];
23228
22900
  }
23229
22901
  function enginePath() {
23230
- const names = process.platform === "win32" ? ["tina4.exe", "tina4.cmd", "tina4"] : ["tina4"];
23231
- for (const dir of (process.env.PATH || "").split(path2.delimiter)) {
23232
- if (!dir) continue;
22902
+ const names = process.platform === "win32" ? ["tina4.exe", "tina4"] : ["tina4"];
22903
+ for (const directory of (process.env.PATH || "").split(path2.delimiter)) {
23233
22904
  for (const name of names) {
23234
- const candidate = path2.join(dir, name);
22905
+ const candidate = path2.join(directory, name);
23235
22906
  try {
23236
- if (!fs3.statSync(candidate).isFile()) continue;
23237
22907
  fs3.accessSync(candidate, fs3.constants.X_OK);
22908
+ if (!fs3.statSync(candidate).isFile()) continue;
22909
+ const descriptor = fs3.openSync(candidate, "r");
22910
+ const header = Buffer.alloc(2);
22911
+ fs3.readSync(descriptor, header, 0, 2, 0);
22912
+ fs3.closeSync(descriptor);
22913
+ if (header.toString("latin1") !== "#!") return candidate;
23238
22914
  } catch {
23239
22915
  continue;
23240
22916
  }
23241
- try {
23242
- const fd = fs3.openSync(candidate, "r");
23243
- const buf = Buffer.alloc(2);
23244
- fs3.readSync(fd, buf, 0, 2, 0);
23245
- fs3.closeSync(fd);
23246
- if (buf.toString("latin1") === "#!") continue;
23247
- } catch {
23248
- }
23249
- return candidate;
23250
22917
  }
23251
22918
  }
23252
22919
  return null;
23253
22920
  }
23254
22921
  function runEngine(target) {
23255
22922
  const binary = enginePath();
23256
- if (binary === null) {
23257
- throw new MetricsEngineError(`tina4 not found on PATH - ${INSTALL_HINT}`);
23258
- }
23259
- const proc = spawnSync(binary, ["metrics", "--path", target, "--json"], {
22923
+ if (!binary) throw new MetricsEngineError(`tina4 not found on PATH - ${INSTALL_HINT}`);
22924
+ const processResult = spawnSync(binary, ["metrics", "--path", target, "--json"], {
23260
22925
  encoding: "utf8",
23261
- timeout: TIMEOUT_MS,
22926
+ timeout: 6e4,
23262
22927
  maxBuffer: 64 * 1024 * 1024
23263
22928
  });
23264
- if (proc.error) {
23265
- const err = proc.error;
23266
- if (err.code === "ETIMEDOUT") {
23267
- throw new MetricsEngineError(`tina4 metrics timed out after ${TIMEOUT_MS / 1e3}s on ${target}`);
23268
- }
23269
- throw new MetricsEngineError(`could not run ${binary}: ${err.message}`);
23270
- }
23271
- if (proc.status !== 0) {
23272
- const detail = (proc.stderr || proc.stdout || "").trim().split("\n")[0];
23273
- throw new MetricsEngineError(
23274
- `tina4 metrics failed on ${target}: ${detail || `exit code ${proc.status}`}`
23275
- );
22929
+ if (processResult.error) {
22930
+ throw new MetricsEngineError(`could not run ${binary}: ${processResult.error.message}`);
23276
22931
  }
23277
- if (!proc.stdout || !proc.stdout.trim()) {
23278
- throw new MetricsEngineError(`tina4 metrics produced no output for ${target}`);
22932
+ if (processResult.status !== 0) {
22933
+ const detail = (processResult.stderr || processResult.stdout || "").trim().split("\n")[0];
22934
+ throw new MetricsEngineError(`tina4 metrics failed on ${target}: ${detail || processResult.status}`);
23279
22935
  }
23280
- let payload;
23281
22936
  try {
23282
- payload = JSON.parse(proc.stdout);
23283
- } catch (e) {
23284
- throw new MetricsEngineError(`tina4 metrics returned unreadable JSON: ${e.message}`);
23285
- }
23286
- if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
23287
- throw new MetricsEngineError("tina4 metrics returned a non-object payload");
22937
+ const payload = JSON.parse(processResult.stdout);
22938
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
22939
+ throw new Error("non-object payload");
22940
+ }
22941
+ return payload;
22942
+ } catch (error) {
22943
+ throw new MetricsEngineError(`tina4 metrics returned unreadable JSON: ${error.message}`);
23288
22944
  }
23289
- return payload;
23290
22945
  }
23291
- function requireKey(payload, key, isArray) {
23292
- const value = payload[key];
23293
- const ok = isArray ? Array.isArray(value) : value !== null && typeof value === "object" && !Array.isArray(value);
23294
- if (!ok) {
23295
- throw new MetricsEngineError(
23296
- `engine payload has no usable '${key}' - the installed tina4 CLI predates a field the dashboard renders. Update it: ${INSTALL_HINT}`
23297
- );
22946
+ function requireArray(payload, key) {
22947
+ if (!Array.isArray(payload[key])) {
22948
+ throw new MetricsEngineError(`engine payload has no usable '${key}' - ${INSTALL_HINT}`);
23298
22949
  }
23299
- return value;
22950
+ return payload[key];
23300
22951
  }
23301
22952
  function fullAnalysis(root = "src") {
23302
- const [resolved, scanMode] = resolveScanTarget(root);
22953
+ const [resolved, scanMode] = resolveTarget(root);
23303
22954
  const payload = runEngine(resolved);
23304
- const summary = requireKey(payload, "summary", false);
23305
- const fileMetrics = requireKey(payload, "file_metrics", true);
23306
- const functions = requireKey(payload, "most_complex_functions", true);
23307
- const missing = SUMMARY_KEYS.filter((k) => !(k in summary));
23308
- if (missing.length) {
23309
- throw new MetricsEngineError(
23310
- `engine summary is missing ${missing.join(", ")} - update the CLI: ${INSTALL_HINT}`
23311
- );
23312
- }
23313
- if (fileMetrics.length) {
23314
- const absent = FILE_KEYS.filter((k) => !(k in fileMetrics[0]));
23315
- if (absent.length) throw new MetricsEngineError(`engine file_metrics is missing ${absent.join(", ")}`);
23316
- }
23317
- if (functions.length) {
23318
- const absent = FUNCTION_KEYS.filter((k) => !(k in functions[0]));
23319
- if (absent.length) throw new MetricsEngineError(`engine function metrics are missing ${absent.join(", ")}`);
22955
+ const summary = payload.summary;
22956
+ if (!summary || typeof summary !== "object" || Array.isArray(summary)) {
22957
+ throw new MetricsEngineError(`engine payload has no usable 'summary' - ${INSTALL_HINT}`);
22958
+ }
22959
+ const fileMetrics = requireArray(payload, "file_metrics");
22960
+ const functions = requireArray(payload, "most_complex_functions");
22961
+ const missingSummary = SUMMARY_KEYS.filter((key) => !(key in summary));
22962
+ if (missingSummary.length) throw new MetricsEngineError(`engine summary is missing ${missingSummary.join(", ")}`);
22963
+ const missingFile = fileMetrics.length ? FILE_KEYS.filter((key) => !(key in fileMetrics[0])) : [];
22964
+ if (missingFile.length) throw new MetricsEngineError(`engine file_metrics is missing ${missingFile.join(", ")}`);
22965
+ const missingFunction = functions.length ? FUNCTION_KEYS.filter((key) => !(key in functions[0])) : [];
22966
+ if (missingFunction.length) {
22967
+ throw new MetricsEngineError(`engine function metrics are missing ${missingFunction.join(", ")}`);
23320
22968
  }
23321
- const result = {};
23322
- for (const key of SUMMARY_KEYS) result[key] = summary[key];
23323
- result.file_metrics = fileMetrics;
23324
- result.most_complex_functions = functions.slice(0, 15);
23325
- result.dependency_graph = payload.dependency_graph || {};
23326
- result.scan_mode = scanMode;
23327
- result.scan_root = path2.resolve(resolved);
23328
- result.engine = "tina4-cli";
23329
- return result;
23330
- }
23331
- function offenders(root = "src", top = 20) {
23332
- const [resolved, scanMode] = resolveScanTarget(root);
23333
- const payload = runEngine(resolved);
23334
- const found = requireKey(payload, "offenders", true);
23335
- const summary = { ...requireKey(payload, "summary", false) };
23336
- summary.scan_mode = scanMode;
23337
- summary.scan_root = path2.resolve(resolved);
23338
- summary.engine = "tina4-cli";
23339
- if (summary.total_offenders === void 0) summary.total_offenders = found.length;
23340
- return { offenders: found.slice(0, top), summary };
22969
+ return {
22970
+ ...Object.fromEntries(SUMMARY_KEYS.map((key) => [key, summary[key]])),
22971
+ file_metrics: fileMetrics,
22972
+ most_complex_functions: functions.slice(0, 15),
22973
+ dependency_graph: payload.dependency_graph || {},
22974
+ scan_mode: scanMode,
22975
+ scan_root: resolved,
22976
+ engine: "tina4-cli"
22977
+ };
23341
22978
  }
23342
22979
  function fileDetail(filePath) {
23343
22980
  if (!filePath) throw new MetricsEngineError("fileDetail needs a path");
23344
22981
  let target = filePath;
23345
- if (!fs3.existsSync(target) && _lastScanRoot) {
23346
- const candidate = path2.join(_lastScanRoot, filePath);
23347
- if (fs3.existsSync(candidate)) target = candidate;
23348
- }
22982
+ if (!fs3.existsSync(target) && lastScanRoot) target = path2.join(lastScanRoot, filePath);
23349
22983
  if (!fs3.existsSync(target)) throw new MetricsEngineError(`no such file: ${filePath}`);
23350
22984
  if (fs3.statSync(target).isDirectory()) throw new MetricsEngineError(`not a file: ${filePath}`);
23351
22985
  const payload = runEngine(target);
23352
- const fileMetrics = requireKey(payload, "file_metrics", true);
23353
- if (!fileMetrics.length) {
23354
- throw new MetricsEngineError(`engine reported no metrics for ${filePath}`);
23355
- }
23356
- return { ...fileMetrics[0], engine: "tina4-cli" };
22986
+ const files = requireArray(payload, "file_metrics");
22987
+ if (!files.length) throw new MetricsEngineError(`engine reported no metrics for ${filePath}`);
22988
+ return {
22989
+ ...files[0],
22990
+ function_count: files[0].functions || 0,
22991
+ functions: requireArray(payload, "most_complex_functions"),
22992
+ engine: "tina4-cli"
22993
+ };
23357
22994
  }
23358
- var _lastScanRoot, MetricsEngineError, TIMEOUT_MS, INSTALL_HINT, SUMMARY_KEYS, FILE_KEYS, FUNCTION_KEYS;
22995
+ var lastScanRoot, MetricsEngineError, INSTALL_HINT, SUMMARY_KEYS, FILE_KEYS, FUNCTION_KEYS;
23359
22996
  var init_metrics = __esm({
23360
22997
  "../core/src/metrics.ts"() {
23361
22998
  "use strict";
23362
- _lastScanRoot = "";
22999
+ lastScanRoot = "";
23363
23000
  MetricsEngineError = class extends Error {
23364
23001
  constructor(message) {
23365
23002
  super(message);
23366
23003
  this.name = "MetricsEngineError";
23367
23004
  }
23368
23005
  };
23369
- TIMEOUT_MS = 6e4;
23370
- INSTALL_HINT = [
23371
- "the tina4 CLI provides the metrics engine (ADR-0002). Install it with",
23372
- " curl -fsSL https://tina4.com/install.sh | sh",
23373
- "or see https://tina4.com/cli"
23374
- ].join("\n");
23006
+ INSTALL_HINT = "update the native tina4 CLI: https://tina4.com/cli";
23375
23007
  SUMMARY_KEYS = ["files_analyzed", "total_functions", "avg_complexity", "avg_maintainability"];
23376
- FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "has_tests"];
23008
+ FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "has_referencing_test"];
23377
23009
  FUNCTION_KEYS = ["name", "file", "line", "complexity", "loc"];
23378
23010
  }
23379
23011
  });
23380
23012
 
23381
23013
  // ../core/src/feedback.ts
23382
- import { readFileSync as readFileSync13, existsSync as existsSync14 } from "node:fs";
23014
+ import { readFileSync as readFileSync12, existsSync as existsSync14 } from "node:fs";
23383
23015
  import { dirname as dirname8, join as join19, resolve as resolve10 } from "node:path";
23384
23016
  import { fileURLToPath as fileURLToPath3 } from "node:url";
23385
23017
  function feedbackEnabled() {
@@ -23520,7 +23152,7 @@ var init_feedback = __esm({
23520
23152
  handleFeedbackWidgetJs = (_req, res) => {
23521
23153
  let body;
23522
23154
  if (existsSync14(WIDGET_BUNDLE_PATH)) {
23523
- body = readFileSync13(WIDGET_BUNDLE_PATH);
23155
+ body = readFileSync12(WIDGET_BUNDLE_PATH);
23524
23156
  } else {
23525
23157
  body = "console.warn('tina4-feedback-widget bundle not built yet');";
23526
23158
  }
@@ -23535,7 +23167,7 @@ var init_feedback = __esm({
23535
23167
  });
23536
23168
 
23537
23169
  // ../core/src/version.ts
23538
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "node:fs";
23170
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
23539
23171
  import { dirname as dirname9, join as join20 } from "node:path";
23540
23172
  import { fileURLToPath as fileURLToPath4 } from "node:url";
23541
23173
  function resolveFrameworkVersion() {
@@ -23544,7 +23176,7 @@ function resolveFrameworkVersion() {
23544
23176
  const pkgPath = join20(dir, "package.json");
23545
23177
  if (existsSync15(pkgPath)) {
23546
23178
  try {
23547
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
23179
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
23548
23180
  if (typeof pkg.version === "string" && pkg.version) return pkg.version;
23549
23181
  } catch {
23550
23182
  }
@@ -26332,8 +25964,8 @@ __export(context_exports, {
26332
25964
  fts5Supported: () => fts5Supported
26333
25965
  });
26334
25966
  import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
26335
- import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync16, readdirSync as readdirSync10, realpathSync as realpathSync5 } from "node:fs";
26336
- import { basename as basename5, dirname as dirname11, extname as extname6, isAbsolute as isAbsolute6, join as join22, relative as relative4, resolve as resolve12 } from "node:path";
25967
+ import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync15, readdirSync as readdirSync10, realpathSync as realpathSync5 } from "node:fs";
25968
+ import { basename as basename5, dirname as dirname11, extname as extname5, isAbsolute as isAbsolute6, join as join22, relative as relative3, resolve as resolve12 } from "node:path";
26337
25969
  function fts5Supported() {
26338
25970
  try {
26339
25971
  const conn = new DatabaseSync4(":memory:");
@@ -26468,7 +26100,7 @@ var init_context = __esm({
26468
26100
  }
26469
26101
  // ── indexing ───────────────────────────────────────────────
26470
26102
  static chunksFor(label, text) {
26471
- const ext = extname6(label).toLowerCase();
26103
+ const ext = extname5(label).toLowerCase();
26472
26104
  const special = SPECIAL_FILES.has(basename5(label).toLowerCase());
26473
26105
  if (CODE_EXTS.has(ext) || CONFIG_EXTS.has(ext) || special) {
26474
26106
  return chunkCode(text, label);
@@ -26486,7 +26118,7 @@ var init_context = __esm({
26486
26118
  const stored = label != null ? String(label) : String(file);
26487
26119
  let text;
26488
26120
  try {
26489
- text = readFileSync16(file, "utf-8");
26121
+ text = readFileSync15(file, "utf-8");
26490
26122
  } catch {
26491
26123
  return 0;
26492
26124
  }
@@ -26507,7 +26139,7 @@ var init_context = __esm({
26507
26139
  static eligible(filename) {
26508
26140
  const fn = filename.toLowerCase();
26509
26141
  if (fn.endsWith(".min.js")) return false;
26510
- const ext = extname6(fn);
26142
+ const ext = extname5(fn);
26511
26143
  return CODE_EXTS.has(ext) || DOC_EXTS.has(ext) || CONFIG_EXTS.has(ext) || SPECIAL_FILES.has(fn);
26512
26144
  }
26513
26145
  /**
@@ -26536,7 +26168,7 @@ var init_context = __esm({
26536
26168
  for (const fn of files) {
26537
26169
  if (!_Context.eligible(fn)) continue;
26538
26170
  const full = join22(dir, fn);
26539
- const rel = relative4(rootAbs, full);
26171
+ const rel = relative3(rootAbs, full);
26540
26172
  total += this.indexPath(full, rel);
26541
26173
  }
26542
26174
  for (const d of subdirs) walk2(join22(dir, d));
@@ -26557,7 +26189,7 @@ var init_context = __esm({
26557
26189
  const raw = String(changedPath);
26558
26190
  const abs = isAbsolute6(raw) ? raw : join22(process.cwd(), raw);
26559
26191
  const resolved = realResolve(resolve12(abs));
26560
- const rel = relative4(this.root, resolved);
26192
+ const rel = relative3(this.root, resolved);
26561
26193
  if (rel === "" || rel.startsWith("..") || isAbsolute6(rel)) {
26562
26194
  return -1;
26563
26195
  }
@@ -28479,7 +28111,7 @@ var init_job = __esm({
28479
28111
  });
28480
28112
 
28481
28113
  // ../core/src/queueBackends/liteBackend.ts
28482
- import { mkdirSync as mkdirSync15, readdirSync as readdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, existsSync as existsSync18 } from "node:fs";
28114
+ import { mkdirSync as mkdirSync15, readdirSync as readdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, existsSync as existsSync18 } from "node:fs";
28483
28115
  import { join as join23 } from "node:path";
28484
28116
  import { randomUUID as randomUUID6 } from "node:crypto";
28485
28117
  var LiteBackend;
@@ -28579,7 +28211,7 @@ var init_liteBackend = __esm({
28579
28211
  const filePath = join23(dir, filename);
28580
28212
  let job;
28581
28213
  try {
28582
- job = JSON.parse(readFileSync17(filePath, "utf-8"));
28214
+ job = JSON.parse(readFileSync16(filePath, "utf-8"));
28583
28215
  } catch {
28584
28216
  continue;
28585
28217
  }
@@ -28643,7 +28275,7 @@ var init_liteBackend = __esm({
28643
28275
  const filePath = join23(reservedDir, filename);
28644
28276
  let record;
28645
28277
  try {
28646
- record = JSON.parse(readFileSync17(filePath, "utf-8"));
28278
+ record = JSON.parse(readFileSync16(filePath, "utf-8"));
28647
28279
  } catch {
28648
28280
  continue;
28649
28281
  }
@@ -28756,7 +28388,7 @@ var init_liteBackend = __esm({
28756
28388
  let count = 0;
28757
28389
  for (const file of files) {
28758
28390
  try {
28759
- const job = JSON.parse(readFileSync17(join23(scanDir, file), "utf-8"));
28391
+ const job = JSON.parse(readFileSync16(join23(scanDir, file), "utf-8"));
28760
28392
  if (job.status === status2) count++;
28761
28393
  } catch {
28762
28394
  }
@@ -28813,7 +28445,7 @@ var init_liteBackend = __esm({
28813
28445
  const files = readdirSync11(dir).filter((f) => f.endsWith(".queue-data")).sort();
28814
28446
  for (const file of files) {
28815
28447
  try {
28816
- const job = JSON.parse(readFileSync17(join23(dir, file), "utf-8"));
28448
+ const job = JSON.parse(readFileSync16(join23(dir, file), "utf-8"));
28817
28449
  const attempts = job.attempts || 0;
28818
28450
  if (attempts > 0 && attempts < maxRetries) {
28819
28451
  results.push(job);
@@ -28839,7 +28471,7 @@ var init_liteBackend = __esm({
28839
28471
  const failedDir = join23(this.basePath, q, "failed");
28840
28472
  const filePath = join23(failedDir, `${jobId}.queue-data`);
28841
28473
  if (existsSync18(filePath)) {
28842
- const job = JSON.parse(readFileSync17(filePath, "utf-8"));
28474
+ const job = JSON.parse(readFileSync16(filePath, "utf-8"));
28843
28475
  job.status = "pending";
28844
28476
  job.attempts = (job.attempts || 0) + 1;
28845
28477
  job.error = void 0;
@@ -28863,7 +28495,7 @@ var init_liteBackend = __esm({
28863
28495
  const files = readdirSync11(failedDir).filter((f) => f.endsWith(".queue-data")).sort();
28864
28496
  for (const file of files) {
28865
28497
  try {
28866
- const job = JSON.parse(readFileSync17(join23(failedDir, file), "utf-8"));
28498
+ const job = JSON.parse(readFileSync16(join23(failedDir, file), "utf-8"));
28867
28499
  if ((job.attempts || 0) >= maxRetries) {
28868
28500
  job.status = "dead";
28869
28501
  results.push(job);
@@ -28897,7 +28529,7 @@ var init_liteBackend = __esm({
28897
28529
  const files = readdirSync11(dir).filter((f) => f.endsWith(".queue-data"));
28898
28530
  for (const file of files) {
28899
28531
  try {
28900
- const job = JSON.parse(readFileSync17(join23(dir, file), "utf-8"));
28532
+ const job = JSON.parse(readFileSync16(join23(dir, file), "utf-8"));
28901
28533
  if (job.status === status2) {
28902
28534
  unlinkSync7(join23(dir, file));
28903
28535
  count++;
@@ -28924,7 +28556,7 @@ var init_liteBackend = __esm({
28924
28556
  for (const file of files) {
28925
28557
  try {
28926
28558
  const filePath = join23(failedDir, file);
28927
- const job = JSON.parse(readFileSync17(filePath, "utf-8"));
28559
+ const job = JSON.parse(readFileSync16(filePath, "utf-8"));
28928
28560
  if ((job.attempts || 0) >= maxRetries) {
28929
28561
  continue;
28930
28562
  }
@@ -28956,7 +28588,7 @@ var init_liteBackend = __esm({
28956
28588
  const filePath = join23(dir, file);
28957
28589
  let job;
28958
28590
  try {
28959
- job = JSON.parse(readFileSync17(filePath, "utf-8"));
28591
+ job = JSON.parse(readFileSync16(filePath, "utf-8"));
28960
28592
  } catch {
28961
28593
  continue;
28962
28594
  }
@@ -30419,7 +30051,7 @@ function detectVersion(projectRoot3) {
30419
30051
  }
30420
30052
  return "0.0.0";
30421
30053
  }
30422
- function relativePath2(absPath, projectRoot3, frameworkRoots) {
30054
+ function relativePath(absPath, projectRoot3, frameworkRoots) {
30423
30055
  const norm = path6.resolve(absPath);
30424
30056
  for (const fw of frameworkRoots) {
30425
30057
  const parent = path6.dirname(fw);
@@ -30884,7 +30516,7 @@ function buildEntriesForFile(absPath, source, fwRoots, projectRoot3, version, ou
30884
30516
  } catch {
30885
30517
  return;
30886
30518
  }
30887
- const rel = relativePath2(absPath, projectRoot3, fwRoots);
30519
+ const rel = relativePath(absPath, projectRoot3, fwRoots);
30888
30520
  for (const cls of parsed.classes) {
30889
30521
  if (!cls.exported && source === "framework") {
30890
30522
  continue;
@@ -31520,8 +31152,8 @@ ${end}
31520
31152
 
31521
31153
  // ../core/src/devAdmin.ts
31522
31154
  import { cpus as osCpus } from "node:os";
31523
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync15, existsSync as existsSync22, readdirSync as readdirSync15, mkdirSync as mkdirSync18, copyFileSync as copyFileSync2, statSync as statSync16 } from "node:fs";
31524
- import { join as join27, dirname as dirname13, resolve as resolve16, relative as relative8 } from "node:path";
31155
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync15, existsSync as existsSync22, readdirSync as readdirSync15, mkdirSync as mkdirSync18, copyFileSync as copyFileSync2, statSync as statSync16 } from "node:fs";
31156
+ import { join as join27, dirname as dirname13, resolve as resolve16, relative as relative7 } from "node:path";
31525
31157
  import { fileURLToPath as fileURLToPath6 } from "node:url";
31526
31158
  import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
31527
31159
  function escapeHtml(value) {
@@ -31640,7 +31272,7 @@ function readQueueDir(dir, topic, status2) {
31640
31272
  for (const filename of readdirSync15(dir).sort()) {
31641
31273
  if (!filename.endsWith(".queue-data")) continue;
31642
31274
  try {
31643
- jobs.push(mapQueueJob(JSON.parse(readFileSync21(join27(dir, filename), "utf-8")), topic, status2));
31275
+ jobs.push(mapQueueJob(JSON.parse(readFileSync20(join27(dir, filename), "utf-8")), topic, status2));
31644
31276
  } catch {
31645
31277
  }
31646
31278
  }
@@ -31759,7 +31391,7 @@ function resolveDevEnvVar(key) {
31759
31391
  if (live !== void 0 && live !== "") return live;
31760
31392
  const envPath = join27(process.cwd(), ".env");
31761
31393
  if (!existsSync22(envPath)) return "";
31762
- for (const line of readFileSync21(envPath, "utf-8").split("\n")) {
31394
+ for (const line of readFileSync20(envPath, "utf-8").split("\n")) {
31763
31395
  const t = line.trim();
31764
31396
  if (!t || t.startsWith("#") || !t.includes("=")) continue;
31765
31397
  const eq = t.indexOf("=");
@@ -31769,7 +31401,7 @@ function resolveDevEnvVar(key) {
31769
31401
  }
31770
31402
  function upsertDevEnvVar(key, value) {
31771
31403
  const envPath = join27(process.cwd(), ".env");
31772
- const lines = existsSync22(envPath) ? readFileSync21(envPath, "utf-8").split("\n") : [];
31404
+ const lines = existsSync22(envPath) ? readFileSync20(envPath, "utf-8").split("\n") : [];
31773
31405
  let found = false;
31774
31406
  const out = [];
31775
31407
  for (const line of lines) {
@@ -31802,7 +31434,7 @@ function parseEnvFile() {
31802
31434
  const envPath = join27(process.cwd(), ".env");
31803
31435
  const result = {};
31804
31436
  if (!existsSync22(envPath)) return result;
31805
- const lines = readFileSync21(envPath, "utf-8").split("\n");
31437
+ const lines = readFileSync20(envPath, "utf-8").split("\n");
31806
31438
  for (const line of lines) {
31807
31439
  const trimmed = line.trim();
31808
31440
  if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;
@@ -31842,7 +31474,7 @@ function handleGalleryDeploy(router) {
31842
31474
  const copied = [];
31843
31475
  const allFiles = walkDirRecursive(gallerySrc);
31844
31476
  for (const srcFile of allFiles) {
31845
- const rel = relative8(gallerySrc, srcFile);
31477
+ const rel = relative7(gallerySrc, srcFile);
31846
31478
  const dest = join27(projectSrc, rel);
31847
31479
  mkdirSync18(dirname13(dest), { recursive: true });
31848
31480
  copyFileSync2(srcFile, dest);
@@ -32504,9 +32136,6 @@ var init_devAdmin = __esm({
32504
32136
  { method: "GET", pattern: "/__dev/api/gallery", handler: handleGalleryList },
32505
32137
  { method: "POST", pattern: "/__dev/api/gallery/deploy", handler: handleGalleryDeploy(router) },
32506
32138
  // Metrics
32507
- { method: "GET", pattern: "/__dev/api/metrics", handler: (_req, res) => {
32508
- res.json(quickMetrics());
32509
- } },
32510
32139
  // No fallback (ADR-0002): a missing or stale CLI is a 503 naming the
32511
32140
  // install command, never zeros that read as a healthy codebase.
32512
32141
  { method: "GET", pattern: "/__dev/api/metrics/full", handler: (_req, res) => {
@@ -33315,7 +32944,7 @@ var init_devAdmin = __esm({
33315
32944
  }
33316
32945
  try {
33317
32946
  const envPath = join27(process.cwd(), ".env");
33318
- const lines = existsSync22(envPath) ? readFileSync21(envPath, "utf-8").split("\n") : [];
32947
+ const lines = existsSync22(envPath) ? readFileSync20(envPath, "utf-8").split("\n") : [];
33319
32948
  const keysFound = { TINA4_DATABASE_URL: false, TINA4_DATABASE_USERNAME: false, TINA4_DATABASE_PASSWORD: false };
33320
32949
  const newLines = [];
33321
32950
  for (const line of lines) {
@@ -33361,12 +32990,12 @@ var init_devAdmin = __esm({
33361
32990
  const metaFile = join27(entryPath, "meta.json");
33362
32991
  if (statSync16(entryPath).isDirectory() && existsSync22(metaFile)) {
33363
32992
  try {
33364
- const meta = JSON.parse(readFileSync21(metaFile, "utf-8"));
32993
+ const meta = JSON.parse(readFileSync20(metaFile, "utf-8"));
33365
32994
  meta.id = entry;
33366
32995
  const srcDir = join27(entryPath, "src");
33367
32996
  if (existsSync22(srcDir)) {
33368
32997
  const allFiles = walkDirRecursive(srcDir);
33369
- meta.files = allFiles.map((f) => relative8(srcDir, f));
32998
+ meta.files = allFiles.map((f) => relative7(srcDir, f));
33370
32999
  }
33371
33000
  const projectSrc = resolve16(process.cwd(), "src");
33372
33001
  if (existsSync22(srcDir) && meta.files) {
@@ -33484,7 +33113,7 @@ var init_devAdmin = __esm({
33484
33113
  for (const name of readdirSync15(target).sort()) {
33485
33114
  if (devFilesHidden(name)) continue;
33486
33115
  const full = join27(target, name);
33487
- const entryRel = relative8(root, full).replace(/\\/g, "/");
33116
+ const entryRel = relative7(root, full).replace(/\\/g, "/");
33488
33117
  if (isSecretPath(entryRel)) continue;
33489
33118
  let isDir = false;
33490
33119
  let size = null;
@@ -33529,7 +33158,7 @@ var init_devAdmin = __esm({
33529
33158
  size
33530
33159
  });
33531
33160
  }
33532
- res.json({ path: relative8(root, target).replace(/\\/g, "/") || ".", branch, entries });
33161
+ res.json({ path: relative7(root, target).replace(/\\/g, "/") || ".", branch, entries });
33533
33162
  };
33534
33163
  DEV_ADMIN_LANG_MAP = {
33535
33164
  ".py": "python",
@@ -33581,8 +33210,8 @@ var init_devAdmin = __esm({
33581
33210
  return;
33582
33211
  }
33583
33212
  try {
33584
- const content = readFileSync21(target, "utf-8");
33585
- const path8 = relative8(root, target);
33213
+ const content = readFileSync20(target, "utf-8");
33214
+ const path8 = relative7(root, target);
33586
33215
  res.json({ path: path8, content, language: devAdminLanguage(path8), bytes: Buffer.byteLength(content, "utf-8") });
33587
33216
  } catch (e) {
33588
33217
  res.json({ error: e.message }, 500);
@@ -33604,10 +33233,10 @@ var init_devAdmin = __esm({
33604
33233
  writeFileSync15(target, content, "utf-8");
33605
33234
  try {
33606
33235
  const { Plan: Plan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
33607
- Plan2.recordAction(existed ? "patched" : "created", relative8(root, target));
33236
+ Plan2.recordAction(existed ? "patched" : "created", relative7(root, target));
33608
33237
  } catch {
33609
33238
  }
33610
- res.json({ ok: true, path: relative8(root, target), bytes: Buffer.byteLength(content, "utf-8") });
33239
+ res.json({ ok: true, path: relative7(root, target), bytes: Buffer.byteLength(content, "utf-8") });
33611
33240
  } catch (e) {
33612
33241
  res.json({ error: e.message }, 500);
33613
33242
  }
@@ -33627,7 +33256,7 @@ var init_devAdmin = __esm({
33627
33256
  return;
33628
33257
  }
33629
33258
  try {
33630
- const buf = readFileSync21(target);
33259
+ const buf = readFileSync20(target);
33631
33260
  const ext = target.slice(target.lastIndexOf(".") + 1).toLowerCase();
33632
33261
  const mime = {
33633
33262
  js: "application/javascript",
@@ -33669,7 +33298,7 @@ var init_devAdmin = __esm({
33669
33298
  const { renameSync: renameSync3 } = await import("node:fs");
33670
33299
  mkdirSync18(dirname13(dst), { recursive: true });
33671
33300
  renameSync3(src, dst);
33672
- res.json({ ok: true, from: relative8(root, src), to: relative8(root, dst) });
33301
+ res.json({ ok: true, from: relative7(root, src), to: relative7(root, dst) });
33673
33302
  } catch (e) {
33674
33303
  res.json({ error: e.message }, 500);
33675
33304
  }
@@ -33690,7 +33319,7 @@ var init_devAdmin = __esm({
33690
33319
  try {
33691
33320
  const { rmSync } = await import("node:fs");
33692
33321
  rmSync(target, { recursive: true, force: true });
33693
- res.json({ ok: true, deleted: relative8(root, target) });
33322
+ res.json({ ok: true, deleted: relative7(root, target) });
33694
33323
  } catch (e) {
33695
33324
  res.json({ error: e.message }, 500);
33696
33325
  }
@@ -34024,7 +33653,7 @@ var init_devAdmin = __esm({
34024
33653
  });
34025
33654
  };
34026
33655
  handleDevAdminJs = async (_req, res) => {
34027
- const { readFileSync: readFileSync30, existsSync: existsSync37 } = await import("node:fs");
33656
+ const { readFileSync: readFileSync29, existsSync: existsSync37 } = await import("node:fs");
34028
33657
  const { dirname: dirname17, join: join40, resolve: resolve30 } = await import("node:path");
34029
33658
  const { fileURLToPath: fileURLToPath10 } = await import("node:url");
34030
33659
  const dir = dirname17(fileURLToPath10(import.meta.url));
@@ -34040,7 +33669,7 @@ var init_devAdmin = __esm({
34040
33669
  for (const jsPath of candidates) {
34041
33670
  if (existsSync37(jsPath)) {
34042
33671
  try {
34043
- const content = readFileSync30(jsPath, "utf-8");
33672
+ const content = readFileSync29(jsPath, "utf-8");
34044
33673
  res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
34045
33674
  res.raw.end(content);
34046
33675
  return;
@@ -34055,7 +33684,7 @@ var init_devAdmin = __esm({
34055
33684
  });
34056
33685
 
34057
33686
  // ../core/src/i18n.ts
34058
- import { readFileSync as readFileSync22, readdirSync as readdirSync16, existsSync as existsSync23 } from "node:fs";
33687
+ import { readFileSync as readFileSync21, readdirSync as readdirSync16, existsSync as existsSync23 } from "node:fs";
34059
33688
  import { join as join28, resolve as resolve17 } from "node:path";
34060
33689
  var I18n;
34061
33690
  var init_i18n = __esm({
@@ -34152,7 +33781,7 @@ var init_i18n = __esm({
34152
33781
  const filePath = join28(this._localeDir, `${locale}.json`);
34153
33782
  if (existsSync23(filePath)) {
34154
33783
  try {
34155
- const raw = readFileSync22(filePath, "utf-8");
33784
+ const raw = readFileSync21(filePath, "utf-8");
34156
33785
  const data = JSON.parse(raw);
34157
33786
  this._translations.set(locale, _I18n._flatten(data));
34158
33787
  return;
@@ -34165,7 +33794,7 @@ var init_i18n = __esm({
34165
33794
  const yamlPath = join28(this._localeDir, `${locale}${ext}`);
34166
33795
  if (existsSync23(yamlPath)) {
34167
33796
  try {
34168
- const raw = readFileSync22(yamlPath, "utf-8");
33797
+ const raw = readFileSync21(yamlPath, "utf-8");
34169
33798
  const data = _I18n._parseSimpleYaml(raw);
34170
33799
  this._translations.set(locale, _I18n._flatten(data));
34171
33800
  return;
@@ -35028,8 +34657,8 @@ var init_docsAutoDiscovery = __esm({
35028
34657
  // ../core/src/server.ts
35029
34658
  import { createServer as createServer2 } from "node:http";
35030
34659
  import { randomBytes as randomBytes7 } from "node:crypto";
35031
- import { resolve as resolve19, dirname as dirname14, join as join30, relative as relative9 } from "node:path";
35032
- import { existsSync as existsSync25, readdirSync as readdirSync17, readFileSync as readFileSync24, statSync as statSync17 } from "node:fs";
34660
+ import { resolve as resolve19, dirname as dirname14, join as join30, relative as relative8 } from "node:path";
34661
+ import { existsSync as existsSync25, readdirSync as readdirSync17, readFileSync as readFileSync23, statSync as statSync17 } from "node:fs";
35033
34662
  import { isatty } from "node:tty";
35034
34663
  import { fileURLToPath as fileURLToPath7 } from "node:url";
35035
34664
  import { execFileSync as execFileSync3, exec } from "node:child_process";
@@ -35253,7 +34882,7 @@ function getGalleryDeployedState() {
35253
34882
  if (existsSync25(srcDir)) {
35254
34883
  const files = walkGalleryFiles(srcDir);
35255
34884
  const projectSrc = resolve19(process.cwd(), "src");
35256
- state[entry] = files.every((f) => existsSync25(join30(projectSrc, relative9(srcDir, f))));
34885
+ state[entry] = files.every((f) => existsSync25(join30(projectSrc, relative8(srcDir, f))));
35257
34886
  } else {
35258
34887
  state[entry] = false;
35259
34888
  }
@@ -35690,7 +35319,7 @@ function serveTemplateFallback(ctx) {
35690
35319
  if ((ctx.req.method ?? "GET") !== "GET") return false;
35691
35320
  const tplFile = resolveTemplate(ctx.pathname, ctx.templatesDir);
35692
35321
  if (!tplFile) return false;
35693
- const html = ctx.frondEngine ? ctx.frondEngine.render(tplFile, {}) : readFileSync24(resolve19(ctx.templatesDir, tplFile), "utf-8");
35322
+ const html = ctx.frondEngine ? ctx.frondEngine.render(tplFile, {}) : readFileSync23(resolve19(ctx.templatesDir, tplFile), "utf-8");
35694
35323
  ctx.res.raw.writeHead(200, void 0, { "Content-Type": "text/html; charset=utf-8" });
35695
35324
  ctx.res.raw.end(html);
35696
35325
  return true;
@@ -36517,7 +36146,7 @@ var init_mqttMessage = __esm({
36517
36146
  import net2 from "node:net";
36518
36147
  import tls from "node:tls";
36519
36148
  import { randomBytes as randomBytes8 } from "node:crypto";
36520
- import { existsSync as existsSync26, readFileSync as readFileSync25 } from "node:fs";
36149
+ import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
36521
36150
  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;
36522
36151
  var init_mqtt = __esm({
36523
36152
  "../core/src/mqtt.ts"() {
@@ -36982,7 +36611,7 @@ var init_mqtt = __esm({
36982
36611
  servername: this.host,
36983
36612
  rejectUnauthorized: this.tlsVerify
36984
36613
  };
36985
- if (this.tlsVerify && this.caFile) opts.ca = readFileSync25(this.caFile);
36614
+ if (this.tlsVerify && this.caFile) opts.ca = readFileSync24(this.caFile);
36986
36615
  sock = tls.connect(opts, () => settle(() => resolve30(sock)));
36987
36616
  } else {
36988
36617
  sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve30(sock)));
@@ -37203,7 +36832,7 @@ var init_mqtt = __esm({
37203
36832
 
37204
36833
  // ../core/src/service.ts
37205
36834
  import { readdirSync as readdirSync18, statSync as statSync18, watchFile, unwatchFile } from "node:fs";
37206
- import { join as join31, extname as extname8 } from "node:path";
36835
+ import { join as join31, extname as extname7 } from "node:path";
37207
36836
  import { pathToFileURL } from "node:url";
37208
36837
  function matchCronField(field, value) {
37209
36838
  if (field === "*") return true;
@@ -37374,7 +37003,7 @@ var init_service = __esm({
37374
37003
  return discovered;
37375
37004
  }
37376
37005
  for (const entry of entries) {
37377
- const ext = extname8(entry);
37006
+ const ext = extname7(entry);
37378
37007
  if (ext !== ".ts" && ext !== ".js") continue;
37379
37008
  const fullPath = join31(dir, entry);
37380
37009
  const stat = statSync18(fullPath);
@@ -37490,7 +37119,7 @@ var init_service = __esm({
37490
37119
  return;
37491
37120
  }
37492
37121
  for (const entry of entries) {
37493
- const ext = extname8(entry);
37122
+ const ext = extname7(entry);
37494
37123
  if (ext !== ".ts" && ext !== ".js") continue;
37495
37124
  const fullPath = join31(dir, entry);
37496
37125
  if (watchedFiles.has(fullPath)) continue;
@@ -38193,7 +37822,7 @@ var init_api = __esm({
38193
37822
  // ../core/src/messenger.ts
38194
37823
  import net3 from "node:net";
38195
37824
  import tls2 from "node:tls";
38196
- import { readFileSync as readFileSync26 } from "node:fs";
37825
+ import { readFileSync as readFileSync25 } from "node:fs";
38197
37826
  import { basename as basename7 } from "node:path";
38198
37827
  import { randomUUID as randomUUID7 } from "node:crypto";
38199
37828
  function tlsRejectUnauthorized() {
@@ -38290,7 +37919,7 @@ function buildMimeMessage(options) {
38290
37919
  }
38291
37920
  for (const filePath of options.attachments) {
38292
37921
  const fileName = basename7(filePath);
38293
- const fileData = readFileSync26(filePath);
37922
+ const fileData = readFileSync25(filePath);
38294
37923
  const base64Data = fileData.toString("base64");
38295
37924
  lines.push("");
38296
37925
  lines.push(`--${boundary}`);
@@ -39771,9 +39400,9 @@ __export(ai_exports, {
39771
39400
  skillBlock: () => skillBlock,
39772
39401
  writeOrMerge: () => writeOrMerge
39773
39402
  });
39774
- import { existsSync as existsSync27, mkdirSync as mkdirSync21, writeFileSync as writeFileSync18, readFileSync as readFileSync27 } from "node:fs";
39403
+ import { existsSync as existsSync27, mkdirSync as mkdirSync21, writeFileSync as writeFileSync18, readFileSync as readFileSync26 } from "node:fs";
39775
39404
  import { homedir } from "node:os";
39776
- import { join as join32, resolve as resolve20, relative as relative10, dirname as dirname15 } from "node:path";
39405
+ import { join as join32, resolve as resolve20, relative as relative9, dirname as dirname15 } from "node:path";
39777
39406
  import { fileURLToPath as fileURLToPath8 } from "node:url";
39778
39407
  import { execSync as execSync2, execFileSync as execFileSync4 } from "node:child_process";
39779
39408
  import { createInterface } from "node:readline";
@@ -39781,7 +39410,7 @@ function readVersion() {
39781
39410
  try {
39782
39411
  const thisDir = dirname15(fileURLToPath8(import.meta.url));
39783
39412
  const rootPkg = resolve20(thisDir, "..", "..", "..", "package.json");
39784
- const pkg = JSON.parse(readFileSync27(rootPkg, "utf-8"));
39413
+ const pkg = JSON.parse(readFileSync26(rootPkg, "utf-8"));
39785
39414
  return pkg.version ?? "0.0.0";
39786
39415
  } catch {
39787
39416
  return "0.0.0";
@@ -40004,7 +39633,7 @@ function writeOrMerge(contextPath, contextFile, frameworkGuide) {
40004
39633
  writeFileSync18(contextPath, frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
40005
39634
  return "Installed";
40006
39635
  }
40007
- const existing = readFileSync27(contextPath, "utf-8");
39636
+ const existing = readFileSync26(contextPath, "utf-8");
40008
39637
  if (hasMarkers(existing, start2, end)) {
40009
39638
  writeFileSync18(contextPath, replaceMarkerBlock(existing, block, start2, end), "utf-8");
40010
39639
  return "Refreshed skill block in";
@@ -40028,7 +39657,7 @@ function installForTool(root, tool, context) {
40028
39657
  const parentDir = dirname15(contextPath);
40029
39658
  mkdirSync21(parentDir, { recursive: true });
40030
39659
  const action = writeOrMerge(contextPath, tool.contextFile, context);
40031
- const rel = relative10(root, contextPath);
39660
+ const rel = relative9(root, contextPath);
40032
39661
  created.push(rel);
40033
39662
  console.log(` ${GREEN2}\u2713${RESET2} ${action} ${rel}`);
40034
39663
  if (tool.name === "claude-code") {
@@ -40406,7 +40035,7 @@ function generateClaudeCodeContext() {
40406
40035
  const repoRoot = resolve20(thisDir, "..", "..", "..");
40407
40036
  const claudeMdPath = join32(repoRoot, "CLAUDE.md");
40408
40037
  if (existsSync27(claudeMdPath)) {
40409
- return readFileSync27(claudeMdPath, "utf-8");
40038
+ return readFileSync26(claudeMdPath, "utf-8");
40410
40039
  }
40411
40040
  } catch {
40412
40041
  }
@@ -40600,6 +40229,292 @@ export default class User {
40600
40229
  }
40601
40230
  });
40602
40231
 
40232
+ // ../core/src/aiClient.ts
40233
+ import http2 from "node:http";
40234
+ import https2 from "node:https";
40235
+ var AiError, AiConfigError, AiTimeoutError, AiParseError, AiHTTPError, Ai;
40236
+ var init_aiClient = __esm({
40237
+ "../core/src/aiClient.ts"() {
40238
+ "use strict";
40239
+ AiError = class extends Error {
40240
+ };
40241
+ AiConfigError = class extends AiError {
40242
+ };
40243
+ AiTimeoutError = class extends AiError {
40244
+ };
40245
+ AiParseError = class extends AiError {
40246
+ };
40247
+ AiHTTPError = class extends AiError {
40248
+ constructor(message, status2 = null) {
40249
+ super(message);
40250
+ this.status = status2;
40251
+ }
40252
+ };
40253
+ Ai = class {
40254
+ static chat(messages, options = {}) {
40255
+ this.validateMessages(messages);
40256
+ const config = this.config("chat", options);
40257
+ const body = this.chatBody(config, messages, options);
40258
+ const headers = this.headers(config);
40259
+ return options.stream ? this.streamRequest(config, headers, body) : this.chatResponse(config, headers, body);
40260
+ }
40261
+ static async complete(prompt, options = {}) {
40262
+ if (typeof prompt !== "string") throw new AiConfigError("AI prompt must be a string");
40263
+ return (await this.chat([{ role: "user", content: prompt }], { ...options, stream: false })).text;
40264
+ }
40265
+ static async embed(textOrTexts, options = {}) {
40266
+ const single = typeof textOrTexts === "string";
40267
+ if (!single && (!Array.isArray(textOrTexts) || textOrTexts.length === 0 || !textOrTexts.every((item) => typeof item === "string"))) {
40268
+ throw new AiConfigError("AI embedding input must be a string or a non-empty list of strings");
40269
+ }
40270
+ const config = this.config("embed", options);
40271
+ if (config.provider === "anthropic") throw new AiConfigError("Anthropic does not provide the embedding endpoint in this contract");
40272
+ const raw = await this.requestJson(config, this.headers(config), { model: config.model, input: textOrTexts });
40273
+ try {
40274
+ const data = raw.data.sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
40275
+ const vectors = data.map((item) => item.embedding);
40276
+ const expected = single ? 1 : textOrTexts.length;
40277
+ 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();
40278
+ return single ? vectors[0] : vectors;
40279
+ } catch {
40280
+ throw new AiParseError("AI provider returned a malformed embedding response");
40281
+ }
40282
+ }
40283
+ static validateMessages(messages) {
40284
+ if (!Array.isArray(messages) || messages.length === 0 || !messages.every((message) => message && ["system", "user", "assistant"].includes(message.role) && typeof message.content === "string")) {
40285
+ throw new AiConfigError("AI messages must contain supported roles and string content");
40286
+ }
40287
+ }
40288
+ static number(name, fallback, minimum) {
40289
+ const value = process.env[name] === void 0 ? fallback : Number(process.env[name]);
40290
+ if (!Number.isFinite(value) || value < minimum) throw new AiConfigError(`${name} must be numeric and at least ${minimum}`);
40291
+ return value;
40292
+ }
40293
+ static config(capability, options) {
40294
+ const provider = (options.provider ?? process.env.TINA4_AI_PROVIDER ?? "local").trim().toLowerCase();
40295
+ if (provider !== "local" && provider !== "openai" && provider !== "anthropic") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, or anthropic");
40296
+ const key = process.env.TINA4_AI_KEY || null;
40297
+ if ((provider === "openai" || provider === "anthropic") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
40298
+ const defaults = {
40299
+ local: ["http://localhost:11437", "llama3.2"],
40300
+ openai: ["https://api.openai.com/v1", "gpt-4o-mini"],
40301
+ anthropic: ["https://api.anthropic.com/v1", "claude-3-5-haiku-latest"]
40302
+ };
40303
+ const rawUrl = capability === "embed" && process.env.TINA4_EMBED_URL ? process.env.TINA4_EMBED_URL : process.env.TINA4_AI_URL ?? defaults[provider][0];
40304
+ const model = (options.model ?? process.env.TINA4_AI_MODEL ?? defaults[provider][1]).trim();
40305
+ if (!model) throw new AiConfigError("AI model must be a non-empty string");
40306
+ const totalTimeout = options.timeout === void 0 ? this.number("TINA4_AI_TIMEOUT", 60, 1e-3) : Number(options.timeout);
40307
+ if (!Number.isFinite(totalTimeout) || totalTimeout <= 0) throw new AiConfigError("AI timeout must be greater than zero");
40308
+ 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)) };
40309
+ }
40310
+ static endpoint(value, capability, provider) {
40311
+ let url;
40312
+ try {
40313
+ url = new URL(value);
40314
+ } catch {
40315
+ throw new AiConfigError("AI URL must be an http or https URL");
40316
+ }
40317
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new AiConfigError("AI URL must be an http or https URL");
40318
+ const path8 = url.pathname.replace(/\/+$/, "");
40319
+ if (path8 === "" || path8 === "/v1" || path8 === "/api") {
40320
+ const suffix = provider === "anthropic" ? "/messages" : capability === "embed" ? "/embeddings" : "/chat/completions";
40321
+ url.pathname = (path8 || "/v1") + suffix;
40322
+ }
40323
+ return url.toString();
40324
+ }
40325
+ static headers(config) {
40326
+ const headers = { "content-type": "application/json", accept: "application/json" };
40327
+ if (config.provider === "openai") headers.authorization = `Bearer ${config.key}`;
40328
+ if (config.provider === "anthropic") {
40329
+ headers["x-api-key"] = config.key;
40330
+ headers["anthropic-version"] = "2023-06-01";
40331
+ }
40332
+ return headers;
40333
+ }
40334
+ static chatBody(config, messages, options) {
40335
+ const body = { model: config.model, messages, stream: options.stream ?? false };
40336
+ if (options.temperature !== void 0) body.temperature = options.temperature;
40337
+ if (options.maxTokens !== void 0) body.max_tokens = options.maxTokens;
40338
+ if (config.provider === "anthropic") {
40339
+ const system = messages.filter((message) => message.role === "system").map((message) => message.content);
40340
+ body.messages = messages.filter((message) => message.role !== "system");
40341
+ body.max_tokens = options.maxTokens ?? 1024;
40342
+ if (system.length) body.system = system.join("\n\n");
40343
+ }
40344
+ return body;
40345
+ }
40346
+ static open(config, deadline, headers, body) {
40347
+ const remainingMs = deadline - performance.now();
40348
+ if (remainingMs <= 0) return Promise.reject(new AiTimeoutError("AI total request timeout expired"));
40349
+ const url = new URL(config.url);
40350
+ const payload = JSON.stringify(body);
40351
+ const controller = new AbortController();
40352
+ const totalTimer = setTimeout(() => controller.abort(new AiTimeoutError("AI total request timeout expired")), remainingMs);
40353
+ return new Promise((resolve30, reject) => {
40354
+ const client = url.protocol === "https:" ? https2 : http2;
40355
+ const request = client.request(url, { method: "POST", headers: { ...headers, "content-length": Buffer.byteLength(payload) }, signal: controller.signal }, (response) => {
40356
+ clearTimeout(connectTimer);
40357
+ resolve30({ response, cleanup: () => {
40358
+ clearTimeout(totalTimer);
40359
+ clearTimeout(connectTimer);
40360
+ } });
40361
+ });
40362
+ const connectTimer = setTimeout(() => request.destroy(new AiTimeoutError("AI connection timeout expired")), Math.min(config.connectTimeout * 1e3, remainingMs));
40363
+ request.on("socket", (socket) => {
40364
+ if (!socket.connecting) clearTimeout(connectTimer);
40365
+ socket.once(url.protocol === "https:" ? "secureConnect" : "connect", () => clearTimeout(connectTimer));
40366
+ });
40367
+ request.once("error", (error) => {
40368
+ clearTimeout(totalTimer);
40369
+ clearTimeout(connectTimer);
40370
+ if (error instanceof AiError) reject(error);
40371
+ else if (controller.signal.aborted) reject(new AiTimeoutError("AI total request timeout expired"));
40372
+ else reject(new AiHTTPError(`AI transport failed (${error.name})`));
40373
+ });
40374
+ request.end(payload);
40375
+ });
40376
+ }
40377
+ static async readBody(response) {
40378
+ const chunks = [];
40379
+ for await (const chunk of response) chunks.push(Buffer.from(chunk));
40380
+ return Buffer.concat(chunks).toString("utf8");
40381
+ }
40382
+ static retryDelay(headers, deadline) {
40383
+ const value = Array.isArray(headers["retry-after"]) ? headers["retry-after"][0] : headers["retry-after"];
40384
+ const requested = value !== void 0 && Number.isFinite(Number(value)) ? Math.max(0, Number(value) * 1e3) : 100;
40385
+ const delay = Math.min(requested, Math.max(0, deadline - performance.now()));
40386
+ return new Promise((resolve30) => setTimeout(resolve30, delay));
40387
+ }
40388
+ static async requestJson(config, headers, body) {
40389
+ const deadline = performance.now() + config.totalTimeout * 1e3;
40390
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
40391
+ let opened = null;
40392
+ try {
40393
+ opened = await this.open(config, deadline, headers, body);
40394
+ const status2 = opened.response.statusCode ?? 0;
40395
+ const responseHeaders = opened.response.headers;
40396
+ const raw = await this.readBody(opened.response);
40397
+ opened.cleanup();
40398
+ opened = null;
40399
+ if (status2 < 200 || status2 >= 300) {
40400
+ if ((status2 === 429 || status2 >= 500) && attempt < config.maxRetries) {
40401
+ await this.retryDelay(responseHeaders, deadline);
40402
+ continue;
40403
+ }
40404
+ throw new AiHTTPError(`AI provider returned HTTP ${status2}`, status2);
40405
+ }
40406
+ let parsed;
40407
+ try {
40408
+ parsed = JSON.parse(raw);
40409
+ } catch {
40410
+ throw new AiParseError("AI provider returned malformed JSON");
40411
+ }
40412
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new AiParseError("AI provider returned a non-object JSON response");
40413
+ return parsed;
40414
+ } catch (error) {
40415
+ opened?.cleanup();
40416
+ if (error instanceof AiParseError || error instanceof AiHTTPError && error.status !== null) throw error;
40417
+ if (attempt >= config.maxRetries) throw error;
40418
+ }
40419
+ }
40420
+ throw new AiHTTPError("AI request failed");
40421
+ }
40422
+ static normalizeChat(provider, raw) {
40423
+ try {
40424
+ if (provider === "anthropic") {
40425
+ const content = raw.content;
40426
+ const parts = content.filter((item) => (item.type ?? "text") === "text").map((item) => item.text);
40427
+ if (!parts.length || !parts.every((part) => typeof part === "string")) throw new Error();
40428
+ const usage2 = raw.usage ?? {};
40429
+ const promptTokens = Number(usage2.input_tokens ?? 0);
40430
+ const completionTokens = Number(usage2.output_tokens ?? 0);
40431
+ 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 };
40432
+ }
40433
+ const choice = raw.choices[0];
40434
+ const text = choice.message.content;
40435
+ if (typeof text !== "string") throw new Error();
40436
+ const usage = raw.usage ?? {};
40437
+ 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 };
40438
+ } catch {
40439
+ throw new AiParseError("AI provider returned a malformed chat response");
40440
+ }
40441
+ }
40442
+ static async chatResponse(config, headers, body) {
40443
+ return this.normalizeChat(config.provider, await this.requestJson(config, headers, body));
40444
+ }
40445
+ static streamDelta(provider, data) {
40446
+ if (data === "[DONE]") return { completed: true };
40447
+ let event;
40448
+ try {
40449
+ event = JSON.parse(data);
40450
+ } catch {
40451
+ throw new AiParseError("AI provider returned malformed stream data");
40452
+ }
40453
+ const text = provider === "anthropic" ? event.type === "content_block_delta" ? event.delta?.text : void 0 : event.choices?.[0]?.delta?.content;
40454
+ if (text !== void 0 && text !== null && typeof text !== "string") throw new AiParseError("AI provider returned malformed stream data");
40455
+ return { completed: false, text };
40456
+ }
40457
+ static async *streamData(response) {
40458
+ let buffer = "";
40459
+ for await (const chunk of response) {
40460
+ buffer += Buffer.from(chunk).toString("utf8");
40461
+ let newline;
40462
+ while ((newline = buffer.indexOf("\n")) >= 0) {
40463
+ const line = buffer.slice(0, newline).trim();
40464
+ buffer = buffer.slice(newline + 1);
40465
+ if (line.startsWith("data:")) yield line.slice(5).trim();
40466
+ }
40467
+ }
40468
+ }
40469
+ static streamError(error) {
40470
+ if (error instanceof AiError) return error;
40471
+ if (error instanceof Error && error.name === "AbortError") return new AiTimeoutError("AI total request timeout expired");
40472
+ return new AiHTTPError(`AI transport failed (${error instanceof Error ? error.name : "Error"})`);
40473
+ }
40474
+ static async *streamRequest(config, headers, body) {
40475
+ const deadline = performance.now() + config.totalTimeout * 1e3;
40476
+ let yielded = false;
40477
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
40478
+ let opened = null;
40479
+ try {
40480
+ opened = await this.open(config, deadline, { ...headers, accept: "text/event-stream" }, body);
40481
+ const status2 = opened.response.statusCode ?? 0;
40482
+ if (status2 < 200 || status2 >= 300) {
40483
+ await this.readBody(opened.response);
40484
+ if ((status2 === 429 || status2 >= 500) && attempt < config.maxRetries) {
40485
+ await this.retryDelay(opened.response.headers, deadline);
40486
+ opened.cleanup();
40487
+ opened = null;
40488
+ continue;
40489
+ }
40490
+ throw new AiHTTPError(`AI provider returned HTTP ${status2}`, status2);
40491
+ }
40492
+ let completed = false;
40493
+ for await (const data of this.streamData(opened.response)) {
40494
+ const delta = this.streamDelta(config.provider, data);
40495
+ if (delta.completed) {
40496
+ completed = true;
40497
+ break;
40498
+ }
40499
+ if (delta.text === void 0) continue;
40500
+ yielded = true;
40501
+ yield delta.text;
40502
+ }
40503
+ opened.cleanup();
40504
+ opened = null;
40505
+ if (completed) return;
40506
+ throw new AiParseError("AI provider stream ended before [DONE]");
40507
+ } catch (error) {
40508
+ opened?.cleanup();
40509
+ const failure = this.streamError(error);
40510
+ if (failure instanceof AiParseError || failure instanceof AiHTTPError && failure.status !== null || yielded || attempt >= config.maxRetries) throw failure;
40511
+ }
40512
+ }
40513
+ }
40514
+ };
40515
+ }
40516
+ });
40517
+
40603
40518
  // ../core/src/queueBackends/rabbitmqBackend.ts
40604
40519
  import { execFileSync as execFileSync5 } from "node:child_process";
40605
40520
  import { randomUUID as randomUUID8 } from "node:crypto";
@@ -42303,6 +42218,12 @@ __export(src_exports3, {
42303
42218
  APPLICATION_JSON: () => APPLICATION_JSON,
42304
42219
  APPLICATION_OCTET: () => APPLICATION_OCTET,
42305
42220
  APPLICATION_XML: () => APPLICATION_XML,
42221
+ Ai: () => Ai,
42222
+ AiConfigError: () => AiConfigError,
42223
+ AiError: () => AiError,
42224
+ AiHTTPError: () => AiHTTPError,
42225
+ AiParseError: () => AiParseError,
42226
+ AiTimeoutError: () => AiTimeoutError,
42306
42227
  Api: () => Api,
42307
42228
  Auth: () => Auth,
42308
42229
  CANONICAL_SESSION_BACKENDS: () => CANONICAL_SESSION_BACKENDS,
@@ -42630,6 +42551,7 @@ var init_src3 = __esm({
42630
42551
  init_htmlElement();
42631
42552
  init_errorOverlay();
42632
42553
  init_ai();
42554
+ init_aiClient();
42633
42555
  init_liteBackend();
42634
42556
  init_rabbitmqBackend();
42635
42557
  init_kafkaBackend();
@@ -43143,7 +43065,7 @@ async function listRoutes() {
43143
43065
  }
43144
43066
 
43145
43067
  // src/commands/test.ts
43146
- import { existsSync as existsSync32, readdirSync as readdirSync20, readFileSync as readFileSync28, statSync as statSync19 } from "node:fs";
43068
+ import { existsSync as existsSync32, readdirSync as readdirSync20, readFileSync as readFileSync27, statSync as statSync19 } from "node:fs";
43147
43069
  import { resolve as resolve27, join as join34 } from "node:path";
43148
43070
  import { pathToFileURL as pathToFileURL2 } from "node:url";
43149
43071
  import { execSync as execSync3 } from "node:child_process";
@@ -43180,7 +43102,7 @@ async function runInlineTests(cwd) {
43180
43102
  for (const file of walkSource(srcDir)) {
43181
43103
  let text;
43182
43104
  try {
43183
- text = readFileSync28(file, "utf-8");
43105
+ text = readFileSync27(file, "utf-8");
43184
43106
  } catch {
43185
43107
  continue;
43186
43108
  }
@@ -43244,8 +43166,8 @@ async function runTests(testPath) {
43244
43166
  console.log(` Found ${testFiles.length} test file(s)
43245
43167
  `);
43246
43168
  for (const file of testFiles) {
43247
- const relative11 = file.replace(cwd + "/", "");
43248
- console.log(` Running: ${relative11}`);
43169
+ const relative10 = file.replace(cwd + "/", "");
43170
+ console.log(` Running: ${relative10}`);
43249
43171
  try {
43250
43172
  execSync3(`npx tsx "${file}"`, { cwd, stdio: "inherit" });
43251
43173
  } catch {
@@ -44894,8 +44816,8 @@ async function runSeeds(seedPath) {
44894
44816
  `);
44895
44817
  let failed = false;
44896
44818
  for (const file of seedFiles) {
44897
- const relative11 = file.replace(cwd + "/", "");
44898
- console.log(` Seeding: ${relative11}`);
44819
+ const relative10 = file.replace(cwd + "/", "");
44820
+ console.log(` Seeding: ${relative10}`);
44899
44821
  try {
44900
44822
  execSync4(`npx tsx "${file}"`, { cwd, stdio: "inherit" });
44901
44823
  } catch {
@@ -44909,121 +44831,14 @@ async function runSeeds(seedPath) {
44909
44831
  console.log("\n All seeds completed.");
44910
44832
  }
44911
44833
 
44912
- // src/commands/metrics.ts
44913
- init_metrics();
44914
- function parseFlags(args) {
44915
- const flags = { top: 20, json: false, path: "src", failOn: null };
44916
- for (let i = 0; i < args.length; i++) {
44917
- const a = args[i];
44918
- switch (a) {
44919
- case "--json":
44920
- flags.json = true;
44921
- break;
44922
- case "--top": {
44923
- const v = args[++i];
44924
- if (v === void 0 || !/^\d+$/.test(v)) {
44925
- return { error: `--top expects a number (got '${v ?? ""}')` };
44926
- }
44927
- flags.top = parseInt(v, 10);
44928
- break;
44929
- }
44930
- case "--path": {
44931
- const v = args[++i];
44932
- if (v === void 0) return { error: "--path expects a directory" };
44933
- flags.path = v;
44934
- break;
44935
- }
44936
- case "--fail-on": {
44937
- const v = args[++i];
44938
- if (v !== "warn" && v !== "error") {
44939
- return { error: `invalid --fail-on '${v ?? ""}' (use warn or error)` };
44940
- }
44941
- flags.failOn = v;
44942
- break;
44943
- }
44944
- default:
44945
- return { error: `unknown option '${a}'` };
44946
- }
44947
- }
44948
- return flags;
44949
- }
44950
- function runMetrics(args = []) {
44951
- const parsed = parseFlags(args);
44952
- if ("error" in parsed) {
44953
- console.log(` ${parsed.error}`);
44954
- return 2;
44955
- }
44956
- const { top, json, path: path8, failOn } = parsed;
44957
- let result;
44958
- try {
44959
- result = offenders(path8, Number.MAX_SAFE_INTEGER);
44960
- } catch (e) {
44961
- if (e instanceof MetricsEngineError) {
44962
- console.error(` metrics error: ${e.message}`);
44963
- return 2;
44964
- }
44965
- throw e;
44966
- }
44967
- const summary = result.summary;
44968
- const allOffenders = result.offenders;
44969
- const found = allOffenders.slice(0, top);
44970
- const severities = new Set(allOffenders.map((o) => o.severity));
44971
- let exitCode = 0;
44972
- if (failOn === "warn" && (severities.has("warn") || severities.has("error"))) {
44973
- exitCode = 1;
44974
- } else if (failOn === "error" && severities.has("error")) {
44975
- exitCode = 1;
44976
- }
44977
- if (json) {
44978
- console.log(JSON.stringify({ summary, offenders: found }, null, 2));
44979
- return exitCode;
44980
- }
44981
- const useColor = Boolean(process.stdout.isTTY);
44982
- const c = (text, code) => useColor ? `\x1B[${code}m${text}\x1B[0m` : text;
44983
- const sevColor = { error: "31", warn: "33", info: "2" };
44984
- console.log("");
44985
- console.log(` Tina4 Metrics \u2014 ${summary.scan_mode} scan (${summary.scan_root})`);
44986
- console.log(
44987
- ` files: ${summary.files_analyzed} functions: ${summary.total_functions} avg complexity: ${summary.avg_complexity} avg maintainability: ${summary.avg_maintainability}`
44988
- );
44989
- console.log(
44990
- ` offenders: ${summary.total_offenders} total` + (found.length ? ` (showing top ${found.length})` : "")
44991
- );
44992
- console.log("");
44993
- if (found.length === 0) {
44994
- console.log(" " + c("\u2713 no offenders \u2014 clean", "32"));
44995
- console.log("");
44996
- return exitCode;
44997
- }
44998
- const locs = found.map((o) => `${o.file}:${o.line}`);
44999
- const locW = Math.max("FILE:LINE".length, ...locs.map((s) => s.length));
45000
- const kindW = Math.max("KIND".length, ...found.map((o) => o.kind.length));
45001
- const pad = (s, w) => s.padEnd(w);
45002
- const header = ` ${pad("#", 3)} ${pad("SEVERITY", 8)} ${pad("KIND", kindW)} ${pad(
45003
- "FILE:LINE",
45004
- locW
45005
- )} DETAIL`;
45006
- console.log(c(header, "1"));
45007
- console.log(" " + "-".repeat(header.length - 2));
45008
- found.forEach((o, idx) => {
45009
- const i = idx + 1;
45010
- const sevCell = c(pad(o.severity, 8), sevColor[o.severity]);
45011
- console.log(
45012
- ` ${String(i).padStart(3)} ${sevCell} ${pad(o.kind, kindW)} ${pad(locs[idx], locW)} ${o.detail}`
45013
- );
45014
- });
45015
- console.log("");
45016
- return exitCode;
45017
- }
45018
-
45019
44834
  // src/commands/queue.ts
45020
44835
  init_dotenv();
45021
44836
  init_queue();
45022
44837
  import { readdirSync as readdirSync22, statSync as statSync20 } from "node:fs";
45023
- import { extname as extname9, join as join37 } from "node:path";
44838
+ import { extname as extname8, join as join37 } from "node:path";
45024
44839
  import { pathToFileURL as pathToFileURL3 } from "node:url";
45025
44840
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["once", "json"]);
45026
- function parseFlags2(args) {
44841
+ function parseFlags(args) {
45027
44842
  const flags = {};
45028
44843
  const positional = [];
45029
44844
  let i = 0;
@@ -45058,7 +44873,7 @@ async function resolveQueueHandler(servicesDir, topic) {
45058
44873
  }
45059
44874
  for (const entry of entries.sort()) {
45060
44875
  if (entry.startsWith("_")) continue;
45061
- const ext = extname9(entry);
44876
+ const ext = extname8(entry);
45062
44877
  if (ext !== ".ts" && ext !== ".js") continue;
45063
44878
  const fullPath = join37(servicesDir, entry);
45064
44879
  try {
@@ -45079,7 +44894,7 @@ function firstJob(yielded) {
45079
44894
  }
45080
44895
  async function queueWork(args) {
45081
44896
  loadEnv();
45082
- const { flags, positional } = parseFlags2(args);
44897
+ const { flags, positional } = parseFlags(args);
45083
44898
  const topic = positional[0] ?? "default";
45084
44899
  const once2 = Boolean(flags.once);
45085
44900
  let pollSeconds;
@@ -45131,7 +44946,7 @@ async function queueWork(args) {
45131
44946
  }
45132
44947
  async function queueStats(args) {
45133
44948
  loadEnv();
45134
- const { flags, positional } = parseFlags2(args);
44949
+ const { flags, positional } = parseFlags(args);
45135
44950
  const topic = positional[0] ?? "default";
45136
44951
  const queue = new Queue({ topic });
45137
44952
  const stats = {
@@ -45162,7 +44977,7 @@ async function queueStats(args) {
45162
44977
  }
45163
44978
  async function queueRetry(args) {
45164
44979
  loadEnv();
45165
- const { positional } = parseFlags2(args);
44980
+ const { positional } = parseFlags(args);
45166
44981
  const topic = positional[0] ?? "default";
45167
44982
  const queue = new Queue({ topic });
45168
44983
  const dead = queue.deadLetters(0);
@@ -45178,7 +44993,7 @@ async function queueRetry(args) {
45178
44993
  }
45179
44994
  async function queueClear(args) {
45180
44995
  loadEnv();
45181
- const { positional } = parseFlags2(args);
44996
+ const { positional } = parseFlags(args);
45182
44997
  const status2 = positional[0] ?? "completed";
45183
44998
  const topic = positional[1] ?? "default";
45184
44999
  const queue = new Queue({ topic });
@@ -45215,7 +45030,7 @@ async function queueCommand(args = []) {
45215
45030
  import { accessSync as accessSync2, constants as constants2, existsSync as existsSync35, statSync as statSync21 } from "node:fs";
45216
45031
  import { basename as basename8, delimiter as delimiter2, join as join38 } from "node:path";
45217
45032
  import { spawnSync as spawnSync3 } from "node:child_process";
45218
- function parseFlags3(args) {
45033
+ function parseFlags2(args) {
45219
45034
  const flags = {};
45220
45035
  let i = 0;
45221
45036
  while (i < args.length) {
@@ -45253,7 +45068,7 @@ function whichDocker() {
45253
45068
  return null;
45254
45069
  }
45255
45070
  function buildImage(args) {
45256
- const flags = parseFlags3(args);
45071
+ const flags = parseFlags2(args);
45257
45072
  let tag = typeof flags.tag === "string" ? flags.tag : "";
45258
45073
  if (!tag) {
45259
45074
  const dirName = basename8(process.cwd()).toLowerCase();
@@ -45288,7 +45103,7 @@ function buildImage(args) {
45288
45103
 
45289
45104
  // src/bin.ts
45290
45105
  import { spawnSync as spawnSync4 } from "node:child_process";
45291
- import { existsSync as existsSync36, readFileSync as readFileSync29, statSync as statSync22 } from "node:fs";
45106
+ import { existsSync as existsSync36, readFileSync as readFileSync28, statSync as statSync22 } from "node:fs";
45292
45107
  import { delimiter as delimiter3, dirname as dirname16, join as join39 } from "node:path";
45293
45108
  import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL4 } from "node:url";
45294
45109
  function readCliVersion() {
@@ -45297,7 +45112,7 @@ function readCliVersion() {
45297
45112
  const pkgPath = join39(dir, "package.json");
45298
45113
  if (existsSync36(pkgPath)) {
45299
45114
  try {
45300
- const pkg = JSON.parse(readFileSync29(pkgPath, "utf-8"));
45115
+ const pkg = JSON.parse(readFileSync28(pkgPath, "utf-8"));
45301
45116
  if (typeof pkg.version === "string" && pkg.version) return pkg.version;
45302
45117
  } catch {
45303
45118
  }
@@ -45533,13 +45348,6 @@ var COMMANDS = {
45533
45348
  usage: "[file]",
45534
45349
  summary: "Run database seed files from src/seeds/"
45535
45350
  },
45536
- metrics: {
45537
- handler: (a) => {
45538
- process.exit(runMetrics(a));
45539
- },
45540
- usage: "[--top N] [--json] [--fail-on warn|error] [--path DIR]",
45541
- summary: "Rank top code-quality offenders"
45542
- },
45543
45351
  console: {
45544
45352
  handler: async () => {
45545
45353
  await openConsole();