tina4-nodejs 3.13.100 → 3.13.101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -3
- package/README.md +16 -0
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +455 -652
- package/packages/cli/src/bin.ts +0 -6
- package/packages/core/dist/index.js +444 -510
- 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 +79 -631
- package/packages/orm/dist/index.js +445 -517
- 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/packages/cli/src/commands/metrics.ts +0 -160
- package/types/cli/src/commands/metrics.d.ts +0 -6
|
@@ -19566,14 +19566,14 @@ async function discoverRoutes(routesDir) {
|
|
|
19566
19566
|
const currentMtime = statSync7(filePath).mtimeMs;
|
|
19567
19567
|
if (_seenFiles.has(filePath) && _seenMtimes.get(filePath) === currentMtime) continue;
|
|
19568
19568
|
const method = name.toUpperCase();
|
|
19569
|
-
const
|
|
19570
|
-
const pattern = filePathToPattern(
|
|
19569
|
+
const relativePath2 = relative(routesDir, filePath);
|
|
19570
|
+
const pattern = filePathToPattern(relativePath2);
|
|
19571
19571
|
try {
|
|
19572
19572
|
const moduleUrl = `file://${filePath}?t=${currentMtime}`;
|
|
19573
19573
|
const mod = await import(moduleUrl);
|
|
19574
19574
|
const handler = mod.default ?? mod.handler;
|
|
19575
19575
|
if (typeof handler !== "function") {
|
|
19576
|
-
console.warn(` Warning: ${
|
|
19576
|
+
console.warn(` Warning: ${relativePath2} does not export a handler function, skipping`);
|
|
19577
19577
|
continue;
|
|
19578
19578
|
}
|
|
19579
19579
|
const meta = mod.meta;
|
|
@@ -19585,7 +19585,7 @@ async function discoverRoutes(routesDir) {
|
|
|
19585
19585
|
_seenMtimes.set(filePath, currentMtime);
|
|
19586
19586
|
registeredFromThisScan++;
|
|
19587
19587
|
} catch (err) {
|
|
19588
|
-
console.error(` Error loading route ${
|
|
19588
|
+
console.error(` Error loading route ${relativePath2}:`, err);
|
|
19589
19589
|
recordBrokenImport(filePath, err);
|
|
19590
19590
|
}
|
|
19591
19591
|
}
|
|
@@ -19619,8 +19619,8 @@ function recordBrokenImport(filePath, error) {
|
|
|
19619
19619
|
} catch {
|
|
19620
19620
|
}
|
|
19621
19621
|
}
|
|
19622
|
-
function filePathToPattern(
|
|
19623
|
-
const parts =
|
|
19622
|
+
function filePathToPattern(relativePath2) {
|
|
19623
|
+
const parts = relativePath2.replace(/\\/g, "/").split("/").slice(0, -1);
|
|
19624
19624
|
const urlParts = parts.map((part) => {
|
|
19625
19625
|
if (part.startsWith("[...") && part.endsWith("]")) {
|
|
19626
19626
|
const name = part.slice(4, -1);
|
|
@@ -22857,489 +22857,127 @@ import * as fs3 from "node:fs";
|
|
|
22857
22857
|
import * as path2 from "node:path";
|
|
22858
22858
|
import { spawnSync } from "node:child_process";
|
|
22859
22859
|
import { fileURLToPath } from "node:url";
|
|
22860
|
-
function
|
|
22861
|
-
|
|
22862
|
-
|
|
22863
|
-
|
|
22864
|
-
|
|
22865
|
-
|
|
22866
|
-
if (entry.isDirectory()) {
|
|
22867
|
-
if (!exclude.includes(entry.name)) {
|
|
22868
|
-
results.push(...walkFiles(fullPath, extensions, exclude));
|
|
22869
|
-
}
|
|
22870
|
-
} else if (entry.isFile()) {
|
|
22871
|
-
const ext = path2.extname(entry.name);
|
|
22872
|
-
if (extensions.includes(ext) && !entry.name.endsWith(".d.ts")) {
|
|
22873
|
-
results.push(fullPath);
|
|
22874
|
-
}
|
|
22875
|
-
}
|
|
22876
|
-
}
|
|
22877
|
-
return results;
|
|
22878
|
-
}
|
|
22879
|
-
function readFileSafe(filePath) {
|
|
22880
|
-
try {
|
|
22881
|
-
return fs3.readFileSync(filePath, "utf-8");
|
|
22882
|
-
} catch {
|
|
22883
|
-
return null;
|
|
22884
|
-
}
|
|
22885
|
-
}
|
|
22886
|
-
function relativePath(filePath, root = ".") {
|
|
22887
|
-
return path2.relative(root, filePath);
|
|
22888
|
-
}
|
|
22889
|
-
function countLines(source) {
|
|
22890
|
-
const lines = source.split("\n");
|
|
22891
|
-
let loc = 0;
|
|
22892
|
-
let blank = 0;
|
|
22893
|
-
let comment = 0;
|
|
22894
|
-
let inBlockComment = false;
|
|
22895
|
-
for (const line of lines) {
|
|
22896
|
-
const stripped = line.trim();
|
|
22897
|
-
if (!stripped) {
|
|
22898
|
-
blank++;
|
|
22899
|
-
continue;
|
|
22900
|
-
}
|
|
22901
|
-
if (inBlockComment) {
|
|
22902
|
-
comment++;
|
|
22903
|
-
if (stripped.includes("*/")) {
|
|
22904
|
-
inBlockComment = false;
|
|
22905
|
-
}
|
|
22906
|
-
continue;
|
|
22907
|
-
}
|
|
22908
|
-
if (stripped.startsWith("/*")) {
|
|
22909
|
-
comment++;
|
|
22910
|
-
if (!stripped.includes("*/") || stripped.endsWith("/*")) {
|
|
22911
|
-
inBlockComment = true;
|
|
22912
|
-
}
|
|
22913
|
-
continue;
|
|
22914
|
-
}
|
|
22915
|
-
if (stripped.startsWith("//")) {
|
|
22916
|
-
comment++;
|
|
22917
|
-
continue;
|
|
22918
|
-
}
|
|
22919
|
-
loc++;
|
|
22920
|
-
}
|
|
22921
|
-
return { loc, blank, comment };
|
|
22922
|
-
}
|
|
22923
|
-
function stripLiterals(source) {
|
|
22924
|
-
const out = [];
|
|
22925
|
-
const n = source.length;
|
|
22926
|
-
let i = 0;
|
|
22927
|
-
let prevSignificant = "";
|
|
22928
|
-
let prevWord = "";
|
|
22929
|
-
const regexKeywords = /* @__PURE__ */ new Set([
|
|
22930
|
-
"return",
|
|
22931
|
-
"typeof",
|
|
22932
|
-
"instanceof",
|
|
22933
|
-
"in",
|
|
22934
|
-
"of",
|
|
22935
|
-
"new",
|
|
22936
|
-
"delete",
|
|
22937
|
-
"void",
|
|
22938
|
-
"throw",
|
|
22939
|
-
"case",
|
|
22940
|
-
"do",
|
|
22941
|
-
"else",
|
|
22942
|
-
"yield",
|
|
22943
|
-
"await"
|
|
22944
|
-
]);
|
|
22945
|
-
function prevEndsExpression() {
|
|
22946
|
-
if (prevSignificant === "") return false;
|
|
22947
|
-
if (/[A-Za-z0-9_$]/.test(prevSignificant)) {
|
|
22948
|
-
return !regexKeywords.has(prevWord);
|
|
22949
|
-
}
|
|
22950
|
-
if (prevSignificant === ")" || prevSignificant === "]") return true;
|
|
22951
|
-
if (prevSignificant === ".") return true;
|
|
22952
|
-
return false;
|
|
22860
|
+
function containsTypeScript(directory) {
|
|
22861
|
+
if (!fs3.existsSync(directory) || !fs3.statSync(directory).isDirectory()) return false;
|
|
22862
|
+
for (const entry of fs3.readdirSync(directory, { withFileTypes: true })) {
|
|
22863
|
+
if (["node_modules", ".git", "dist", "build"].includes(entry.name)) continue;
|
|
22864
|
+
const target = path2.join(directory, entry.name);
|
|
22865
|
+
if (entry.isDirectory() ? containsTypeScript(target) : /\.[cm]?[jt]sx?$/.test(entry.name)) return true;
|
|
22953
22866
|
}
|
|
22954
|
-
|
|
22955
|
-
const ch = source[i];
|
|
22956
|
-
const next = i + 1 < n ? source[i + 1] : "";
|
|
22957
|
-
if (ch === "/" && next === "/") {
|
|
22958
|
-
out.push("//");
|
|
22959
|
-
i += 2;
|
|
22960
|
-
while (i < n && source[i] !== "\n") {
|
|
22961
|
-
out.push(" ");
|
|
22962
|
-
i++;
|
|
22963
|
-
}
|
|
22964
|
-
continue;
|
|
22965
|
-
}
|
|
22966
|
-
if (ch === "/" && next === "*") {
|
|
22967
|
-
out.push("/*");
|
|
22968
|
-
i += 2;
|
|
22969
|
-
while (i < n && !(source[i] === "*" && source[i + 1] === "/")) {
|
|
22970
|
-
out.push(source[i] === "\n" ? "\n" : " ");
|
|
22971
|
-
i++;
|
|
22972
|
-
}
|
|
22973
|
-
if (i < n) {
|
|
22974
|
-
out.push("*/");
|
|
22975
|
-
i += 2;
|
|
22976
|
-
}
|
|
22977
|
-
continue;
|
|
22978
|
-
}
|
|
22979
|
-
if (ch === '"' || ch === "'") {
|
|
22980
|
-
const quote = ch;
|
|
22981
|
-
out.push(quote);
|
|
22982
|
-
i++;
|
|
22983
|
-
while (i < n && source[i] !== quote) {
|
|
22984
|
-
if (source[i] === "\\" && i + 1 < n) {
|
|
22985
|
-
out.push(" ");
|
|
22986
|
-
i += 2;
|
|
22987
|
-
continue;
|
|
22988
|
-
}
|
|
22989
|
-
if (source[i] === "\n") {
|
|
22990
|
-
out.push("\n");
|
|
22991
|
-
i++;
|
|
22992
|
-
break;
|
|
22993
|
-
}
|
|
22994
|
-
out.push(" ");
|
|
22995
|
-
i++;
|
|
22996
|
-
}
|
|
22997
|
-
if (i < n && source[i] === quote) {
|
|
22998
|
-
out.push(quote);
|
|
22999
|
-
i++;
|
|
23000
|
-
}
|
|
23001
|
-
prevSignificant = quote;
|
|
23002
|
-
prevWord = "";
|
|
23003
|
-
continue;
|
|
23004
|
-
}
|
|
23005
|
-
if (ch === "`") {
|
|
23006
|
-
out.push("`");
|
|
23007
|
-
i++;
|
|
23008
|
-
while (i < n && source[i] !== "`") {
|
|
23009
|
-
if (source[i] === "\\" && i + 1 < n) {
|
|
23010
|
-
out.push(source[i + 1] === "\n" ? " \n" : " ");
|
|
23011
|
-
i += 2;
|
|
23012
|
-
continue;
|
|
23013
|
-
}
|
|
23014
|
-
if (source[i] === "$" && source[i + 1] === "{") {
|
|
23015
|
-
out.push("${");
|
|
23016
|
-
i += 2;
|
|
23017
|
-
let depth = 1;
|
|
23018
|
-
const exprStart = i;
|
|
23019
|
-
while (i < n && depth > 0) {
|
|
23020
|
-
if (source[i] === "{") depth++;
|
|
23021
|
-
else if (source[i] === "}") depth--;
|
|
23022
|
-
if (depth === 0) break;
|
|
23023
|
-
i++;
|
|
23024
|
-
}
|
|
23025
|
-
out.push(stripLiterals(source.slice(exprStart, i)));
|
|
23026
|
-
if (i < n && source[i] === "}") {
|
|
23027
|
-
out.push("}");
|
|
23028
|
-
i++;
|
|
23029
|
-
}
|
|
23030
|
-
continue;
|
|
23031
|
-
}
|
|
23032
|
-
out.push(source[i] === "\n" ? "\n" : " ");
|
|
23033
|
-
i++;
|
|
23034
|
-
}
|
|
23035
|
-
if (i < n && source[i] === "`") {
|
|
23036
|
-
out.push("`");
|
|
23037
|
-
i++;
|
|
23038
|
-
}
|
|
23039
|
-
prevSignificant = "`";
|
|
23040
|
-
prevWord = "";
|
|
23041
|
-
continue;
|
|
23042
|
-
}
|
|
23043
|
-
if (ch === "/" && !prevEndsExpression()) {
|
|
23044
|
-
let j = i + 1;
|
|
23045
|
-
let ok = false;
|
|
23046
|
-
let inClass = false;
|
|
23047
|
-
while (j < n) {
|
|
23048
|
-
const c = source[j];
|
|
23049
|
-
if (c === "\\") {
|
|
23050
|
-
j += 2;
|
|
23051
|
-
continue;
|
|
23052
|
-
}
|
|
23053
|
-
if (c === "\n") break;
|
|
23054
|
-
if (c === "[") inClass = true;
|
|
23055
|
-
else if (c === "]") inClass = false;
|
|
23056
|
-
else if (c === "/" && !inClass) {
|
|
23057
|
-
ok = true;
|
|
23058
|
-
break;
|
|
23059
|
-
}
|
|
23060
|
-
j++;
|
|
23061
|
-
}
|
|
23062
|
-
if (ok) {
|
|
23063
|
-
out.push("/");
|
|
23064
|
-
for (let k = i + 1; k < j; k++) out.push(" ");
|
|
23065
|
-
out.push("/");
|
|
23066
|
-
i = j + 1;
|
|
23067
|
-
while (i < n && /[a-z]/i.test(source[i])) {
|
|
23068
|
-
out.push(source[i]);
|
|
23069
|
-
i++;
|
|
23070
|
-
}
|
|
23071
|
-
prevSignificant = "/";
|
|
23072
|
-
prevWord = "";
|
|
23073
|
-
continue;
|
|
23074
|
-
}
|
|
23075
|
-
}
|
|
23076
|
-
out.push(ch);
|
|
23077
|
-
if (!/\s/.test(ch)) {
|
|
23078
|
-
prevSignificant = ch;
|
|
23079
|
-
if (/[A-Za-z0-9_$]/.test(ch)) {
|
|
23080
|
-
prevWord = /[A-Za-z0-9_$]/.test(source[i - 1] ?? "") ? prevWord + ch : ch;
|
|
23081
|
-
} else {
|
|
23082
|
-
prevWord = "";
|
|
23083
|
-
}
|
|
23084
|
-
}
|
|
23085
|
-
i++;
|
|
23086
|
-
}
|
|
23087
|
-
return out.join("");
|
|
23088
|
-
}
|
|
23089
|
-
function countClassesQuick(source) {
|
|
23090
|
-
const matches = source.match(
|
|
23091
|
-
/(?:^|\n)\s*(?:export\s+)?(?:abstract\s+)?class\s+\w+/g
|
|
23092
|
-
);
|
|
23093
|
-
return matches ? matches.length : 0;
|
|
23094
|
-
}
|
|
23095
|
-
function countFunctionsQuick(source) {
|
|
23096
|
-
const clean = stripLiterals(source);
|
|
23097
|
-
let count = 0;
|
|
23098
|
-
const funcDecls = clean.match(
|
|
23099
|
-
/(?:^|\n)\s*(?:export\s+)?(?:async\s+)?function\s+\w+\s*\(/g
|
|
23100
|
-
);
|
|
23101
|
-
if (funcDecls) count += funcDecls.length;
|
|
23102
|
-
const methods = clean.match(
|
|
23103
|
-
/(?:^|\n)\s*(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*\([^)]*\)\s*(?::\s*\S+)?\s*\{/g
|
|
23104
|
-
);
|
|
23105
|
-
if (methods) count += methods.length;
|
|
23106
|
-
const arrows = clean.match(
|
|
23107
|
-
/(?:^|\n)\s*(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*(?:async\s+)?\(/g
|
|
23108
|
-
);
|
|
23109
|
-
if (arrows) count += arrows.length;
|
|
23110
|
-
return count;
|
|
23111
|
-
}
|
|
23112
|
-
function resolveRoot(root = "src") {
|
|
23113
|
-
const rootPath = path2.resolve(root);
|
|
23114
|
-
if (fs3.existsSync(rootPath) && walkFiles(rootPath, [".ts", ".js"]).length > 0) {
|
|
23115
|
-
_lastScanRoot = rootPath;
|
|
23116
|
-
return root;
|
|
23117
|
-
}
|
|
23118
|
-
const fwDir = path2.resolve(path2.dirname(new URL(import.meta.url).pathname));
|
|
23119
|
-
_lastScanRoot = fwDir;
|
|
23120
|
-
return fwDir;
|
|
23121
|
-
}
|
|
23122
|
-
function quickMetrics(root = "src") {
|
|
23123
|
-
root = resolveRoot(root);
|
|
23124
|
-
const rootPath = path2.resolve(root);
|
|
23125
|
-
if (!fs3.existsSync(rootPath)) {
|
|
23126
|
-
return { error: `Directory not found: ${root}` };
|
|
23127
|
-
}
|
|
23128
|
-
const tsFiles = walkFiles(rootPath, [".ts", ".js"]);
|
|
23129
|
-
const twigFiles = walkFiles(rootPath, [".twig", ".html"]);
|
|
23130
|
-
const migrationsDir = path2.resolve("migrations");
|
|
23131
|
-
const migrationFiles = [
|
|
23132
|
-
...walkFiles(migrationsDir, [".sql"]),
|
|
23133
|
-
...walkFiles(migrationsDir, [".ts"])
|
|
23134
|
-
];
|
|
23135
|
-
const scssFiles = walkFiles(rootPath, [".scss", ".css"]);
|
|
23136
|
-
let totalLoc = 0;
|
|
23137
|
-
let totalBlank = 0;
|
|
23138
|
-
let totalComment = 0;
|
|
23139
|
-
let totalClasses = 0;
|
|
23140
|
-
let totalFunctions = 0;
|
|
23141
|
-
const fileDetails = [];
|
|
23142
|
-
for (const f of tsFiles) {
|
|
23143
|
-
const source = readFileSafe(f);
|
|
23144
|
-
if (source === null) continue;
|
|
23145
|
-
const counts = countLines(source);
|
|
23146
|
-
const classes = countClassesQuick(source);
|
|
23147
|
-
const functions = countFunctionsQuick(source);
|
|
23148
|
-
totalLoc += counts.loc;
|
|
23149
|
-
totalBlank += counts.blank;
|
|
23150
|
-
totalComment += counts.comment;
|
|
23151
|
-
totalClasses += classes;
|
|
23152
|
-
totalFunctions += functions;
|
|
23153
|
-
fileDetails.push({
|
|
23154
|
-
path: relativePath(f, rootPath),
|
|
23155
|
-
loc: counts.loc,
|
|
23156
|
-
blank: counts.blank,
|
|
23157
|
-
comment: counts.comment,
|
|
23158
|
-
classes,
|
|
23159
|
-
functions
|
|
23160
|
-
});
|
|
23161
|
-
}
|
|
23162
|
-
fileDetails.sort((a, b) => b.loc - a.loc);
|
|
23163
|
-
let routeCount = 0;
|
|
23164
|
-
let ormCount = 0;
|
|
23165
|
-
for (const f of tsFiles) {
|
|
23166
|
-
const source = readFileSafe(f);
|
|
23167
|
-
if (source === null) continue;
|
|
23168
|
-
const routes = source.match(
|
|
23169
|
-
/(?:router\s*\.\s*(?:get|post|put|delete|patch|any)\s*\(|@(?:get|post|put|delete|patch)\s*\()/g
|
|
23170
|
-
);
|
|
23171
|
-
if (routes) routeCount += routes.length;
|
|
23172
|
-
const orms = source.match(
|
|
23173
|
-
/class\s+\w+\s+extends\s+(?:ORM|Model)\b/g
|
|
23174
|
-
);
|
|
23175
|
-
if (orms) ormCount += orms.length;
|
|
23176
|
-
}
|
|
23177
|
-
const breakdown = {
|
|
23178
|
-
typescript: tsFiles.filter((f) => f.endsWith(".ts")).length,
|
|
23179
|
-
javascript: tsFiles.filter((f) => f.endsWith(".js")).length,
|
|
23180
|
-
templates: twigFiles.length,
|
|
23181
|
-
migrations: migrationFiles.length,
|
|
23182
|
-
stylesheets: scssFiles.length
|
|
23183
|
-
};
|
|
23184
|
-
return {
|
|
23185
|
-
file_count: tsFiles.length,
|
|
23186
|
-
total_loc: totalLoc,
|
|
23187
|
-
total_blank: totalBlank,
|
|
23188
|
-
total_comment: totalComment,
|
|
23189
|
-
lloc: totalLoc,
|
|
23190
|
-
classes: totalClasses,
|
|
23191
|
-
functions: totalFunctions,
|
|
23192
|
-
route_count: routeCount,
|
|
23193
|
-
orm_count: ormCount,
|
|
23194
|
-
template_count: twigFiles.length,
|
|
23195
|
-
migration_count: migrationFiles.length,
|
|
23196
|
-
avg_file_size: tsFiles.length > 0 ? Math.round(totalLoc / tsFiles.length * 10) / 10 : 0,
|
|
23197
|
-
largest_files: fileDetails.slice(0, 10),
|
|
23198
|
-
breakdown
|
|
23199
|
-
};
|
|
22867
|
+
return false;
|
|
23200
22868
|
}
|
|
23201
|
-
function
|
|
23202
|
-
const resolved =
|
|
23203
|
-
const
|
|
23204
|
-
|
|
23205
|
-
|
|
23206
|
-
return [resolved, scanningFramework ? "framework" : "project"];
|
|
22869
|
+
function resolveTarget(root = "src") {
|
|
22870
|
+
const resolved = containsTypeScript(root) ? path2.resolve(root) : path2.dirname(fileURLToPath(import.meta.url));
|
|
22871
|
+
const mode = containsTypeScript(root) ? "project" : "framework";
|
|
22872
|
+
lastScanRoot = resolved;
|
|
22873
|
+
return [resolved, mode];
|
|
23207
22874
|
}
|
|
23208
22875
|
function enginePath() {
|
|
23209
|
-
const names = process.platform === "win32" ? ["tina4.exe", "tina4
|
|
23210
|
-
for (const
|
|
23211
|
-
if (!dir) continue;
|
|
22876
|
+
const names = process.platform === "win32" ? ["tina4.exe", "tina4"] : ["tina4"];
|
|
22877
|
+
for (const directory of (process.env.PATH || "").split(path2.delimiter)) {
|
|
23212
22878
|
for (const name of names) {
|
|
23213
|
-
const candidate = path2.join(
|
|
22879
|
+
const candidate = path2.join(directory, name);
|
|
23214
22880
|
try {
|
|
23215
|
-
if (!fs3.statSync(candidate).isFile()) continue;
|
|
23216
22881
|
fs3.accessSync(candidate, fs3.constants.X_OK);
|
|
22882
|
+
if (!fs3.statSync(candidate).isFile()) continue;
|
|
22883
|
+
const descriptor = fs3.openSync(candidate, "r");
|
|
22884
|
+
const header = Buffer.alloc(2);
|
|
22885
|
+
fs3.readSync(descriptor, header, 0, 2, 0);
|
|
22886
|
+
fs3.closeSync(descriptor);
|
|
22887
|
+
if (header.toString("latin1") !== "#!") return candidate;
|
|
23217
22888
|
} catch {
|
|
23218
22889
|
continue;
|
|
23219
22890
|
}
|
|
23220
|
-
try {
|
|
23221
|
-
const fd = fs3.openSync(candidate, "r");
|
|
23222
|
-
const buf = Buffer.alloc(2);
|
|
23223
|
-
fs3.readSync(fd, buf, 0, 2, 0);
|
|
23224
|
-
fs3.closeSync(fd);
|
|
23225
|
-
if (buf.toString("latin1") === "#!") continue;
|
|
23226
|
-
} catch {
|
|
23227
|
-
}
|
|
23228
|
-
return candidate;
|
|
23229
22891
|
}
|
|
23230
22892
|
}
|
|
23231
22893
|
return null;
|
|
23232
22894
|
}
|
|
23233
22895
|
function runEngine(target) {
|
|
23234
22896
|
const binary = enginePath();
|
|
23235
|
-
if (binary
|
|
23236
|
-
|
|
23237
|
-
}
|
|
23238
|
-
const proc = spawnSync(binary, ["metrics", "--path", target, "--json"], {
|
|
22897
|
+
if (!binary) throw new MetricsEngineError(`tina4 not found on PATH - ${INSTALL_HINT}`);
|
|
22898
|
+
const processResult = spawnSync(binary, ["metrics", "--path", target, "--json"], {
|
|
23239
22899
|
encoding: "utf8",
|
|
23240
|
-
timeout:
|
|
22900
|
+
timeout: 6e4,
|
|
23241
22901
|
maxBuffer: 64 * 1024 * 1024
|
|
23242
22902
|
});
|
|
23243
|
-
if (
|
|
23244
|
-
|
|
23245
|
-
if (err.code === "ETIMEDOUT") {
|
|
23246
|
-
throw new MetricsEngineError(`tina4 metrics timed out after ${TIMEOUT_MS / 1e3}s on ${target}`);
|
|
23247
|
-
}
|
|
23248
|
-
throw new MetricsEngineError(`could not run ${binary}: ${err.message}`);
|
|
23249
|
-
}
|
|
23250
|
-
if (proc.status !== 0) {
|
|
23251
|
-
const detail = (proc.stderr || proc.stdout || "").trim().split("\n")[0];
|
|
23252
|
-
throw new MetricsEngineError(
|
|
23253
|
-
`tina4 metrics failed on ${target}: ${detail || `exit code ${proc.status}`}`
|
|
23254
|
-
);
|
|
22903
|
+
if (processResult.error) {
|
|
22904
|
+
throw new MetricsEngineError(`could not run ${binary}: ${processResult.error.message}`);
|
|
23255
22905
|
}
|
|
23256
|
-
if (
|
|
23257
|
-
|
|
22906
|
+
if (processResult.status !== 0) {
|
|
22907
|
+
const detail = (processResult.stderr || processResult.stdout || "").trim().split("\n")[0];
|
|
22908
|
+
throw new MetricsEngineError(`tina4 metrics failed on ${target}: ${detail || processResult.status}`);
|
|
23258
22909
|
}
|
|
23259
|
-
let payload;
|
|
23260
22910
|
try {
|
|
23261
|
-
payload = JSON.parse(
|
|
23262
|
-
|
|
23263
|
-
|
|
23264
|
-
|
|
23265
|
-
|
|
23266
|
-
|
|
22911
|
+
const payload = JSON.parse(processResult.stdout);
|
|
22912
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
22913
|
+
throw new Error("non-object payload");
|
|
22914
|
+
}
|
|
22915
|
+
return payload;
|
|
22916
|
+
} catch (error) {
|
|
22917
|
+
throw new MetricsEngineError(`tina4 metrics returned unreadable JSON: ${error.message}`);
|
|
23267
22918
|
}
|
|
23268
|
-
return payload;
|
|
23269
22919
|
}
|
|
23270
|
-
function
|
|
23271
|
-
|
|
23272
|
-
|
|
23273
|
-
if (!ok) {
|
|
23274
|
-
throw new MetricsEngineError(
|
|
23275
|
-
`engine payload has no usable '${key}' - the installed tina4 CLI predates a field the dashboard renders. Update it: ${INSTALL_HINT}`
|
|
23276
|
-
);
|
|
22920
|
+
function requireArray(payload, key) {
|
|
22921
|
+
if (!Array.isArray(payload[key])) {
|
|
22922
|
+
throw new MetricsEngineError(`engine payload has no usable '${key}' - ${INSTALL_HINT}`);
|
|
23277
22923
|
}
|
|
23278
|
-
return
|
|
22924
|
+
return payload[key];
|
|
23279
22925
|
}
|
|
23280
22926
|
function fullAnalysis(root = "src") {
|
|
23281
|
-
const [resolved, scanMode] =
|
|
22927
|
+
const [resolved, scanMode] = resolveTarget(root);
|
|
23282
22928
|
const payload = runEngine(resolved);
|
|
23283
|
-
const summary =
|
|
23284
|
-
|
|
23285
|
-
|
|
23286
|
-
|
|
23287
|
-
|
|
23288
|
-
|
|
23289
|
-
|
|
23290
|
-
|
|
23291
|
-
|
|
23292
|
-
if (
|
|
23293
|
-
|
|
23294
|
-
|
|
22929
|
+
const summary = payload.summary;
|
|
22930
|
+
if (!summary || typeof summary !== "object" || Array.isArray(summary)) {
|
|
22931
|
+
throw new MetricsEngineError(`engine payload has no usable 'summary' - ${INSTALL_HINT}`);
|
|
22932
|
+
}
|
|
22933
|
+
const fileMetrics = requireArray(payload, "file_metrics");
|
|
22934
|
+
const functions = requireArray(payload, "most_complex_functions");
|
|
22935
|
+
const missingSummary = SUMMARY_KEYS.filter((key) => !(key in summary));
|
|
22936
|
+
if (missingSummary.length) throw new MetricsEngineError(`engine summary is missing ${missingSummary.join(", ")}`);
|
|
22937
|
+
const missingFile = fileMetrics.length ? FILE_KEYS.filter((key) => !(key in fileMetrics[0])) : [];
|
|
22938
|
+
if (missingFile.length) throw new MetricsEngineError(`engine file_metrics is missing ${missingFile.join(", ")}`);
|
|
22939
|
+
const missingFunction = functions.length ? FUNCTION_KEYS.filter((key) => !(key in functions[0])) : [];
|
|
22940
|
+
if (missingFunction.length) {
|
|
22941
|
+
throw new MetricsEngineError(`engine function metrics are missing ${missingFunction.join(", ")}`);
|
|
23295
22942
|
}
|
|
23296
|
-
|
|
23297
|
-
|
|
23298
|
-
|
|
23299
|
-
|
|
23300
|
-
|
|
23301
|
-
|
|
23302
|
-
|
|
23303
|
-
|
|
23304
|
-
|
|
23305
|
-
result.scan_mode = scanMode;
|
|
23306
|
-
result.scan_root = path2.resolve(resolved);
|
|
23307
|
-
result.engine = "tina4-cli";
|
|
23308
|
-
return result;
|
|
22943
|
+
return {
|
|
22944
|
+
...Object.fromEntries(SUMMARY_KEYS.map((key) => [key, summary[key]])),
|
|
22945
|
+
file_metrics: fileMetrics,
|
|
22946
|
+
most_complex_functions: functions.slice(0, 15),
|
|
22947
|
+
dependency_graph: payload.dependency_graph || {},
|
|
22948
|
+
scan_mode: scanMode,
|
|
22949
|
+
scan_root: resolved,
|
|
22950
|
+
engine: "tina4-cli"
|
|
22951
|
+
};
|
|
23309
22952
|
}
|
|
23310
22953
|
function fileDetail(filePath) {
|
|
23311
22954
|
if (!filePath) throw new MetricsEngineError("fileDetail needs a path");
|
|
23312
22955
|
let target = filePath;
|
|
23313
|
-
if (!fs3.existsSync(target) &&
|
|
23314
|
-
const candidate = path2.join(_lastScanRoot, filePath);
|
|
23315
|
-
if (fs3.existsSync(candidate)) target = candidate;
|
|
23316
|
-
}
|
|
22956
|
+
if (!fs3.existsSync(target) && lastScanRoot) target = path2.join(lastScanRoot, filePath);
|
|
23317
22957
|
if (!fs3.existsSync(target)) throw new MetricsEngineError(`no such file: ${filePath}`);
|
|
23318
22958
|
if (fs3.statSync(target).isDirectory()) throw new MetricsEngineError(`not a file: ${filePath}`);
|
|
23319
22959
|
const payload = runEngine(target);
|
|
23320
|
-
const
|
|
23321
|
-
if (!
|
|
23322
|
-
|
|
23323
|
-
|
|
23324
|
-
|
|
22960
|
+
const files = requireArray(payload, "file_metrics");
|
|
22961
|
+
if (!files.length) throw new MetricsEngineError(`engine reported no metrics for ${filePath}`);
|
|
22962
|
+
return {
|
|
22963
|
+
...files[0],
|
|
22964
|
+
function_count: files[0].functions || 0,
|
|
22965
|
+
functions: requireArray(payload, "most_complex_functions"),
|
|
22966
|
+
engine: "tina4-cli"
|
|
22967
|
+
};
|
|
23325
22968
|
}
|
|
23326
|
-
var
|
|
22969
|
+
var lastScanRoot, MetricsEngineError, INSTALL_HINT, SUMMARY_KEYS, FILE_KEYS, FUNCTION_KEYS;
|
|
23327
22970
|
var init_metrics = __esm({
|
|
23328
22971
|
"src/metrics.ts"() {
|
|
23329
22972
|
"use strict";
|
|
23330
|
-
|
|
22973
|
+
lastScanRoot = "";
|
|
23331
22974
|
MetricsEngineError = class extends Error {
|
|
23332
22975
|
constructor(message) {
|
|
23333
22976
|
super(message);
|
|
23334
22977
|
this.name = "MetricsEngineError";
|
|
23335
22978
|
}
|
|
23336
22979
|
};
|
|
23337
|
-
|
|
23338
|
-
INSTALL_HINT = [
|
|
23339
|
-
"the tina4 CLI provides the metrics engine (ADR-0002). Install it with",
|
|
23340
|
-
" curl -fsSL https://tina4.com/install.sh | sh",
|
|
23341
|
-
"or see https://tina4.com/cli"
|
|
23342
|
-
].join("\n");
|
|
22980
|
+
INSTALL_HINT = "update the native tina4 CLI: https://tina4.com/cli";
|
|
23343
22981
|
SUMMARY_KEYS = ["files_analyzed", "total_functions", "avg_complexity", "avg_maintainability"];
|
|
23344
22982
|
FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "has_tests"];
|
|
23345
22983
|
FUNCTION_KEYS = ["name", "file", "line", "complexity", "loc"];
|
|
@@ -23347,7 +22985,7 @@ var init_metrics = __esm({
|
|
|
23347
22985
|
});
|
|
23348
22986
|
|
|
23349
22987
|
// src/feedback.ts
|
|
23350
|
-
import { readFileSync as
|
|
22988
|
+
import { readFileSync as readFileSync12, existsSync as existsSync13 } from "node:fs";
|
|
23351
22989
|
import { dirname as dirname7, join as join18, resolve as resolve9 } from "node:path";
|
|
23352
22990
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
23353
22991
|
function feedbackEnabled() {
|
|
@@ -23488,7 +23126,7 @@ var init_feedback = __esm({
|
|
|
23488
23126
|
handleFeedbackWidgetJs = (_req, res) => {
|
|
23489
23127
|
let body;
|
|
23490
23128
|
if (existsSync13(WIDGET_BUNDLE_PATH)) {
|
|
23491
|
-
body =
|
|
23129
|
+
body = readFileSync12(WIDGET_BUNDLE_PATH);
|
|
23492
23130
|
} else {
|
|
23493
23131
|
body = "console.warn('tina4-feedback-widget bundle not built yet');";
|
|
23494
23132
|
}
|
|
@@ -23503,7 +23141,7 @@ var init_feedback = __esm({
|
|
|
23503
23141
|
});
|
|
23504
23142
|
|
|
23505
23143
|
// src/version.ts
|
|
23506
|
-
import { existsSync as existsSync14, readFileSync as
|
|
23144
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
|
|
23507
23145
|
import { dirname as dirname8, join as join19 } from "node:path";
|
|
23508
23146
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
23509
23147
|
function resolveFrameworkVersion() {
|
|
@@ -23512,7 +23150,7 @@ function resolveFrameworkVersion() {
|
|
|
23512
23150
|
const pkgPath = join19(dir, "package.json");
|
|
23513
23151
|
if (existsSync14(pkgPath)) {
|
|
23514
23152
|
try {
|
|
23515
|
-
const pkg = JSON.parse(
|
|
23153
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
23516
23154
|
if (typeof pkg.version === "string" && pkg.version) return pkg.version;
|
|
23517
23155
|
} catch {
|
|
23518
23156
|
}
|
|
@@ -26300,8 +25938,8 @@ __export(context_exports, {
|
|
|
26300
25938
|
fts5Supported: () => fts5Supported
|
|
26301
25939
|
});
|
|
26302
25940
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
26303
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as
|
|
26304
|
-
import { basename as basename4, dirname as dirname10, extname as
|
|
25941
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as readFileSync15, readdirSync as readdirSync10, realpathSync as realpathSync5 } from "node:fs";
|
|
25942
|
+
import { basename as basename4, dirname as dirname10, extname as extname5, isAbsolute as isAbsolute6, join as join21, relative as relative3, resolve as resolve11 } from "node:path";
|
|
26305
25943
|
function fts5Supported() {
|
|
26306
25944
|
try {
|
|
26307
25945
|
const conn = new DatabaseSync4(":memory:");
|
|
@@ -26436,7 +26074,7 @@ var init_context = __esm({
|
|
|
26436
26074
|
}
|
|
26437
26075
|
// ── indexing ───────────────────────────────────────────────
|
|
26438
26076
|
static chunksFor(label, text) {
|
|
26439
|
-
const ext =
|
|
26077
|
+
const ext = extname5(label).toLowerCase();
|
|
26440
26078
|
const special = SPECIAL_FILES.has(basename4(label).toLowerCase());
|
|
26441
26079
|
if (CODE_EXTS.has(ext) || CONFIG_EXTS.has(ext) || special) {
|
|
26442
26080
|
return chunkCode(text, label);
|
|
@@ -26454,7 +26092,7 @@ var init_context = __esm({
|
|
|
26454
26092
|
const stored = label != null ? String(label) : String(file);
|
|
26455
26093
|
let text;
|
|
26456
26094
|
try {
|
|
26457
|
-
text =
|
|
26095
|
+
text = readFileSync15(file, "utf-8");
|
|
26458
26096
|
} catch {
|
|
26459
26097
|
return 0;
|
|
26460
26098
|
}
|
|
@@ -26475,7 +26113,7 @@ var init_context = __esm({
|
|
|
26475
26113
|
static eligible(filename) {
|
|
26476
26114
|
const fn = filename.toLowerCase();
|
|
26477
26115
|
if (fn.endsWith(".min.js")) return false;
|
|
26478
|
-
const ext =
|
|
26116
|
+
const ext = extname5(fn);
|
|
26479
26117
|
return CODE_EXTS.has(ext) || DOC_EXTS.has(ext) || CONFIG_EXTS.has(ext) || SPECIAL_FILES.has(fn);
|
|
26480
26118
|
}
|
|
26481
26119
|
/**
|
|
@@ -26504,7 +26142,7 @@ var init_context = __esm({
|
|
|
26504
26142
|
for (const fn of files) {
|
|
26505
26143
|
if (!_Context.eligible(fn)) continue;
|
|
26506
26144
|
const full = join21(dir, fn);
|
|
26507
|
-
const rel =
|
|
26145
|
+
const rel = relative3(rootAbs, full);
|
|
26508
26146
|
total += this.indexPath(full, rel);
|
|
26509
26147
|
}
|
|
26510
26148
|
for (const d of subdirs) walk2(join21(dir, d));
|
|
@@ -26525,7 +26163,7 @@ var init_context = __esm({
|
|
|
26525
26163
|
const raw = String(changedPath);
|
|
26526
26164
|
const abs = isAbsolute6(raw) ? raw : join21(process.cwd(), raw);
|
|
26527
26165
|
const resolved = realResolve(resolve11(abs));
|
|
26528
|
-
const rel =
|
|
26166
|
+
const rel = relative3(this.root, resolved);
|
|
26529
26167
|
if (rel === "" || rel.startsWith("..") || isAbsolute6(rel)) {
|
|
26530
26168
|
return -1;
|
|
26531
26169
|
}
|
|
@@ -28447,7 +28085,7 @@ var init_job = __esm({
|
|
|
28447
28085
|
});
|
|
28448
28086
|
|
|
28449
28087
|
// src/queueBackends/liteBackend.ts
|
|
28450
|
-
import { mkdirSync as mkdirSync14, readdirSync as readdirSync11, readFileSync as
|
|
28088
|
+
import { mkdirSync as mkdirSync14, readdirSync as readdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync10, unlinkSync as unlinkSync7, existsSync as existsSync17 } from "node:fs";
|
|
28451
28089
|
import { join as join22 } from "node:path";
|
|
28452
28090
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
28453
28091
|
var LiteBackend;
|
|
@@ -28547,7 +28185,7 @@ var init_liteBackend = __esm({
|
|
|
28547
28185
|
const filePath = join22(dir, filename);
|
|
28548
28186
|
let job;
|
|
28549
28187
|
try {
|
|
28550
|
-
job = JSON.parse(
|
|
28188
|
+
job = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
28551
28189
|
} catch {
|
|
28552
28190
|
continue;
|
|
28553
28191
|
}
|
|
@@ -28611,7 +28249,7 @@ var init_liteBackend = __esm({
|
|
|
28611
28249
|
const filePath = join22(reservedDir, filename);
|
|
28612
28250
|
let record;
|
|
28613
28251
|
try {
|
|
28614
|
-
record = JSON.parse(
|
|
28252
|
+
record = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
28615
28253
|
} catch {
|
|
28616
28254
|
continue;
|
|
28617
28255
|
}
|
|
@@ -28724,7 +28362,7 @@ var init_liteBackend = __esm({
|
|
|
28724
28362
|
let count = 0;
|
|
28725
28363
|
for (const file of files) {
|
|
28726
28364
|
try {
|
|
28727
|
-
const job = JSON.parse(
|
|
28365
|
+
const job = JSON.parse(readFileSync16(join22(scanDir, file), "utf-8"));
|
|
28728
28366
|
if (job.status === status2) count++;
|
|
28729
28367
|
} catch {
|
|
28730
28368
|
}
|
|
@@ -28781,7 +28419,7 @@ var init_liteBackend = __esm({
|
|
|
28781
28419
|
const files = readdirSync11(dir).filter((f) => f.endsWith(".queue-data")).sort();
|
|
28782
28420
|
for (const file of files) {
|
|
28783
28421
|
try {
|
|
28784
|
-
const job = JSON.parse(
|
|
28422
|
+
const job = JSON.parse(readFileSync16(join22(dir, file), "utf-8"));
|
|
28785
28423
|
const attempts = job.attempts || 0;
|
|
28786
28424
|
if (attempts > 0 && attempts < maxRetries) {
|
|
28787
28425
|
results.push(job);
|
|
@@ -28807,7 +28445,7 @@ var init_liteBackend = __esm({
|
|
|
28807
28445
|
const failedDir = join22(this.basePath, q, "failed");
|
|
28808
28446
|
const filePath = join22(failedDir, `${jobId}.queue-data`);
|
|
28809
28447
|
if (existsSync17(filePath)) {
|
|
28810
|
-
const job = JSON.parse(
|
|
28448
|
+
const job = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
28811
28449
|
job.status = "pending";
|
|
28812
28450
|
job.attempts = (job.attempts || 0) + 1;
|
|
28813
28451
|
job.error = void 0;
|
|
@@ -28831,7 +28469,7 @@ var init_liteBackend = __esm({
|
|
|
28831
28469
|
const files = readdirSync11(failedDir).filter((f) => f.endsWith(".queue-data")).sort();
|
|
28832
28470
|
for (const file of files) {
|
|
28833
28471
|
try {
|
|
28834
|
-
const job = JSON.parse(
|
|
28472
|
+
const job = JSON.parse(readFileSync16(join22(failedDir, file), "utf-8"));
|
|
28835
28473
|
if ((job.attempts || 0) >= maxRetries) {
|
|
28836
28474
|
job.status = "dead";
|
|
28837
28475
|
results.push(job);
|
|
@@ -28865,7 +28503,7 @@ var init_liteBackend = __esm({
|
|
|
28865
28503
|
const files = readdirSync11(dir).filter((f) => f.endsWith(".queue-data"));
|
|
28866
28504
|
for (const file of files) {
|
|
28867
28505
|
try {
|
|
28868
|
-
const job = JSON.parse(
|
|
28506
|
+
const job = JSON.parse(readFileSync16(join22(dir, file), "utf-8"));
|
|
28869
28507
|
if (job.status === status2) {
|
|
28870
28508
|
unlinkSync7(join22(dir, file));
|
|
28871
28509
|
count++;
|
|
@@ -28892,7 +28530,7 @@ var init_liteBackend = __esm({
|
|
|
28892
28530
|
for (const file of files) {
|
|
28893
28531
|
try {
|
|
28894
28532
|
const filePath = join22(failedDir, file);
|
|
28895
|
-
const job = JSON.parse(
|
|
28533
|
+
const job = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
28896
28534
|
if ((job.attempts || 0) >= maxRetries) {
|
|
28897
28535
|
continue;
|
|
28898
28536
|
}
|
|
@@ -28924,7 +28562,7 @@ var init_liteBackend = __esm({
|
|
|
28924
28562
|
const filePath = join22(dir, file);
|
|
28925
28563
|
let job;
|
|
28926
28564
|
try {
|
|
28927
|
-
job = JSON.parse(
|
|
28565
|
+
job = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
28928
28566
|
} catch {
|
|
28929
28567
|
continue;
|
|
28930
28568
|
}
|
|
@@ -30387,7 +30025,7 @@ function detectVersion(projectRoot3) {
|
|
|
30387
30025
|
}
|
|
30388
30026
|
return "0.0.0";
|
|
30389
30027
|
}
|
|
30390
|
-
function
|
|
30028
|
+
function relativePath(absPath, projectRoot3, frameworkRoots) {
|
|
30391
30029
|
const norm = path6.resolve(absPath);
|
|
30392
30030
|
for (const fw of frameworkRoots) {
|
|
30393
30031
|
const parent = path6.dirname(fw);
|
|
@@ -30852,7 +30490,7 @@ function buildEntriesForFile(absPath, source, fwRoots, projectRoot3, version, ou
|
|
|
30852
30490
|
} catch {
|
|
30853
30491
|
return;
|
|
30854
30492
|
}
|
|
30855
|
-
const rel =
|
|
30493
|
+
const rel = relativePath(absPath, projectRoot3, fwRoots);
|
|
30856
30494
|
for (const cls of parsed.classes) {
|
|
30857
30495
|
if (!cls.exported && source === "framework") {
|
|
30858
30496
|
continue;
|
|
@@ -31488,8 +31126,8 @@ ${end}
|
|
|
31488
31126
|
|
|
31489
31127
|
// src/devAdmin.ts
|
|
31490
31128
|
import { cpus as osCpus } from "node:os";
|
|
31491
|
-
import { readFileSync as
|
|
31492
|
-
import { join as join26, dirname as dirname12, resolve as resolve15, relative as
|
|
31129
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync14, existsSync as existsSync21, readdirSync as readdirSync15, mkdirSync as mkdirSync17, copyFileSync, statSync as statSync16 } from "node:fs";
|
|
31130
|
+
import { join as join26, dirname as dirname12, resolve as resolve15, relative as relative7 } from "node:path";
|
|
31493
31131
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
31494
31132
|
import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
31495
31133
|
function escapeHtml(value) {
|
|
@@ -31608,7 +31246,7 @@ function readQueueDir(dir, topic, status2) {
|
|
|
31608
31246
|
for (const filename of readdirSync15(dir).sort()) {
|
|
31609
31247
|
if (!filename.endsWith(".queue-data")) continue;
|
|
31610
31248
|
try {
|
|
31611
|
-
jobs.push(mapQueueJob(JSON.parse(
|
|
31249
|
+
jobs.push(mapQueueJob(JSON.parse(readFileSync20(join26(dir, filename), "utf-8")), topic, status2));
|
|
31612
31250
|
} catch {
|
|
31613
31251
|
}
|
|
31614
31252
|
}
|
|
@@ -31727,7 +31365,7 @@ function resolveDevEnvVar(key) {
|
|
|
31727
31365
|
if (live !== void 0 && live !== "") return live;
|
|
31728
31366
|
const envPath = join26(process.cwd(), ".env");
|
|
31729
31367
|
if (!existsSync21(envPath)) return "";
|
|
31730
|
-
for (const line of
|
|
31368
|
+
for (const line of readFileSync20(envPath, "utf-8").split("\n")) {
|
|
31731
31369
|
const t = line.trim();
|
|
31732
31370
|
if (!t || t.startsWith("#") || !t.includes("=")) continue;
|
|
31733
31371
|
const eq = t.indexOf("=");
|
|
@@ -31737,7 +31375,7 @@ function resolveDevEnvVar(key) {
|
|
|
31737
31375
|
}
|
|
31738
31376
|
function upsertDevEnvVar(key, value) {
|
|
31739
31377
|
const envPath = join26(process.cwd(), ".env");
|
|
31740
|
-
const lines = existsSync21(envPath) ?
|
|
31378
|
+
const lines = existsSync21(envPath) ? readFileSync20(envPath, "utf-8").split("\n") : [];
|
|
31741
31379
|
let found = false;
|
|
31742
31380
|
const out = [];
|
|
31743
31381
|
for (const line of lines) {
|
|
@@ -31770,7 +31408,7 @@ function parseEnvFile() {
|
|
|
31770
31408
|
const envPath = join26(process.cwd(), ".env");
|
|
31771
31409
|
const result = {};
|
|
31772
31410
|
if (!existsSync21(envPath)) return result;
|
|
31773
|
-
const lines =
|
|
31411
|
+
const lines = readFileSync20(envPath, "utf-8").split("\n");
|
|
31774
31412
|
for (const line of lines) {
|
|
31775
31413
|
const trimmed = line.trim();
|
|
31776
31414
|
if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
@@ -31810,7 +31448,7 @@ function handleGalleryDeploy(router) {
|
|
|
31810
31448
|
const copied = [];
|
|
31811
31449
|
const allFiles = walkDirRecursive(gallerySrc);
|
|
31812
31450
|
for (const srcFile of allFiles) {
|
|
31813
|
-
const rel =
|
|
31451
|
+
const rel = relative7(gallerySrc, srcFile);
|
|
31814
31452
|
const dest = join26(projectSrc, rel);
|
|
31815
31453
|
mkdirSync17(dirname12(dest), { recursive: true });
|
|
31816
31454
|
copyFileSync(srcFile, dest);
|
|
@@ -32472,9 +32110,6 @@ var init_devAdmin = __esm({
|
|
|
32472
32110
|
{ method: "GET", pattern: "/__dev/api/gallery", handler: handleGalleryList },
|
|
32473
32111
|
{ method: "POST", pattern: "/__dev/api/gallery/deploy", handler: handleGalleryDeploy(router) },
|
|
32474
32112
|
// Metrics
|
|
32475
|
-
{ method: "GET", pattern: "/__dev/api/metrics", handler: (_req, res) => {
|
|
32476
|
-
res.json(quickMetrics());
|
|
32477
|
-
} },
|
|
32478
32113
|
// No fallback (ADR-0002): a missing or stale CLI is a 503 naming the
|
|
32479
32114
|
// install command, never zeros that read as a healthy codebase.
|
|
32480
32115
|
{ method: "GET", pattern: "/__dev/api/metrics/full", handler: (_req, res) => {
|
|
@@ -33283,7 +32918,7 @@ var init_devAdmin = __esm({
|
|
|
33283
32918
|
}
|
|
33284
32919
|
try {
|
|
33285
32920
|
const envPath = join26(process.cwd(), ".env");
|
|
33286
|
-
const lines = existsSync21(envPath) ?
|
|
32921
|
+
const lines = existsSync21(envPath) ? readFileSync20(envPath, "utf-8").split("\n") : [];
|
|
33287
32922
|
const keysFound = { TINA4_DATABASE_URL: false, TINA4_DATABASE_USERNAME: false, TINA4_DATABASE_PASSWORD: false };
|
|
33288
32923
|
const newLines = [];
|
|
33289
32924
|
for (const line of lines) {
|
|
@@ -33329,12 +32964,12 @@ var init_devAdmin = __esm({
|
|
|
33329
32964
|
const metaFile = join26(entryPath, "meta.json");
|
|
33330
32965
|
if (statSync16(entryPath).isDirectory() && existsSync21(metaFile)) {
|
|
33331
32966
|
try {
|
|
33332
|
-
const meta = JSON.parse(
|
|
32967
|
+
const meta = JSON.parse(readFileSync20(metaFile, "utf-8"));
|
|
33333
32968
|
meta.id = entry;
|
|
33334
32969
|
const srcDir = join26(entryPath, "src");
|
|
33335
32970
|
if (existsSync21(srcDir)) {
|
|
33336
32971
|
const allFiles = walkDirRecursive(srcDir);
|
|
33337
|
-
meta.files = allFiles.map((f) =>
|
|
32972
|
+
meta.files = allFiles.map((f) => relative7(srcDir, f));
|
|
33338
32973
|
}
|
|
33339
32974
|
const projectSrc = resolve15(process.cwd(), "src");
|
|
33340
32975
|
if (existsSync21(srcDir) && meta.files) {
|
|
@@ -33452,7 +33087,7 @@ var init_devAdmin = __esm({
|
|
|
33452
33087
|
for (const name of readdirSync15(target).sort()) {
|
|
33453
33088
|
if (devFilesHidden(name)) continue;
|
|
33454
33089
|
const full = join26(target, name);
|
|
33455
|
-
const entryRel =
|
|
33090
|
+
const entryRel = relative7(root, full).replace(/\\/g, "/");
|
|
33456
33091
|
if (isSecretPath(entryRel)) continue;
|
|
33457
33092
|
let isDir = false;
|
|
33458
33093
|
let size = null;
|
|
@@ -33497,7 +33132,7 @@ var init_devAdmin = __esm({
|
|
|
33497
33132
|
size
|
|
33498
33133
|
});
|
|
33499
33134
|
}
|
|
33500
|
-
res.json({ path:
|
|
33135
|
+
res.json({ path: relative7(root, target).replace(/\\/g, "/") || ".", branch, entries });
|
|
33501
33136
|
};
|
|
33502
33137
|
DEV_ADMIN_LANG_MAP = {
|
|
33503
33138
|
".py": "python",
|
|
@@ -33549,8 +33184,8 @@ var init_devAdmin = __esm({
|
|
|
33549
33184
|
return;
|
|
33550
33185
|
}
|
|
33551
33186
|
try {
|
|
33552
|
-
const content =
|
|
33553
|
-
const path8 =
|
|
33187
|
+
const content = readFileSync20(target, "utf-8");
|
|
33188
|
+
const path8 = relative7(root, target);
|
|
33554
33189
|
res.json({ path: path8, content, language: devAdminLanguage(path8), bytes: Buffer.byteLength(content, "utf-8") });
|
|
33555
33190
|
} catch (e) {
|
|
33556
33191
|
res.json({ error: e.message }, 500);
|
|
@@ -33572,10 +33207,10 @@ var init_devAdmin = __esm({
|
|
|
33572
33207
|
writeFileSync14(target, content, "utf-8");
|
|
33573
33208
|
try {
|
|
33574
33209
|
const { Plan: Plan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
|
|
33575
|
-
Plan2.recordAction(existed ? "patched" : "created",
|
|
33210
|
+
Plan2.recordAction(existed ? "patched" : "created", relative7(root, target));
|
|
33576
33211
|
} catch {
|
|
33577
33212
|
}
|
|
33578
|
-
res.json({ ok: true, path:
|
|
33213
|
+
res.json({ ok: true, path: relative7(root, target), bytes: Buffer.byteLength(content, "utf-8") });
|
|
33579
33214
|
} catch (e) {
|
|
33580
33215
|
res.json({ error: e.message }, 500);
|
|
33581
33216
|
}
|
|
@@ -33595,7 +33230,7 @@ var init_devAdmin = __esm({
|
|
|
33595
33230
|
return;
|
|
33596
33231
|
}
|
|
33597
33232
|
try {
|
|
33598
|
-
const buf =
|
|
33233
|
+
const buf = readFileSync20(target);
|
|
33599
33234
|
const ext = target.slice(target.lastIndexOf(".") + 1).toLowerCase();
|
|
33600
33235
|
const mime = {
|
|
33601
33236
|
js: "application/javascript",
|
|
@@ -33637,7 +33272,7 @@ var init_devAdmin = __esm({
|
|
|
33637
33272
|
const { renameSync: renameSync3 } = await import("node:fs");
|
|
33638
33273
|
mkdirSync17(dirname12(dst), { recursive: true });
|
|
33639
33274
|
renameSync3(src, dst);
|
|
33640
|
-
res.json({ ok: true, from:
|
|
33275
|
+
res.json({ ok: true, from: relative7(root, src), to: relative7(root, dst) });
|
|
33641
33276
|
} catch (e) {
|
|
33642
33277
|
res.json({ error: e.message }, 500);
|
|
33643
33278
|
}
|
|
@@ -33658,7 +33293,7 @@ var init_devAdmin = __esm({
|
|
|
33658
33293
|
try {
|
|
33659
33294
|
const { rmSync } = await import("node:fs");
|
|
33660
33295
|
rmSync(target, { recursive: true, force: true });
|
|
33661
|
-
res.json({ ok: true, deleted:
|
|
33296
|
+
res.json({ ok: true, deleted: relative7(root, target) });
|
|
33662
33297
|
} catch (e) {
|
|
33663
33298
|
res.json({ error: e.message }, 500);
|
|
33664
33299
|
}
|
|
@@ -33992,7 +33627,7 @@ var init_devAdmin = __esm({
|
|
|
33992
33627
|
});
|
|
33993
33628
|
};
|
|
33994
33629
|
handleDevAdminJs = async (_req, res) => {
|
|
33995
|
-
const { readFileSync:
|
|
33630
|
+
const { readFileSync: readFileSync27, existsSync: existsSync27 } = await import("node:fs");
|
|
33996
33631
|
const { dirname: dirname15, join: join32, resolve: resolve20 } = await import("node:path");
|
|
33997
33632
|
const { fileURLToPath: fileURLToPath8 } = await import("node:url");
|
|
33998
33633
|
const dir = dirname15(fileURLToPath8(import.meta.url));
|
|
@@ -34008,7 +33643,7 @@ var init_devAdmin = __esm({
|
|
|
34008
33643
|
for (const jsPath of candidates) {
|
|
34009
33644
|
if (existsSync27(jsPath)) {
|
|
34010
33645
|
try {
|
|
34011
|
-
const content =
|
|
33646
|
+
const content = readFileSync27(jsPath, "utf-8");
|
|
34012
33647
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
34013
33648
|
res.raw.end(content);
|
|
34014
33649
|
return;
|
|
@@ -34023,7 +33658,7 @@ var init_devAdmin = __esm({
|
|
|
34023
33658
|
});
|
|
34024
33659
|
|
|
34025
33660
|
// src/i18n.ts
|
|
34026
|
-
import { readFileSync as
|
|
33661
|
+
import { readFileSync as readFileSync21, readdirSync as readdirSync16, existsSync as existsSync22 } from "node:fs";
|
|
34027
33662
|
import { join as join27, resolve as resolve16 } from "node:path";
|
|
34028
33663
|
var I18n;
|
|
34029
33664
|
var init_i18n = __esm({
|
|
@@ -34120,7 +33755,7 @@ var init_i18n = __esm({
|
|
|
34120
33755
|
const filePath = join27(this._localeDir, `${locale}.json`);
|
|
34121
33756
|
if (existsSync22(filePath)) {
|
|
34122
33757
|
try {
|
|
34123
|
-
const raw =
|
|
33758
|
+
const raw = readFileSync21(filePath, "utf-8");
|
|
34124
33759
|
const data = JSON.parse(raw);
|
|
34125
33760
|
this._translations.set(locale, _I18n._flatten(data));
|
|
34126
33761
|
return;
|
|
@@ -34133,7 +33768,7 @@ var init_i18n = __esm({
|
|
|
34133
33768
|
const yamlPath = join27(this._localeDir, `${locale}${ext}`);
|
|
34134
33769
|
if (existsSync22(yamlPath)) {
|
|
34135
33770
|
try {
|
|
34136
|
-
const raw =
|
|
33771
|
+
const raw = readFileSync21(yamlPath, "utf-8");
|
|
34137
33772
|
const data = _I18n._parseSimpleYaml(raw);
|
|
34138
33773
|
this._translations.set(locale, _I18n._flatten(data));
|
|
34139
33774
|
return;
|
|
@@ -34996,8 +34631,8 @@ var init_docsAutoDiscovery = __esm({
|
|
|
34996
34631
|
// src/server.ts
|
|
34997
34632
|
import { createServer as createServer2 } from "node:http";
|
|
34998
34633
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
34999
|
-
import { resolve as resolve18, dirname as dirname13, join as join29, relative as
|
|
35000
|
-
import { existsSync as existsSync24, readdirSync as readdirSync17, readFileSync as
|
|
34634
|
+
import { resolve as resolve18, dirname as dirname13, join as join29, relative as relative8 } from "node:path";
|
|
34635
|
+
import { existsSync as existsSync24, readdirSync as readdirSync17, readFileSync as readFileSync23, statSync as statSync17 } from "node:fs";
|
|
35001
34636
|
import { isatty } from "node:tty";
|
|
35002
34637
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
35003
34638
|
import { execFileSync as execFileSync3, exec } from "node:child_process";
|
|
@@ -35221,7 +34856,7 @@ function getGalleryDeployedState() {
|
|
|
35221
34856
|
if (existsSync24(srcDir)) {
|
|
35222
34857
|
const files = walkGalleryFiles(srcDir);
|
|
35223
34858
|
const projectSrc = resolve18(process.cwd(), "src");
|
|
35224
|
-
state[entry] = files.every((f) => existsSync24(join29(projectSrc,
|
|
34859
|
+
state[entry] = files.every((f) => existsSync24(join29(projectSrc, relative8(srcDir, f))));
|
|
35225
34860
|
} else {
|
|
35226
34861
|
state[entry] = false;
|
|
35227
34862
|
}
|
|
@@ -35658,7 +35293,7 @@ function serveTemplateFallback(ctx) {
|
|
|
35658
35293
|
if ((ctx.req.method ?? "GET") !== "GET") return false;
|
|
35659
35294
|
const tplFile = resolveTemplate(ctx.pathname, ctx.templatesDir);
|
|
35660
35295
|
if (!tplFile) return false;
|
|
35661
|
-
const html = ctx.frondEngine ? ctx.frondEngine.render(tplFile, {}) :
|
|
35296
|
+
const html = ctx.frondEngine ? ctx.frondEngine.render(tplFile, {}) : readFileSync23(resolve18(ctx.templatesDir, tplFile), "utf-8");
|
|
35662
35297
|
ctx.res.raw.writeHead(200, void 0, { "Content-Type": "text/html; charset=utf-8" });
|
|
35663
35298
|
ctx.res.raw.end(html);
|
|
35664
35299
|
return true;
|
|
@@ -36485,7 +36120,7 @@ var init_mqttMessage = __esm({
|
|
|
36485
36120
|
import net2 from "node:net";
|
|
36486
36121
|
import tls from "node:tls";
|
|
36487
36122
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
36488
|
-
import { existsSync as existsSync25, readFileSync as
|
|
36123
|
+
import { existsSync as existsSync25, readFileSync as readFileSync24 } from "node:fs";
|
|
36489
36124
|
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;
|
|
36490
36125
|
var init_mqtt = __esm({
|
|
36491
36126
|
"src/mqtt.ts"() {
|
|
@@ -36950,7 +36585,7 @@ var init_mqtt = __esm({
|
|
|
36950
36585
|
servername: this.host,
|
|
36951
36586
|
rejectUnauthorized: this.tlsVerify
|
|
36952
36587
|
};
|
|
36953
|
-
if (this.tlsVerify && this.caFile) opts.ca =
|
|
36588
|
+
if (this.tlsVerify && this.caFile) opts.ca = readFileSync24(this.caFile);
|
|
36954
36589
|
sock = tls.connect(opts, () => settle(() => resolve20(sock)));
|
|
36955
36590
|
} else {
|
|
36956
36591
|
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve20(sock)));
|
|
@@ -37171,7 +36806,7 @@ var init_mqtt = __esm({
|
|
|
37171
36806
|
|
|
37172
36807
|
// src/service.ts
|
|
37173
36808
|
import { readdirSync as readdirSync18, statSync as statSync18, watchFile, unwatchFile } from "node:fs";
|
|
37174
|
-
import { join as join30, extname as
|
|
36809
|
+
import { join as join30, extname as extname7 } from "node:path";
|
|
37175
36810
|
import { pathToFileURL } from "node:url";
|
|
37176
36811
|
function matchCronField(field, value) {
|
|
37177
36812
|
if (field === "*") return true;
|
|
@@ -37342,7 +36977,7 @@ var init_service = __esm({
|
|
|
37342
36977
|
return discovered;
|
|
37343
36978
|
}
|
|
37344
36979
|
for (const entry of entries) {
|
|
37345
|
-
const ext =
|
|
36980
|
+
const ext = extname7(entry);
|
|
37346
36981
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
37347
36982
|
const fullPath = join30(dir, entry);
|
|
37348
36983
|
const stat = statSync18(fullPath);
|
|
@@ -37458,7 +37093,7 @@ var init_service = __esm({
|
|
|
37458
37093
|
return;
|
|
37459
37094
|
}
|
|
37460
37095
|
for (const entry of entries) {
|
|
37461
|
-
const ext =
|
|
37096
|
+
const ext = extname7(entry);
|
|
37462
37097
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
37463
37098
|
const fullPath = join30(dir, entry);
|
|
37464
37099
|
if (watchedFiles.has(fullPath)) continue;
|
|
@@ -38161,7 +37796,7 @@ var init_api = __esm({
|
|
|
38161
37796
|
// src/messenger.ts
|
|
38162
37797
|
import net3 from "node:net";
|
|
38163
37798
|
import tls2 from "node:tls";
|
|
38164
|
-
import { readFileSync as
|
|
37799
|
+
import { readFileSync as readFileSync25 } from "node:fs";
|
|
38165
37800
|
import { basename as basename6 } from "node:path";
|
|
38166
37801
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
38167
37802
|
function tlsRejectUnauthorized() {
|
|
@@ -38258,7 +37893,7 @@ function buildMimeMessage(options) {
|
|
|
38258
37893
|
}
|
|
38259
37894
|
for (const filePath of options.attachments) {
|
|
38260
37895
|
const fileName = basename6(filePath);
|
|
38261
|
-
const fileData =
|
|
37896
|
+
const fileData = readFileSync25(filePath);
|
|
38262
37897
|
const base64Data = fileData.toString("base64");
|
|
38263
37898
|
lines.push("");
|
|
38264
37899
|
lines.push(`--${boundary}`);
|
|
@@ -39721,9 +39356,9 @@ var init_htmlElement = __esm({
|
|
|
39721
39356
|
});
|
|
39722
39357
|
|
|
39723
39358
|
// src/ai.ts
|
|
39724
|
-
import { existsSync as existsSync26, mkdirSync as mkdirSync20, writeFileSync as writeFileSync17, readFileSync as
|
|
39359
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync20, writeFileSync as writeFileSync17, readFileSync as readFileSync26 } from "node:fs";
|
|
39725
39360
|
import { homedir } from "node:os";
|
|
39726
|
-
import { join as join31, resolve as resolve19, relative as
|
|
39361
|
+
import { join as join31, resolve as resolve19, relative as relative9, dirname as dirname14 } from "node:path";
|
|
39727
39362
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
39728
39363
|
import { execSync, execFileSync as execFileSync4 } from "node:child_process";
|
|
39729
39364
|
import { createInterface } from "node:readline";
|
|
@@ -39731,7 +39366,7 @@ function readVersion() {
|
|
|
39731
39366
|
try {
|
|
39732
39367
|
const thisDir = dirname14(fileURLToPath7(import.meta.url));
|
|
39733
39368
|
const rootPkg = resolve19(thisDir, "..", "..", "..", "package.json");
|
|
39734
|
-
const pkg = JSON.parse(
|
|
39369
|
+
const pkg = JSON.parse(readFileSync26(rootPkg, "utf-8"));
|
|
39735
39370
|
return pkg.version ?? "0.0.0";
|
|
39736
39371
|
} catch {
|
|
39737
39372
|
return "0.0.0";
|
|
@@ -39954,7 +39589,7 @@ function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
|
39954
39589
|
writeFileSync17(contextPath, frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
39955
39590
|
return "Installed";
|
|
39956
39591
|
}
|
|
39957
|
-
const existing =
|
|
39592
|
+
const existing = readFileSync26(contextPath, "utf-8");
|
|
39958
39593
|
if (hasMarkers(existing, start2, end)) {
|
|
39959
39594
|
writeFileSync17(contextPath, replaceMarkerBlock(existing, block, start2, end), "utf-8");
|
|
39960
39595
|
return "Refreshed skill block in";
|
|
@@ -39978,7 +39613,7 @@ function installForTool(root, tool, context) {
|
|
|
39978
39613
|
const parentDir = dirname14(contextPath);
|
|
39979
39614
|
mkdirSync20(parentDir, { recursive: true });
|
|
39980
39615
|
const action = writeOrMerge(contextPath, tool.contextFile, context);
|
|
39981
|
-
const rel =
|
|
39616
|
+
const rel = relative9(root, contextPath);
|
|
39982
39617
|
created.push(rel);
|
|
39983
39618
|
console.log(` ${GREEN2}\u2713${RESET2} ${action} ${rel}`);
|
|
39984
39619
|
if (tool.name === "claude-code") {
|
|
@@ -40356,7 +39991,7 @@ function generateClaudeCodeContext() {
|
|
|
40356
39991
|
const repoRoot = resolve19(thisDir, "..", "..", "..");
|
|
40357
39992
|
const claudeMdPath = join31(repoRoot, "CLAUDE.md");
|
|
40358
39993
|
if (existsSync26(claudeMdPath)) {
|
|
40359
|
-
return
|
|
39994
|
+
return readFileSync26(claudeMdPath, "utf-8");
|
|
40360
39995
|
}
|
|
40361
39996
|
} catch {
|
|
40362
39997
|
}
|
|
@@ -40550,6 +40185,292 @@ export default class User {
|
|
|
40550
40185
|
}
|
|
40551
40186
|
});
|
|
40552
40187
|
|
|
40188
|
+
// src/aiClient.ts
|
|
40189
|
+
import http2 from "node:http";
|
|
40190
|
+
import https2 from "node:https";
|
|
40191
|
+
var AiError, AiConfigError, AiTimeoutError, AiParseError, AiHTTPError, Ai;
|
|
40192
|
+
var init_aiClient = __esm({
|
|
40193
|
+
"src/aiClient.ts"() {
|
|
40194
|
+
"use strict";
|
|
40195
|
+
AiError = class extends Error {
|
|
40196
|
+
};
|
|
40197
|
+
AiConfigError = class extends AiError {
|
|
40198
|
+
};
|
|
40199
|
+
AiTimeoutError = class extends AiError {
|
|
40200
|
+
};
|
|
40201
|
+
AiParseError = class extends AiError {
|
|
40202
|
+
};
|
|
40203
|
+
AiHTTPError = class extends AiError {
|
|
40204
|
+
constructor(message, status2 = null) {
|
|
40205
|
+
super(message);
|
|
40206
|
+
this.status = status2;
|
|
40207
|
+
}
|
|
40208
|
+
};
|
|
40209
|
+
Ai = class {
|
|
40210
|
+
static chat(messages, options = {}) {
|
|
40211
|
+
this.validateMessages(messages);
|
|
40212
|
+
const config = this.config("chat", options);
|
|
40213
|
+
const body = this.chatBody(config, messages, options);
|
|
40214
|
+
const headers = this.headers(config);
|
|
40215
|
+
return options.stream ? this.streamRequest(config, headers, body) : this.chatResponse(config, headers, body);
|
|
40216
|
+
}
|
|
40217
|
+
static async complete(prompt, options = {}) {
|
|
40218
|
+
if (typeof prompt !== "string") throw new AiConfigError("AI prompt must be a string");
|
|
40219
|
+
return (await this.chat([{ role: "user", content: prompt }], { ...options, stream: false })).text;
|
|
40220
|
+
}
|
|
40221
|
+
static async embed(textOrTexts, options = {}) {
|
|
40222
|
+
const single = typeof textOrTexts === "string";
|
|
40223
|
+
if (!single && (!Array.isArray(textOrTexts) || textOrTexts.length === 0 || !textOrTexts.every((item) => typeof item === "string"))) {
|
|
40224
|
+
throw new AiConfigError("AI embedding input must be a string or a non-empty list of strings");
|
|
40225
|
+
}
|
|
40226
|
+
const config = this.config("embed", options);
|
|
40227
|
+
if (config.provider === "anthropic") throw new AiConfigError("Anthropic does not provide the embedding endpoint in this contract");
|
|
40228
|
+
const raw = await this.requestJson(config, this.headers(config), { model: config.model, input: textOrTexts });
|
|
40229
|
+
try {
|
|
40230
|
+
const data = raw.data.sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
40231
|
+
const vectors = data.map((item) => item.embedding);
|
|
40232
|
+
const expected = single ? 1 : textOrTexts.length;
|
|
40233
|
+
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();
|
|
40234
|
+
return single ? vectors[0] : vectors;
|
|
40235
|
+
} catch {
|
|
40236
|
+
throw new AiParseError("AI provider returned a malformed embedding response");
|
|
40237
|
+
}
|
|
40238
|
+
}
|
|
40239
|
+
static validateMessages(messages) {
|
|
40240
|
+
if (!Array.isArray(messages) || messages.length === 0 || !messages.every((message) => message && ["system", "user", "assistant"].includes(message.role) && typeof message.content === "string")) {
|
|
40241
|
+
throw new AiConfigError("AI messages must contain supported roles and string content");
|
|
40242
|
+
}
|
|
40243
|
+
}
|
|
40244
|
+
static number(name, fallback, minimum) {
|
|
40245
|
+
const value = process.env[name] === void 0 ? fallback : Number(process.env[name]);
|
|
40246
|
+
if (!Number.isFinite(value) || value < minimum) throw new AiConfigError(`${name} must be numeric and at least ${minimum}`);
|
|
40247
|
+
return value;
|
|
40248
|
+
}
|
|
40249
|
+
static config(capability, options) {
|
|
40250
|
+
const provider = (options.provider ?? process.env.TINA4_AI_PROVIDER ?? "local").trim().toLowerCase();
|
|
40251
|
+
if (provider !== "local" && provider !== "openai" && provider !== "anthropic") throw new AiConfigError("TINA4_AI_PROVIDER must be local, openai, or anthropic");
|
|
40252
|
+
const key = process.env.TINA4_AI_KEY || null;
|
|
40253
|
+
if ((provider === "openai" || provider === "anthropic") && !key) throw new AiConfigError(`TINA4_AI_KEY is required for the ${provider} provider`);
|
|
40254
|
+
const defaults = {
|
|
40255
|
+
local: ["http://localhost:11437", "llama3.2"],
|
|
40256
|
+
openai: ["https://api.openai.com/v1", "gpt-4o-mini"],
|
|
40257
|
+
anthropic: ["https://api.anthropic.com/v1", "claude-3-5-haiku-latest"]
|
|
40258
|
+
};
|
|
40259
|
+
const rawUrl = capability === "embed" && process.env.TINA4_EMBED_URL ? process.env.TINA4_EMBED_URL : process.env.TINA4_AI_URL ?? defaults[provider][0];
|
|
40260
|
+
const model = (options.model ?? process.env.TINA4_AI_MODEL ?? defaults[provider][1]).trim();
|
|
40261
|
+
if (!model) throw new AiConfigError("AI model must be a non-empty string");
|
|
40262
|
+
const totalTimeout = options.timeout === void 0 ? this.number("TINA4_AI_TIMEOUT", 60, 1e-3) : Number(options.timeout);
|
|
40263
|
+
if (!Number.isFinite(totalTimeout) || totalTimeout <= 0) throw new AiConfigError("AI timeout must be greater than zero");
|
|
40264
|
+
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)) };
|
|
40265
|
+
}
|
|
40266
|
+
static endpoint(value, capability, provider) {
|
|
40267
|
+
let url;
|
|
40268
|
+
try {
|
|
40269
|
+
url = new URL(value);
|
|
40270
|
+
} catch {
|
|
40271
|
+
throw new AiConfigError("AI URL must be an http or https URL");
|
|
40272
|
+
}
|
|
40273
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new AiConfigError("AI URL must be an http or https URL");
|
|
40274
|
+
const path8 = url.pathname.replace(/\/+$/, "");
|
|
40275
|
+
if (path8 === "" || path8 === "/v1" || path8 === "/api") {
|
|
40276
|
+
const suffix = provider === "anthropic" ? "/messages" : capability === "embed" ? "/embeddings" : "/chat/completions";
|
|
40277
|
+
url.pathname = (path8 || "/v1") + suffix;
|
|
40278
|
+
}
|
|
40279
|
+
return url.toString();
|
|
40280
|
+
}
|
|
40281
|
+
static headers(config) {
|
|
40282
|
+
const headers = { "content-type": "application/json", accept: "application/json" };
|
|
40283
|
+
if (config.provider === "openai") headers.authorization = `Bearer ${config.key}`;
|
|
40284
|
+
if (config.provider === "anthropic") {
|
|
40285
|
+
headers["x-api-key"] = config.key;
|
|
40286
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
40287
|
+
}
|
|
40288
|
+
return headers;
|
|
40289
|
+
}
|
|
40290
|
+
static chatBody(config, messages, options) {
|
|
40291
|
+
const body = { model: config.model, messages, stream: options.stream ?? false };
|
|
40292
|
+
if (options.temperature !== void 0) body.temperature = options.temperature;
|
|
40293
|
+
if (options.maxTokens !== void 0) body.max_tokens = options.maxTokens;
|
|
40294
|
+
if (config.provider === "anthropic") {
|
|
40295
|
+
const system = messages.filter((message) => message.role === "system").map((message) => message.content);
|
|
40296
|
+
body.messages = messages.filter((message) => message.role !== "system");
|
|
40297
|
+
body.max_tokens = options.maxTokens ?? 1024;
|
|
40298
|
+
if (system.length) body.system = system.join("\n\n");
|
|
40299
|
+
}
|
|
40300
|
+
return body;
|
|
40301
|
+
}
|
|
40302
|
+
static open(config, deadline, headers, body) {
|
|
40303
|
+
const remainingMs = deadline - performance.now();
|
|
40304
|
+
if (remainingMs <= 0) return Promise.reject(new AiTimeoutError("AI total request timeout expired"));
|
|
40305
|
+
const url = new URL(config.url);
|
|
40306
|
+
const payload = JSON.stringify(body);
|
|
40307
|
+
const controller = new AbortController();
|
|
40308
|
+
const totalTimer = setTimeout(() => controller.abort(new AiTimeoutError("AI total request timeout expired")), remainingMs);
|
|
40309
|
+
return new Promise((resolve20, reject) => {
|
|
40310
|
+
const client = url.protocol === "https:" ? https2 : http2;
|
|
40311
|
+
const request = client.request(url, { method: "POST", headers: { ...headers, "content-length": Buffer.byteLength(payload) }, signal: controller.signal }, (response) => {
|
|
40312
|
+
clearTimeout(connectTimer);
|
|
40313
|
+
resolve20({ response, cleanup: () => {
|
|
40314
|
+
clearTimeout(totalTimer);
|
|
40315
|
+
clearTimeout(connectTimer);
|
|
40316
|
+
} });
|
|
40317
|
+
});
|
|
40318
|
+
const connectTimer = setTimeout(() => request.destroy(new AiTimeoutError("AI connection timeout expired")), Math.min(config.connectTimeout * 1e3, remainingMs));
|
|
40319
|
+
request.on("socket", (socket) => {
|
|
40320
|
+
if (!socket.connecting) clearTimeout(connectTimer);
|
|
40321
|
+
socket.once(url.protocol === "https:" ? "secureConnect" : "connect", () => clearTimeout(connectTimer));
|
|
40322
|
+
});
|
|
40323
|
+
request.once("error", (error) => {
|
|
40324
|
+
clearTimeout(totalTimer);
|
|
40325
|
+
clearTimeout(connectTimer);
|
|
40326
|
+
if (error instanceof AiError) reject(error);
|
|
40327
|
+
else if (controller.signal.aborted) reject(new AiTimeoutError("AI total request timeout expired"));
|
|
40328
|
+
else reject(new AiHTTPError(`AI transport failed (${error.name})`));
|
|
40329
|
+
});
|
|
40330
|
+
request.end(payload);
|
|
40331
|
+
});
|
|
40332
|
+
}
|
|
40333
|
+
static async readBody(response) {
|
|
40334
|
+
const chunks = [];
|
|
40335
|
+
for await (const chunk of response) chunks.push(Buffer.from(chunk));
|
|
40336
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
40337
|
+
}
|
|
40338
|
+
static retryDelay(headers, deadline) {
|
|
40339
|
+
const value = Array.isArray(headers["retry-after"]) ? headers["retry-after"][0] : headers["retry-after"];
|
|
40340
|
+
const requested = value !== void 0 && Number.isFinite(Number(value)) ? Math.max(0, Number(value) * 1e3) : 100;
|
|
40341
|
+
const delay = Math.min(requested, Math.max(0, deadline - performance.now()));
|
|
40342
|
+
return new Promise((resolve20) => setTimeout(resolve20, delay));
|
|
40343
|
+
}
|
|
40344
|
+
static async requestJson(config, headers, body) {
|
|
40345
|
+
const deadline = performance.now() + config.totalTimeout * 1e3;
|
|
40346
|
+
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
|
|
40347
|
+
let opened = null;
|
|
40348
|
+
try {
|
|
40349
|
+
opened = await this.open(config, deadline, headers, body);
|
|
40350
|
+
const status2 = opened.response.statusCode ?? 0;
|
|
40351
|
+
const responseHeaders = opened.response.headers;
|
|
40352
|
+
const raw = await this.readBody(opened.response);
|
|
40353
|
+
opened.cleanup();
|
|
40354
|
+
opened = null;
|
|
40355
|
+
if (status2 < 200 || status2 >= 300) {
|
|
40356
|
+
if ((status2 === 429 || status2 >= 500) && attempt < config.maxRetries) {
|
|
40357
|
+
await this.retryDelay(responseHeaders, deadline);
|
|
40358
|
+
continue;
|
|
40359
|
+
}
|
|
40360
|
+
throw new AiHTTPError(`AI provider returned HTTP ${status2}`, status2);
|
|
40361
|
+
}
|
|
40362
|
+
let parsed;
|
|
40363
|
+
try {
|
|
40364
|
+
parsed = JSON.parse(raw);
|
|
40365
|
+
} catch {
|
|
40366
|
+
throw new AiParseError("AI provider returned malformed JSON");
|
|
40367
|
+
}
|
|
40368
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new AiParseError("AI provider returned a non-object JSON response");
|
|
40369
|
+
return parsed;
|
|
40370
|
+
} catch (error) {
|
|
40371
|
+
opened?.cleanup();
|
|
40372
|
+
if (error instanceof AiParseError || error instanceof AiHTTPError && error.status !== null) throw error;
|
|
40373
|
+
if (attempt >= config.maxRetries) throw error;
|
|
40374
|
+
}
|
|
40375
|
+
}
|
|
40376
|
+
throw new AiHTTPError("AI request failed");
|
|
40377
|
+
}
|
|
40378
|
+
static normalizeChat(provider, raw) {
|
|
40379
|
+
try {
|
|
40380
|
+
if (provider === "anthropic") {
|
|
40381
|
+
const content = raw.content;
|
|
40382
|
+
const parts = content.filter((item) => (item.type ?? "text") === "text").map((item) => item.text);
|
|
40383
|
+
if (!parts.length || !parts.every((part) => typeof part === "string")) throw new Error();
|
|
40384
|
+
const usage2 = raw.usage ?? {};
|
|
40385
|
+
const promptTokens = Number(usage2.input_tokens ?? 0);
|
|
40386
|
+
const completionTokens = Number(usage2.output_tokens ?? 0);
|
|
40387
|
+
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 };
|
|
40388
|
+
}
|
|
40389
|
+
const choice = raw.choices[0];
|
|
40390
|
+
const text = choice.message.content;
|
|
40391
|
+
if (typeof text !== "string") throw new Error();
|
|
40392
|
+
const usage = raw.usage ?? {};
|
|
40393
|
+
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 };
|
|
40394
|
+
} catch {
|
|
40395
|
+
throw new AiParseError("AI provider returned a malformed chat response");
|
|
40396
|
+
}
|
|
40397
|
+
}
|
|
40398
|
+
static async chatResponse(config, headers, body) {
|
|
40399
|
+
return this.normalizeChat(config.provider, await this.requestJson(config, headers, body));
|
|
40400
|
+
}
|
|
40401
|
+
static streamDelta(provider, data) {
|
|
40402
|
+
if (data === "[DONE]") return { completed: true };
|
|
40403
|
+
let event;
|
|
40404
|
+
try {
|
|
40405
|
+
event = JSON.parse(data);
|
|
40406
|
+
} catch {
|
|
40407
|
+
throw new AiParseError("AI provider returned malformed stream data");
|
|
40408
|
+
}
|
|
40409
|
+
const text = provider === "anthropic" ? event.type === "content_block_delta" ? event.delta?.text : void 0 : event.choices?.[0]?.delta?.content;
|
|
40410
|
+
if (text !== void 0 && text !== null && typeof text !== "string") throw new AiParseError("AI provider returned malformed stream data");
|
|
40411
|
+
return { completed: false, text };
|
|
40412
|
+
}
|
|
40413
|
+
static async *streamData(response) {
|
|
40414
|
+
let buffer = "";
|
|
40415
|
+
for await (const chunk of response) {
|
|
40416
|
+
buffer += Buffer.from(chunk).toString("utf8");
|
|
40417
|
+
let newline;
|
|
40418
|
+
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
40419
|
+
const line = buffer.slice(0, newline).trim();
|
|
40420
|
+
buffer = buffer.slice(newline + 1);
|
|
40421
|
+
if (line.startsWith("data:")) yield line.slice(5).trim();
|
|
40422
|
+
}
|
|
40423
|
+
}
|
|
40424
|
+
}
|
|
40425
|
+
static streamError(error) {
|
|
40426
|
+
if (error instanceof AiError) return error;
|
|
40427
|
+
if (error instanceof Error && error.name === "AbortError") return new AiTimeoutError("AI total request timeout expired");
|
|
40428
|
+
return new AiHTTPError(`AI transport failed (${error instanceof Error ? error.name : "Error"})`);
|
|
40429
|
+
}
|
|
40430
|
+
static async *streamRequest(config, headers, body) {
|
|
40431
|
+
const deadline = performance.now() + config.totalTimeout * 1e3;
|
|
40432
|
+
let yielded = false;
|
|
40433
|
+
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
|
|
40434
|
+
let opened = null;
|
|
40435
|
+
try {
|
|
40436
|
+
opened = await this.open(config, deadline, { ...headers, accept: "text/event-stream" }, body);
|
|
40437
|
+
const status2 = opened.response.statusCode ?? 0;
|
|
40438
|
+
if (status2 < 200 || status2 >= 300) {
|
|
40439
|
+
await this.readBody(opened.response);
|
|
40440
|
+
if ((status2 === 429 || status2 >= 500) && attempt < config.maxRetries) {
|
|
40441
|
+
await this.retryDelay(opened.response.headers, deadline);
|
|
40442
|
+
opened.cleanup();
|
|
40443
|
+
opened = null;
|
|
40444
|
+
continue;
|
|
40445
|
+
}
|
|
40446
|
+
throw new AiHTTPError(`AI provider returned HTTP ${status2}`, status2);
|
|
40447
|
+
}
|
|
40448
|
+
let completed = false;
|
|
40449
|
+
for await (const data of this.streamData(opened.response)) {
|
|
40450
|
+
const delta = this.streamDelta(config.provider, data);
|
|
40451
|
+
if (delta.completed) {
|
|
40452
|
+
completed = true;
|
|
40453
|
+
break;
|
|
40454
|
+
}
|
|
40455
|
+
if (delta.text === void 0) continue;
|
|
40456
|
+
yielded = true;
|
|
40457
|
+
yield delta.text;
|
|
40458
|
+
}
|
|
40459
|
+
opened.cleanup();
|
|
40460
|
+
opened = null;
|
|
40461
|
+
if (completed) return;
|
|
40462
|
+
throw new AiParseError("AI provider stream ended before [DONE]");
|
|
40463
|
+
} catch (error) {
|
|
40464
|
+
opened?.cleanup();
|
|
40465
|
+
const failure = this.streamError(error);
|
|
40466
|
+
if (failure instanceof AiParseError || failure instanceof AiHTTPError && failure.status !== null || yielded || attempt >= config.maxRetries) throw failure;
|
|
40467
|
+
}
|
|
40468
|
+
}
|
|
40469
|
+
}
|
|
40470
|
+
};
|
|
40471
|
+
}
|
|
40472
|
+
});
|
|
40473
|
+
|
|
40553
40474
|
// src/queueBackends/rabbitmqBackend.ts
|
|
40554
40475
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
40555
40476
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
@@ -42253,6 +42174,12 @@ __export(index_exports, {
|
|
|
42253
42174
|
APPLICATION_JSON: () => APPLICATION_JSON,
|
|
42254
42175
|
APPLICATION_OCTET: () => APPLICATION_OCTET,
|
|
42255
42176
|
APPLICATION_XML: () => APPLICATION_XML,
|
|
42177
|
+
Ai: () => Ai,
|
|
42178
|
+
AiConfigError: () => AiConfigError,
|
|
42179
|
+
AiError: () => AiError,
|
|
42180
|
+
AiHTTPError: () => AiHTTPError,
|
|
42181
|
+
AiParseError: () => AiParseError,
|
|
42182
|
+
AiTimeoutError: () => AiTimeoutError,
|
|
42256
42183
|
Api: () => Api,
|
|
42257
42184
|
Auth: () => Auth,
|
|
42258
42185
|
CANONICAL_SESSION_BACKENDS: () => CANONICAL_SESSION_BACKENDS,
|
|
@@ -42579,6 +42506,7 @@ var init_index = __esm({
|
|
|
42579
42506
|
init_htmlElement();
|
|
42580
42507
|
init_errorOverlay();
|
|
42581
42508
|
init_ai();
|
|
42509
|
+
init_aiClient();
|
|
42582
42510
|
init_liteBackend();
|
|
42583
42511
|
init_rabbitmqBackend();
|
|
42584
42512
|
init_kafkaBackend();
|
|
@@ -42608,6 +42536,12 @@ export {
|
|
|
42608
42536
|
APPLICATION_JSON,
|
|
42609
42537
|
APPLICATION_OCTET,
|
|
42610
42538
|
APPLICATION_XML,
|
|
42539
|
+
Ai,
|
|
42540
|
+
AiConfigError,
|
|
42541
|
+
AiError,
|
|
42542
|
+
AiHTTPError,
|
|
42543
|
+
AiParseError,
|
|
42544
|
+
AiTimeoutError,
|
|
42611
42545
|
Api,
|
|
42612
42546
|
Auth,
|
|
42613
42547
|
CANONICAL_SESSION_BACKENDS,
|