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.
- package/CLAUDE.md +3 -3
- package/README.md +16 -0
- package/package.json +2 -2
- package/packages/cli/dist/bin.js +572 -764
- package/packages/cli/src/bin.ts +0 -6
- package/packages/core/dist/index.js +561 -622
- package/packages/core/public/js/tina4-dev-admin.min.js +2 -2
- package/packages/core/src/aiClient.ts +288 -0
- package/packages/core/src/devAdmin.ts +1 -2
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/metrics.ts +80 -632
- package/packages/frond/dist/index.js +113 -109
- package/packages/frond/src/engine.ts +128 -130
- package/packages/orm/dist/index.js +562 -629
- package/types/core/src/aiClient.d.ts +66 -0
- package/types/core/src/index.d.ts +2 -0
- package/types/core/src/metrics.d.ts +0 -35
- package/types/frond/src/engine.d.ts +4 -2
- package/packages/cli/src/commands/metrics.ts +0 -160
- package/types/cli/src/commands/metrics.d.ts +0 -6
package/packages/cli/dist/bin.js
CHANGED
|
@@ -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
|
|
1875
|
-
expr
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
if (
|
|
1879
|
-
|
|
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
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
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
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
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
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
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
|
|
19571
|
-
const pattern = filePathToPattern(
|
|
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: ${
|
|
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 ${
|
|
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(
|
|
19624
|
-
const parts =
|
|
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
|
|
22882
|
-
|
|
22883
|
-
|
|
22884
|
-
|
|
22885
|
-
|
|
22886
|
-
|
|
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
|
|
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
|
|
23223
|
-
const resolved =
|
|
23224
|
-
const
|
|
23225
|
-
|
|
23226
|
-
|
|
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
|
|
23231
|
-
for (const
|
|
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(
|
|
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
|
|
23257
|
-
|
|
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:
|
|
22926
|
+
timeout: 6e4,
|
|
23262
22927
|
maxBuffer: 64 * 1024 * 1024
|
|
23263
22928
|
});
|
|
23264
|
-
if (
|
|
23265
|
-
|
|
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 (
|
|
23278
|
-
|
|
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(
|
|
23283
|
-
|
|
23284
|
-
|
|
23285
|
-
|
|
23286
|
-
|
|
23287
|
-
|
|
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
|
|
23292
|
-
|
|
23293
|
-
|
|
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
|
|
22950
|
+
return payload[key];
|
|
23300
22951
|
}
|
|
23301
22952
|
function fullAnalysis(root = "src") {
|
|
23302
|
-
const [resolved, scanMode] =
|
|
22953
|
+
const [resolved, scanMode] = resolveTarget(root);
|
|
23303
22954
|
const payload = runEngine(resolved);
|
|
23304
|
-
const summary =
|
|
23305
|
-
|
|
23306
|
-
|
|
23307
|
-
|
|
23308
|
-
|
|
23309
|
-
|
|
23310
|
-
|
|
23311
|
-
|
|
23312
|
-
|
|
23313
|
-
if (
|
|
23314
|
-
|
|
23315
|
-
|
|
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
|
-
|
|
23322
|
-
|
|
23323
|
-
|
|
23324
|
-
|
|
23325
|
-
|
|
23326
|
-
|
|
23327
|
-
|
|
23328
|
-
|
|
23329
|
-
|
|
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) &&
|
|
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
|
|
23353
|
-
if (!
|
|
23354
|
-
|
|
23355
|
-
|
|
23356
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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", "
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
26336
|
-
import { basename as basename5, dirname as dirname11, extname as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
31524
|
-
import { join as join27, dirname as dirname13, resolve as resolve16, relative as
|
|
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(
|
|
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
|
|
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) ?
|
|
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 =
|
|
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 =
|
|
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) ?
|
|
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(
|
|
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) =>
|
|
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 =
|
|
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:
|
|
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 =
|
|
33585
|
-
const path8 =
|
|
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",
|
|
33236
|
+
Plan2.recordAction(existed ? "patched" : "created", relative7(root, target));
|
|
33608
33237
|
} catch {
|
|
33609
33238
|
}
|
|
33610
|
-
res.json({ ok: true, path:
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
35032
|
-
import { existsSync as existsSync25, readdirSync as readdirSync17, readFileSync as
|
|
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,
|
|
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, {}) :
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
43248
|
-
console.log(` Running: ${
|
|
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
|
|
44898
|
-
console.log(` Seeding: ${
|
|
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
|
|
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
|
|
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 =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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();
|