@stainless-code/codemap 0.1.0

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,885 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { a as LANG_MAP, i as extractMarkers, n as hashContent, r as extractCssData, t as extractFileData } from "./parser-VtP0EJiw.mjs";
4
+ import { createRequire } from "node:module";
5
+ import { basename, dirname, extname, join, resolve } from "node:path";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { spawnSync } from "node:child_process";
8
+ import { existsSync, readFileSync, statSync } from "node:fs";
9
+ import fg from "fast-glob";
10
+ import { ResolverFactory } from "oxc-resolver";
11
+ import { cpus } from "node:os";
12
+ import { Worker as Worker$1 } from "node:worker_threads";
13
+ import { readFile } from "node:fs/promises";
14
+ import { z } from "zod";
15
+ //#region \0rolldown/runtime.js
16
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
17
+ //#endregion
18
+ //#region src/runtime.ts
19
+ let _config = null;
20
+ /**
21
+ * Store resolved config for the current process (`getProjectRoot`, `openDb`, etc.).
22
+ * Must run before indexing or `openDb()`; typically via `createCodemap` or the CLI.
23
+ */
24
+ function initCodemap(config) {
25
+ _config = config;
26
+ }
27
+ function getCodemapConfig() {
28
+ if (!_config) throw new Error("Codemap: not initialized — call initCodemap() after resolving config");
29
+ return _config;
30
+ }
31
+ function getProjectRoot() {
32
+ return getCodemapConfig().root;
33
+ }
34
+ function getDatabasePath() {
35
+ return getCodemapConfig().databasePath;
36
+ }
37
+ function getIncludePatterns() {
38
+ return getCodemapConfig().include;
39
+ }
40
+ function getExcludeDirNames() {
41
+ return getCodemapConfig().excludeDirNames;
42
+ }
43
+ function getTsconfigPath() {
44
+ return getCodemapConfig().tsconfigPath;
45
+ }
46
+ /** True if any path segment matches an excluded directory name (e.g. `node_modules`). */
47
+ function isPathExcluded(relPath) {
48
+ const parts = relPath.split(/[/\\]/).filter(Boolean);
49
+ const set = getExcludeDirNames();
50
+ return parts.some((p) => set.has(p));
51
+ }
52
+ //#endregion
53
+ //#region src/sqlite-db.ts
54
+ const require$1 = createRequire(import.meta.url);
55
+ /**
56
+ * `better-sqlite3` allows only one statement per `prepare()`; `bun:sqlite` accepts several.
57
+ * On Node we split on `;` — do not put `;` inside `--` line comments in `db.ts` SQL strings.
58
+ */
59
+ function runSql(inner, sql, params) {
60
+ if (params !== void 0 && params.length > 0) {
61
+ inner.run(sql, params);
62
+ return;
63
+ }
64
+ if (typeof Bun !== "undefined") {
65
+ inner.run(sql);
66
+ return;
67
+ }
68
+ const parts = sql.split(";").map((s) => s.trim()).filter(Boolean);
69
+ if (parts.length <= 1) inner.run(sql.trim());
70
+ else for (const p of parts) inner.run(p);
71
+ }
72
+ function openRaw(path) {
73
+ if (typeof Bun !== "undefined") {
74
+ const { Database } = require$1("bun:sqlite");
75
+ return new Database(path, { create: true });
76
+ }
77
+ const rawDb = new (require$1("better-sqlite3"))(path);
78
+ return {
79
+ run(sql, params) {
80
+ const stmt = rawDb.prepare(sql);
81
+ if (params !== void 0 && params.length > 0) stmt.run(...params);
82
+ else stmt.run();
83
+ },
84
+ query(sql) {
85
+ const stmt = rawDb.prepare(sql);
86
+ return {
87
+ get(...params) {
88
+ return stmt.get(...params);
89
+ },
90
+ all(...params) {
91
+ return stmt.all(...params);
92
+ }
93
+ };
94
+ },
95
+ transaction(fn) {
96
+ return rawDb.transaction(fn);
97
+ },
98
+ close() {
99
+ rawDb.close();
100
+ }
101
+ };
102
+ }
103
+ function wrap(inner) {
104
+ return {
105
+ run(sql, params) {
106
+ runSql(inner, sql, params);
107
+ },
108
+ query(sql) {
109
+ return {
110
+ get(...params) {
111
+ return inner.query(sql).get(...params);
112
+ },
113
+ all(...params) {
114
+ return inner.query(sql).all(...params);
115
+ }
116
+ };
117
+ },
118
+ transaction(fn) {
119
+ return inner.transaction(fn);
120
+ },
121
+ close() {
122
+ inner.close();
123
+ }
124
+ };
125
+ }
126
+ function openCodemapDatabase(path) {
127
+ const db = wrap(openRaw(path ?? getDatabasePath()));
128
+ db.run("PRAGMA journal_mode = WAL");
129
+ db.run("PRAGMA synchronous = NORMAL");
130
+ db.run("PRAGMA foreign_keys = ON");
131
+ db.run("PRAGMA case_sensitive_like = ON");
132
+ db.run("PRAGMA temp_store = MEMORY");
133
+ db.run("PRAGMA mmap_size = 268435456");
134
+ db.run("PRAGMA cache_size = -16384");
135
+ return db;
136
+ }
137
+ function openDb() {
138
+ return openCodemapDatabase();
139
+ }
140
+ function closeDb(db) {
141
+ db.run("PRAGMA analysis_limit = 400");
142
+ db.run("PRAGMA optimize");
143
+ db.close();
144
+ }
145
+ function createTables(db) {
146
+ db.run(`
147
+ CREATE TABLE IF NOT EXISTS files (
148
+ path TEXT PRIMARY KEY,
149
+ content_hash TEXT NOT NULL,
150
+ size INTEGER,
151
+ line_count INTEGER,
152
+ language TEXT,
153
+ last_modified INTEGER,
154
+ indexed_at INTEGER
155
+ ) STRICT;
156
+
157
+ CREATE TABLE IF NOT EXISTS symbols (
158
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
159
+ file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
160
+ name TEXT NOT NULL,
161
+ kind TEXT NOT NULL,
162
+ line_start INTEGER,
163
+ line_end INTEGER,
164
+ signature TEXT,
165
+ is_exported INTEGER DEFAULT 0,
166
+ is_default_export INTEGER DEFAULT 0
167
+ ) STRICT;
168
+
169
+ CREATE TABLE IF NOT EXISTS imports (
170
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
171
+ file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
172
+ source TEXT NOT NULL,
173
+ resolved_path TEXT,
174
+ specifiers TEXT,
175
+ is_type_only INTEGER DEFAULT 0,
176
+ line_number INTEGER
177
+ ) STRICT;
178
+
179
+ CREATE TABLE IF NOT EXISTS exports (
180
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
181
+ file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
182
+ name TEXT NOT NULL,
183
+ kind TEXT,
184
+ is_default INTEGER DEFAULT 0,
185
+ re_export_source TEXT
186
+ ) STRICT;
187
+
188
+ CREATE TABLE IF NOT EXISTS components (
189
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
190
+ file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
191
+ name TEXT NOT NULL,
192
+ props_type TEXT,
193
+ hooks_used TEXT,
194
+ is_default_export INTEGER DEFAULT 0
195
+ ) STRICT;
196
+
197
+ CREATE TABLE IF NOT EXISTS dependencies (
198
+ from_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
199
+ to_path TEXT NOT NULL,
200
+ PRIMARY KEY (from_path, to_path)
201
+ ) STRICT, WITHOUT ROWID;
202
+
203
+ CREATE TABLE IF NOT EXISTS markers (
204
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
205
+ file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
206
+ line_number INTEGER,
207
+ kind TEXT NOT NULL,
208
+ content TEXT
209
+ ) STRICT;
210
+
211
+ CREATE TABLE IF NOT EXISTS css_variables (
212
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
213
+ file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
214
+ name TEXT NOT NULL,
215
+ value TEXT,
216
+ scope TEXT,
217
+ line_number INTEGER
218
+ ) STRICT;
219
+
220
+ CREATE TABLE IF NOT EXISTS css_classes (
221
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
222
+ file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
223
+ name TEXT NOT NULL,
224
+ is_module INTEGER DEFAULT 0,
225
+ line_number INTEGER
226
+ ) STRICT;
227
+
228
+ CREATE TABLE IF NOT EXISTS css_keyframes (
229
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
230
+ file_path TEXT NOT NULL REFERENCES files(path) ON DELETE CASCADE,
231
+ name TEXT NOT NULL,
232
+ line_number INTEGER
233
+ ) STRICT;
234
+
235
+ CREATE TABLE IF NOT EXISTS meta (
236
+ key TEXT PRIMARY KEY,
237
+ value TEXT
238
+ ) STRICT, WITHOUT ROWID;
239
+ `);
240
+ }
241
+ function createIndexes(db) {
242
+ db.run(`
243
+ -- Covering indexes: include columns returned by common queries to avoid table lookups
244
+ CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name, kind, file_path, line_start, line_end, signature, is_exported);
245
+ CREATE INDEX IF NOT EXISTS idx_symbols_kind ON symbols(kind, is_exported, name, file_path);
246
+ CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path);
247
+
248
+ -- Partial indexes: subset indexes for common filtered AI agent queries
249
+ CREATE INDEX IF NOT EXISTS idx_symbols_exported ON symbols(name, kind, file_path, signature)
250
+ WHERE is_exported = 1;
251
+ CREATE INDEX IF NOT EXISTS idx_symbols_functions ON symbols(name, file_path, line_start, line_end, signature)
252
+ WHERE kind = 'function';
253
+
254
+ CREATE INDEX IF NOT EXISTS idx_imports_source ON imports(source, file_path);
255
+ CREATE INDEX IF NOT EXISTS idx_imports_resolved ON imports(resolved_path, file_path);
256
+ CREATE INDEX IF NOT EXISTS idx_imports_file ON imports(file_path);
257
+
258
+ CREATE INDEX IF NOT EXISTS idx_exports_name ON exports(name, file_path, kind, is_default);
259
+ CREATE INDEX IF NOT EXISTS idx_exports_file ON exports(file_path);
260
+
261
+ CREATE INDEX IF NOT EXISTS idx_components_name ON components(name, file_path, props_type, hooks_used);
262
+ CREATE INDEX IF NOT EXISTS idx_components_file ON components(file_path, name);
263
+
264
+ -- WITHOUT ROWID tables already have a clustered PK — this covers reverse lookups
265
+ CREATE INDEX IF NOT EXISTS idx_dependencies_to ON dependencies(to_path, from_path);
266
+
267
+ CREATE INDEX IF NOT EXISTS idx_markers_kind ON markers(kind, file_path, line_number, content);
268
+ CREATE INDEX IF NOT EXISTS idx_markers_file ON markers(file_path);
269
+
270
+ CREATE INDEX IF NOT EXISTS idx_css_variables_name ON css_variables(name, value, scope, file_path);
271
+ CREATE INDEX IF NOT EXISTS idx_css_variables_file ON css_variables(file_path);
272
+ CREATE INDEX IF NOT EXISTS idx_css_classes_name ON css_classes(name, file_path, is_module);
273
+ CREATE INDEX IF NOT EXISTS idx_css_classes_file ON css_classes(file_path);
274
+ CREATE INDEX IF NOT EXISTS idx_css_keyframes_name ON css_keyframes(name, file_path);
275
+ `);
276
+ }
277
+ function createSchema(db) {
278
+ if (db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='meta'").get()) {
279
+ const row = db.query("SELECT value FROM meta WHERE key = ?").get("schema_version");
280
+ if (row && row.value !== String(1)) {
281
+ console.log(` Schema version mismatch (${row.value} -> 1), rebuilding...`);
282
+ dropAll(db);
283
+ }
284
+ }
285
+ createTables(db);
286
+ createIndexes(db);
287
+ setMeta(db, "schema_version", String(1));
288
+ }
289
+ function dropAll(db) {
290
+ db.run(`
291
+ DROP TABLE IF EXISTS dependencies;
292
+ DROP TABLE IF EXISTS markers;
293
+ DROP TABLE IF EXISTS components;
294
+ DROP TABLE IF EXISTS imports;
295
+ DROP TABLE IF EXISTS exports;
296
+ DROP TABLE IF EXISTS symbols;
297
+ DROP TABLE IF EXISTS css_variables;
298
+ DROP TABLE IF EXISTS css_classes;
299
+ DROP TABLE IF EXISTS css_keyframes;
300
+ DROP TABLE IF EXISTS files;
301
+ DROP TABLE IF EXISTS meta;
302
+ `);
303
+ }
304
+ function getMeta(db, key) {
305
+ return db.query("SELECT value FROM meta WHERE key = ?").get(key)?.value;
306
+ }
307
+ function setMeta(db, key, value) {
308
+ db.run("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)", [key, value]);
309
+ }
310
+ function deleteFileData(db, filePath) {
311
+ db.run("DELETE FROM files WHERE path = ?", [filePath]);
312
+ }
313
+ function insertFile(db, file) {
314
+ db.run(`INSERT INTO files (path, content_hash, size, line_count, language, last_modified, indexed_at)
315
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, [
316
+ file.path,
317
+ file.content_hash,
318
+ file.size,
319
+ file.line_count,
320
+ file.language,
321
+ file.last_modified,
322
+ file.indexed_at
323
+ ]);
324
+ }
325
+ const BATCH_SIZE = 100;
326
+ function batchInsert(db, items, sqlPrefix, one, extract) {
327
+ if (items.length === 0) return;
328
+ const fullPlaceholders = Array(BATCH_SIZE).fill(one).join(",");
329
+ for (let i = 0; i < items.length; i += BATCH_SIZE) {
330
+ const end = Math.min(i + BATCH_SIZE, items.length);
331
+ const batchLen = end - i;
332
+ const placeholders = batchLen === BATCH_SIZE ? fullPlaceholders : Array(batchLen).fill(one).join(",");
333
+ const values = [];
334
+ for (let j = i; j < end; j++) extract(items[j], values);
335
+ db.run(`${sqlPrefix} VALUES ${placeholders}`, values);
336
+ }
337
+ }
338
+ function insertSymbols(db, symbols) {
339
+ batchInsert(db, symbols, "INSERT INTO symbols (file_path, name, kind, line_start, line_end, signature, is_exported, is_default_export)", "(?,?,?,?,?,?,?,?)", (s, v) => v.push(s.file_path, s.name, s.kind, s.line_start, s.line_end, s.signature, s.is_exported, s.is_default_export));
340
+ }
341
+ function insertImports(db, imports) {
342
+ batchInsert(db, imports, "INSERT INTO imports (file_path, source, resolved_path, specifiers, is_type_only, line_number)", "(?,?,?,?,?,?)", (imp, v) => v.push(imp.file_path, imp.source, imp.resolved_path, imp.specifiers, imp.is_type_only, imp.line_number));
343
+ }
344
+ function insertExports(db, exports) {
345
+ batchInsert(db, exports, "INSERT INTO exports (file_path, name, kind, is_default, re_export_source)", "(?,?,?,?,?)", (e, v) => v.push(e.file_path, e.name, e.kind, e.is_default, e.re_export_source));
346
+ }
347
+ function insertComponents(db, components) {
348
+ batchInsert(db, components, "INSERT INTO components (file_path, name, props_type, hooks_used, is_default_export)", "(?,?,?,?,?)", (c, v) => v.push(c.file_path, c.name, c.props_type, c.hooks_used, c.is_default_export));
349
+ }
350
+ function insertDependencies(db, deps) {
351
+ batchInsert(db, deps, "INSERT OR IGNORE INTO dependencies (from_path, to_path)", "(?,?)", (d, v) => v.push(d.from_path, d.to_path));
352
+ }
353
+ function insertMarkers(db, markers) {
354
+ batchInsert(db, markers, "INSERT INTO markers (file_path, line_number, kind, content)", "(?,?,?,?)", (m, v) => v.push(m.file_path, m.line_number, m.kind, m.content));
355
+ }
356
+ function insertCssVariables(db, variables) {
357
+ batchInsert(db, variables, "INSERT INTO css_variables (file_path, name, value, scope, line_number)", "(?,?,?,?,?)", (cv, v) => v.push(cv.file_path, cv.name, cv.value, cv.scope, cv.line_number));
358
+ }
359
+ function insertCssClasses(db, classes) {
360
+ batchInsert(db, classes, "INSERT INTO css_classes (file_path, name, is_module, line_number)", "(?,?,?,?)", (c, v) => v.push(c.file_path, c.name, c.is_module, c.line_number));
361
+ }
362
+ function insertCssKeyframes(db, keyframes) {
363
+ batchInsert(db, keyframes, "INSERT INTO css_keyframes (file_path, name, line_number)", "(?,?,?)", (k, v) => v.push(k.file_path, k.name, k.line_number));
364
+ }
365
+ function getAllFileHashes(db) {
366
+ const rows = db.query("SELECT path, content_hash FROM files").all();
367
+ const map = /* @__PURE__ */ new Map();
368
+ for (let i = 0; i < rows.length; i++) map.set(rows[i].path, rows[i].content_hash);
369
+ return map;
370
+ }
371
+ //#endregion
372
+ //#region src/glob-sync.ts
373
+ /**
374
+ * Glob files relative to `cwd` (dotfiles included). On Bun uses `Glob` from `bun`;
375
+ * on Node uses `fast-glob` for identical published behavior.
376
+ */
377
+ function globSync(pattern, cwd) {
378
+ if (typeof Bun !== "undefined") {
379
+ const { Glob } = __require("bun");
380
+ const glob = new Glob(pattern);
381
+ return Array.from(glob.scanSync({
382
+ cwd,
383
+ dot: true
384
+ }));
385
+ }
386
+ return fg.sync(pattern, {
387
+ cwd,
388
+ dot: true,
389
+ absolute: false
390
+ });
391
+ }
392
+ //#endregion
393
+ //#region src/resolver.ts
394
+ let _projectRoot = null;
395
+ let _tsconfigPath = null;
396
+ let _resolver = null;
397
+ /**
398
+ * Wire oxc-resolver to the project root and optional `tsconfig.json`.
399
+ * Call after `initCodemap()` (CLI and `createCodemap` do this for you).
400
+ */
401
+ function configureResolver(projectRoot, tsconfigPath) {
402
+ _projectRoot = projectRoot;
403
+ _tsconfigPath = tsconfigPath;
404
+ _resolver = null;
405
+ }
406
+ function getResolver() {
407
+ if (!_projectRoot) throw new Error("Codemap: configureResolver() must run before resolving imports");
408
+ if (!_resolver) {
409
+ const options = {
410
+ conditionNames: ["node", "import"],
411
+ extensions: [
412
+ ".ts",
413
+ ".tsx",
414
+ ".mts",
415
+ ".cts",
416
+ ".js",
417
+ ".jsx",
418
+ ".mjs",
419
+ ".cjs",
420
+ ".json",
421
+ ".css"
422
+ ],
423
+ mainFields: ["module", "main"]
424
+ };
425
+ if (_tsconfigPath) options.tsconfig = { configFile: _tsconfigPath };
426
+ _resolver = new ResolverFactory(options);
427
+ }
428
+ return _resolver;
429
+ }
430
+ /**
431
+ * Resolve static imports, mutating each row's `resolved_path`, and collect `dependencies`
432
+ * edges only when the target path is in `indexedPaths` (project files).
433
+ */
434
+ function resolveImports(absoluteFilePath, imports, indexedPaths) {
435
+ const root = _projectRoot;
436
+ const resolver = getResolver();
437
+ const deps = [];
438
+ for (const imp of imports) try {
439
+ const result = resolver.resolveFileSync(absoluteFilePath, imp.source);
440
+ if (result.path) {
441
+ const resolved = result.path;
442
+ const relResolved = resolved.startsWith(root) ? resolved.slice(root.length + 1) : resolved;
443
+ imp.resolved_path = relResolved;
444
+ if (indexedPaths.has(relResolved)) deps.push({
445
+ from_path: imp.file_path,
446
+ to_path: relResolved
447
+ });
448
+ }
449
+ } catch {}
450
+ return deps;
451
+ }
452
+ //#endregion
453
+ //#region src/worker-pool.ts
454
+ const fromDist = basename(dirname(fileURLToPath(import.meta.url))) === "dist";
455
+ const WORKER_URL_BUN = new URL(fromDist ? "./parse-worker.mjs" : "./parse-worker.ts", import.meta.url);
456
+ const WORKER_URL_NODE = new URL(fromDist ? "./parse-worker-node.mjs" : "./parse-worker-node.ts", import.meta.url);
457
+ const WORKER_COUNT = Math.max(2, Math.min(cpus().length || 4, 6));
458
+ function parseFilesParallel(filePaths) {
459
+ const chunkSize = Math.ceil(filePaths.length / WORKER_COUNT);
460
+ const chunks = [];
461
+ for (let i = 0; i < filePaths.length; i += chunkSize) chunks.push(filePaths.slice(i, i + chunkSize));
462
+ return Promise.all(chunks.map((chunk) => new Promise((resolve, reject) => {
463
+ const input = {
464
+ files: chunk,
465
+ projectRoot: getProjectRoot()
466
+ };
467
+ if (typeof Bun !== "undefined") {
468
+ const worker = new Worker(WORKER_URL_BUN);
469
+ worker.onmessage = (event) => {
470
+ resolve(event.data.results);
471
+ worker.terminate();
472
+ };
473
+ worker.postMessage(input);
474
+ return;
475
+ }
476
+ const worker = new Worker$1(fileURLToPath(WORKER_URL_NODE), { type: "module" });
477
+ worker.on("message", (data) => {
478
+ resolve(data.results);
479
+ worker.terminate();
480
+ });
481
+ worker.on("error", reject);
482
+ worker.postMessage(input);
483
+ }))).then((parts) => parts.flat());
484
+ }
485
+ //#endregion
486
+ //#region src/application/index-engine.ts
487
+ const VALID_EXTENSIONS = new Set(Object.keys(LANG_MAP));
488
+ const TS_EXTENSIONS = new Set([
489
+ ".ts",
490
+ ".tsx",
491
+ ".mts",
492
+ ".cts",
493
+ ".js",
494
+ ".jsx",
495
+ ".mjs",
496
+ ".cjs"
497
+ ]);
498
+ const CSS_EXTENSIONS = new Set([".css"]);
499
+ function langFromExt(ext) {
500
+ return LANG_MAP[ext] ?? "text";
501
+ }
502
+ function fileCategory(path) {
503
+ const ext = extname(path);
504
+ if (TS_EXTENSIONS.has(ext)) return "ts";
505
+ if (CSS_EXTENSIONS.has(ext)) return "css";
506
+ return "text";
507
+ }
508
+ function collectFiles() {
509
+ const files = [];
510
+ const root = getProjectRoot();
511
+ for (const pattern of getIncludePatterns()) {
512
+ const matches = globSync(pattern, root);
513
+ for (const path of matches) {
514
+ if (isPathExcluded(path)) continue;
515
+ files.push(path);
516
+ }
517
+ }
518
+ return [...new Set(files)].sort();
519
+ }
520
+ function getChangedFiles(db) {
521
+ const lastCommit = getMeta(db, "last_indexed_commit");
522
+ if (!lastCommit) return null;
523
+ try {
524
+ if (spawnSync("git", [
525
+ "merge-base",
526
+ "--is-ancestor",
527
+ lastCommit,
528
+ "HEAD"
529
+ ], { cwd: getProjectRoot() }).status !== 0) return null;
530
+ const diffResult = spawnSync("git", [
531
+ "diff",
532
+ "--name-only",
533
+ `${lastCommit}..HEAD`
534
+ ], { cwd: getProjectRoot() });
535
+ const statusResult = spawnSync("git", [
536
+ "status",
537
+ "--porcelain",
538
+ "--no-renames"
539
+ ], { cwd: getProjectRoot() });
540
+ const diffFiles = diffResult.stdout.toString().trim().split("\n").filter(Boolean);
541
+ const statusFiles = statusResult.stdout.toString().trim().split("\n").filter(Boolean).map((line) => line.slice(3).trim());
542
+ const allChanged = [...new Set([...diffFiles, ...statusFiles])].filter((f) => {
543
+ return extname(f) in LANG_MAP;
544
+ });
545
+ const changed = [];
546
+ const deleted = [];
547
+ for (const f of allChanged) try {
548
+ statSync(join(getProjectRoot(), f));
549
+ changed.push(f);
550
+ } catch {
551
+ deleted.push(f);
552
+ }
553
+ return {
554
+ changed,
555
+ deleted
556
+ };
557
+ } catch {
558
+ return null;
559
+ }
560
+ }
561
+ function getCurrentCommit() {
562
+ return spawnSync("git", ["rev-parse", "HEAD"], { cwd: getProjectRoot() }).stdout.toString().trim();
563
+ }
564
+ function insertParsedResults(db, results, indexedPaths) {
565
+ let indexed = 0;
566
+ db.transaction(() => {
567
+ for (const parsed of results) {
568
+ if (parsed.error) continue;
569
+ insertFile(db, parsed.fileRow);
570
+ try {
571
+ if (parsed.category === "text") {
572
+ if (parsed.markers?.length) insertMarkers(db, parsed.markers);
573
+ } else if (parsed.category === "css") {
574
+ if (parsed.cssVariables?.length) insertCssVariables(db, parsed.cssVariables);
575
+ if (parsed.cssClasses?.length) insertCssClasses(db, parsed.cssClasses);
576
+ if (parsed.cssKeyframes?.length) insertCssKeyframes(db, parsed.cssKeyframes);
577
+ if (parsed.markers?.length) insertMarkers(db, parsed.markers);
578
+ if (parsed.cssImportSources) for (const importSource of parsed.cssImportSources) insertImports(db, [{
579
+ file_path: parsed.relPath,
580
+ source: importSource,
581
+ resolved_path: null,
582
+ specifiers: "[]",
583
+ is_type_only: 0,
584
+ line_number: 0
585
+ }]);
586
+ } else {
587
+ if (parsed.symbols?.length) insertSymbols(db, parsed.symbols);
588
+ if (parsed.imports?.length) {
589
+ const deps = resolveImports(join(getProjectRoot(), parsed.relPath), parsed.imports, indexedPaths);
590
+ insertImports(db, parsed.imports);
591
+ if (deps.length) insertDependencies(db, deps);
592
+ }
593
+ if (parsed.exports?.length) insertExports(db, parsed.exports);
594
+ if (parsed.components?.length) insertComponents(db, parsed.components);
595
+ if (parsed.markers?.length) insertMarkers(db, parsed.markers);
596
+ }
597
+ } catch (err) {
598
+ console.error(` Parse error in ${parsed.relPath}: ${err instanceof Error ? err.message : err}`);
599
+ }
600
+ indexed++;
601
+ }
602
+ })();
603
+ return indexed;
604
+ }
605
+ function fetchTableStats(db) {
606
+ return db.query(`SELECT
607
+ (SELECT COUNT(*) FROM files) as files,
608
+ (SELECT COUNT(*) FROM symbols) as symbols,
609
+ (SELECT COUNT(*) FROM imports) as imports,
610
+ (SELECT COUNT(*) FROM exports) as exports,
611
+ (SELECT COUNT(*) FROM components) as components,
612
+ (SELECT COUNT(*) FROM dependencies) as dependencies,
613
+ (SELECT COUNT(*) FROM markers) as markers,
614
+ (SELECT COUNT(*) FROM css_variables) as css_vars,
615
+ (SELECT COUNT(*) FROM css_classes) as css_classes,
616
+ (SELECT COUNT(*) FROM css_keyframes) as css_keyframes`).get();
617
+ }
618
+ async function indexFiles(db, filePaths, fullRebuild, knownIndexedPaths, options) {
619
+ const quiet = options?.quiet ?? false;
620
+ const startTime = performance.now();
621
+ if (fullRebuild) {
622
+ dropAll(db);
623
+ createTables(db);
624
+ db.run("PRAGMA synchronous = OFF");
625
+ db.run("PRAGMA foreign_keys = OFF");
626
+ } else createSchema(db);
627
+ const indexedPaths = knownIndexedPaths ?? new Set(filePaths);
628
+ let indexed = 0;
629
+ let skipped = 0;
630
+ if (fullRebuild) {
631
+ const results = await parseFilesParallel(filePaths);
632
+ results.sort((a, b) => a.relPath.localeCompare(b.relPath));
633
+ indexed = insertParsedResults(db, results, indexedPaths);
634
+ } else {
635
+ const existingHashes = getAllFileHashes(db);
636
+ db.transaction(() => {
637
+ for (const relPath of filePaths) {
638
+ const absPath = join(getProjectRoot(), relPath);
639
+ let source;
640
+ try {
641
+ source = readFileSync(absPath, "utf-8");
642
+ } catch {
643
+ deleteFileData(db, relPath);
644
+ continue;
645
+ }
646
+ const hash = hashContent(source);
647
+ if (existingHashes.get(relPath) === hash) {
648
+ skipped++;
649
+ continue;
650
+ }
651
+ deleteFileData(db, relPath);
652
+ const stat = statSync(absPath);
653
+ let lineCount = 1;
654
+ for (let i = 0; i < source.length; i++) if (source.charCodeAt(i) === 10) lineCount++;
655
+ insertFile(db, {
656
+ path: relPath,
657
+ content_hash: hash,
658
+ size: stat.size,
659
+ line_count: lineCount,
660
+ language: langFromExt(extname(relPath)),
661
+ last_modified: Math.floor(stat.mtimeMs),
662
+ indexed_at: Date.now()
663
+ });
664
+ try {
665
+ const category = fileCategory(relPath);
666
+ if (category === "text") {
667
+ const markers = extractMarkers(source, relPath);
668
+ if (markers.length) insertMarkers(db, markers);
669
+ } else if (category === "css") {
670
+ const cssData = extractCssData(absPath, source, relPath);
671
+ if (cssData.variables.length) insertCssVariables(db, cssData.variables);
672
+ if (cssData.classes.length) insertCssClasses(db, cssData.classes);
673
+ if (cssData.keyframes.length) insertCssKeyframes(db, cssData.keyframes);
674
+ if (cssData.markers.length) insertMarkers(db, cssData.markers);
675
+ for (const importSource of cssData.importSources) insertImports(db, [{
676
+ file_path: relPath,
677
+ source: importSource,
678
+ resolved_path: null,
679
+ specifiers: "[]",
680
+ is_type_only: 0,
681
+ line_number: 0
682
+ }]);
683
+ } else {
684
+ const data = extractFileData(absPath, source, relPath);
685
+ if (data.symbols.length) insertSymbols(db, data.symbols);
686
+ const deps = resolveImports(absPath, data.imports, indexedPaths);
687
+ if (data.imports.length) insertImports(db, data.imports);
688
+ if (deps.length) insertDependencies(db, deps);
689
+ if (data.exports.length) insertExports(db, data.exports);
690
+ if (data.components.length) insertComponents(db, data.components);
691
+ if (data.markers.length) insertMarkers(db, data.markers);
692
+ }
693
+ } catch (err) {
694
+ console.error(` Parse error in ${relPath}: ${err instanceof Error ? err.message : err}`);
695
+ }
696
+ indexed++;
697
+ }
698
+ })();
699
+ }
700
+ if (fullRebuild) {
701
+ createIndexes(db);
702
+ db.run("PRAGMA synchronous = NORMAL");
703
+ db.run("PRAGMA foreign_keys = ON");
704
+ setMeta(db, "schema_version", String(1));
705
+ }
706
+ setMeta(db, "last_indexed_commit", getCurrentCommit());
707
+ setMeta(db, "indexed_at", (/* @__PURE__ */ new Date()).toISOString());
708
+ const fileCount = db.query("SELECT COUNT(*) as c FROM files").get().c;
709
+ setMeta(db, "file_count", String(fileCount));
710
+ setMeta(db, "project_root", getProjectRoot());
711
+ const elapsed = Math.round(performance.now() - startTime);
712
+ const stats = fetchTableStats(db);
713
+ if (!quiet) {
714
+ console.log(`\n Codemap ${fullRebuild ? "(full rebuild)" : "(incremental)"}`);
715
+ console.log(` ${indexed} files indexed, ${skipped} unchanged, ${elapsed}ms`);
716
+ console.log(` ───────────────────────────────────`);
717
+ for (const [key, value] of Object.entries(stats)) console.log(` ${(key + ":").padEnd(14)}${value}`);
718
+ console.log();
719
+ }
720
+ return {
721
+ indexed,
722
+ skipped,
723
+ elapsedMs: elapsed,
724
+ fullRebuild,
725
+ stats
726
+ };
727
+ }
728
+ function deleteFilesFromIndex(db, deleted, quiet) {
729
+ if (deleted.length === 0) return;
730
+ for (const f of deleted) deleteFileData(db, f);
731
+ if (!quiet) console.log(` Removed ${deleted.length} deleted files from index`);
732
+ }
733
+ async function targetedReindex(db, targetFiles, quiet) {
734
+ const startTime = performance.now();
735
+ createSchema(db);
736
+ const existingPaths = new Set(getAllFileHashes(db).keys());
737
+ for (const f of targetFiles) existingPaths.add(f);
738
+ const elapsed = Math.round(performance.now() - startTime);
739
+ if (!quiet) console.log(` Targeted reindex: ${targetFiles.length} files (setup ${elapsed}ms)`);
740
+ return indexFiles(db, targetFiles, false, existingPaths, { quiet });
741
+ }
742
+ /**
743
+ * Run SQL and print results to stdout (`console.table`), or a friendly error to stderr.
744
+ * Does not throw on invalid SQL (matches CLI `query` UX).
745
+ */
746
+ function printQueryResult(sql) {
747
+ const db = openDb();
748
+ try {
749
+ const rows = db.query(sql).all();
750
+ if (rows.length === 0) console.log("(no results)");
751
+ else console.table(rows);
752
+ } catch (err) {
753
+ console.error(`Query error: ${err instanceof Error ? err.message : err}`);
754
+ } finally {
755
+ closeDb(db);
756
+ }
757
+ }
758
+ /**
759
+ * Open the index, run SQL, return all rows, then close (used by the public `Codemap.query` API).
760
+ */
761
+ function queryRows(sql) {
762
+ const db = openDb();
763
+ try {
764
+ return db.query(sql).all();
765
+ } finally {
766
+ closeDb(db);
767
+ }
768
+ }
769
+ //#endregion
770
+ //#region src/config.ts
771
+ async function readJsonFile(filePath) {
772
+ if (typeof Bun !== "undefined") return Bun.file(filePath).json();
773
+ const text = await readFile(filePath, "utf-8");
774
+ return JSON.parse(text);
775
+ }
776
+ /**
777
+ * Default glob patterns for indexing (relative to project root).
778
+ * Override with `include` in `codemap.config`.
779
+ */
780
+ const DEFAULT_INCLUDE_PATTERNS = [
781
+ "**/*.{ts,tsx,js,jsx,cjs,mjs,mts,cts}",
782
+ "**/*.css",
783
+ "**/*.{md,mdx,mdc}",
784
+ "**/*.{json,yml,yaml}",
785
+ "**/*.sh"
786
+ ];
787
+ /**
788
+ * Directory **names** excluded when they appear as any path segment (not full paths).
789
+ * Override with `excludeDirNames` in `codemap.config`.
790
+ */
791
+ const DEFAULT_EXCLUDE_DIR_NAMES = [
792
+ "node_modules",
793
+ ".git",
794
+ "dist",
795
+ "build",
796
+ ".next",
797
+ ".output",
798
+ "coverage",
799
+ "storybook-static",
800
+ ".turbo",
801
+ ".cache",
802
+ ".parcel-cache",
803
+ "vendor"
804
+ ];
805
+ /**
806
+ * Zod schema for user config (`codemap.config.*`, `defineConfig`, API).
807
+ * Unknown keys are rejected (`.strict()`).
808
+ */
809
+ const codemapUserConfigSchema = z.object({
810
+ root: z.string().optional().describe("Project root. Defaults via CLI `--root` or `process.cwd()`."),
811
+ databasePath: z.string().optional().describe("SQLite database path, relative to root or absolute. Default: `<root>/.codemap.db`."),
812
+ include: z.array(z.string()).optional().describe("Glob patterns relative to root; replaces default include list when set."),
813
+ excludeDirNames: z.array(z.string()).optional().describe("Directory name segments to skip; replaces default exclude list when set."),
814
+ tsconfigPath: z.union([z.string(), z.null()]).optional().describe("Path to `tsconfig.json` for import alias resolution. Use `null` to disable.")
815
+ }).strict();
816
+ function formatCodemapConfigError(error) {
817
+ return error.issues.map((issue) => {
818
+ return `${issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)"}: ${issue.message}`;
819
+ }).join("; ");
820
+ }
821
+ /**
822
+ * Runtime validation for {@link CodemapUserConfig} (from JSON, `defineConfig`, or API).
823
+ *
824
+ * @throws TypeError when the shape is invalid or unknown keys are present.
825
+ */
826
+ function parseCodemapUserConfig(config) {
827
+ const result = codemapUserConfigSchema.safeParse(config);
828
+ if (!result.success) throw new TypeError(`Codemap config: ${formatCodemapConfigError(result.error)}`);
829
+ return result.data;
830
+ }
831
+ /**
832
+ * Helper for `export default defineConfig({ ... })` in `codemap.config.ts`.
833
+ */
834
+ function defineConfig(config) {
835
+ return parseCodemapUserConfig(config);
836
+ }
837
+ /**
838
+ * Merge user config with defaults (absolute paths, default DB location, tsconfig discovery).
839
+ */
840
+ function resolveCodemapConfig(root, user) {
841
+ const parsed = user !== void 0 ? parseCodemapUserConfig(user) : void 0;
842
+ const absRoot = resolve(root);
843
+ const databasePath = parsed?.databasePath ? resolve(absRoot, parsed.databasePath) : join(absRoot, ".codemap.db");
844
+ const include = parsed?.include?.length ? [...parsed.include] : [...DEFAULT_INCLUDE_PATTERNS];
845
+ const excludeDirNames = new Set(parsed?.excludeDirNames?.length ? parsed.excludeDirNames : DEFAULT_EXCLUDE_DIR_NAMES);
846
+ let tsconfigPath;
847
+ if (parsed?.tsconfigPath === null) tsconfigPath = null;
848
+ else if (parsed?.tsconfigPath) tsconfigPath = resolve(absRoot, parsed.tsconfigPath);
849
+ else {
850
+ const d = join(absRoot, "tsconfig.json");
851
+ tsconfigPath = existsSync(d) ? d : null;
852
+ }
853
+ return {
854
+ root: absRoot,
855
+ databasePath,
856
+ include,
857
+ excludeDirNames,
858
+ tsconfigPath
859
+ };
860
+ }
861
+ /**
862
+ * Load optional `codemap.config.ts` / `codemap.config.json` from the project root,
863
+ * or from `explicitPath` (CLI `--config`).
864
+ */
865
+ async function loadUserConfig(root, explicitPath) {
866
+ const tryImport = async (file) => {
867
+ if (!existsSync(file)) return void 0;
868
+ const def = (await import(pathToFileURL(file).href)).default;
869
+ if (typeof def === "function") return await def();
870
+ if (def && typeof def === "object") return def;
871
+ };
872
+ if (explicitPath) {
873
+ if (explicitPath.endsWith(".json")) {
874
+ if (!existsSync(explicitPath)) return void 0;
875
+ return await readJsonFile(explicitPath);
876
+ }
877
+ return tryImport(explicitPath);
878
+ }
879
+ const fromTs = await tryImport(join(root, "codemap.config.ts"));
880
+ if (fromTs) return fromTs;
881
+ const jsonPath = join(root, "codemap.config.json");
882
+ if (existsSync(jsonPath)) return await readJsonFile(jsonPath);
883
+ }
884
+ //#endregion
885
+ export { getTsconfigPath as C, getProjectRoot as S, createSchema as _, resolveCodemapConfig as a, setMeta as b, deleteFilesFromIndex as c, indexFiles as d, printQueryResult as f, closeDb as g, configureResolver as h, parseCodemapUserConfig as i, getChangedFiles as l, targetedReindex as m, defineConfig as n, VALID_EXTENSIONS as o, queryRows as p, loadUserConfig as r, collectFiles as s, codemapUserConfigSchema as t, getCurrentCommit as u, getAllFileHashes as v, initCodemap as w, getDatabasePath as x, openDb as y };