@drzl/cli 4.37.0 → 4.38.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/cli.js CHANGED
@@ -30,13 +30,14 @@ import {
30
30
  nestjsOutDir,
31
31
  nextOutDir,
32
32
  openApiFetchOutDir,
33
+ pothosOutDir,
33
34
  seedOutDir,
34
35
  tableAliases,
35
36
  tableFilterWarnings,
36
37
  tanstackStartOutDir,
37
38
  trpcOutDir,
38
39
  tsRestOutDir
39
- } from "./chunk-EQ4OSVMJ.js";
40
+ } from "./chunk-VG6SD2GP.js";
40
41
 
41
42
  // src/cli.ts
42
43
  import { qualifiedTableName as qualifiedTableName2, SchemaAnalyzer as SchemaAnalyzer2 } from "@drzl/analyzer";
@@ -531,6 +532,17 @@ function fastCheckOptions(g, cfg) {
531
532
  };
532
533
  }
533
534
 
535
+ // src/pothos-options.ts
536
+ function pothosOptions(g, cfg) {
537
+ return {
538
+ outputDir: pothosOutDir(g, cfg),
539
+ naming: g.naming,
540
+ outputHeader: g.outputHeader,
541
+ format: g.format,
542
+ importExtension: g.importExtension
543
+ };
544
+ }
545
+
534
546
  // src/h3-options.ts
535
547
  var VALIDATOR_DEFAULT_DIRS6 = {
536
548
  zod: "src/validators/zod",
@@ -893,6 +905,14 @@ var GENERATORS = [
893
905
  outputDir: (g, cfg) => fastCheckOutDir(g, cfg),
894
906
  options: (g, cfg) => fastCheckOptions(g, cfg)
895
907
  },
908
+ {
909
+ kind: "pothos",
910
+ specifier: "@drzl/generator-pothos",
911
+ load: () => import("@drzl/generator-pothos"),
912
+ construct: (m, analysis) => new m.PothosGenerator(analysis),
913
+ outputDir: (g, cfg) => pothosOutDir(g, cfg),
914
+ options: (g, cfg) => pothosOptions(g, cfg)
915
+ },
896
916
  {
897
917
  kind: "service",
898
918
  specifier: "@drzl/generator-service",
@@ -1136,6 +1156,101 @@ import { parseCheck as parseCheck2 } from "@drzl/validation-core";
1136
1156
 
1137
1157
  // src/doctor.ts
1138
1158
  import { lengthMeasure, parseCheck } from "@drzl/validation-core";
1159
+
1160
+ // src/auth-tables.ts
1161
+ var SIGNATURES = [
1162
+ {
1163
+ model: "account",
1164
+ // `providerId` beside `accountId` is the distinctive pair: it is a link to an external identity
1165
+ // provider, which an application table has no reason to model this way.
1166
+ required: ["accountId", "providerId", "userId"],
1167
+ supporting: ["accessToken", "refreshToken", "idToken", "password", "scope"],
1168
+ conventionalName: "account",
1169
+ secrets: ["accessToken", "refreshToken", "idToken", "password"]
1170
+ },
1171
+ {
1172
+ model: "session",
1173
+ required: ["token", "expiresAt", "userId"],
1174
+ supporting: ["ipAddress", "userAgent"],
1175
+ conventionalName: "session",
1176
+ // A session token is a bearer credential: whoever reads it is that user until it expires.
1177
+ secrets: ["token"]
1178
+ },
1179
+ {
1180
+ model: "verification",
1181
+ // `identifier` and `value` are deliberately generic names, which is why `expiresAt` is required
1182
+ // too: the three together are a short-lived token store and little else.
1183
+ required: ["identifier", "value", "expiresAt"],
1184
+ supporting: [],
1185
+ conventionalName: "verification",
1186
+ secrets: ["value"]
1187
+ },
1188
+ {
1189
+ model: "user",
1190
+ required: ["email", "emailVerified"],
1191
+ supporting: ["name", "image"],
1192
+ conventionalName: "user",
1193
+ secrets: []
1194
+ }
1195
+ ];
1196
+ function columnKeys(table) {
1197
+ const out = /* @__PURE__ */ new Map();
1198
+ for (const c of table.columns) out.set(normalise(c.name), c);
1199
+ return out;
1200
+ }
1201
+ function normalise(name) {
1202
+ return name.replace(/[_-]/g, "").toLowerCase();
1203
+ }
1204
+ function has(keys, name) {
1205
+ return keys.has(normalise(name));
1206
+ }
1207
+ function actualName(table, wanted) {
1208
+ return table.columns.find((c) => normalise(c.name) === normalise(wanted))?.name;
1209
+ }
1210
+ function matchOne(table, sig) {
1211
+ const keys = columnKeys(table);
1212
+ const missing = sig.required.filter((r) => !has(keys, r));
1213
+ if (missing.length) return void 0;
1214
+ const supporting = sig.supporting.filter((s) => has(keys, s));
1215
+ const nameMatches = normalise(table.name) === normalise(sig.conventionalName) || normalise(table.name) === `${normalise(sig.conventionalName)}s`;
1216
+ const confidence = supporting.length > 0 || nameMatches ? "strong" : "likely";
1217
+ const matched = [...sig.required, ...supporting].map((c) => actualName(table, c)).filter((c) => Boolean(c));
1218
+ const secrets = sig.secrets.map((c) => actualName(table, c)).filter((c) => Boolean(c));
1219
+ return { table: table.name, model: sig.model, confidence, matched, secrets };
1220
+ }
1221
+ function detectAuthTables(analysis) {
1222
+ const found = [];
1223
+ for (const table of analysis.tables) {
1224
+ for (const sig of SIGNATURES) {
1225
+ const m = matchOne(table, sig);
1226
+ if (m) {
1227
+ found.push(m);
1228
+ break;
1229
+ }
1230
+ }
1231
+ }
1232
+ const hasNonUser = found.some((f) => f.model !== "user");
1233
+ return hasNonUser ? found : found.filter((f) => f.model !== "user");
1234
+ }
1235
+ function authTablesWithSecrets(matches) {
1236
+ return matches.filter((m) => m.secrets.length > 0);
1237
+ }
1238
+ function excludeSuggestion(matches) {
1239
+ const names = [...new Set(matches.map((m) => m.table))].sort();
1240
+ return `exclude: [${names.map((n) => `'${n}'`).join(", ")}]`;
1241
+ }
1242
+ function authTableWarnings(analysis) {
1243
+ const risky = authTablesWithSecrets(detectAuthTables(analysis));
1244
+ if (!risky.length) return [];
1245
+ const lines = risky.map(
1246
+ (m) => `"${m.table}" looks like an authentication library's ${m.model} table and holds ${m.secrets.map((c) => `"${c}"`).join(", ")}. Generating for it publishes ${m.secrets.length === 1 ? "that column" : "those columns"}.`
1247
+ );
1248
+ return [
1249
+ `drzl generate: ${lines.join(" ")} Keep ${risky.length === 1 ? "it" : "them"} out with ${excludeSuggestion(risky)}, or leave it if the route is deliberate.`
1250
+ ];
1251
+ }
1252
+
1253
+ // src/doctor.ts
1139
1254
  import { Chalk as Chalk2 } from "chalk";
1140
1255
  var PLAIN = new Chalk2({ level: 0 });
1141
1256
  var HANDLED_CODES = /* @__PURE__ */ new Set(["DRZL_ANL_UNKNOWN_COLUMN"]);
@@ -1308,6 +1423,18 @@ function buildDoctorReport(analysis, schemaPath) {
1308
1423
  hint: i.hint
1309
1424
  });
1310
1425
  }
1426
+ for (const match of detectAuthTables(analysis)) {
1427
+ const secrets = match.secrets.length ? ` It holds ${match.secrets.map((c) => `\`${c}\``).join(", ")}, which a generated read route would return to whoever calls it.` : "";
1428
+ findings.push({
1429
+ kind: "auth-table",
1430
+ // A warning rather than an error: this is a real leak and it is also a guess about someone
1431
+ // else's schema, and a doctor that failed the build on a guess would be switched off.
1432
+ level: "warn",
1433
+ table: match.table,
1434
+ message: `"${match.table}" matches the shape of an authentication library's ${match.model} table (${match.matched.join(", ")}).${secrets}`,
1435
+ hint: `Keep it out of every generator with ${excludeSuggestion([match])}.`
1436
+ });
1437
+ }
1311
1438
  for (const i of analysis.issues) {
1312
1439
  if (i.code !== "DRZL_ANL_UNKNOWN_COLUMN") continue;
1313
1440
  findings.push({
@@ -1346,6 +1473,11 @@ var SECTIONS = [
1346
1473
  title: "Columns DRZL cannot type",
1347
1474
  why: "These get a validator that accepts any value."
1348
1475
  },
1476
+ {
1477
+ kinds: ["auth-table"],
1478
+ title: "Tables that look like an authentication library's",
1479
+ why: "Every generator loops over every table it finds, so these get routes too."
1480
+ },
1349
1481
  {
1350
1482
  kinds: ["check-declined", "check-unknown-column", "check-not-scalar", "check-uncountable"],
1351
1483
  title: "CHECK constraints DRZL does not enforce",
@@ -3925,6 +4057,7 @@ withOutputFlags(
3925
4057
  analysis.tables = filterTables(narrowed.tables, cfg);
3926
4058
  for (const w of [...narrowed.warnings, ...filterWarnings]) warn(w);
3927
4059
  for (const w of wideColumnWarning(analysis.issues)) warn(w);
4060
+ for (const w of authTableWarnings(analysis)) warn(w);
3928
4061
  const empty = nothingToGenerate({
3929
4062
  schema: source.schema,
3930
4063
  analyzed: narrowed.tables,