@anatolykoptev/krolik-cli 0.11.0 → 0.12.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.
- package/dist/bin/cli.js +564 -44
- package/dist/bin/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -56,7 +56,7 @@ var KROLIK_VERSION, TEMPLATE_VERSION;
|
|
|
56
56
|
var init_version = __esm({
|
|
57
57
|
"src/version.ts"() {
|
|
58
58
|
init_esm_shims();
|
|
59
|
-
KROLIK_VERSION = "0.
|
|
59
|
+
KROLIK_VERSION = "0.12.0";
|
|
60
60
|
TEMPLATE_VERSION = "6.1.0";
|
|
61
61
|
}
|
|
62
62
|
});
|
|
@@ -13289,18 +13289,33 @@ var init_routes2 = __esm({
|
|
|
13289
13289
|
init_esm_shims();
|
|
13290
13290
|
init_core();
|
|
13291
13291
|
routesFlagSchema = {
|
|
13292
|
-
json: COMMON_FLAGS.json
|
|
13292
|
+
json: COMMON_FLAGS.json,
|
|
13293
|
+
compact: { flag: "--compact" },
|
|
13294
|
+
full: { flag: "--full" }
|
|
13293
13295
|
};
|
|
13294
13296
|
routesTool = {
|
|
13295
13297
|
name: "krolik_routes",
|
|
13296
|
-
description:
|
|
13298
|
+
description: `Analyze tRPC API routes. Returns all procedures with types and protection status.
|
|
13299
|
+
|
|
13300
|
+
**Output modes:**
|
|
13301
|
+
- (default) Smart format - groups procedures by type (queries/mutations), shows only unprotected as exceptions
|
|
13302
|
+
- compact: true - overview only, routers with Q/M counts (smallest output)
|
|
13303
|
+
- full: true - verbose legacy format with all procedure attributes`,
|
|
13297
13304
|
inputSchema: {
|
|
13298
13305
|
type: "object",
|
|
13299
13306
|
properties: {
|
|
13300
13307
|
...PROJECT_PROPERTY,
|
|
13301
13308
|
json: {
|
|
13302
13309
|
type: "boolean",
|
|
13303
|
-
description: "Return JSON format instead of
|
|
13310
|
+
description: "Return JSON format instead of XML"
|
|
13311
|
+
},
|
|
13312
|
+
compact: {
|
|
13313
|
+
type: "boolean",
|
|
13314
|
+
description: "Compact output - routers with procedure counts only. Smallest output."
|
|
13315
|
+
},
|
|
13316
|
+
full: {
|
|
13317
|
+
type: "boolean",
|
|
13318
|
+
description: "Full verbose output - all procedures with all attributes. Use only when you need complete details."
|
|
13304
13319
|
}
|
|
13305
13320
|
}
|
|
13306
13321
|
},
|
|
@@ -13328,14 +13343,19 @@ var init_schema2 = __esm({
|
|
|
13328
13343
|
json: COMMON_FLAGS.json,
|
|
13329
13344
|
model: { flag: "--model" },
|
|
13330
13345
|
domain: { flag: "--domain" },
|
|
13331
|
-
compact: { flag: "--compact" }
|
|
13346
|
+
compact: { flag: "--compact" },
|
|
13347
|
+
full: { flag: "--full" }
|
|
13332
13348
|
};
|
|
13333
13349
|
schemaTool = {
|
|
13334
13350
|
name: "krolik_schema",
|
|
13335
13351
|
description: `Analyze Prisma database schema. Returns models, fields, relations, and enums.
|
|
13336
13352
|
|
|
13337
|
-
**
|
|
13338
|
-
-
|
|
13353
|
+
**Output modes:**
|
|
13354
|
+
- (default) Smart format - AI-optimized, hides standard fields (id, createdAt, updatedAt), obvious defaults
|
|
13355
|
+
- compact: true - overview only, models with relations (smallest output)
|
|
13356
|
+
- full: true - verbose legacy format with all fields and attributes
|
|
13357
|
+
|
|
13358
|
+
**Filters:**
|
|
13339
13359
|
- model: "User" - filter by model name (partial match)
|
|
13340
13360
|
- domain: "Auth" - filter by domain (derived from schema filename)`,
|
|
13341
13361
|
inputSchema: {
|
|
@@ -13356,7 +13376,11 @@ var init_schema2 = __esm({
|
|
|
13356
13376
|
},
|
|
13357
13377
|
compact: {
|
|
13358
13378
|
type: "boolean",
|
|
13359
|
-
description: "Compact output - models with relations only, no field details.
|
|
13379
|
+
description: "Compact output - models with relations only, no field details. Smallest output."
|
|
13380
|
+
},
|
|
13381
|
+
full: {
|
|
13382
|
+
type: "boolean",
|
|
13383
|
+
description: "Full verbose output - all fields with all attributes. Use only when you need complete details."
|
|
13360
13384
|
}
|
|
13361
13385
|
}
|
|
13362
13386
|
},
|
|
@@ -39185,8 +39209,8 @@ function formatAI2(data) {
|
|
|
39185
39209
|
` <stats routers="${data.routers.length}" procedures="${data.totalProcedures}" queries="${data.queries}" mutations="${data.mutations}" protected="${data.protectedCount}" />`
|
|
39186
39210
|
);
|
|
39187
39211
|
lines.push("");
|
|
39188
|
-
const grouped =
|
|
39189
|
-
for (const [domain, routers] of
|
|
39212
|
+
const grouped = groupByDomainDynamic(data.routers);
|
|
39213
|
+
for (const [domain, routers] of grouped) {
|
|
39190
39214
|
if (routers.length === 0) continue;
|
|
39191
39215
|
lines.push(` <domain name="${domain}">`);
|
|
39192
39216
|
for (const router of routers) {
|
|
@@ -39206,6 +39230,80 @@ function formatAI2(data) {
|
|
|
39206
39230
|
lines.push("</trpc-routes>");
|
|
39207
39231
|
return lines.join("\n");
|
|
39208
39232
|
}
|
|
39233
|
+
function formatSmart(data) {
|
|
39234
|
+
const lines = [];
|
|
39235
|
+
const protectedRatio = data.protectedCount / data.totalProcedures;
|
|
39236
|
+
const mostProtected = protectedRatio > 0.6;
|
|
39237
|
+
lines.push("<trpc-routes>");
|
|
39238
|
+
lines.push(
|
|
39239
|
+
` <stats routers="${data.routers.length}" procedures="${data.totalProcedures}" queries="${data.queries}" mutations="${data.mutations}" />`
|
|
39240
|
+
);
|
|
39241
|
+
if (mostProtected) {
|
|
39242
|
+
lines.push(
|
|
39243
|
+
` <note>Most procedures are protected (${Math.round(protectedRatio * 100)}%). Unprotected shown explicitly.</note>`
|
|
39244
|
+
);
|
|
39245
|
+
}
|
|
39246
|
+
const grouped = groupByDomainDynamic(data.routers);
|
|
39247
|
+
const domains = Array.from(grouped.keys());
|
|
39248
|
+
lines.push(` <domains>${domains.join(", ")}</domains>`);
|
|
39249
|
+
lines.push("");
|
|
39250
|
+
for (const [domain, routers] of grouped) {
|
|
39251
|
+
if (routers.length === 0) continue;
|
|
39252
|
+
lines.push(` <domain name="${domain}">`);
|
|
39253
|
+
for (const router of routers) {
|
|
39254
|
+
const queries = router.procedures.filter((p) => p.type === "query").map((p) => p.name);
|
|
39255
|
+
const mutations = router.procedures.filter((p) => p.type === "mutation").map((p) => p.name);
|
|
39256
|
+
const unprotected = router.procedures.filter((p) => !p.isProtected).map((p) => p.name);
|
|
39257
|
+
lines.push(` <router file="${router.file}">`);
|
|
39258
|
+
if (queries.length > 0) {
|
|
39259
|
+
lines.push(` <queries>${queries.join(", ")}</queries>`);
|
|
39260
|
+
}
|
|
39261
|
+
if (mutations.length > 0) {
|
|
39262
|
+
lines.push(` <mutations>${mutations.join(", ")}</mutations>`);
|
|
39263
|
+
}
|
|
39264
|
+
if (mostProtected && unprotected.length > 0) {
|
|
39265
|
+
lines.push(` <public>${unprotected.join(", ")}</public>`);
|
|
39266
|
+
}
|
|
39267
|
+
if (!mostProtected) {
|
|
39268
|
+
const protectedProcs = router.procedures.filter((p) => p.isProtected).map((p) => p.name);
|
|
39269
|
+
if (protectedProcs.length > 0) {
|
|
39270
|
+
lines.push(` <protected>${protectedProcs.join(", ")}</protected>`);
|
|
39271
|
+
}
|
|
39272
|
+
}
|
|
39273
|
+
lines.push(" </router>");
|
|
39274
|
+
}
|
|
39275
|
+
lines.push(" </domain>");
|
|
39276
|
+
lines.push("");
|
|
39277
|
+
}
|
|
39278
|
+
lines.push("</trpc-routes>");
|
|
39279
|
+
return lines.join("\n");
|
|
39280
|
+
}
|
|
39281
|
+
function formatCompact(data) {
|
|
39282
|
+
const lines = [];
|
|
39283
|
+
lines.push('<trpc-routes mode="compact">');
|
|
39284
|
+
lines.push(
|
|
39285
|
+
` <stats routers="${data.routers.length}" procedures="${data.totalProcedures}" queries="${data.queries}" mutations="${data.mutations}" />`
|
|
39286
|
+
);
|
|
39287
|
+
const grouped = groupByDomainDynamic(data.routers);
|
|
39288
|
+
const domains = Array.from(grouped.keys());
|
|
39289
|
+
lines.push(` <domains>${domains.join(", ")}</domains>`);
|
|
39290
|
+
lines.push("");
|
|
39291
|
+
for (const [domain, routers] of grouped) {
|
|
39292
|
+
if (routers.length === 0) continue;
|
|
39293
|
+
const routerSummaries = routers.map((r) => {
|
|
39294
|
+
const q = r.procedures.filter((p) => p.type === "query").length;
|
|
39295
|
+
const m = r.procedures.filter((p) => p.type === "mutation").length;
|
|
39296
|
+
return `${r.file}(${q}Q/${m}M)`;
|
|
39297
|
+
});
|
|
39298
|
+
lines.push(` <domain name="${domain}">`);
|
|
39299
|
+
lines.push(` ${routerSummaries.join(", ")}`);
|
|
39300
|
+
lines.push(" </domain>");
|
|
39301
|
+
}
|
|
39302
|
+
lines.push("");
|
|
39303
|
+
lines.push(" <hint>Use --full for procedure details</hint>");
|
|
39304
|
+
lines.push("</trpc-routes>");
|
|
39305
|
+
return lines.join("\n");
|
|
39306
|
+
}
|
|
39209
39307
|
function formatMarkdown2(data) {
|
|
39210
39308
|
const lines = [
|
|
39211
39309
|
"# API Routes (tRPC)",
|
|
@@ -39244,28 +39342,34 @@ function formatMarkdown2(data) {
|
|
|
39244
39342
|
lines.push("*Generated by krolik-cli*");
|
|
39245
39343
|
return lines.join("\n");
|
|
39246
39344
|
}
|
|
39247
|
-
function
|
|
39248
|
-
const
|
|
39249
|
-
Business: [],
|
|
39250
|
-
User: [],
|
|
39251
|
-
Content: [],
|
|
39252
|
-
Social: [],
|
|
39253
|
-
System: []
|
|
39254
|
-
};
|
|
39345
|
+
function groupByDomainDynamic(routers) {
|
|
39346
|
+
const result = /* @__PURE__ */ new Map();
|
|
39255
39347
|
for (const router of routers) {
|
|
39256
|
-
|
|
39257
|
-
|
|
39258
|
-
|
|
39259
|
-
domains.User?.push(router);
|
|
39260
|
-
} else if (["places", "events", "reviews", "search"].some((k) => router.file.includes(k))) {
|
|
39261
|
-
domains.Content?.push(router);
|
|
39262
|
-
} else if (["social", "activity", "referral", "interactions"].some((k) => router.file.includes(k))) {
|
|
39263
|
-
domains.Social?.push(router);
|
|
39264
|
-
} else {
|
|
39265
|
-
domains.System?.push(router);
|
|
39348
|
+
const domain = inferDomainFromPath(router.file);
|
|
39349
|
+
if (!result.has(domain)) {
|
|
39350
|
+
result.set(domain, []);
|
|
39266
39351
|
}
|
|
39352
|
+
result.get(domain).push(router);
|
|
39267
39353
|
}
|
|
39268
|
-
return
|
|
39354
|
+
return new Map([...result.entries()].sort((a, b) => a[0].localeCompare(b[0])));
|
|
39355
|
+
}
|
|
39356
|
+
function inferDomainFromPath(filePath) {
|
|
39357
|
+
const firstSegment = filePath.split("/")[0] ?? filePath;
|
|
39358
|
+
if (firstSegment.startsWith("business")) {
|
|
39359
|
+
return "Business";
|
|
39360
|
+
}
|
|
39361
|
+
if (firstSegment.startsWith("admin")) {
|
|
39362
|
+
return "Admin";
|
|
39363
|
+
}
|
|
39364
|
+
return firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1);
|
|
39365
|
+
}
|
|
39366
|
+
function groupByDomain2(routers) {
|
|
39367
|
+
const grouped = groupByDomainDynamic(routers);
|
|
39368
|
+
const result = {};
|
|
39369
|
+
for (const [domain, routerList] of grouped) {
|
|
39370
|
+
result[domain] = routerList;
|
|
39371
|
+
}
|
|
39372
|
+
return result;
|
|
39269
39373
|
}
|
|
39270
39374
|
var MAX_LENGTH2, SLICE_ARG1_VALUE2;
|
|
39271
39375
|
var init_output6 = __esm({
|
|
@@ -39603,7 +39707,15 @@ async function runRoutes(ctx) {
|
|
|
39603
39707
|
printRoutes(result, logger2);
|
|
39604
39708
|
return;
|
|
39605
39709
|
}
|
|
39606
|
-
|
|
39710
|
+
if (options.compact) {
|
|
39711
|
+
console.log(formatCompact(result));
|
|
39712
|
+
return;
|
|
39713
|
+
}
|
|
39714
|
+
if (options.full) {
|
|
39715
|
+
console.log(xmlOutput);
|
|
39716
|
+
return;
|
|
39717
|
+
}
|
|
39718
|
+
console.log(formatSmart(result));
|
|
39607
39719
|
}
|
|
39608
39720
|
var init_routes3 = __esm({
|
|
39609
39721
|
"src/commands/routes/index.ts"() {
|
|
@@ -39614,16 +39726,229 @@ var init_routes3 = __esm({
|
|
|
39614
39726
|
}
|
|
39615
39727
|
});
|
|
39616
39728
|
|
|
39729
|
+
// src/commands/schema/relation-graph.ts
|
|
39730
|
+
function buildRelationGraph(models) {
|
|
39731
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
39732
|
+
for (const model of models) {
|
|
39733
|
+
nodes.set(model.name, {
|
|
39734
|
+
model: model.name,
|
|
39735
|
+
file: model.file,
|
|
39736
|
+
relations: model.relations,
|
|
39737
|
+
referencedBy: [],
|
|
39738
|
+
inDegree: 0,
|
|
39739
|
+
outDegree: model.relations.length
|
|
39740
|
+
});
|
|
39741
|
+
}
|
|
39742
|
+
for (const model of models) {
|
|
39743
|
+
for (const relation of model.relations) {
|
|
39744
|
+
const targetNode = nodes.get(relation);
|
|
39745
|
+
if (targetNode) {
|
|
39746
|
+
targetNode.referencedBy.push(model.name);
|
|
39747
|
+
targetNode.inDegree++;
|
|
39748
|
+
}
|
|
39749
|
+
}
|
|
39750
|
+
}
|
|
39751
|
+
return nodes;
|
|
39752
|
+
}
|
|
39753
|
+
function findSCC(nodes) {
|
|
39754
|
+
const state = {
|
|
39755
|
+
index: /* @__PURE__ */ new Map(),
|
|
39756
|
+
lowlink: /* @__PURE__ */ new Map(),
|
|
39757
|
+
onStack: /* @__PURE__ */ new Set(),
|
|
39758
|
+
stack: [],
|
|
39759
|
+
sccs: [],
|
|
39760
|
+
currentIndex: 0
|
|
39761
|
+
};
|
|
39762
|
+
function strongConnect(nodeId) {
|
|
39763
|
+
state.index.set(nodeId, state.currentIndex);
|
|
39764
|
+
state.lowlink.set(nodeId, state.currentIndex);
|
|
39765
|
+
state.currentIndex++;
|
|
39766
|
+
state.stack.push(nodeId);
|
|
39767
|
+
state.onStack.add(nodeId);
|
|
39768
|
+
const node = nodes.get(nodeId);
|
|
39769
|
+
const deps = node?.relations ?? [];
|
|
39770
|
+
for (const depId of deps) {
|
|
39771
|
+
if (!nodes.has(depId)) continue;
|
|
39772
|
+
if (!state.index.has(depId)) {
|
|
39773
|
+
strongConnect(depId);
|
|
39774
|
+
state.lowlink.set(nodeId, Math.min(state.lowlink.get(nodeId), state.lowlink.get(depId)));
|
|
39775
|
+
} else if (state.onStack.has(depId)) {
|
|
39776
|
+
state.lowlink.set(nodeId, Math.min(state.lowlink.get(nodeId), state.index.get(depId)));
|
|
39777
|
+
}
|
|
39778
|
+
}
|
|
39779
|
+
if (state.lowlink.get(nodeId) === state.index.get(nodeId)) {
|
|
39780
|
+
const scc = [];
|
|
39781
|
+
let w;
|
|
39782
|
+
do {
|
|
39783
|
+
w = state.stack.pop();
|
|
39784
|
+
state.onStack.delete(w);
|
|
39785
|
+
scc.push(w);
|
|
39786
|
+
} while (w !== nodeId);
|
|
39787
|
+
state.sccs.push(scc);
|
|
39788
|
+
}
|
|
39789
|
+
}
|
|
39790
|
+
for (const nodeId of nodes.keys()) {
|
|
39791
|
+
if (!state.index.has(nodeId)) {
|
|
39792
|
+
strongConnect(nodeId);
|
|
39793
|
+
}
|
|
39794
|
+
}
|
|
39795
|
+
return state.sccs;
|
|
39796
|
+
}
|
|
39797
|
+
function findCommonPrefix(models) {
|
|
39798
|
+
if (models.length < 2) return null;
|
|
39799
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
39800
|
+
for (const model of models) {
|
|
39801
|
+
const parts = model.split(/(?=[A-Z])/);
|
|
39802
|
+
if (parts.length >= 2) {
|
|
39803
|
+
const prefix = parts[0];
|
|
39804
|
+
if (prefix.length >= 3) {
|
|
39805
|
+
prefixes.set(prefix, (prefixes.get(prefix) ?? 0) + 1);
|
|
39806
|
+
}
|
|
39807
|
+
}
|
|
39808
|
+
}
|
|
39809
|
+
for (const [prefix, count] of prefixes) {
|
|
39810
|
+
const ratio = count / models.length;
|
|
39811
|
+
if (ratio >= 0.6) {
|
|
39812
|
+
return prefix;
|
|
39813
|
+
}
|
|
39814
|
+
}
|
|
39815
|
+
return null;
|
|
39816
|
+
}
|
|
39817
|
+
function findCoreEntity(models, nodes) {
|
|
39818
|
+
let maxInDegree = 0;
|
|
39819
|
+
let core = null;
|
|
39820
|
+
for (const model of models) {
|
|
39821
|
+
const node = nodes.get(model);
|
|
39822
|
+
if (node && node.inDegree > maxInDegree) {
|
|
39823
|
+
maxInDegree = node.inDegree;
|
|
39824
|
+
core = model;
|
|
39825
|
+
}
|
|
39826
|
+
}
|
|
39827
|
+
return maxInDegree >= 2 ? core : null;
|
|
39828
|
+
}
|
|
39829
|
+
function inferClusterName(models, nodes) {
|
|
39830
|
+
const prefix = findCommonPrefix(models);
|
|
39831
|
+
if (prefix) {
|
|
39832
|
+
return {
|
|
39833
|
+
name: prefix.endsWith("s") ? prefix : `${prefix}s`,
|
|
39834
|
+
confidence: 85,
|
|
39835
|
+
source: "prefix"
|
|
39836
|
+
};
|
|
39837
|
+
}
|
|
39838
|
+
const core = findCoreEntity(models, nodes);
|
|
39839
|
+
if (core && models.length >= 2) {
|
|
39840
|
+
return {
|
|
39841
|
+
name: core.endsWith("s") ? core : `${core}s`,
|
|
39842
|
+
confidence: 75,
|
|
39843
|
+
source: "core-entity"
|
|
39844
|
+
};
|
|
39845
|
+
}
|
|
39846
|
+
const files = new Set(models.map((m) => nodes.get(m)?.file ?? ""));
|
|
39847
|
+
if (files.size === 1) {
|
|
39848
|
+
const file = [...files][0];
|
|
39849
|
+
const domain = file.replace(/\.prisma$/, "").replace(/^.*\//, "");
|
|
39850
|
+
if (domain && domain !== "schema") {
|
|
39851
|
+
return {
|
|
39852
|
+
name: domain.charAt(0).toUpperCase() + domain.slice(1),
|
|
39853
|
+
confidence: 50,
|
|
39854
|
+
source: "filename"
|
|
39855
|
+
};
|
|
39856
|
+
}
|
|
39857
|
+
}
|
|
39858
|
+
return { name: "", confidence: 0, source: "filename" };
|
|
39859
|
+
}
|
|
39860
|
+
function analyzeRelationGraph(models) {
|
|
39861
|
+
const nodes = buildRelationGraph(models);
|
|
39862
|
+
const sccs = findSCC(nodes);
|
|
39863
|
+
const clusters = [];
|
|
39864
|
+
const unclustered = [];
|
|
39865
|
+
for (const scc of sccs) {
|
|
39866
|
+
if (scc.length < MIN_CLUSTER_SIZE) {
|
|
39867
|
+
const node = nodes.get(scc[0]);
|
|
39868
|
+
if (!node || node.inDegree < 3 && node.outDegree < 3) {
|
|
39869
|
+
unclustered.push(...scc);
|
|
39870
|
+
continue;
|
|
39871
|
+
}
|
|
39872
|
+
}
|
|
39873
|
+
const { name, confidence, source } = inferClusterName(scc, nodes);
|
|
39874
|
+
if (confidence < MIN_CONFIDENCE2) {
|
|
39875
|
+
unclustered.push(...scc);
|
|
39876
|
+
continue;
|
|
39877
|
+
}
|
|
39878
|
+
const core = findCoreEntity(scc, nodes);
|
|
39879
|
+
clusters.push({
|
|
39880
|
+
name,
|
|
39881
|
+
core,
|
|
39882
|
+
models: scc.sort(),
|
|
39883
|
+
confidence,
|
|
39884
|
+
source
|
|
39885
|
+
});
|
|
39886
|
+
}
|
|
39887
|
+
clusters.sort((a, b) => b.models.length - a.models.length);
|
|
39888
|
+
return {
|
|
39889
|
+
nodes: Array.from(nodes.values()),
|
|
39890
|
+
clusters,
|
|
39891
|
+
unclustered: unclustered.sort()
|
|
39892
|
+
};
|
|
39893
|
+
}
|
|
39894
|
+
var MIN_CONFIDENCE2, MIN_CLUSTER_SIZE;
|
|
39895
|
+
var init_relation_graph = __esm({
|
|
39896
|
+
"src/commands/schema/relation-graph.ts"() {
|
|
39897
|
+
init_esm_shims();
|
|
39898
|
+
MIN_CONFIDENCE2 = 60;
|
|
39899
|
+
MIN_CLUSTER_SIZE = 2;
|
|
39900
|
+
}
|
|
39901
|
+
});
|
|
39902
|
+
|
|
39617
39903
|
// src/commands/schema/grouping.ts
|
|
39618
39904
|
function groupByFile2(models) {
|
|
39619
39905
|
return groupByProperty(models, "file");
|
|
39620
39906
|
}
|
|
39621
39907
|
function groupByDomain3(models) {
|
|
39622
|
-
|
|
39908
|
+
if (models.length === 0) {
|
|
39909
|
+
return /* @__PURE__ */ new Map();
|
|
39910
|
+
}
|
|
39911
|
+
const files = new Set(models.map((m) => m.file));
|
|
39912
|
+
const isSingleFile = files.size === 1;
|
|
39913
|
+
if (!isSingleFile) {
|
|
39914
|
+
return groupByFilename(models);
|
|
39915
|
+
}
|
|
39916
|
+
const { clusters, unclustered } = analyzeRelationGraph(models);
|
|
39917
|
+
if (clusters.length === 0) {
|
|
39918
|
+
return groupByFilename(models);
|
|
39919
|
+
}
|
|
39920
|
+
const result = /* @__PURE__ */ new Map();
|
|
39921
|
+
const modelMap = new Map(models.map((m) => [m.name, m]));
|
|
39922
|
+
for (const cluster of clusters) {
|
|
39923
|
+
const clusterModels = cluster.models.map((name) => modelMap.get(name)).filter((m) => m !== void 0);
|
|
39924
|
+
if (clusterModels.length > 0) {
|
|
39925
|
+
result.set(cluster.name, clusterModels);
|
|
39926
|
+
}
|
|
39927
|
+
}
|
|
39928
|
+
if (unclustered.length > 0) {
|
|
39929
|
+
const unclusteredModels = unclustered.map((name) => modelMap.get(name)).filter((m) => m !== void 0);
|
|
39930
|
+
for (const model of unclusteredModels) {
|
|
39931
|
+
const domain = inferDomainFromFilename(model.file);
|
|
39932
|
+
const existing = result.get(domain) ?? [];
|
|
39933
|
+
existing.push(model);
|
|
39934
|
+
result.set(domain, existing);
|
|
39935
|
+
}
|
|
39936
|
+
}
|
|
39937
|
+
return new Map([...result.entries()].sort((a, b) => a[0].localeCompare(b[0])));
|
|
39623
39938
|
}
|
|
39624
|
-
function
|
|
39939
|
+
function groupByFilename(models) {
|
|
39940
|
+
const result = /* @__PURE__ */ new Map();
|
|
39941
|
+
for (const model of models) {
|
|
39942
|
+
const domain = inferDomainFromFilename(model.file);
|
|
39943
|
+
const existing = result.get(domain) ?? [];
|
|
39944
|
+
existing.push(model);
|
|
39945
|
+
result.set(domain, existing);
|
|
39946
|
+
}
|
|
39947
|
+
return new Map([...result.entries()].sort((a, b) => a[0].localeCompare(b[0])));
|
|
39948
|
+
}
|
|
39949
|
+
function inferDomainFromFilename(filename) {
|
|
39625
39950
|
const base = filename.replace(/\.prisma$/, "").replace(/^.*\//, "");
|
|
39626
|
-
if (!base) return "Other";
|
|
39951
|
+
if (!base || base === "schema") return "Other";
|
|
39627
39952
|
const parts = base.split(/[-_]/);
|
|
39628
39953
|
const formatted = parts.map((part) => {
|
|
39629
39954
|
const lower = part.toLowerCase();
|
|
@@ -39639,6 +39964,7 @@ var init_grouping4 = __esm({
|
|
|
39639
39964
|
"src/commands/schema/grouping.ts"() {
|
|
39640
39965
|
init_esm_shims();
|
|
39641
39966
|
init_core2();
|
|
39967
|
+
init_relation_graph();
|
|
39642
39968
|
UPPERCASE_ABBREVS = ["crm", "api", "ugc", "sso", "oauth", "jwt", "sql"];
|
|
39643
39969
|
}
|
|
39644
39970
|
});
|
|
@@ -39766,7 +40092,137 @@ function formatMarkdown3(data) {
|
|
|
39766
40092
|
lines.push("*Generated by krolik-cli*");
|
|
39767
40093
|
return lines.join("\n");
|
|
39768
40094
|
}
|
|
39769
|
-
function
|
|
40095
|
+
function isObviousDefault(value) {
|
|
40096
|
+
if (!value) return false;
|
|
40097
|
+
return OBVIOUS_DEFAULTS.has(value) || value.startsWith('"') || value.startsWith("'");
|
|
40098
|
+
}
|
|
40099
|
+
function isRelationArray(field) {
|
|
40100
|
+
return field.isArray && field.type.charAt(0) === field.type.charAt(0).toUpperCase() && ![
|
|
40101
|
+
"String",
|
|
40102
|
+
"Int",
|
|
40103
|
+
"Float",
|
|
40104
|
+
"Boolean",
|
|
40105
|
+
"DateTime",
|
|
40106
|
+
"Json",
|
|
40107
|
+
"Bytes",
|
|
40108
|
+
"BigInt",
|
|
40109
|
+
"Decimal"
|
|
40110
|
+
].includes(field.type);
|
|
40111
|
+
}
|
|
40112
|
+
function classifyField(field) {
|
|
40113
|
+
if (STANDARD_FIELDS.has(field.name)) return "skip";
|
|
40114
|
+
if (isRelationArray(field)) return "skip";
|
|
40115
|
+
if (field.name.endsWith("Id") || field.isUnique || field.isId) return "key";
|
|
40116
|
+
if (field.name.includes("At") || field.name.includes("Date") || field.name.startsWith("created") || field.name.startsWith("updated")) {
|
|
40117
|
+
return "meta";
|
|
40118
|
+
}
|
|
40119
|
+
return "business";
|
|
40120
|
+
}
|
|
40121
|
+
function formatSmart2(data, fullData) {
|
|
40122
|
+
const lines = [];
|
|
40123
|
+
const total = fullData ?? data;
|
|
40124
|
+
const isFiltered = fullData && data.modelCount !== fullData.modelCount;
|
|
40125
|
+
lines.push("<prisma-schema>");
|
|
40126
|
+
if (isFiltered) {
|
|
40127
|
+
lines.push(
|
|
40128
|
+
` <stats models="${data.modelCount}" enums="${data.enumCount}" filtered="true" total="${total.modelCount}" />`
|
|
40129
|
+
);
|
|
40130
|
+
} else {
|
|
40131
|
+
lines.push(` <stats models="${data.modelCount}" enums="${data.enumCount}" />`);
|
|
40132
|
+
}
|
|
40133
|
+
const byDomain = groupByDomain3(data.models);
|
|
40134
|
+
const domains = Array.from(byDomain.keys()).sort();
|
|
40135
|
+
lines.push(` <domains>${domains.join(", ")}</domains>`);
|
|
40136
|
+
lines.push("");
|
|
40137
|
+
for (const [domain, models] of byDomain) {
|
|
40138
|
+
lines.push(` <domain name="${domain}">`);
|
|
40139
|
+
for (const model of models) {
|
|
40140
|
+
const keyFields = [];
|
|
40141
|
+
const businessFields = [];
|
|
40142
|
+
const metaFields = [];
|
|
40143
|
+
const relationArrays = [];
|
|
40144
|
+
for (const field of model.fields) {
|
|
40145
|
+
if (isRelationArray(field)) {
|
|
40146
|
+
relationArrays.push(`${field.name}\u2192${field.type}[]`);
|
|
40147
|
+
continue;
|
|
40148
|
+
}
|
|
40149
|
+
const category = classifyField(field);
|
|
40150
|
+
if (category === "skip") continue;
|
|
40151
|
+
const formatted = formatFieldSmart(field);
|
|
40152
|
+
switch (category) {
|
|
40153
|
+
case "key":
|
|
40154
|
+
keyFields.push(formatted);
|
|
40155
|
+
break;
|
|
40156
|
+
case "business":
|
|
40157
|
+
businessFields.push(formatted);
|
|
40158
|
+
break;
|
|
40159
|
+
case "meta":
|
|
40160
|
+
metaFields.push(formatted);
|
|
40161
|
+
break;
|
|
40162
|
+
}
|
|
40163
|
+
}
|
|
40164
|
+
lines.push(` <model name="${model.name}">`);
|
|
40165
|
+
const allRelations = [
|
|
40166
|
+
...model.relations,
|
|
40167
|
+
...relationArrays.length > 0 ? relationArrays : []
|
|
40168
|
+
];
|
|
40169
|
+
if (allRelations.length > 0) {
|
|
40170
|
+
lines.push(` <relations>${allRelations.join(", ")}</relations>`);
|
|
40171
|
+
}
|
|
40172
|
+
if (keyFields.length > 0) {
|
|
40173
|
+
lines.push(` <keys>${keyFields.join(", ")}</keys>`);
|
|
40174
|
+
}
|
|
40175
|
+
if (businessFields.length > 0) {
|
|
40176
|
+
lines.push(` <data>${businessFields.join(", ")}</data>`);
|
|
40177
|
+
}
|
|
40178
|
+
if (metaFields.length > 0) {
|
|
40179
|
+
lines.push(` <meta>${metaFields.join(", ")}</meta>`);
|
|
40180
|
+
}
|
|
40181
|
+
lines.push(" </model>");
|
|
40182
|
+
}
|
|
40183
|
+
lines.push(" </domain>");
|
|
40184
|
+
lines.push("");
|
|
40185
|
+
}
|
|
40186
|
+
if (data.enums.length > 0) {
|
|
40187
|
+
lines.push(" <enums>");
|
|
40188
|
+
const enumsByPrefix = groupEnumsByPrefix(data.enums);
|
|
40189
|
+
for (const [prefix, names] of enumsByPrefix) {
|
|
40190
|
+
if (names.length === 1) {
|
|
40191
|
+
lines.push(` ${names[0]}`);
|
|
40192
|
+
} else {
|
|
40193
|
+
lines.push(` ${prefix}*: ${names.join(", ")}`);
|
|
40194
|
+
}
|
|
40195
|
+
}
|
|
40196
|
+
lines.push(" </enums>");
|
|
40197
|
+
}
|
|
40198
|
+
lines.push("</prisma-schema>");
|
|
40199
|
+
return lines.join("\n");
|
|
40200
|
+
}
|
|
40201
|
+
function formatFieldSmart(field) {
|
|
40202
|
+
let result = field.name;
|
|
40203
|
+
const simpleTypes = ["String", "Int", "Float", "Boolean", "DateTime", "Json"];
|
|
40204
|
+
if (!simpleTypes.includes(field.type)) {
|
|
40205
|
+
result += `:${field.type}`;
|
|
40206
|
+
}
|
|
40207
|
+
if (!field.isRequired) result += "?";
|
|
40208
|
+
if (field.isUnique && !field.name.endsWith("Id")) result += "!";
|
|
40209
|
+
if (field.isArray) result += "[]";
|
|
40210
|
+
if (field.default && !isObviousDefault(field.default)) {
|
|
40211
|
+
result += `=${field.default}`;
|
|
40212
|
+
}
|
|
40213
|
+
return result;
|
|
40214
|
+
}
|
|
40215
|
+
function groupEnumsByPrefix(enums) {
|
|
40216
|
+
const result = /* @__PURE__ */ new Map();
|
|
40217
|
+
for (const e of enums) {
|
|
40218
|
+
const match = e.name.match(/^([A-Z][a-z]+)/);
|
|
40219
|
+
const prefix = match?.[1] ?? "Other";
|
|
40220
|
+
if (!result.has(prefix)) result.set(prefix, []);
|
|
40221
|
+
result.get(prefix).push(e.name);
|
|
40222
|
+
}
|
|
40223
|
+
return new Map([...result.entries()].sort((a, b) => a[0].localeCompare(b[0])));
|
|
40224
|
+
}
|
|
40225
|
+
function formatCompact2(data, fullData) {
|
|
39770
40226
|
const lines = [];
|
|
39771
40227
|
const total = fullData ?? data;
|
|
39772
40228
|
const isFiltered = fullData && data.modelCount !== fullData.modelCount;
|
|
@@ -39820,11 +40276,24 @@ function formatCompact(data, fullData) {
|
|
|
39820
40276
|
lines.push("</prisma-schema>");
|
|
39821
40277
|
return lines.join("\n");
|
|
39822
40278
|
}
|
|
40279
|
+
var STANDARD_FIELDS, OBVIOUS_DEFAULTS;
|
|
39823
40280
|
var init_output7 = __esm({
|
|
39824
40281
|
"src/commands/schema/output.ts"() {
|
|
39825
40282
|
init_esm_shims();
|
|
39826
40283
|
init_format();
|
|
39827
40284
|
init_grouping4();
|
|
40285
|
+
STANDARD_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt"]);
|
|
40286
|
+
OBVIOUS_DEFAULTS = /* @__PURE__ */ new Set([
|
|
40287
|
+
"cuid(",
|
|
40288
|
+
"uuid(",
|
|
40289
|
+
"now(",
|
|
40290
|
+
"autoincrement(",
|
|
40291
|
+
"false",
|
|
40292
|
+
"true",
|
|
40293
|
+
"0",
|
|
40294
|
+
"1",
|
|
40295
|
+
'""'
|
|
40296
|
+
]);
|
|
39828
40297
|
}
|
|
39829
40298
|
});
|
|
39830
40299
|
|
|
@@ -39921,10 +40390,14 @@ async function runSchema(ctx) {
|
|
|
39921
40390
|
return;
|
|
39922
40391
|
}
|
|
39923
40392
|
if (options.compact) {
|
|
39924
|
-
console.log(
|
|
40393
|
+
console.log(formatCompact2(result, fullResult));
|
|
39925
40394
|
return;
|
|
39926
40395
|
}
|
|
39927
|
-
|
|
40396
|
+
if (options.full) {
|
|
40397
|
+
console.log(hasFilters ? formatAI3(result) : xmlOutput);
|
|
40398
|
+
return;
|
|
40399
|
+
}
|
|
40400
|
+
console.log(formatSmart2(result, hasFilters ? fullResult : void 0));
|
|
39928
40401
|
}
|
|
39929
40402
|
var init_schema3 = __esm({
|
|
39930
40403
|
"src/commands/schema/index.ts"() {
|
|
@@ -59165,28 +59638,74 @@ function registerReviewCommand(program) {
|
|
|
59165
59638
|
|
|
59166
59639
|
// src/cli/commands/routes.ts
|
|
59167
59640
|
init_esm_shims();
|
|
59641
|
+
init_projects();
|
|
59168
59642
|
function registerRoutesCommand(program) {
|
|
59169
|
-
program.command("routes").description("Analyze tRPC routes").option("--save", "Save to ROUTES.md").
|
|
59643
|
+
program.command("routes").description("Analyze tRPC routes").option("--save", "Save to ROUTES.md").option("-c, --compact", "Compact output (routers with procedure counts only)").option("-f, --full", "Full verbose output (all procedures with all attributes)").option("-p, --project <name>", "Project folder name (for multi-project workspaces)").addHelpText(
|
|
59644
|
+
"after",
|
|
59645
|
+
`
|
|
59646
|
+
Output modes:
|
|
59647
|
+
(default) Smart format - groups procedures by type, shows only exceptions
|
|
59648
|
+
--compact Overview only - routers with Q/M counts
|
|
59649
|
+
--full Verbose - all procedures, all attributes (legacy format)
|
|
59650
|
+
|
|
59651
|
+
Examples:
|
|
59652
|
+
krolik routes # Smart format (recommended)
|
|
59653
|
+
krolik routes --compact # Compact overview
|
|
59654
|
+
krolik routes --full # Full verbose output
|
|
59655
|
+
krolik routes --project piternow-wt-fix # Specific project
|
|
59656
|
+
`
|
|
59657
|
+
).action(async (options) => {
|
|
59170
59658
|
const { runRoutes: runRoutes2 } = await Promise.resolve().then(() => (init_routes3(), routes_exports));
|
|
59659
|
+
if (options.project) {
|
|
59660
|
+
const resolved = resolveProjectPath(process.cwd(), options.project);
|
|
59661
|
+
if ("error" in resolved) {
|
|
59662
|
+
console.error(resolved.error);
|
|
59663
|
+
process.exit(1);
|
|
59664
|
+
}
|
|
59665
|
+
process.env.KROLIK_PROJECT_ROOT = resolved.path;
|
|
59666
|
+
}
|
|
59171
59667
|
const ctx = await createContext3(program, options);
|
|
59172
|
-
await runRoutes2(
|
|
59668
|
+
await runRoutes2({
|
|
59669
|
+
...ctx,
|
|
59670
|
+
options: {
|
|
59671
|
+
...ctx.options,
|
|
59672
|
+
compact: options.compact,
|
|
59673
|
+
full: options.full
|
|
59674
|
+
}
|
|
59675
|
+
});
|
|
59173
59676
|
});
|
|
59174
59677
|
}
|
|
59175
59678
|
|
|
59176
59679
|
// src/cli/commands/schema.ts
|
|
59177
59680
|
init_esm_shims();
|
|
59681
|
+
init_projects();
|
|
59178
59682
|
function registerSchemaCommand(program) {
|
|
59179
|
-
program.command("schema").description("Analyze Prisma schema").option("--save", "Save to SCHEMA.md").option("-m, --model <name>", "Filter by model name (partial match)").option("-d, --domain <name>", "Filter by domain name").option("-c, --compact", "Compact output (models with relations only, no field details)").addHelpText(
|
|
59683
|
+
program.command("schema").description("Analyze Prisma schema").option("--save", "Save to SCHEMA.md").option("-m, --model <name>", "Filter by model name (partial match)").option("-d, --domain <name>", "Filter by domain name").option("-c, --compact", "Compact output (models with relations only, no field details)").option("-f, --full", "Full verbose output (all fields with all attributes)").option("-p, --project <name>", "Project folder name (for multi-project workspaces)").addHelpText(
|
|
59180
59684
|
"after",
|
|
59181
59685
|
`
|
|
59686
|
+
Output modes:
|
|
59687
|
+
(default) Smart format - optimized for AI, hides standard fields
|
|
59688
|
+
--compact Overview only - models with relations, no field details
|
|
59689
|
+
--full Verbose - all fields, all attributes (legacy format)
|
|
59690
|
+
|
|
59182
59691
|
Examples:
|
|
59183
|
-
krolik schema
|
|
59184
|
-
krolik schema --compact
|
|
59185
|
-
krolik schema --
|
|
59186
|
-
krolik schema --
|
|
59692
|
+
krolik schema # Smart format (recommended)
|
|
59693
|
+
krolik schema --compact # Compact overview
|
|
59694
|
+
krolik schema --full # Full verbose output
|
|
59695
|
+
krolik schema --domain Bookings # Only Bookings domain
|
|
59696
|
+
krolik schema --model User # Only models containing "User"
|
|
59697
|
+
krolik schema --project piternow-wt-fix # Specific project
|
|
59187
59698
|
`
|
|
59188
59699
|
).action(async (options) => {
|
|
59189
59700
|
const { runSchema: runSchema2 } = await Promise.resolve().then(() => (init_schema3(), schema_exports));
|
|
59701
|
+
if (options.project) {
|
|
59702
|
+
const resolved = resolveProjectPath(process.cwd(), options.project);
|
|
59703
|
+
if ("error" in resolved) {
|
|
59704
|
+
console.error(resolved.error);
|
|
59705
|
+
process.exit(1);
|
|
59706
|
+
}
|
|
59707
|
+
process.env.KROLIK_PROJECT_ROOT = resolved.path;
|
|
59708
|
+
}
|
|
59190
59709
|
const ctx = await createContext3(program, options);
|
|
59191
59710
|
await runSchema2({
|
|
59192
59711
|
...ctx,
|
|
@@ -59194,7 +59713,8 @@ Examples:
|
|
|
59194
59713
|
...ctx.options,
|
|
59195
59714
|
model: options.model,
|
|
59196
59715
|
domain: options.domain,
|
|
59197
|
-
compact: options.compact
|
|
59716
|
+
compact: options.compact,
|
|
59717
|
+
full: options.full
|
|
59198
59718
|
}
|
|
59199
59719
|
});
|
|
59200
59720
|
});
|