@massu/core 1.10.6 → 1.10.8
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/dist/cli.js +405 -337
- package/dist/hooks/post-edit-context.js +6 -1
- package/dist/hooks/pre-delete-check.js +12 -7
- package/package.json +1 -1
- package/src/db.ts +63 -62
- package/src/domains.ts +2 -1
- package/src/hooks/pre-delete-check.ts +7 -5
- package/src/import-resolver.ts +3 -2
- package/src/knowledge-tools.ts +3 -2
- package/src/lib/framework-supports.ts +85 -0
- package/src/lib/sql-table-names.ts +71 -0
- package/src/middleware-tree.ts +6 -5
- package/src/page-deps.ts +9 -8
- package/src/python/coupling-detector.ts +4 -3
- package/src/python/domain-enforcer.ts +3 -2
- package/src/python/impact-analyzer.ts +7 -6
- package/src/python/import-resolver.ts +3 -2
- package/src/python/migration-indexer.ts +5 -4
- package/src/python/model-indexer.ts +5 -4
- package/src/python/route-indexer.ts +4 -3
- package/src/python-tools.ts +30 -29
- package/src/security/license-pubkey.generated.ts +1 -1
- package/src/security/registry-pubkey.generated.ts +1 -1
- package/src/sentinel-db.ts +47 -46
- package/src/sentinel-scanner.ts +3 -2
- package/src/sentinel-tools.ts +2 -1
- package/src/tools.ts +18 -16
- package/src/trpc-index.ts +5 -4
- package/src/validate-features-runner.ts +5 -4
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
+
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* P-H032 (plan-stage-c-high-batch / 1.10.7): SoT for SQL table names that
|
|
6
|
+
* are prefixed with the customer's configured `toolPrefix`.
|
|
7
|
+
*
|
|
8
|
+
* **Bug class closed**: 20 distinct SQL table names (`massu_imports`,
|
|
9
|
+
* `massu_trpc_procedures`, `massu_sentinel`, etc.) were hardcoded with the
|
|
10
|
+
* `massu_` prefix in ~145 SQL string sites across 21 source files. Tool
|
|
11
|
+
* names were already config-driven via `getConfig().toolPrefix` but
|
|
12
|
+
* persistence wasn't — split-brain where a non-default-prefix customer
|
|
13
|
+
* (e.g. `toolPrefix: 'foo'`) would have tools named `foo_*` but tables
|
|
14
|
+
* still named `massu_*`. Default-prefix customers (100% of current
|
|
15
|
+
* installs) see ZERO behavior change.
|
|
16
|
+
*
|
|
17
|
+
* **Structural fix**: every SQL string that references a prefix-scoped
|
|
18
|
+
* table now goes through `t(suffix)` which returns
|
|
19
|
+
* `\`${getConfig().toolPrefix}_${suffix}\``. The ESLint rule
|
|
20
|
+
* `no-hardcoded-massu-tables` bans bare `'massu_X'` literals so future
|
|
21
|
+
* regressions are impossible. The drift-guard test
|
|
22
|
+
* `sql-table-names-source-grep.test.ts` scans the source tree and asserts
|
|
23
|
+
* zero bare `massu_<table>` literals outside this module + the test files.
|
|
24
|
+
*
|
|
25
|
+
* **20 canonical table suffixes** (registered here as a typed const so
|
|
26
|
+
* typos at callsites are caught at compile time):
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { getConfig } from '../config.ts';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The complete set of suffixes for prefix-scoped SQL tables. Keep this in
|
|
33
|
+
* sync with `packages/core/src/db.ts` (CodeGraph schema) +
|
|
34
|
+
* `sentinel-db.ts` (sentinel schema) + Python indexer DDL in
|
|
35
|
+
* `packages/core/src/python/*.ts`.
|
|
36
|
+
*/
|
|
37
|
+
export type MassuTableSuffix =
|
|
38
|
+
| 'imports'
|
|
39
|
+
| 'meta'
|
|
40
|
+
| 'middleware_tree'
|
|
41
|
+
| 'page_deps'
|
|
42
|
+
| 'py_fk_edges'
|
|
43
|
+
| 'py_imports'
|
|
44
|
+
| 'py_meta'
|
|
45
|
+
| 'py_migrations'
|
|
46
|
+
| 'py_models'
|
|
47
|
+
| 'py_route_callers'
|
|
48
|
+
| 'py_routes'
|
|
49
|
+
| 'sentinel'
|
|
50
|
+
| 'sentinel_changelog'
|
|
51
|
+
| 'sentinel_components'
|
|
52
|
+
| 'sentinel_deps'
|
|
53
|
+
| 'sentinel_fts'
|
|
54
|
+
| 'sentinel_pages'
|
|
55
|
+
| 'sentinel_procedures'
|
|
56
|
+
| 'trpc_call_sites'
|
|
57
|
+
| 'trpc_procedures';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a prefix-scoped table name to its concrete identifier using the
|
|
61
|
+
* current `getConfig().toolPrefix`. For default-prefix installs returns
|
|
62
|
+
* `massu_<suffix>` (current behavior). For custom-prefix installs returns
|
|
63
|
+
* `<customPrefix>_<suffix>`.
|
|
64
|
+
*
|
|
65
|
+
* Use this in EVERY SQL string that references a prefix-scoped table.
|
|
66
|
+
* The ESLint rule bans bare `'massu_X'` literals; the only legitimate
|
|
67
|
+
* exit is this function.
|
|
68
|
+
*/
|
|
69
|
+
export function t(suffix: MassuTableSuffix): string {
|
|
70
|
+
return `${getConfig().toolPrefix}_${suffix}`;
|
|
71
|
+
}
|
package/src/middleware-tree.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type Database from 'better-sqlite3';
|
|
|
5
5
|
import { getConfig } from './config.ts';
|
|
6
6
|
import { existsSync } from 'fs';
|
|
7
7
|
import { resolve } from 'path';
|
|
8
|
+
import { t } from './lib/sql-table-names.ts';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Build the middleware import tree by tracing all transitive imports
|
|
@@ -13,7 +14,7 @@ import { resolve } from 'path';
|
|
|
13
14
|
*/
|
|
14
15
|
export function buildMiddlewareTree(dataDb: Database.Database): number {
|
|
15
16
|
// Clear existing data
|
|
16
|
-
dataDb.exec(
|
|
17
|
+
dataDb.exec(`DELETE FROM ${t('middleware_tree')}`);
|
|
17
18
|
|
|
18
19
|
const config = getConfig();
|
|
19
20
|
const middlewareFile = config.paths.middleware ?? 'src/middleware.ts';
|
|
@@ -30,7 +31,7 @@ export function buildMiddlewareTree(dataDb: Database.Database): number {
|
|
|
30
31
|
const current = queue.shift()!;
|
|
31
32
|
|
|
32
33
|
const imports = dataDb.prepare(
|
|
33
|
-
|
|
34
|
+
`SELECT target_file FROM ${t('imports')} WHERE source_file = ?`
|
|
34
35
|
).all(current) as { target_file: string }[];
|
|
35
36
|
|
|
36
37
|
for (const imp of imports) {
|
|
@@ -42,7 +43,7 @@ export function buildMiddlewareTree(dataDb: Database.Database): number {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// Store the tree
|
|
45
|
-
const insertStmt = dataDb.prepare(
|
|
46
|
+
const insertStmt = dataDb.prepare(`INSERT INTO ${t('middleware_tree')} (file) VALUES (?)`);
|
|
46
47
|
const insertAll = dataDb.transaction(() => {
|
|
47
48
|
for (const file of visited) {
|
|
48
49
|
insertStmt.run(file);
|
|
@@ -57,7 +58,7 @@ export function buildMiddlewareTree(dataDb: Database.Database): number {
|
|
|
57
58
|
* Check if a file is in the middleware import tree.
|
|
58
59
|
*/
|
|
59
60
|
export function isInMiddlewareTree(dataDb: Database.Database, file: string): boolean {
|
|
60
|
-
const result = dataDb.prepare(
|
|
61
|
+
const result = dataDb.prepare(`SELECT 1 FROM ${t('middleware_tree')} WHERE file = ?`).get(file);
|
|
61
62
|
return result !== undefined;
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -65,6 +66,6 @@ export function isInMiddlewareTree(dataDb: Database.Database, file: string): boo
|
|
|
65
66
|
* Get all files in the middleware import tree.
|
|
66
67
|
*/
|
|
67
68
|
export function getMiddlewareTree(dataDb: Database.Database): string[] {
|
|
68
|
-
const rows = dataDb.prepare(
|
|
69
|
+
const rows = dataDb.prepare(`SELECT file FROM ${t('middleware_tree')} ORDER BY file`).all() as { file: string }[];
|
|
69
70
|
return rows.map(r => r.file);
|
|
70
71
|
}
|
package/src/page-deps.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { resolve } from 'path';
|
|
|
6
6
|
import type Database from 'better-sqlite3';
|
|
7
7
|
import { getConfig, getProjectRoot } from './config.ts';
|
|
8
8
|
import { ensureWithinRoot } from './security-utils.ts';
|
|
9
|
+
import { t } from './lib/sql-table-names.ts';
|
|
9
10
|
|
|
10
11
|
export interface PageChain {
|
|
11
12
|
page: string;
|
|
@@ -61,7 +62,7 @@ function traceImports(
|
|
|
61
62
|
visited.add(startFile);
|
|
62
63
|
|
|
63
64
|
const imports = dataDb.prepare(
|
|
64
|
-
|
|
65
|
+
`SELECT target_file, imported_names FROM ${t('imports')} WHERE source_file = ?`
|
|
65
66
|
).all(startFile) as { target_file: string; imported_names: string }[];
|
|
66
67
|
|
|
67
68
|
for (const imp of imports) {
|
|
@@ -117,7 +118,7 @@ function findTablesFromRouters(routerNames: string[], dataDb: Database.Database)
|
|
|
117
118
|
// Look up router files from the tRPC index
|
|
118
119
|
for (const routerName of routerNames) {
|
|
119
120
|
const procs = dataDb.prepare(
|
|
120
|
-
|
|
121
|
+
`SELECT DISTINCT router_file FROM ${t('trpc_procedures')} WHERE router_name = ?`
|
|
121
122
|
).all(routerName) as { router_file: string }[];
|
|
122
123
|
|
|
123
124
|
for (const proc of procs) {
|
|
@@ -150,7 +151,7 @@ function findTablesFromRouters(routerNames: string[], dataDb: Database.Database)
|
|
|
150
151
|
*/
|
|
151
152
|
export function buildPageDeps(dataDb: Database.Database, codegraphDb: Database.Database): number {
|
|
152
153
|
// Clear existing data
|
|
153
|
-
dataDb.exec(
|
|
154
|
+
dataDb.exec(`DELETE FROM ${t('page_deps')}`);
|
|
154
155
|
|
|
155
156
|
// Find all page.tsx files from CodeGraph
|
|
156
157
|
const pages = codegraphDb.prepare(
|
|
@@ -158,7 +159,7 @@ export function buildPageDeps(dataDb: Database.Database, codegraphDb: Database.D
|
|
|
158
159
|
).all() as { path: string }[];
|
|
159
160
|
|
|
160
161
|
const insertStmt = dataDb.prepare(
|
|
161
|
-
|
|
162
|
+
`INSERT INTO ${t('page_deps')} (page_file, route, portal, components, hooks, routers, tables_touched) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
162
163
|
);
|
|
163
164
|
|
|
164
165
|
let count = 0;
|
|
@@ -200,7 +201,7 @@ export function buildPageDeps(dataDb: Database.Database, codegraphDb: Database.D
|
|
|
200
201
|
* Get the dependency chain for a specific page.
|
|
201
202
|
*/
|
|
202
203
|
export function getPageChain(dataDb: Database.Database, pageFile: string): PageChain | null {
|
|
203
|
-
const row = dataDb.prepare(
|
|
204
|
+
const row = dataDb.prepare(`SELECT * FROM ${t('page_deps')} WHERE page_file = ?`).get(pageFile) as {
|
|
204
205
|
page_file: string; route: string; portal: string;
|
|
205
206
|
components: string; hooks: string; routers: string; tables_touched: string;
|
|
206
207
|
} | undefined;
|
|
@@ -229,7 +230,7 @@ export function findAffectedPages(dataDb: Database.Database, file: string): Page
|
|
|
229
230
|
// Find all pages that import this file (directly or transitively)
|
|
230
231
|
// First, find who imports this file
|
|
231
232
|
const importers = dataDb.prepare(
|
|
232
|
-
|
|
233
|
+
`SELECT source_file FROM ${t('imports')} WHERE target_file = ?`
|
|
233
234
|
).all(file) as { source_file: string }[];
|
|
234
235
|
|
|
235
236
|
const affectedFiles = new Set<string>([file, ...importers.map(i => i.source_file)]);
|
|
@@ -244,7 +245,7 @@ export function findAffectedPages(dataDb: Database.Database, file: string): Page
|
|
|
244
245
|
const next: string[] = [];
|
|
245
246
|
for (const f of frontier) {
|
|
246
247
|
const upstreamImporters = dataDb.prepare(
|
|
247
|
-
|
|
248
|
+
`SELECT source_file FROM ${t('imports')} WHERE target_file = ?`
|
|
248
249
|
).all(f) as { source_file: string }[];
|
|
249
250
|
|
|
250
251
|
for (const imp of upstreamImporters) {
|
|
@@ -260,7 +261,7 @@ export function findAffectedPages(dataDb: Database.Database, file: string): Page
|
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
// Now find which pages are in the affected set
|
|
263
|
-
const allPages = dataDb.prepare(
|
|
264
|
+
const allPages = dataDb.prepare(`SELECT * FROM ${t('page_deps')}`).all() as {
|
|
264
265
|
page_file: string; route: string; portal: string;
|
|
265
266
|
components: string; hooks: string; routers: string; tables_touched: string;
|
|
266
267
|
}[];
|
|
@@ -5,6 +5,7 @@ import { readFileSync, readdirSync } from 'fs';
|
|
|
5
5
|
import { join, relative } from 'path';
|
|
6
6
|
import type Database from 'better-sqlite3';
|
|
7
7
|
import { getProjectRoot, getConfig } from '../config.ts';
|
|
8
|
+
import { t } from '../lib/sql-table-names.ts';
|
|
8
9
|
|
|
9
10
|
interface CouplingMatch {
|
|
10
11
|
frontendFile: string;
|
|
@@ -23,20 +24,20 @@ export function buildPythonCouplingIndex(dataDb: Database.Database): number {
|
|
|
23
24
|
const srcDir = join(projectRoot, config.paths.source);
|
|
24
25
|
|
|
25
26
|
// Get all routes from DB
|
|
26
|
-
const routes = dataDb.prepare(
|
|
27
|
+
const routes = dataDb.prepare(`SELECT id, method, path FROM ${t('py_routes')}`).all() as {
|
|
27
28
|
id: number; method: string; path: string;
|
|
28
29
|
}[];
|
|
29
30
|
|
|
30
31
|
if (routes.length === 0) return 0;
|
|
31
32
|
|
|
32
33
|
// Clear existing callers
|
|
33
|
-
dataDb.exec(
|
|
34
|
+
dataDb.exec(`DELETE FROM ${t('py_route_callers')}`);
|
|
34
35
|
|
|
35
36
|
// Walk frontend files (TS/TSX/JS/JSX)
|
|
36
37
|
const frontendFiles = walkFrontendFiles(srcDir);
|
|
37
38
|
|
|
38
39
|
const insertStmt = dataDb.prepare(
|
|
39
|
-
|
|
40
|
+
`INSERT INTO ${t('py_route_callers')} (route_id, frontend_file, line, call_pattern) VALUES (?, ?, ?, ?)`
|
|
40
41
|
);
|
|
41
42
|
|
|
42
43
|
let count = 0;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import type Database from 'better-sqlite3';
|
|
5
5
|
import { getConfig } from '../config.ts';
|
|
6
|
+
import { t } from '../lib/sql-table-names.ts';
|
|
6
7
|
|
|
7
8
|
export interface DomainViolation {
|
|
8
9
|
sourceFile: string;
|
|
@@ -44,7 +45,7 @@ export function findPythonDomainViolations(dataDb: Database.Database): DomainVio
|
|
|
44
45
|
if (domains.length === 0) return [];
|
|
45
46
|
|
|
46
47
|
const imports = dataDb.prepare(
|
|
47
|
-
|
|
48
|
+
`SELECT source_file, target_file FROM ${t('py_imports')}`
|
|
48
49
|
).all() as { source_file: string; target_file: string }[];
|
|
49
50
|
|
|
50
51
|
const violations: DomainViolation[] = [];
|
|
@@ -74,7 +75,7 @@ export function findPythonDomainViolations(dataDb: Database.Database): DomainVio
|
|
|
74
75
|
*/
|
|
75
76
|
export function getPythonFilesInDomain(dataDb: Database.Database, domainName: string): string[] {
|
|
76
77
|
const allFiles = dataDb.prepare(
|
|
77
|
-
|
|
78
|
+
`SELECT DISTINCT source_file as f FROM ${t('py_imports')} UNION SELECT DISTINCT target_file as f FROM ${t('py_imports')}`
|
|
78
79
|
).all() as { f: string }[];
|
|
79
80
|
|
|
80
81
|
return allFiles
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import type Database from 'better-sqlite3';
|
|
5
5
|
import { classifyPythonFileDomain } from './domain-enforcer.ts';
|
|
6
|
+
import { t } from '../lib/sql-table-names.ts';
|
|
6
7
|
|
|
7
8
|
export interface PythonImpactReport {
|
|
8
9
|
file: string;
|
|
@@ -26,28 +27,28 @@ export function analyzePythonImpact(dataDb: Database.Database, file: string): Py
|
|
|
26
27
|
|
|
27
28
|
// 2. Routes defined in this file
|
|
28
29
|
const routes = dataDb.prepare(
|
|
29
|
-
|
|
30
|
+
`SELECT method, path, function_name FROM ${t('py_routes')} WHERE file = ?`
|
|
30
31
|
).all(file) as { method: string; path: string; function_name: string }[];
|
|
31
32
|
|
|
32
33
|
// 3. Models defined in this file
|
|
33
34
|
const models = dataDb.prepare(
|
|
34
|
-
|
|
35
|
+
`SELECT class_name, table_name FROM ${t('py_models')} WHERE file = ?`
|
|
35
36
|
).all(file) as { class_name: string; table_name: string | null }[];
|
|
36
37
|
|
|
37
38
|
// 4. Frontend callers (via routes in this file)
|
|
38
|
-
const routeIds = dataDb.prepare(
|
|
39
|
+
const routeIds = dataDb.prepare(`SELECT id FROM ${t('py_routes')} WHERE file = ?`).all(file) as { id: number }[];
|
|
39
40
|
const frontendCallers: string[] = [];
|
|
40
41
|
if (routeIds.length > 0) {
|
|
41
42
|
const placeholders = routeIds.map(() => '?').join(',');
|
|
42
43
|
const callers = dataDb.prepare(
|
|
43
|
-
`SELECT DISTINCT frontend_file FROM
|
|
44
|
+
`SELECT DISTINCT frontend_file FROM ${t('py_route_callers')} WHERE route_id IN (${placeholders})`
|
|
44
45
|
).all(...routeIds.map(r => r.id)) as { frontend_file: string }[];
|
|
45
46
|
frontendCallers.push(...callers.map(c => c.frontend_file));
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// 5. Domain crossings
|
|
49
50
|
const imports = dataDb.prepare(
|
|
50
|
-
|
|
51
|
+
`SELECT target_file FROM ${t('py_imports')} WHERE source_file = ?`
|
|
51
52
|
).all(file) as { target_file: string }[];
|
|
52
53
|
|
|
53
54
|
const domainCrossings = imports
|
|
@@ -71,7 +72,7 @@ function collectTransitiveImporters(dataDb: Database.Database, file: string, max
|
|
|
71
72
|
let depth = 0;
|
|
72
73
|
|
|
73
74
|
const importerStmt = dataDb.prepare(
|
|
74
|
-
|
|
75
|
+
`SELECT source_file FROM ${t('py_imports')} WHERE target_file = ?`
|
|
75
76
|
);
|
|
76
77
|
|
|
77
78
|
while (queue.length > 0 && depth < maxDepth) {
|
|
@@ -6,6 +6,7 @@ import { resolve, join, relative, dirname } from 'path';
|
|
|
6
6
|
import type Database from 'better-sqlite3';
|
|
7
7
|
import { parsePythonImports } from './import-parser.ts';
|
|
8
8
|
import { getProjectRoot } from '../config.ts';
|
|
9
|
+
import { t } from '../lib/sql-table-names.ts';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Resolve a Python module path to a file path.
|
|
@@ -82,10 +83,10 @@ export function buildPythonImportIndex(dataDb: Database.Database, pythonRoot: st
|
|
|
82
83
|
const absRoot = resolve(projectRoot, pythonRoot);
|
|
83
84
|
|
|
84
85
|
// Clear existing Python import edges
|
|
85
|
-
dataDb.exec(
|
|
86
|
+
dataDb.exec(`DELETE FROM ${t('py_imports')}`);
|
|
86
87
|
|
|
87
88
|
const insertStmt = dataDb.prepare(
|
|
88
|
-
|
|
89
|
+
`INSERT INTO ${t('py_imports')} (source_file, target_file, import_type, imported_names, line) VALUES (?, ?, ?, ?, ?)`
|
|
89
90
|
);
|
|
90
91
|
|
|
91
92
|
const files = walkPythonFiles(absRoot, excludeDirs);
|
|
@@ -6,11 +6,12 @@ import { join, relative } from 'path';
|
|
|
6
6
|
import type Database from 'better-sqlite3';
|
|
7
7
|
import { parseAlembicMigration } from './migration-parser.ts';
|
|
8
8
|
import { getProjectRoot } from '../config.ts';
|
|
9
|
+
import { t } from '../lib/sql-table-names.ts';
|
|
9
10
|
|
|
10
11
|
export function buildPythonMigrationIndex(dataDb: Database.Database, alembicDir: string): number {
|
|
11
12
|
const projectRoot = getProjectRoot();
|
|
12
13
|
const absDir = join(projectRoot, alembicDir);
|
|
13
|
-
dataDb.exec(
|
|
14
|
+
dataDb.exec(`DELETE FROM ${t('py_migrations')}`);
|
|
14
15
|
|
|
15
16
|
// Look for version files in versions/ subdirectory
|
|
16
17
|
const versionsDir = join(absDir, 'versions');
|
|
@@ -28,7 +29,7 @@ export function buildPythonMigrationIndex(dataDb: Database.Database, alembicDir:
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
const insertStmt = dataDb.prepare(
|
|
31
|
-
|
|
32
|
+
`INSERT INTO ${t('py_migrations')} (revision, down_revision, file, description, operations, is_head) VALUES (?, ?, ?, ?, ?, ?)`
|
|
32
33
|
);
|
|
33
34
|
|
|
34
35
|
let count = 0;
|
|
@@ -80,11 +81,11 @@ export interface DriftReport {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
export function detectMigrationDrift(dataDb: Database.Database): DriftReport {
|
|
83
|
-
const models = dataDb.prepare(
|
|
84
|
+
const models = dataDb.prepare(`SELECT class_name, table_name, columns FROM ${t('py_models')} WHERE table_name IS NOT NULL`).all() as {
|
|
84
85
|
class_name: string; table_name: string; columns: string;
|
|
85
86
|
}[];
|
|
86
87
|
|
|
87
|
-
const migrations = dataDb.prepare(
|
|
88
|
+
const migrations = dataDb.prepare(`SELECT operations FROM ${t('py_migrations')}`).all() as { operations: string }[];
|
|
88
89
|
|
|
89
90
|
// Collect all tables and columns mentioned in migrations
|
|
90
91
|
const migratedTables = new Set<string>();
|
|
@@ -6,6 +6,7 @@ import { join, relative } from 'path';
|
|
|
6
6
|
import type Database from 'better-sqlite3';
|
|
7
7
|
import { parsePythonModels } from './model-parser.ts';
|
|
8
8
|
import { getProjectRoot } from '../config.ts';
|
|
9
|
+
import { t } from '../lib/sql-table-names.ts';
|
|
9
10
|
|
|
10
11
|
function walkPyFiles(dir: string, excludeDirs: string[]): string[] {
|
|
11
12
|
const files: string[] = [];
|
|
@@ -26,14 +27,14 @@ function walkPyFiles(dir: string, excludeDirs: string[]): string[] {
|
|
|
26
27
|
export function buildPythonModelIndex(dataDb: Database.Database, pythonRoot: string, excludeDirs: string[] = ['__pycache__', '.venv', 'venv', '.mypy_cache', '.pytest_cache']): number {
|
|
27
28
|
const projectRoot = getProjectRoot();
|
|
28
29
|
const absRoot = join(projectRoot, pythonRoot);
|
|
29
|
-
dataDb.exec(
|
|
30
|
-
dataDb.exec(
|
|
30
|
+
dataDb.exec(`DELETE FROM ${t('py_models')}`);
|
|
31
|
+
dataDb.exec(`DELETE FROM ${t('py_fk_edges')}`);
|
|
31
32
|
|
|
32
33
|
const insertModel = dataDb.prepare(
|
|
33
|
-
|
|
34
|
+
`INSERT INTO ${t('py_models')} (class_name, table_name, file, line, columns, relationships, foreign_keys) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
34
35
|
);
|
|
35
36
|
const insertFk = dataDb.prepare(
|
|
36
|
-
|
|
37
|
+
`INSERT INTO ${t('py_fk_edges')} (source_table, source_column, target_table, target_column) VALUES (?, ?, ?, ?)`
|
|
37
38
|
);
|
|
38
39
|
|
|
39
40
|
const files = walkPyFiles(absRoot, excludeDirs);
|
|
@@ -6,6 +6,7 @@ import { join, relative } from 'path';
|
|
|
6
6
|
import type Database from 'better-sqlite3';
|
|
7
7
|
import { parsePythonRoutes } from './route-parser.ts';
|
|
8
8
|
import { getProjectRoot } from '../config.ts';
|
|
9
|
+
import { t } from '../lib/sql-table-names.ts';
|
|
9
10
|
|
|
10
11
|
function walkPyFiles(dir: string, excludeDirs: string[]): string[] {
|
|
11
12
|
const files: string[] = [];
|
|
@@ -26,11 +27,11 @@ function walkPyFiles(dir: string, excludeDirs: string[]): string[] {
|
|
|
26
27
|
export function buildPythonRouteIndex(dataDb: Database.Database, pythonRoot: string, excludeDirs: string[] = ['__pycache__', '.venv', 'venv', '.mypy_cache', '.pytest_cache']): number {
|
|
27
28
|
const projectRoot = getProjectRoot();
|
|
28
29
|
const absRoot = join(projectRoot, pythonRoot);
|
|
29
|
-
dataDb.exec(
|
|
30
|
-
dataDb.exec(
|
|
30
|
+
dataDb.exec(`DELETE FROM ${t('py_routes')}`);
|
|
31
|
+
dataDb.exec(`DELETE FROM ${t('py_route_callers')}`);
|
|
31
32
|
|
|
32
33
|
const insertStmt = dataDb.prepare(
|
|
33
|
-
|
|
34
|
+
`INSERT INTO ${t('py_routes')} (file, method, path, function_name, dependencies, request_model, response_model, is_authenticated, line) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
34
35
|
);
|
|
35
36
|
|
|
36
37
|
const files = walkPyFiles(absRoot, excludeDirs);
|