tina4-nodejs 3.10.64 → 3.10.66

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.10.64",
3
+ "version": "3.10.66",
4
4
  "type": "module",
5
5
  "description": "Tina4 for Node.js/TypeScript — 54 built-in features, zero dependencies",
6
6
  "keywords": ["tina4", "framework", "web", "api", "orm", "graphql", "websocket", "typescript"],
@@ -50,22 +50,74 @@ function readFileSafe(filePath: string): string | null {
50
50
  }
51
51
  }
52
52
 
53
- function relativePath(filePath: string): string {
54
- return path.relative(".", filePath);
53
+ function relativePath(filePath: string, root: string = "."): string {
54
+ return path.relative(root, filePath);
55
55
  }
56
56
 
57
+ // Stores the resolved scan root so fileDetail() can locate framework files.
58
+ let _lastScanRoot = "";
59
+
57
60
  // ── Test file detection ─────────────────────────────────────
58
61
 
59
62
  function hasMatchingTest(relPath: string): boolean {
60
- const name = relPath.split('/').pop()?.replace('.ts', '').replace('.js', '') || '';
61
- const patterns = [
62
- `test/${name}.test.ts`,
63
- `test/${name}.test.js`,
64
- `${relPath.replace('.ts', '.test.ts').replace('.js', '.test.js')}`,
65
- `tests/${name}.test.ts`,
66
- `spec/${name}.spec.ts`,
67
- ];
68
- return patterns.some(p => fs.existsSync(p));
63
+ const parts = relPath.split('/');
64
+ const basename = parts[parts.length - 1] || '';
65
+ const name = basename.replace(/\.(ts|js)$/, '');
66
+ // Parent directory name (e.g. "adapters" from "adapters/sqlite.ts")
67
+ const parentModule = parts.length > 1 ? parts[parts.length - 2] : '';
68
+
69
+ // Stage 1: Filename matching — name.test, name.spec, test_name patterns
70
+ const testDirs = ['test', 'tests', 'spec'];
71
+ for (const td of testDirs) {
72
+ const patterns = [
73
+ `${td}/${name}.test.ts`,
74
+ `${td}/${name}.test.js`,
75
+ `${td}/${name}.spec.ts`,
76
+ `${td}/${name}.spec.js`,
77
+ // Also check parent-named tests (test/database.test.ts covers adapters/sqlite.ts)
78
+ ...(parentModule && parentModule !== name ? [
79
+ `${td}/${parentModule}.test.ts`,
80
+ `${td}/${parentModule}.test.js`,
81
+ `${td}/${parentModule}.spec.ts`,
82
+ ] : []),
83
+ ];
84
+ if (patterns.some(p => fs.existsSync(p))) {
85
+ return true;
86
+ }
87
+ }
88
+
89
+ // Build the module import path for content matching
90
+ // e.g. "packages/core/src/metrics.ts" → "metrics" or "src/metrics"
91
+ const pathWithoutExt = relPath.replace(/\.(ts|js)$/, '');
92
+ // Build CamelCase class name from camelCase/kebab module name
93
+ // e.g. "sqlTranslation" → "SqlTranslation", "sql-translation" → "SqlTranslation"
94
+ const className = name
95
+ .replace(/[-_](.)/g, (_, c: string) => c.toUpperCase())
96
+ .replace(/^(.)/, (_, c: string) => c.toUpperCase());
97
+
98
+ // Stage 2+3: Content scan — check if any test file imports or references this module
99
+ for (const td of testDirs) {
100
+ if (!fs.existsSync(td)) continue;
101
+ const testFiles = walkFiles(td, ['.ts', '.js']);
102
+ for (const testFile of testFiles) {
103
+ const content = readFileSafe(testFile);
104
+ if (content === null) continue;
105
+ // Stage 2: import path matching
106
+ if (content.includes(name) && (
107
+ content.includes(`"${name}"`) || content.includes(`'${name}'`) ||
108
+ content.includes(`/${name}"`) || content.includes(`/${name}'`) ||
109
+ content.includes(pathWithoutExt)
110
+ )) {
111
+ return true;
112
+ }
113
+ // Stage 3: class name mention
114
+ if (className !== name && new RegExp(`\\b${className.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(content)) {
115
+ return true;
116
+ }
117
+ }
118
+ }
119
+
120
+ return false;
69
121
  }
70
122
 
71
123
  // ── Line counting ────────────────────────────────────────────
@@ -192,7 +244,7 @@ interface FunctionInfo {
192
244
  file?: string;
193
245
  }
194
246
 
195
- function extractFunctions(source: string, filePath: string): FunctionInfo[] {
247
+ function extractFunctions(source: string, filePath: string, root: string = "."): FunctionInfo[] {
196
248
  const functions: FunctionInfo[] = [];
197
249
  const lines = source.split("\n");
198
250
 
@@ -268,7 +320,7 @@ function extractFunctions(source: string, filePath: string): FunctionInfo[] {
268
320
  complexity,
269
321
  loc: funcLoc,
270
322
  args,
271
- file: relativePath(filePath),
323
+ file: relativePath(filePath, root),
272
324
  });
273
325
 
274
326
  break; // Only match first pattern per line
@@ -542,10 +594,13 @@ function detectViolations(
542
594
  function resolveRoot(root: string = "src"): string {
543
595
  const rootPath = path.resolve(root);
544
596
  if (fs.existsSync(rootPath) && walkFiles(rootPath, [".ts", ".js"]).length > 0) {
597
+ _lastScanRoot = rootPath;
545
598
  return root;
546
599
  }
547
600
  // Fallback: scan the framework package itself
548
- return path.resolve(path.dirname(new URL(import.meta.url).pathname));
601
+ const fwDir = path.resolve(path.dirname(new URL(import.meta.url).pathname));
602
+ _lastScanRoot = fwDir;
603
+ return fwDir;
549
604
  }
550
605
 
551
606
  // ── Quick Metrics ────────────────────────────────────────────
@@ -590,7 +645,7 @@ export function quickMetrics(root: string = "src"): Record<string, any> {
590
645
  totalFunctions += functions;
591
646
 
592
647
  fileDetails.push({
593
- path: relativePath(f),
648
+ path: relativePath(f, rootPath),
594
649
  loc: counts.loc,
595
650
  blank: counts.blank,
596
651
  comment: counts.comment,
@@ -704,7 +759,7 @@ export function fullAnalysis(root: string = "src"): Record<string, any> {
704
759
  const source = readFileSafe(f);
705
760
  if (source === null) continue;
706
761
 
707
- const relPath = relativePath(f);
762
+ const relPath = relativePath(f, rootPath);
708
763
  const lines = source.split("\n");
709
764
  const loc = lines.filter(
710
765
  (l) => l.trim() && !l.trim().startsWith("//")
@@ -722,7 +777,7 @@ export function fullAnalysis(root: string = "src"): Record<string, any> {
722
777
  }
723
778
 
724
779
  // Analyze functions/methods
725
- const fileFunctions = extractFunctions(source, f);
780
+ const fileFunctions = extractFunctions(source, f, rootPath);
726
781
  let fileComplexity = 0;
727
782
 
728
783
  for (const func of fileFunctions) {
@@ -805,7 +860,15 @@ export function fullAnalysis(root: string = "src"): Record<string, any> {
805
860
  // ── File Detail ──────────────────────────────────────────────
806
861
 
807
862
  export function fileDetail(filePath: string): Record<string, any> {
808
- const resolved = path.resolve(filePath);
863
+ let resolved = path.resolve(filePath);
864
+ if (!fs.existsSync(resolved) && _lastScanRoot) {
865
+ // Try resolving relative to the last scan root (framework mode)
866
+ const candidate = path.resolve(_lastScanRoot, filePath);
867
+ if (fs.existsSync(candidate)) {
868
+ resolved = candidate;
869
+ filePath = candidate;
870
+ }
871
+ }
809
872
  if (!fs.existsSync(resolved)) {
810
873
  return { error: `File not found: ${filePath}` };
811
874
  }