@vibgrate/cli 2026.711.2 → 2026.715.1

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.
@@ -1,10 +1,11 @@
1
- import { runCoreScan, writeJsonFile, compactUiPurpose } from './chunk-NNU2PW2H.js';
1
+ import { runCoreScan, writeJsonFile, compactUiPurpose } from './chunk-C5AVF7PT.js';
2
2
  import { findPackageJsonFiles, readJsonFile, findFiles, readTextFile, pathExists } from './chunk-GI6W53LM.js';
3
3
  import * as path3 from 'path';
4
4
  import { Command } from 'commander';
5
5
  import chalk from 'chalk';
6
6
  import * as semver from 'semver';
7
7
  import * as fs from 'fs/promises';
8
+ import ts2 from 'typescript';
8
9
 
9
10
  var NATIVE_MODULE_PACKAGES = /* @__PURE__ */ new Set([
10
11
  // Image / media processing
@@ -2846,6 +2847,612 @@ function visitEach(node, cb) {
2846
2847
  node.forEachChild(cb);
2847
2848
  }
2848
2849
 
2850
+ // src/reporting/scanners/database-schema-sql.ts
2851
+ var CREDENTIAL_URL_RE = /:\/\/[^/\s'"]*:[^/\s'"]*@/;
2852
+ function stripCredentialLines(sql) {
2853
+ return sql.split("\n").map((line) => CREDENTIAL_URL_RE.test(line) ? "" : line).join("\n");
2854
+ }
2855
+ var TABLE_LEVEL_KEYWORDS = /* @__PURE__ */ new Set(["PRIMARY", "FOREIGN", "CONSTRAINT", "UNIQUE", "CHECK", "INDEX", "KEY"]);
2856
+ var CONSTRAINT_KEYWORDS = /* @__PURE__ */ new Set([
2857
+ "NOT",
2858
+ "NULL",
2859
+ "DEFAULT",
2860
+ "PRIMARY",
2861
+ "UNIQUE",
2862
+ "REFERENCES",
2863
+ "CHECK",
2864
+ "CONSTRAINT",
2865
+ "GENERATED",
2866
+ "IDENTITY",
2867
+ "AUTO_INCREMENT",
2868
+ "AUTOINCREMENT",
2869
+ "COLLATE",
2870
+ "ON",
2871
+ "COMMENT",
2872
+ "ENCODE",
2873
+ "DISTKEY",
2874
+ "SORTKEY",
2875
+ "WITH"
2876
+ ]);
2877
+ function cleanIdentifier(raw) {
2878
+ const parts = raw.split(".");
2879
+ const last = (parts[parts.length - 1] ?? raw).trim();
2880
+ return last.replace(/^[["'`]+/, "").replace(/[\]"'`;]+$/, "").trim();
2881
+ }
2882
+ function extractBalanced(sql, openParenIdx) {
2883
+ let depth = 0;
2884
+ for (let i = openParenIdx; i < sql.length; i++) {
2885
+ if (sql[i] === "(") depth++;
2886
+ else if (sql[i] === ")") {
2887
+ depth--;
2888
+ if (depth === 0) return sql.slice(openParenIdx + 1, i);
2889
+ }
2890
+ }
2891
+ return null;
2892
+ }
2893
+ function splitTopLevel(body) {
2894
+ const parts = [];
2895
+ let depth = 0;
2896
+ let current = "";
2897
+ for (const ch of body) {
2898
+ if (ch === "(") depth++;
2899
+ if (ch === ")") depth--;
2900
+ if (ch === "," && depth === 0) {
2901
+ parts.push(current);
2902
+ current = "";
2903
+ } else {
2904
+ current += ch;
2905
+ }
2906
+ }
2907
+ if (current.trim()) parts.push(current);
2908
+ return parts.map((s) => s.trim()).filter(Boolean);
2909
+ }
2910
+ function tokenize(def) {
2911
+ const tokens = [];
2912
+ const re = /[^\s(]+\([^)]*\)|[^\s]+/g;
2913
+ let m;
2914
+ while ((m = re.exec(def)) !== null) tokens.push(m[0]);
2915
+ return tokens;
2916
+ }
2917
+ function containsSeq(tokens, seq) {
2918
+ for (let i = 0; i + seq.length <= tokens.length; i++) {
2919
+ if (seq.every((s, j) => tokens[i + j] === s)) return true;
2920
+ }
2921
+ return false;
2922
+ }
2923
+ function parseColumnDef(def) {
2924
+ const tokens = tokenize(def);
2925
+ if (tokens.length < 2) return null;
2926
+ const first = tokens[0].toUpperCase();
2927
+ if (TABLE_LEVEL_KEYWORDS.has(first)) return null;
2928
+ const name = cleanIdentifier(tokens[0]);
2929
+ if (!name) return null;
2930
+ const rest = tokens.slice(1);
2931
+ const typeTokens = [];
2932
+ for (const t of rest) {
2933
+ if (CONSTRAINT_KEYWORDS.has(t.toUpperCase())) break;
2934
+ typeTokens.push(t);
2935
+ }
2936
+ const type = typeTokens.join(" ").trim();
2937
+ if (!type) return null;
2938
+ const upperRest = rest.map((t) => t.toUpperCase());
2939
+ const isPrimaryKey = containsSeq(upperRest, ["PRIMARY", "KEY"]);
2940
+ const isNotNull = containsSeq(upperRest, ["NOT", "NULL"]);
2941
+ const isUnique = upperRest.includes("UNIQUE");
2942
+ const isReference = upperRest.includes("REFERENCES");
2943
+ return {
2944
+ name,
2945
+ type,
2946
+ isList: false,
2947
+ isOptional: !isNotNull && !isPrimaryKey,
2948
+ isRelation: isReference,
2949
+ isId: isPrimaryKey,
2950
+ isUnique
2951
+ };
2952
+ }
2953
+ function parseCreateTableStatements(rawSql) {
2954
+ const sql = stripCredentialLines(rawSql);
2955
+ const results = [];
2956
+ const seen = /* @__PURE__ */ new Set();
2957
+ const re = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([A-Za-z0-9_."[\]`]+)\s*\(/gi;
2958
+ let m;
2959
+ while ((m = re.exec(sql)) !== null) {
2960
+ const openParenIdx = re.lastIndex - 1;
2961
+ const body = extractBalanced(sql, openParenIdx);
2962
+ if (body === null) continue;
2963
+ re.lastIndex = openParenIdx + body.length + 2;
2964
+ const name = cleanIdentifier(m[1]);
2965
+ if (!name || seen.has(name)) continue;
2966
+ seen.add(name);
2967
+ const fields = [];
2968
+ const fieldNames = /* @__PURE__ */ new Set();
2969
+ for (const part of splitTopLevel(body)) {
2970
+ const field = parseColumnDef(part);
2971
+ if (field && !fieldNames.has(field.name)) {
2972
+ fields.push(field);
2973
+ fieldNames.add(field.name);
2974
+ }
2975
+ }
2976
+ results.push({ name, fields });
2977
+ }
2978
+ return results;
2979
+ }
2980
+ var DRIZZLE_TABLE_FUNCS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
2981
+ function collectDrizzleTableImportNames(sf) {
2982
+ const names = /* @__PURE__ */ new Set();
2983
+ for (const stmt of sf.statements) {
2984
+ if (!ts2.isImportDeclaration(stmt)) continue;
2985
+ const spec = stmt.moduleSpecifier;
2986
+ if (!ts2.isStringLiteral(spec) || !spec.text.startsWith("drizzle-orm")) continue;
2987
+ const clause = stmt.importClause;
2988
+ if (!clause?.namedBindings || !ts2.isNamedImports(clause.namedBindings)) continue;
2989
+ for (const el of clause.namedBindings.elements) {
2990
+ const imported = (el.propertyName ?? el.name).text;
2991
+ if (DRIZZLE_TABLE_FUNCS.has(imported)) names.add(el.name.text);
2992
+ }
2993
+ }
2994
+ return names;
2995
+ }
2996
+ function propertyKeyName(name) {
2997
+ if (ts2.isIdentifier(name) || ts2.isStringLiteral(name)) return name.text;
2998
+ return null;
2999
+ }
3000
+ function parseColumnBuilderExpr(colName, expr) {
3001
+ let node = expr;
3002
+ let builderName = null;
3003
+ let isPrimaryKey = false;
3004
+ let isNotNull = false;
3005
+ let isUnique = false;
3006
+ let isReference = false;
3007
+ while (ts2.isCallExpression(node)) {
3008
+ const callee = node.expression;
3009
+ if (ts2.isIdentifier(callee)) {
3010
+ builderName = callee.text;
3011
+ break;
3012
+ }
3013
+ if (ts2.isPropertyAccessExpression(callee)) {
3014
+ switch (callee.name.text) {
3015
+ case "primaryKey":
3016
+ isPrimaryKey = true;
3017
+ break;
3018
+ case "notNull":
3019
+ isNotNull = true;
3020
+ break;
3021
+ case "unique":
3022
+ isUnique = true;
3023
+ break;
3024
+ case "references":
3025
+ isReference = true;
3026
+ break;
3027
+ }
3028
+ node = callee.expression;
3029
+ continue;
3030
+ }
3031
+ break;
3032
+ }
3033
+ if (!builderName) return null;
3034
+ return {
3035
+ name: colName,
3036
+ type: builderName,
3037
+ isList: false,
3038
+ isOptional: !isNotNull && !isPrimaryKey,
3039
+ isRelation: isReference,
3040
+ isId: isPrimaryKey,
3041
+ isUnique
3042
+ };
3043
+ }
3044
+ function parseDrizzleTableCall(call) {
3045
+ const [nameArg, columnsArg] = call.arguments;
3046
+ if (!nameArg || !ts2.isStringLiteral(nameArg)) return null;
3047
+ const fields = [];
3048
+ if (columnsArg && ts2.isObjectLiteralExpression(columnsArg)) {
3049
+ for (const prop of columnsArg.properties) {
3050
+ if (!ts2.isPropertyAssignment(prop)) continue;
3051
+ const colName = propertyKeyName(prop.name);
3052
+ if (!colName) continue;
3053
+ const field = parseColumnBuilderExpr(colName, prop.initializer);
3054
+ if (field) fields.push(field);
3055
+ }
3056
+ }
3057
+ return { name: nameArg.text, fields };
3058
+ }
3059
+ function parseDrizzleFile(sourceText, fileName) {
3060
+ const sf = ts2.createSourceFile(fileName, sourceText, ts2.ScriptTarget.ES2022, true, ts2.ScriptKind.TSX);
3061
+ const tableImportNames = collectDrizzleTableImportNames(sf);
3062
+ if (tableImportNames.size === 0) return [];
3063
+ const tables = [];
3064
+ const visit = (node) => {
3065
+ if (ts2.isCallExpression(node) && ts2.isIdentifier(node.expression) && tableImportNames.has(node.expression.text)) {
3066
+ const table = parseDrizzleTableCall(node);
3067
+ if (table) tables.push(table);
3068
+ }
3069
+ ts2.forEachChild(node, visit);
3070
+ };
3071
+ visit(sf);
3072
+ return tables;
3073
+ }
3074
+ var COLUMN_DECORATORS = /* @__PURE__ */ new Set(["Column", "PrimaryColumn", "PrimaryGeneratedColumn"]);
3075
+ var RELATION_DECORATORS = /* @__PURE__ */ new Set(["OneToMany", "ManyToOne", "ManyToMany", "OneToOne", "JoinColumn"]);
3076
+ var MANY_RELATION_DECORATORS = /* @__PURE__ */ new Set(["OneToMany", "ManyToMany"]);
3077
+ var ID_DECORATORS = /* @__PURE__ */ new Set(["PrimaryColumn", "PrimaryGeneratedColumn"]);
3078
+ function decoratorInfo(dec) {
3079
+ const expr = dec.expression;
3080
+ if (ts2.isCallExpression(expr) && ts2.isIdentifier(expr.expression)) {
3081
+ return { name: expr.expression.text, args: expr.arguments };
3082
+ }
3083
+ if (ts2.isIdentifier(expr)) return { name: expr.text, args: [] };
3084
+ return null;
3085
+ }
3086
+ function decoratorsOf(node) {
3087
+ const decs = ts2.getDecorators(node) ?? [];
3088
+ const out = [];
3089
+ for (const d of decs) {
3090
+ const info = decoratorInfo(d);
3091
+ if (info) out.push(info);
3092
+ }
3093
+ return out;
3094
+ }
3095
+ function stringOrNamedArg(args, propName) {
3096
+ const first = args[0];
3097
+ if (!first) return null;
3098
+ if (ts2.isStringLiteral(first)) return first.text;
3099
+ if (ts2.isObjectLiteralExpression(first)) {
3100
+ for (const prop of first.properties) {
3101
+ if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name) && prop.name.text === propName && ts2.isStringLiteral(prop.initializer)) {
3102
+ return prop.initializer.text;
3103
+ }
3104
+ }
3105
+ }
3106
+ return null;
3107
+ }
3108
+ function parseEntity(cls, entityDec) {
3109
+ const tableName = stringOrNamedArg(entityDec.args, "name") ?? cls.name.text;
3110
+ const fields = [];
3111
+ for (const member of cls.members) {
3112
+ if (!ts2.isPropertyDeclaration(member) || !ts2.isIdentifier(member.name)) continue;
3113
+ const decNames = new Set(decoratorsOf(member).map((d) => d.name));
3114
+ const isColumn = [...decNames].some((n) => COLUMN_DECORATORS.has(n));
3115
+ const isRelation = [...decNames].some((n) => RELATION_DECORATORS.has(n));
3116
+ if (!isColumn && !isRelation) continue;
3117
+ fields.push({
3118
+ name: member.name.text,
3119
+ type: member.type ? member.type.getText() : "unknown",
3120
+ isList: [...decNames].some((n) => MANY_RELATION_DECORATORS.has(n)),
3121
+ isOptional: member.questionToken !== void 0,
3122
+ isRelation,
3123
+ isId: [...decNames].some((n) => ID_DECORATORS.has(n)),
3124
+ isUnique: false
3125
+ });
3126
+ }
3127
+ return { name: tableName, fields: fields.sort((a, b) => a.name.localeCompare(b.name)) };
3128
+ }
3129
+ function parseTypeOrmFile(sourceText, fileName) {
3130
+ const sf = ts2.createSourceFile(fileName, sourceText, ts2.ScriptTarget.ES2022, true, ts2.ScriptKind.TS);
3131
+ const entities = [];
3132
+ const visit = (node) => {
3133
+ if (ts2.isClassDeclaration(node) && node.name) {
3134
+ const entityDec = decoratorsOf(node).find((d) => d.name === "Entity");
3135
+ if (entityDec) entities.push(parseEntity(node, entityDec));
3136
+ }
3137
+ ts2.forEachChild(node, visit);
3138
+ };
3139
+ visit(sf);
3140
+ return entities;
3141
+ }
3142
+
3143
+ // src/reporting/scanners/database-schema.ts
3144
+ var SCALAR_TYPES = /* @__PURE__ */ new Set([
3145
+ "String",
3146
+ "Int",
3147
+ "Float",
3148
+ "Boolean",
3149
+ "DateTime",
3150
+ "Json",
3151
+ "Bytes",
3152
+ "Decimal",
3153
+ "BigInt"
3154
+ ]);
3155
+ async function findPrismaFiles(rootDir, cache) {
3156
+ if (cache) {
3157
+ const entries = await cache.walkDir(rootDir);
3158
+ return entries.filter((e) => e.isFile && e.name.endsWith(".prisma")).map((e) => e.absPath);
3159
+ }
3160
+ return findFiles(rootDir, (name) => name.endsWith(".prisma"));
3161
+ }
3162
+ function normalizeRel(relPath) {
3163
+ return relPath.replace(/\\/g, "/");
3164
+ }
3165
+ function extractBlocks(raw, keyword) {
3166
+ const re = new RegExp(`${keyword}\\s+(\\w+)\\s*\\{([^}]*)\\}`, "gs");
3167
+ const blocks = [];
3168
+ let match;
3169
+ while ((match = re.exec(raw)) !== null) {
3170
+ blocks.push({ name: match[1], body: match[2] });
3171
+ }
3172
+ return blocks;
3173
+ }
3174
+ function extractDatasourceProviders(raw) {
3175
+ const providers = [];
3176
+ for (const block of extractBlocks(raw, "datasource")) {
3177
+ const m = block.body.match(/(?:^|\n)\s*provider\s*=\s*"([^"]+)"/);
3178
+ if (m) providers.push(m[1]);
3179
+ }
3180
+ return providers;
3181
+ }
3182
+ function extractModels(raw) {
3183
+ return extractBlocks(raw, "model").map((block) => ({
3184
+ name: block.name,
3185
+ fields: parseFieldLines(block.body)
3186
+ }));
3187
+ }
3188
+ function extractEnums(raw) {
3189
+ return extractBlocks(raw, "enum").map((block) => {
3190
+ const values = [];
3191
+ for (const line of block.body.split("\n")) {
3192
+ const trimmed = line.trim();
3193
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("@@")) continue;
3194
+ const token = trimmed.split(/\s+/)[0];
3195
+ if (token) values.push(token);
3196
+ }
3197
+ return { name: block.name, values: [...new Set(values)].sort() };
3198
+ });
3199
+ }
3200
+ function parseFieldLines(body) {
3201
+ const fields = [];
3202
+ for (const rawLine of body.split("\n")) {
3203
+ const trimmed = rawLine.trim();
3204
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("@@") || trimmed.startsWith("/*")) continue;
3205
+ const tokens = trimmed.split(/\s+/);
3206
+ if (tokens.length < 2) continue;
3207
+ const [name, rawType] = tokens;
3208
+ if (!/^\w+$/.test(name)) continue;
3209
+ const attrs = [...trimmed.matchAll(/@(\w+)/g)].map((m) => m[1]);
3210
+ fields.push({ name, rawType, attrs });
3211
+ }
3212
+ return fields;
3213
+ }
3214
+ function finalizeField(field, modelNames) {
3215
+ const isList = field.rawType.includes("[]");
3216
+ const isOptional = field.rawType.includes("?");
3217
+ const baseType = field.rawType.replace(/\[\]/g, "").replace(/\?/g, "");
3218
+ const isRelation = field.attrs.includes("relation") || !SCALAR_TYPES.has(baseType) && modelNames.has(baseType);
3219
+ return {
3220
+ name: field.name,
3221
+ type: baseType,
3222
+ isList,
3223
+ isOptional,
3224
+ isRelation,
3225
+ isId: field.attrs.includes("id"),
3226
+ isUnique: field.attrs.includes("unique")
3227
+ };
3228
+ }
3229
+ function findOwningProject(fileRel, projects) {
3230
+ let best;
3231
+ let bestLen = -1;
3232
+ for (const project of projects) {
3233
+ const projPath = project.path === "." ? "" : normalizeRel(project.path).replace(/\/$/, "");
3234
+ const matches = projPath === "" || fileRel === projPath || fileRel.startsWith(`${projPath}/`);
3235
+ if (matches && projPath.length > bestLen) {
3236
+ best = project;
3237
+ bestLen = projPath.length;
3238
+ }
3239
+ }
3240
+ return best;
3241
+ }
3242
+ function buildProjectBreakdown(projects, filesScanned, modelNamesByFile, enumNamesByFile) {
3243
+ const byProject = /* @__PURE__ */ new Map();
3244
+ for (const fileRel of filesScanned) {
3245
+ const owner = findOwningProject(fileRel, projects);
3246
+ const key = owner?.path ?? ".";
3247
+ if (!byProject.has(key)) byProject.set(key, { files: /* @__PURE__ */ new Set(), models: /* @__PURE__ */ new Set(), enums: /* @__PURE__ */ new Set() });
3248
+ const bucket = byProject.get(key);
3249
+ bucket.files.add(fileRel);
3250
+ for (const name of modelNamesByFile.get(fileRel) ?? []) bucket.models.add(name);
3251
+ for (const name of enumNamesByFile.get(fileRel) ?? []) bucket.enums.add(name);
3252
+ }
3253
+ return [...byProject.entries()].map(([project, bucket]) => ({
3254
+ project,
3255
+ filesScanned: [...bucket.files].sort(),
3256
+ models: [...bucket.models].sort(),
3257
+ enums: [...bucket.enums].sort()
3258
+ })).sort((a, b) => a.project.localeCompare(b.project));
3259
+ }
3260
+ async function scanPrismaSource(rootDir, cache) {
3261
+ const filePaths = await findPrismaFiles(rootDir, cache);
3262
+ const providers = /* @__PURE__ */ new Set();
3263
+ const modelsByFile = /* @__PURE__ */ new Map();
3264
+ const enumsByFile = /* @__PURE__ */ new Map();
3265
+ const filesScanned = [];
3266
+ for (const filePath of filePaths) {
3267
+ let raw = "";
3268
+ try {
3269
+ raw = cache ? await cache.readTextFile(filePath) : await readTextFile(filePath);
3270
+ } catch {
3271
+ continue;
3272
+ }
3273
+ if (!raw.trim()) continue;
3274
+ const rel = normalizeRel(path3.relative(rootDir, filePath));
3275
+ filesScanned.push(rel);
3276
+ for (const provider of extractDatasourceProviders(raw)) providers.add(provider);
3277
+ modelsByFile.set(rel, extractModels(raw));
3278
+ enumsByFile.set(rel, extractEnums(raw));
3279
+ }
3280
+ const modelNameSet = /* @__PURE__ */ new Set();
3281
+ for (const models2 of modelsByFile.values()) {
3282
+ for (const m of models2) modelNameSet.add(m.name);
3283
+ }
3284
+ const modelMap = /* @__PURE__ */ new Map();
3285
+ for (const [fileRel, rawModels] of modelsByFile.entries()) {
3286
+ for (const rawModel of rawModels) {
3287
+ const fields = rawModel.fields.map((f) => finalizeField(f, modelNameSet));
3288
+ const existing = modelMap.get(rawModel.name);
3289
+ if (!existing) {
3290
+ modelMap.set(rawModel.name, { name: rawModel.name, fields, files: /* @__PURE__ */ new Set([fileRel]) });
3291
+ continue;
3292
+ }
3293
+ existing.files.add(fileRel);
3294
+ const seen = new Set(existing.fields.map((f) => f.name));
3295
+ for (const field of fields) {
3296
+ if (!seen.has(field.name)) {
3297
+ existing.fields.push(field);
3298
+ seen.add(field.name);
3299
+ }
3300
+ }
3301
+ }
3302
+ }
3303
+ const models = [...modelMap.values()].map((m) => ({
3304
+ name: m.name,
3305
+ fields: [...m.fields].sort((a, b) => a.name.localeCompare(b.name)),
3306
+ source: "prisma",
3307
+ files: [...m.files].sort()
3308
+ })).sort((a, b) => a.name.localeCompare(b.name));
3309
+ const enumMap = /* @__PURE__ */ new Map();
3310
+ for (const enums2 of enumsByFile.values()) {
3311
+ for (const e of enums2) {
3312
+ if (!enumMap.has(e.name)) enumMap.set(e.name, e);
3313
+ }
3314
+ }
3315
+ const enums = [...enumMap.values()].map((e) => ({ name: e.name, values: [...e.values].sort() })).sort((a, b) => a.name.localeCompare(b.name));
3316
+ const enumNamesByFile = /* @__PURE__ */ new Map();
3317
+ for (const [fileRel, es] of enumsByFile.entries()) {
3318
+ enumNamesByFile.set(fileRel, new Set(es.map((e) => e.name)));
3319
+ }
3320
+ return { models, enums, providers: [...providers].sort(), filesScanned: filesScanned.sort(), enumNamesByFile };
3321
+ }
3322
+ function isSqlFile(name) {
3323
+ return name.endsWith(".sql");
3324
+ }
3325
+ async function readFileSafely(filePath, cache) {
3326
+ try {
3327
+ return cache ? await cache.readTextFile(filePath) : await readTextFile(filePath);
3328
+ } catch {
3329
+ return null;
3330
+ }
3331
+ }
3332
+ async function scanSqlSource(rootDir, filePaths, source, cache) {
3333
+ const sortedPaths = [...filePaths].sort((a, b) => a.localeCompare(b));
3334
+ const seenTables = /* @__PURE__ */ new Set();
3335
+ const models = [];
3336
+ const filesScanned = [];
3337
+ for (const filePath of sortedPaths) {
3338
+ const raw = await readFileSafely(filePath, cache);
3339
+ if (raw == null || !raw.trim()) continue;
3340
+ const tables = parseCreateTableStatements(raw);
3341
+ if (tables.length === 0) continue;
3342
+ const rel = normalizeRel(path3.relative(rootDir, filePath));
3343
+ filesScanned.push(rel);
3344
+ for (const t of tables) {
3345
+ if (seenTables.has(t.name)) continue;
3346
+ seenTables.add(t.name);
3347
+ models.push({
3348
+ name: t.name,
3349
+ fields: [...t.fields].sort((a, b) => a.name.localeCompare(b.name)),
3350
+ source,
3351
+ files: [rel]
3352
+ });
3353
+ }
3354
+ }
3355
+ return { models: models.sort((a, b) => a.name.localeCompare(b.name)), filesScanned: filesScanned.sort() };
3356
+ }
3357
+ async function findSqlprojFiles(rootDir, cache) {
3358
+ return cache ? cache.findFiles(rootDir, (name) => name.endsWith(".sqlproj")) : findFiles(rootDir, (name) => name.endsWith(".sqlproj"));
3359
+ }
3360
+ async function scanSqlprojSource(rootDir, cache) {
3361
+ const sqlprojPaths = await findSqlprojFiles(rootDir, cache);
3362
+ const sqlAbsPaths = /* @__PURE__ */ new Set();
3363
+ for (const sqlprojPath of sqlprojPaths) {
3364
+ const dir = path3.dirname(sqlprojPath);
3365
+ const files = cache ? await cache.findFiles(dir, isSqlFile) : await findFiles(dir, isSqlFile);
3366
+ for (const f of files) sqlAbsPaths.add(f);
3367
+ }
3368
+ const result = await scanSqlSource(rootDir, [...sqlAbsPaths], "sqlproj", cache);
3369
+ return { ...result, sqlAbsPaths };
3370
+ }
3371
+ async function scanSqlMigrationsSource(rootDir, excludeAbsPaths, cache) {
3372
+ const allSqlFiles = cache ? await cache.findFiles(rootDir, isSqlFile) : await findFiles(rootDir, isSqlFile);
3373
+ const migrationFiles = allSqlFiles.filter((f) => !excludeAbsPaths.has(f));
3374
+ return scanSqlSource(rootDir, migrationFiles, "sql-migration", cache);
3375
+ }
3376
+ function isTsSourceFile(name) {
3377
+ return (name.endsWith(".ts") || name.endsWith(".tsx")) && !name.endsWith(".d.ts");
3378
+ }
3379
+ async function findTsSourceFiles(rootDir, cache) {
3380
+ return cache ? cache.findFiles(rootDir, isTsSourceFile) : findFiles(rootDir, isTsSourceFile);
3381
+ }
3382
+ async function scanDrizzleSource(rootDir, cache) {
3383
+ const filePaths = (await findTsSourceFiles(rootDir, cache)).sort((a, b) => a.localeCompare(b));
3384
+ const seenTables = /* @__PURE__ */ new Set();
3385
+ const models = [];
3386
+ const filesScanned = [];
3387
+ for (const filePath of filePaths) {
3388
+ const raw = await readFileSafely(filePath, cache);
3389
+ if (raw == null || !raw.includes("drizzle-orm")) continue;
3390
+ const tables = parseDrizzleFile(raw, filePath);
3391
+ if (tables.length === 0) continue;
3392
+ const rel = normalizeRel(path3.relative(rootDir, filePath));
3393
+ filesScanned.push(rel);
3394
+ for (const t of tables) {
3395
+ if (seenTables.has(t.name)) continue;
3396
+ seenTables.add(t.name);
3397
+ models.push({
3398
+ name: t.name,
3399
+ fields: [...t.fields].sort((a, b) => a.name.localeCompare(b.name)),
3400
+ source: "drizzle",
3401
+ files: [rel]
3402
+ });
3403
+ }
3404
+ }
3405
+ return { models: models.sort((a, b) => a.name.localeCompare(b.name)), filesScanned: filesScanned.sort() };
3406
+ }
3407
+ async function scanTypeOrmSource(rootDir, cache) {
3408
+ const filePaths = (await findTsSourceFiles(rootDir, cache)).sort((a, b) => a.localeCompare(b));
3409
+ const seenTables = /* @__PURE__ */ new Set();
3410
+ const models = [];
3411
+ const filesScanned = [];
3412
+ for (const filePath of filePaths) {
3413
+ const raw = await readFileSafely(filePath, cache);
3414
+ if (raw == null || !raw.includes("@Entity")) continue;
3415
+ const entities = parseTypeOrmFile(raw, filePath);
3416
+ if (entities.length === 0) continue;
3417
+ const rel = normalizeRel(path3.relative(rootDir, filePath));
3418
+ filesScanned.push(rel);
3419
+ for (const e of entities) {
3420
+ if (seenTables.has(e.name)) continue;
3421
+ seenTables.add(e.name);
3422
+ models.push({ name: e.name, fields: e.fields, source: "typeorm", files: [rel] });
3423
+ }
3424
+ }
3425
+ return { models: models.sort((a, b) => a.name.localeCompare(b.name)), filesScanned: filesScanned.sort() };
3426
+ }
3427
+ async function scanDatabaseSchema(rootDir, projects, cache) {
3428
+ const prisma = await scanPrismaSource(rootDir, cache);
3429
+ const sqlproj = await scanSqlprojSource(rootDir, cache);
3430
+ const sqlMigrations = await scanSqlMigrationsSource(rootDir, sqlproj.sqlAbsPaths, cache);
3431
+ const drizzle = await scanDrizzleSource(rootDir, cache);
3432
+ const typeorm = await scanTypeOrmSource(rootDir, cache);
3433
+ const models = [...prisma.models, ...sqlMigrations.models, ...sqlproj.models, ...drizzle.models, ...typeorm.models].sort(
3434
+ (a, b) => a.name.localeCompare(b.name) || a.source.localeCompare(b.source)
3435
+ );
3436
+ if (models.length === 0 && prisma.enums.length === 0) return void 0;
3437
+ const filesScanned = [
3438
+ .../* @__PURE__ */ new Set([...prisma.filesScanned, ...sqlMigrations.filesScanned, ...sqlproj.filesScanned, ...drizzle.filesScanned, ...typeorm.filesScanned])
3439
+ ].sort();
3440
+ const modelNamesByFile = /* @__PURE__ */ new Map();
3441
+ for (const m of models) {
3442
+ for (const f of m.files) {
3443
+ if (!modelNamesByFile.has(f)) modelNamesByFile.set(f, /* @__PURE__ */ new Set());
3444
+ modelNamesByFile.get(f).add(m.name);
3445
+ }
3446
+ }
3447
+ return {
3448
+ providers: prisma.providers,
3449
+ models,
3450
+ enums: prisma.enums,
3451
+ filesScanned,
3452
+ projects: buildProjectBreakdown(projects, filesScanned, modelNamesByFile, prisma.enumNamesByFile)
3453
+ };
3454
+ }
3455
+
2849
3456
  // src/reporting/scanners/ui-purpose.ts
2850
3457
  var UI_EXTENSIONS = /* @__PURE__ */ new Set([
2851
3458
  ".tsx",
@@ -3494,7 +4101,8 @@ async function runAdvancedAnalysis(ctx) {
3494
4101
  tsModernity: !maxPrivacyMode,
3495
4102
  fileHotspots: !maxPrivacyMode,
3496
4103
  architecture: !maxPrivacyMode,
3497
- codeQuality: !maxPrivacyMode};
4104
+ codeQuality: !maxPrivacyMode,
4105
+ databaseSchema: !maxPrivacyMode};
3498
4106
  const advancedSteps = [
3499
4107
  ...scannerPolicy.platformMatrix && scanners?.platformMatrix?.enabled !== false ? [{ id: "platform", label: "Platform matrix" }] : [],
3500
4108
  ...scanners?.toolingInventory?.enabled !== false ? [{ id: "tooling", label: "Tooling inventory" }] : [],
@@ -3508,6 +4116,7 @@ async function runAdvancedAnalysis(ctx) {
3508
4116
  ...scanners?.dependencyRisk?.enabled !== false ? [{ id: "deprisk", label: "Dependency risk" }] : [],
3509
4117
  ...scannerPolicy.architecture && scanners?.architecture?.enabled !== false ? [{ id: "architecture", label: "Architecture layers" }] : [],
3510
4118
  ...scannerPolicy.codeQuality && scanners?.codeQuality?.enabled !== false ? [{ id: "codequality", label: "Code quality metrics" }] : [],
4119
+ ...scannerPolicy.databaseSchema && scanners?.databaseSchema?.enabled !== false ? [{ id: "databaseschema", label: "Database schema" }] : [],
3511
4120
  ...!maxPrivacyMode && (opts.uiPurpose || scanners?.uiPurpose?.enabled === true) ? [{ id: "uipurpose", label: "UI purpose evidence" }] : []
3512
4121
  ];
3513
4122
  for (const step of advancedSteps) {
@@ -3630,6 +4239,20 @@ async function runAdvancedAnalysis(ctx) {
3630
4239
  })
3631
4240
  );
3632
4241
  }
4242
+ if (scannerPolicy.databaseSchema && scanners?.databaseSchema?.enabled !== false) {
4243
+ progress.startStep("databaseschema");
4244
+ scannerTasks.push(
4245
+ scanDatabaseSchema(rootDir, allProjects, fileCache).then((result) => {
4246
+ extended.databaseSchema = result;
4247
+ const modelCount = result?.models.length ?? 0;
4248
+ const enumCount = result?.enums.length ?? 0;
4249
+ const dsParts = [];
4250
+ if (modelCount > 0) dsParts.push(`${modelCount} model${modelCount !== 1 ? "s" : ""}`);
4251
+ if (enumCount > 0) dsParts.push(`${enumCount} enum${enumCount !== 1 ? "s" : ""}`);
4252
+ progress.completeStep("databaseschema", dsParts.join(", ") || "none found", modelCount);
4253
+ })
4254
+ );
4255
+ }
3633
4256
  if (scanners?.dependencyRisk?.enabled !== false) {
3634
4257
  progress.startStep("deprisk");
3635
4258
  scannerTasks.push(
@@ -3702,6 +4325,7 @@ async function runAdvancedAnalysis(ctx) {
3702
4325
  advancedFiles += extended.buildDeploy.ci.length;
3703
4326
  }
3704
4327
  if (extended.codeQuality) advancedFiles += extended.codeQuality.filesAnalyzed;
4328
+ if (extended.databaseSchema) advancedFiles += extended.databaseSchema.filesScanned.length;
3705
4329
  if (extended.uiPurpose) advancedFiles += extended.uiPurpose.topEvidence.length;
3706
4330
  if (advancedFiles > 0) ctx.addFilesScanned(advancedFiles);
3707
4331
  }
@@ -3731,5 +4355,5 @@ var baselineCommand = new Command("baseline").description("Create a drift baseli
3731
4355
  });
3732
4356
 
3733
4357
  export { baselineCommand, loadAdvancedScanHook, runBaseline };
3734
- //# sourceMappingURL=chunk-4IHO6VUL.js.map
3735
- //# sourceMappingURL=chunk-4IHO6VUL.js.map
4358
+ //# sourceMappingURL=chunk-6DKWB2OI.js.map
4359
+ //# sourceMappingURL=chunk-6DKWB2OI.js.map