scai 0.1.57 → 0.1.59
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.
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Project, SyntaxKind } from 'ts-morph';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { generateEmbedding } from '../../lib/generateEmbedding.js';
|
|
4
|
+
import { log } from '../../utils/log.js';
|
|
5
|
+
import { getDbForRepo } from '../client.js';
|
|
6
|
+
import { markFileAsSkippedTemplate, markFileAsExtractedTemplate, markFileAsFailedTemplate } from '../sqlTemplates.js';
|
|
7
|
+
export async function extractFromTS(filePath, content, fileId) {
|
|
8
|
+
const db = getDbForRepo();
|
|
9
|
+
try {
|
|
10
|
+
const project = new Project({ useInMemoryFileSystem: true });
|
|
11
|
+
const sourceFile = project.createSourceFile(filePath, content);
|
|
12
|
+
const functions = [];
|
|
13
|
+
const allFuncs = [
|
|
14
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration),
|
|
15
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionExpression),
|
|
16
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.ArrowFunction),
|
|
17
|
+
];
|
|
18
|
+
for (const fn of allFuncs) {
|
|
19
|
+
const name = fn.getSymbol()?.getName() ?? `${path.basename(filePath)}:<anon>`;
|
|
20
|
+
const start = fn.getStartLineNumber();
|
|
21
|
+
const end = fn.getEndLineNumber();
|
|
22
|
+
const code = fn.getText();
|
|
23
|
+
functions.push({ name, start_line: start, end_line: end, content: code });
|
|
24
|
+
}
|
|
25
|
+
if (functions.length === 0) {
|
|
26
|
+
log(`⚠️ No functions found in TS file: ${filePath}`);
|
|
27
|
+
db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
log(`🔍 Found ${functions.length} TypeScript functions in ${filePath}`);
|
|
31
|
+
for (const fn of functions) {
|
|
32
|
+
const embedding = await generateEmbedding(fn.content);
|
|
33
|
+
const result = db.prepare(`
|
|
34
|
+
INSERT INTO functions (
|
|
35
|
+
file_id, name, start_line, end_line, content, embedding, lang
|
|
36
|
+
) VALUES (
|
|
37
|
+
@file_id, @name, @start_line, @end_line, @content, @embedding, @lang
|
|
38
|
+
)
|
|
39
|
+
`).run({
|
|
40
|
+
file_id: fileId,
|
|
41
|
+
name: fn.name,
|
|
42
|
+
start_line: fn.start_line,
|
|
43
|
+
end_line: fn.end_line,
|
|
44
|
+
content: fn.content,
|
|
45
|
+
embedding: JSON.stringify(embedding),
|
|
46
|
+
lang: 'ts'
|
|
47
|
+
});
|
|
48
|
+
const callerId = result.lastInsertRowid;
|
|
49
|
+
// Simplified call detection (no walking for now)
|
|
50
|
+
const callMatches = fn.content.matchAll(/(\w+)\s*\(/g);
|
|
51
|
+
for (const match of callMatches) {
|
|
52
|
+
db.prepare(`
|
|
53
|
+
INSERT INTO function_calls (caller_id, callee_name)
|
|
54
|
+
VALUES (@caller_id, @callee_name)
|
|
55
|
+
`).run({
|
|
56
|
+
caller_id: callerId,
|
|
57
|
+
callee_name: match[1],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
log(`📌 Indexed TS function: ${fn.name}`);
|
|
61
|
+
}
|
|
62
|
+
db.prepare(markFileAsExtractedTemplate).run({ id: fileId });
|
|
63
|
+
log(`✅ Marked TS functions as extracted for ${filePath}`);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
log(`❌ Failed to extract from TS file: ${filePath}`);
|
|
68
|
+
log(` ↳ ${String(err.message)}`);
|
|
69
|
+
db.prepare(markFileAsFailedTemplate).run({ id: fileId });
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -5,6 +5,7 @@ import { extractFromJS } from './extractFromJs.js';
|
|
|
5
5
|
import { extractFromXML } from './extractFromXML.js';
|
|
6
6
|
import { getDbForRepo } from '../client.js';
|
|
7
7
|
import { markFileAsFailedTemplate, markFileAsSkippedByPath } from '../sqlTemplates.js';
|
|
8
|
+
import { extractFromTS } from './extractFromTs.js';
|
|
8
9
|
/**
|
|
9
10
|
* Detects file type and delegates to the appropriate extractor.
|
|
10
11
|
*/
|
|
@@ -12,10 +13,14 @@ export async function extractFunctionsFromFile(filePath, content, fileId) {
|
|
|
12
13
|
const type = detectFileType(filePath).trim().toLowerCase();
|
|
13
14
|
const db = getDbForRepo();
|
|
14
15
|
try {
|
|
15
|
-
if (type === 'js' || type === '
|
|
16
|
+
if (type === 'js' || type === 'javascript') {
|
|
16
17
|
log(`✅ Attempting to extract JS functions from ${filePath}`);
|
|
17
18
|
return await extractFromJS(filePath, content, fileId);
|
|
18
19
|
}
|
|
20
|
+
if (type === 'ts' || type === 'typescript') {
|
|
21
|
+
log(`📘 Extracting TS functions from ${filePath}`);
|
|
22
|
+
return await extractFromTS(filePath, content, fileId);
|
|
23
|
+
}
|
|
19
24
|
if (type === 'java') {
|
|
20
25
|
log(`❌ Nothing extracted for ${filePath} due to missing implementation`);
|
|
21
26
|
await extractFromJava(filePath, content, fileId);
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,9 @@ import { promptForToken } from "./github/token.js";
|
|
|
26
26
|
import { validateGitHubTokenAgainstRepo } from "./github/githubAuthCheck.js";
|
|
27
27
|
import { checkGit } from "./commands/GitCmd.js";
|
|
28
28
|
import { runSwitchCommand, runInteractiveSwitch } from "./commands/SwitchCmd.js";
|
|
29
|
+
import { execSync } from "child_process";
|
|
30
|
+
import path from "path";
|
|
31
|
+
import fs from "fs";
|
|
29
32
|
// 🎛️ CLI Setup
|
|
30
33
|
const cmd = new Command('scai')
|
|
31
34
|
.version(version)
|
|
@@ -174,6 +177,32 @@ index
|
|
|
174
177
|
runInteractiveSwitch();
|
|
175
178
|
}
|
|
176
179
|
});
|
|
180
|
+
// This will help resolve the current directory in an ES Module
|
|
181
|
+
cmd
|
|
182
|
+
.command('check-db')
|
|
183
|
+
.description('Run the dbcheck script to check the database status')
|
|
184
|
+
.action(() => {
|
|
185
|
+
try {
|
|
186
|
+
const cfg = Config.getRaw();
|
|
187
|
+
const indexDir = cfg.activeRepo;
|
|
188
|
+
console.log('RepoKey: ', indexDir);
|
|
189
|
+
if (!indexDir) {
|
|
190
|
+
console.error('❌ No indexDir found for the active repo.');
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
// Prefer dist version if it exists (built), otherwise use src
|
|
194
|
+
let scriptPath = path.resolve(indexDir, 'dist/scripts/dbcheck.js');
|
|
195
|
+
if (!fs.existsSync(scriptPath)) {
|
|
196
|
+
scriptPath = path.resolve(indexDir, 'src/scripts/dbcheck.ts');
|
|
197
|
+
}
|
|
198
|
+
console.log(`🚀 Running database check script: ${scriptPath}`);
|
|
199
|
+
execSync(`node ${scriptPath}`, { stdio: 'inherit' });
|
|
200
|
+
console.log('✅ Database check completed!');
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
console.error('❌ Error running dbcheck script:', err instanceof Error ? err.message : err);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
177
206
|
cmd
|
|
178
207
|
.command('backup')
|
|
179
208
|
.description('Backup the current .scai folder')
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { Config } from "../config.js";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
const cfg = Config.getRaw();
|
|
7
|
+
const repoKey = cfg.activeRepo;
|
|
8
|
+
if (!repoKey) {
|
|
9
|
+
console.error("❌ No active repo found. Use `scai set-index` to set one.");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
// Get the basename (repo name) from the full repoKey (which is the path)
|
|
13
|
+
const repoName = path.basename(repoKey);
|
|
14
|
+
const scaiRepoRoot = path.join(os.homedir(), ".scai", "repos", repoName);
|
|
15
|
+
const dbPath = path.join(scaiRepoRoot, "db.sqlite");
|
|
16
|
+
if (!fs.existsSync(dbPath)) {
|
|
17
|
+
console.error(`❌ No database found at ${dbPath}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const db = new Database(dbPath);
|
|
21
|
+
// === Basic Stats ===
|
|
22
|
+
const stats = {
|
|
23
|
+
total: db.prepare('SELECT COUNT(*) as count FROM files').get().count,
|
|
24
|
+
withSummary: db.prepare(`SELECT COUNT(*) as count FROM files WHERE summary IS NOT NULL AND summary != ''`).get().count,
|
|
25
|
+
withoutSummary: db.prepare(`SELECT COUNT(*) as count FROM files WHERE summary IS NULL OR summary = ''`).get().count,
|
|
26
|
+
withEmbedding: db.prepare(`SELECT COUNT(*) as count FROM files WHERE embedding IS NOT NULL AND embedding != ''`).get().count,
|
|
27
|
+
withoutEmbedding: db.prepare(`SELECT COUNT(*) as count FROM files WHERE embedding IS NULL OR embedding = ''`).get().count,
|
|
28
|
+
withProcessingStatusExtracted: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'extracted'`).get().count,
|
|
29
|
+
withoutProcessingStatusExtracted: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status IS NULL OR processing_status != 'extracted'`).get().count,
|
|
30
|
+
withProcessingStatusSkipped: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'skipped'`).get().count,
|
|
31
|
+
withProcessingStatusFailed: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'failed'`).get().count,
|
|
32
|
+
withProcessingStatusUnprocessed: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'unprocessed'`).get().count,
|
|
33
|
+
};
|
|
34
|
+
console.log("📊 SQLite Stats for Table: files");
|
|
35
|
+
console.log("-------------------------------------------");
|
|
36
|
+
console.log(`🔢 Total rows: ${stats.total}`);
|
|
37
|
+
console.log(`✅ With summary: ${stats.withSummary}`);
|
|
38
|
+
console.log(`❌ Without summary: ${stats.withoutSummary}`);
|
|
39
|
+
console.log(`✅ With embedding: ${stats.withEmbedding}`);
|
|
40
|
+
console.log(`❌ Without embedding: ${stats.withoutEmbedding}`);
|
|
41
|
+
console.log(`✅ With processing_status = 'extracted': ${stats.withProcessingStatusExtracted}`);
|
|
42
|
+
console.log(`❌ Without processing_status = 'extracted': ${stats.withoutProcessingStatusExtracted}`);
|
|
43
|
+
console.log(`✅ With processing_status = 'skipped': ${stats.withProcessingStatusSkipped}`);
|
|
44
|
+
console.log(`✅ With processing_status = 'failed': ${stats.withProcessingStatusFailed}`);
|
|
45
|
+
console.log(`✅ With processing_status = 'unprocessed': ${stats.withProcessingStatusUnprocessed}`);
|
|
46
|
+
// === Example Summaries ===
|
|
47
|
+
console.log("\n🧾 Example summaries and embeddings:\n--------------------------");
|
|
48
|
+
console.log("Example summaries (first 50 characters):");
|
|
49
|
+
const summaries = db.prepare(`
|
|
50
|
+
SELECT id, substr(summary, 1, 50) || '...' AS short_summary
|
|
51
|
+
FROM files
|
|
52
|
+
WHERE summary IS NOT NULL AND summary != ''
|
|
53
|
+
LIMIT 10
|
|
54
|
+
`).all();
|
|
55
|
+
summaries.forEach(row => {
|
|
56
|
+
console.log(`${row.id.toString().padEnd(4)} ${row.short_summary}`);
|
|
57
|
+
});
|
|
58
|
+
console.log("\nExample embeddings (first 50 characters):");
|
|
59
|
+
const embeddings = db.prepare(`
|
|
60
|
+
SELECT id, substr(embedding, 1, 50) || '...' AS short_embedding
|
|
61
|
+
FROM files
|
|
62
|
+
WHERE embedding IS NOT NULL AND embedding != ''
|
|
63
|
+
LIMIT 10
|
|
64
|
+
`).all();
|
|
65
|
+
embeddings.forEach(row => {
|
|
66
|
+
console.log(`${row.id.toString().padEnd(4)} ${row.short_embedding}`);
|
|
67
|
+
});
|
|
68
|
+
// === FTS5 Check ===
|
|
69
|
+
console.log("\n🔍 FTS5 Check");
|
|
70
|
+
console.log("--------------------------");
|
|
71
|
+
try {
|
|
72
|
+
const ftsExists = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='files_fts'`).get();
|
|
73
|
+
if (!ftsExists) {
|
|
74
|
+
console.log("❌ No FTS5 table `files_fts` found in database.");
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const ftsRowCount = db.prepare(`SELECT COUNT(*) AS count FROM files_fts`).get().count;
|
|
78
|
+
console.log(`✅ files_fts table exists. Rows: ${ftsRowCount}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.error("❌ Error while accessing files_fts:", err.message);
|
|
83
|
+
}
|
|
84
|
+
// === Rebuild FTS Index ===
|
|
85
|
+
console.log("\n🔧 Rebuilding FTS5 index...");
|
|
86
|
+
try {
|
|
87
|
+
db.prepare(`INSERT INTO files_fts(files_fts) VALUES ('rebuild')`).run();
|
|
88
|
+
console.log(`✅ Rebuild completed.`);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.error("❌ FTS5 rebuild failed:", err.message);
|
|
92
|
+
}
|
|
93
|
+
// === FTS Search Test ===
|
|
94
|
+
console.log('\n🔍 Test MATCH query for "minimap":');
|
|
95
|
+
try {
|
|
96
|
+
const minimapMatches = db.prepare(`
|
|
97
|
+
SELECT f.id, f.path
|
|
98
|
+
FROM files f
|
|
99
|
+
JOIN files_fts fts ON f.id = fts.rowid
|
|
100
|
+
WHERE fts.files_fts MATCH 'minimap'
|
|
101
|
+
LIMIT 10
|
|
102
|
+
`).all();
|
|
103
|
+
if (minimapMatches.length === 0) {
|
|
104
|
+
console.warn('⚠️ No matches for "minimap" in FTS index');
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
minimapMatches.forEach(row => {
|
|
108
|
+
console.log(`📄 ${row.path}`);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
console.error('❌ Error running MATCH query:', err.message);
|
|
114
|
+
}
|
|
115
|
+
// === Direct LIKE Fallback ===
|
|
116
|
+
console.log('\n🔍 Direct LIKE query on path for "minimap":');
|
|
117
|
+
const likeMatches = db.prepare(`
|
|
118
|
+
SELECT id, path
|
|
119
|
+
FROM files
|
|
120
|
+
WHERE path LIKE '%minimap%'
|
|
121
|
+
LIMIT 10
|
|
122
|
+
`).all();
|
|
123
|
+
if (likeMatches.length === 0) {
|
|
124
|
+
console.warn('⚠️ No file paths contain "minimap"');
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
likeMatches.forEach(row => {
|
|
128
|
+
console.log(`📄 ${row.path}`);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// === Function Table Stats ===
|
|
132
|
+
console.log('\n📊 Stats for Table: functions');
|
|
133
|
+
console.log('-------------------------------------------');
|
|
134
|
+
try {
|
|
135
|
+
const funcCount = db.prepare(`SELECT COUNT(*) AS count FROM functions`).get().count;
|
|
136
|
+
const distinctFiles = db.prepare(`SELECT COUNT(DISTINCT file_id) AS count FROM functions`).get().count;
|
|
137
|
+
console.log(`🔢 Total functions: ${funcCount}`);
|
|
138
|
+
console.log(`📂 Distinct files: ${distinctFiles}`);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
console.error('❌ Error accessing functions table:', err.message);
|
|
142
|
+
}
|
|
143
|
+
// === Example Functions ===
|
|
144
|
+
console.log('\n🧪 Example extracted functions:');
|
|
145
|
+
try {
|
|
146
|
+
const sampleFunctions = db.prepare(`
|
|
147
|
+
SELECT id, name, start_line, end_line, substr(content, 1, 100) || '...' AS short_body
|
|
148
|
+
FROM functions
|
|
149
|
+
ORDER BY id DESC
|
|
150
|
+
LIMIT 5
|
|
151
|
+
`).all();
|
|
152
|
+
sampleFunctions.forEach(fn => {
|
|
153
|
+
console.log(`🔹 ID: ${fn.id}`);
|
|
154
|
+
console.log(` Name: ${fn.name}`);
|
|
155
|
+
console.log(` Lines: ${fn.start_line}-${fn.end_line}`);
|
|
156
|
+
console.log(` Body: ${fn.short_body}\n`);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
console.error('❌ Error printing function examples:', err.message);
|
|
161
|
+
}
|
|
162
|
+
// === Function Calls Table Stats ===
|
|
163
|
+
console.log('\n📊 Stats for Table: function_calls');
|
|
164
|
+
console.log('-------------------------------------------');
|
|
165
|
+
try {
|
|
166
|
+
const callCount = db.prepare(`SELECT COUNT(*) AS count FROM function_calls`).get().count;
|
|
167
|
+
const topCallers = db.prepare(`
|
|
168
|
+
SELECT caller_id, COUNT(*) AS num_calls
|
|
169
|
+
FROM function_calls
|
|
170
|
+
GROUP BY caller_id
|
|
171
|
+
ORDER BY num_calls DESC
|
|
172
|
+
LIMIT 5
|
|
173
|
+
`).all();
|
|
174
|
+
console.log(`🔁 Total function calls: ${callCount}`);
|
|
175
|
+
console.log('📞 Top callers:');
|
|
176
|
+
topCallers.forEach(row => {
|
|
177
|
+
console.log(` - Caller ${row.caller_id} made ${row.num_calls} calls`);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
console.error('❌ Error accessing function_calls table:', err.message);
|
|
182
|
+
}
|
|
183
|
+
// === Random Summary Samples ===
|
|
184
|
+
console.log('\n🧾 10 Random Summaries (ID + Preview):');
|
|
185
|
+
console.log('-------------------------------------------');
|
|
186
|
+
const randomSummaries = db.prepare(`
|
|
187
|
+
SELECT id, filename, substr(summary, 1, 1000) || '...' AS preview
|
|
188
|
+
FROM files
|
|
189
|
+
WHERE summary IS NOT NULL AND summary != ''
|
|
190
|
+
ORDER BY RANDOM()
|
|
191
|
+
LIMIT 10
|
|
192
|
+
`).all();
|
|
193
|
+
randomSummaries.forEach(row => {
|
|
194
|
+
console.log(`📄 [${row.id}] ${row.filename}: ${row.preview}`);
|
|
195
|
+
});
|
|
196
|
+
// === Random Functions Samples ===
|
|
197
|
+
console.log('\n🧑💻 20 Random Functions (ID, name, body):');
|
|
198
|
+
console.log('-------------------------------------------');
|
|
199
|
+
const randomFunctions = db.prepare(`
|
|
200
|
+
SELECT id, name, file_id, substr(content, 1, 100) || '...' AS preview
|
|
201
|
+
FROM functions
|
|
202
|
+
ORDER BY RANDOM()
|
|
203
|
+
LIMIT 20
|
|
204
|
+
`).all();
|
|
205
|
+
randomFunctions.forEach(row => {
|
|
206
|
+
console.log(`🔹 [${row.id}] ${row.name} (file_id: ${row.file_id})`);
|
|
207
|
+
console.log(` ${row.preview}\n`);
|
|
208
|
+
});
|
|
209
|
+
// === Column View of 100 Files ===
|
|
210
|
+
console.log('\n📊 Table View: First 100 Files');
|
|
211
|
+
console.log('-------------------------------------------');
|
|
212
|
+
const fileRows = db.prepare(`
|
|
213
|
+
SELECT id, filename, type, processing_status, functions_extracted_at, length(summary) AS summary_len
|
|
214
|
+
FROM files
|
|
215
|
+
LIMIT 1000
|
|
216
|
+
`).all();
|
|
217
|
+
console.table(fileRows);
|
|
218
|
+
// === Column View of 100 Functions ===
|
|
219
|
+
console.log('\n📊 Table View: First 100 Functions');
|
|
220
|
+
console.log('-------------------------------------------');
|
|
221
|
+
const functionRows = db.prepare(`
|
|
222
|
+
SELECT id, file_id, name, start_line, end_line, length(content) AS length
|
|
223
|
+
FROM functions
|
|
224
|
+
LIMIT 1000
|
|
225
|
+
`).all();
|
|
226
|
+
console.table(functionRows);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.59",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"scai": "./dist/index.js"
|
|
@@ -44,15 +44,17 @@
|
|
|
44
44
|
"commander": "^11.0.0",
|
|
45
45
|
"fast-glob": "^3.3.3",
|
|
46
46
|
"proper-lockfile": "^4.1.2",
|
|
47
|
-
"string-similarity-js": "^2.1.4"
|
|
47
|
+
"string-similarity-js": "^2.1.4",
|
|
48
|
+
"ts-morph": "^26.0.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@types/better-sqlite3": "^7.6.13",
|
|
51
52
|
"@types/jest": "^30.0.0",
|
|
52
|
-
"@types/node": "^24.0
|
|
53
|
+
"@types/node": "^24.1.0",
|
|
53
54
|
"@types/proper-lockfile": "^4.1.4",
|
|
54
55
|
"jest": "^30.0.2",
|
|
55
56
|
"ts-jest": "^29.4.0",
|
|
57
|
+
"ts-node": "^10.9.2",
|
|
56
58
|
"typescript": "^5.8.3"
|
|
57
59
|
},
|
|
58
60
|
"files": [
|