@drzl/cli 4.16.0 → 4.18.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.cjs CHANGED
@@ -261,7 +261,7 @@ ${entries}
261
261
  const decided = [...declared, body].join("\n\n");
262
262
  const imports = [];
263
263
  if (useShared) {
264
- const sharedAffix = (0, import_validation_core2.resolveAffix)({
264
+ const sharedAffix = (0, import_validation_core3.resolveAffix)({
265
265
  affix: opts.validation?.affix,
266
266
  schemaSuffix: opts.validation?.schemaSuffix
267
267
  });
@@ -271,21 +271,21 @@ ${entries}
271
271
  ["select", selectName]
272
272
  ].filter(([, local]) => decided.includes(local));
273
273
  if (wanted.length) {
274
- const spec = (0, import_validation_core2.resolveConfiguredImport)(
274
+ const spec = (0, import_validation_core3.resolveConfiguredImport)(
275
275
  opts.validation.importPath,
276
276
  ctx.out,
277
277
  process.cwd(),
278
278
  opts.importExtension
279
279
  );
280
280
  const names = wanted.map(([mode, local]) => {
281
- const exported = (0, import_validation_core2.schemaName)(mode, table.tsName, sharedAffix);
281
+ const exported = (0, import_validation_core3.schemaName)(mode, table.tsName, sharedAffix);
282
282
  return exported === local ? local : `${exported} as ${local}`;
283
283
  }).join(", ");
284
284
  imports.push(`import { ${names} } from '${spec}';`);
285
285
  }
286
286
  }
287
287
  imports.push(
288
- `import { ${[builder, "router"].sort().join(", ")} } from '${(0, import_validation_core2.importSpecifier)(
288
+ `import { ${[builder, "router"].sort().join(", ")} } from '${(0, import_validation_core3.importSpecifier)(
289
289
  `./${BASE_MODULE}.ts`,
290
290
  opts.importExtension
291
291
  )}';`
@@ -334,7 +334,7 @@ function relationProcedures(table, lib, selectSchemaName, taken, service) {
334
334
  return out;
335
335
  }
336
336
  function renderBarrel(routers, ctx, path6, opts) {
337
- const baseSpec = (0, import_validation_core2.importSpecifier)(`./${BASE_MODULE}.ts`, opts.importExtension);
337
+ const baseSpec = (0, import_validation_core3.importSpecifier)(`./${BASE_MODULE}.ts`, opts.importExtension);
338
338
  const reExports = `export { createCallerFactory, publicProcedure, router } from '${baseSpec}';
339
339
  ` + (opts.databaseInjection?.enabled === true ? `export { dbProcedure } from '${baseSpec}';
340
340
  ` : "") + `export type { Context } from '${baseSpec}';
@@ -352,7 +352,7 @@ export type AppRouter = typeof appRouter;
352
352
  ${reExports}`;
353
353
  }
354
354
  const entries = routers.map(({ filePath, exportName, table }) => ({
355
- rel: (0, import_validation_core2.importSpecifier)(
355
+ rel: (0, import_validation_core3.importSpecifier)(
356
356
  "./" + path6.relative(ctx.out, filePath).replace(/\\/g, "/"),
357
357
  opts.importExtension
358
358
  ),
@@ -393,7 +393,7 @@ function routerExportName(table, naming) {
393
393
  function serviceImportSpecifier(table, ctx, opts) {
394
394
  const rel = relativePosix(ctx.out, ctx.services);
395
395
  const dir = !rel ? "." : rel.startsWith(".") ? rel : `./${rel}`;
396
- return (0, import_validation_core2.importSpecifier)(`${dir}/${singularize(table.tsName)}Service.ts`, opts.importExtension);
396
+ return (0, import_validation_core3.importSpecifier)(`${dir}/${singularize(table.tsName)}Service.ts`, opts.importExtension);
397
397
  }
398
398
  function relativePosix(from, to) {
399
399
  const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -413,11 +413,11 @@ function buildHeader(h) {
413
413
  ];
414
414
  return lines.join("\n") + "\n\n";
415
415
  }
416
- var import_validation_core2, TRPC_MAJOR, q, LIB_IMPORTS, LIB_USAGE, LIBS, cap, singularize, isIdent, BASE_MODULE, TRPCGenerator, index_default;
416
+ var import_validation_core3, TRPC_MAJOR, q, LIB_IMPORTS, LIB_USAGE, LIBS, cap, singularize, isIdent, BASE_MODULE, TRPCGenerator, index_default;
417
417
  var init_dist = __esm({
418
418
  "../generator-trpc/dist/index.js"() {
419
419
  "use strict";
420
- import_validation_core2 = require("@drzl/validation-core");
420
+ import_validation_core3 = require("@drzl/validation-core");
421
421
  TRPC_MAJOR = 11;
422
422
  q = (v) => JSON.stringify(v);
423
423
  LIB_IMPORTS = {
@@ -510,7 +510,7 @@ ${body}
510
510
  await fs3.mkdir(out, { recursive: true });
511
511
  const files = [];
512
512
  const write = async (filePath, content) => {
513
- const formatted = await (0, import_validation_core2.formatCode)(
513
+ const formatted = await (0, import_validation_core3.formatCode)(
514
514
  buildHeader(opts.outputHeader) + content,
515
515
  filePath,
516
516
  opts.format
@@ -547,9 +547,11 @@ ${body}
547
547
  // ../generator-json-schema/dist/index.js
548
548
  var dist_exports2 = {};
549
549
  __export(dist_exports2, {
550
+ DRAFT: () => DRAFT,
550
551
  JsonSchemaGenerator: () => JsonSchemaGenerator,
551
552
  componentsDocument: () => componentsDocument,
552
553
  default: () => index_default2,
554
+ openApiDocument: () => openApiDocument,
553
555
  tableSchemas: () => tableSchemas
554
556
  });
555
557
  function baseSchema(c, mode, target, checks, sets, lengths) {
@@ -561,7 +563,7 @@ function baseSchema(c, mode, target, checks, sets, lengths) {
561
563
  case "custom":
562
564
  return {};
563
565
  case "buffer":
564
- return { type: "string", contentEncoding: "base64" };
566
+ return base64(target);
565
567
  case "tuple":
566
568
  return target === "openapi-3.0" ? { type: "array", items: { type: "number" }, minItems: s.length, maxItems: s.length } : {
567
569
  type: "array",
@@ -596,19 +598,22 @@ function baseSchema(c, mode, target, checks, sets, lengths) {
596
598
  if (c.enumValues && c.enumValues.length) return { enum: [...c.enumValues] };
597
599
  const mine = c.arrayDimensions ? [] : checks.filter((k) => k.column === c.name);
598
600
  const eq = mine.find((k) => k.operator === "=");
599
- if (eq) return { const: eq.kind === "string" ? eq.value : Number(eq.value) };
601
+ if (eq) {
602
+ const only = eq.kind === "string" ? eq.value : Number(eq.value);
603
+ return target === "openapi-3.0" ? { enum: [only] } : { const: only };
604
+ }
600
605
  switch (c.tsType) {
601
606
  case "string": {
602
607
  const out = { type: "string" };
603
608
  if (c.format === "uuid") out.format = UUID_FORMAT;
604
- else if (c.format && import_validation_core3.COLUMN_FORMATS[c.format]) out.pattern = import_validation_core3.COLUMN_FORMATS[c.format];
609
+ else if (c.format && import_validation_core5.COLUMN_FORMATS[c.format]) out.pattern = import_validation_core5.COLUMN_FORMATS[c.format];
605
610
  if (c.maxLength !== void 0) out.maxLength = c.maxLength;
606
611
  applyByteCap(out, c);
607
612
  applyLengths(out, c, lengths);
608
613
  return out;
609
614
  }
610
615
  case "number": {
611
- const out = { type: (0, import_validation_core3.isIntegerColumn)(c) ? "integer" : "number" };
616
+ const out = { type: (0, import_validation_core5.isIntegerColumn)(c) ? "integer" : "number" };
612
617
  if (!c.arrayDimensions) applyNumericBounds(out, c, checks, target);
613
618
  return out;
614
619
  }
@@ -619,7 +624,7 @@ function baseSchema(c, mode, target, checks, sets, lengths) {
619
624
  case "Date":
620
625
  return { type: "string", format: "date-time" };
621
626
  case "Uint8Array":
622
- return { type: "string", contentEncoding: "base64" };
627
+ return base64(target);
623
628
  default:
624
629
  return {};
625
630
  }
@@ -745,7 +750,7 @@ function tableSchema(table, cols, mode, target, applyDefaults, parsed) {
745
750
  };
746
751
  }
747
752
  function collect(table) {
748
- const parsed = (table.checks ?? []).map((k) => (0, import_validation_core3.parseCheck)(k.expression, k.name));
753
+ const parsed = (table.checks ?? []).map((k) => (0, import_validation_core5.parseCheck)(k.expression, k.name));
749
754
  return {
750
755
  checks: parsed.flatMap((p) => p.ok ? p.checks : []),
751
756
  sets: parsed.flatMap((p) => p.ok ? p.sets ?? [] : []),
@@ -757,11 +762,11 @@ function collect(table) {
757
762
  function tableSchemas(table, opts = {}) {
758
763
  const target = opts.target ?? "draft-2020-12";
759
764
  const parsed = collect(table);
760
- const build = (cols, mode) => tableSchema(table, cols, mode, target, !!opts.applyDefaults, parsed);
765
+ const build2 = (cols, mode) => tableSchema(table, cols, mode, target, !!opts.applyDefaults, parsed);
761
766
  return {
762
- insert: build((0, import_validation_core3.insertColumns)(table), "insert"),
763
- update: build((0, import_validation_core3.updateColumns)(table), "update"),
764
- select: build((0, import_validation_core3.selectColumns)(table), "select")
767
+ insert: build2((0, import_validation_core5.insertColumns)(table), "insert"),
768
+ update: build2((0, import_validation_core5.updateColumns)(table), "update"),
769
+ select: build2((0, import_validation_core5.selectColumns)(table), "select")
765
770
  };
766
771
  }
767
772
  function componentsDocument(tables, opts = {}) {
@@ -776,14 +781,231 @@ function componentsDocument(tables, opts = {}) {
776
781
  }
777
782
  return { schemas };
778
783
  }
784
+ function keyColumns2(table) {
785
+ const names = table.primaryKey?.columns ?? [];
786
+ if (!names.length) return null;
787
+ const cols = names.map((n) => table.columns.find((c) => c.name === n));
788
+ if (cols.some((c) => !c)) return null;
789
+ return cols;
790
+ }
791
+ function foreignKeysOf(table) {
792
+ if (table.foreignKeys?.length) return table.foreignKeys;
793
+ return table.columns.filter((c) => c.references).map((c) => ({
794
+ columns: [c.name],
795
+ foreignTable: c.references.table,
796
+ foreignColumns: [c.references.column]
797
+ }));
798
+ }
799
+ function build(tables, opts) {
800
+ const target = opts.target ?? "draft-2020-12";
801
+ const schemaTarget = target === "openapi-3.0" ? "openapi-3.0" : "openapi-3.1";
802
+ const failure = String(opts.validationStatus ?? 400);
803
+ const paths = {};
804
+ const schemas = {};
805
+ const tags = [];
806
+ const operationIds = /* @__PURE__ */ new Map();
807
+ const owner = /* @__PURE__ */ new Map();
808
+ const claim = (path6, by, label) => {
809
+ const taken = owner.get(path6);
810
+ if (taken !== void 0 && taken.by !== by) {
811
+ throw new Error(
812
+ `@drzl/generator-json-schema: the OpenAPI path "${path6}" is claimed twice: by table "${taken.label}" (exported as ${taken.by}) and by table "${label}" (exported as ${by}). A path names one resource, so one of the two has to be left out of this generator with the config's "exclude" list.`
813
+ );
814
+ }
815
+ owner.set(path6, { by, label });
816
+ };
817
+ const operation = (id, table, rest) => {
818
+ const clash = operationIds.get(id);
819
+ if (clash !== void 0) {
820
+ throw new Error(
821
+ `@drzl/generator-json-schema: the operationId "${id}" would be emitted for both "${clash}" and "${table.name}". An operationId is the method name a client generator derives, and the specification requires it to be unique across the document.`
822
+ );
823
+ }
824
+ operationIds.set(id, table.name);
825
+ return { operationId: id, tags: [table.name], ...rest };
826
+ };
827
+ const built = tables.map((table) => ({
828
+ table,
829
+ key: keyColumns2(table),
830
+ segment: resourceSegment(table),
831
+ schemas: tableSchemas(table, { target: schemaTarget, applyDefaults: opts.applyDefaults })
832
+ }));
833
+ for (const { table, key, segment, schemas: built3 } of built) {
834
+ for (const mode of modesFor(table, key)) {
835
+ const { $schema: _dialect, $id: _id, ...rest } = built3[mode];
836
+ schemas[componentName(table, mode)] = rest;
837
+ }
838
+ const notes = [];
839
+ if (!key) notes.push("It has no primary key, so no path addresses a single row.");
840
+ if (table.readOnly) {
841
+ notes.push("It refuses every write, so only reads are described.");
842
+ }
843
+ tags.push({ name: table.name, description: [`Table "${table.name}".`, ...notes].join(" ") });
844
+ const T = pascal(table.tsName);
845
+ const select = ref(componentName(table, "select"));
846
+ const validationFailed = {
847
+ description: "The request does not match the schema for this operation.",
848
+ ...jsonBody(ref(ERROR_SCHEMA))
849
+ };
850
+ const collidable = [
851
+ ...table.primaryKey ? [`primary key (${table.primaryKey.columns.join(", ")})`] : [],
852
+ ...table.unique.map((u) => `${u.name ? `${u.name} ` : ""}(${u.columns.join(", ")})`)
853
+ ];
854
+ const conflict = (constraints) => ({
855
+ description: `The row collides with an existing one on ${constraints.join("; ")}.`,
856
+ ...jsonBody(ref(ERROR_SCHEMA))
857
+ });
858
+ const collection = `/${segment}`;
859
+ claim(collection, table.tsName, table.name);
860
+ const item = {
861
+ get: operation(`list${T}`, table, {
862
+ summary: `List every ${table.name} row.`,
863
+ // No pagination parameters. Whether the server implements a limit, an offset or a cursor is
864
+ // not something a Drizzle schema states, and a declared parameter nothing honours is worse
865
+ // than an undeclared one.
866
+ responses: {
867
+ "200": {
868
+ description: `Every ${table.name} row.`,
869
+ ...jsonBody({ type: "array", items: select })
870
+ }
871
+ }
872
+ })
873
+ };
874
+ if (!table.readOnly) {
875
+ item.post = operation(`create${T}`, table, {
876
+ summary: `Create a ${table.name} row.`,
877
+ requestBody: { required: true, ...jsonBody(ref(componentName(table, "insert"))) },
878
+ responses: {
879
+ "201": { description: `The ${table.name} row that was created.`, ...jsonBody(select) },
880
+ [failure]: validationFailed,
881
+ ...collidable.length ? { "409": conflict(collidable) } : {}
882
+ }
883
+ });
884
+ }
885
+ paths[collection] = item;
886
+ if (!key) continue;
887
+ const itemPath = `${collection}/${key.map((c) => `{${c.name}}`).join("/")}`;
888
+ claim(itemPath, table.tsName, table.name);
889
+ const parameters = key.map((c) => ({
890
+ name: c.name,
891
+ in: "path",
892
+ required: true,
893
+ description: `${c.name}, from the primary key of ${table.name}.`,
894
+ // The column's own schema rather than a string, so an integer key is declared as one and a
895
+ // uuid key carries its format. This is the whole point of reading the real key.
896
+ schema: built3.select.properties[c.name] ?? {}
897
+ }));
898
+ const missing = {
899
+ description: `No ${table.name} row has that ${key.map((c) => c.name).join(" and ")}.`,
900
+ ...jsonBody(ref(ERROR_SCHEMA))
901
+ };
902
+ const byId = {
903
+ parameters,
904
+ get: operation(`get${T}`, table, {
905
+ summary: `Read one ${table.name} row.`,
906
+ responses: {
907
+ "200": { description: `The requested ${table.name} row.`, ...jsonBody(select) },
908
+ [failure]: validationFailed,
909
+ "404": missing
910
+ }
911
+ })
912
+ };
913
+ if (!table.readOnly) {
914
+ byId.patch = operation(`update${T}`, table, {
915
+ summary: `Patch one ${table.name} row.`,
916
+ requestBody: { required: true, ...jsonBody(ref(componentName(table, "update"))) },
917
+ responses: {
918
+ "200": { description: `The ${table.name} row after the patch.`, ...jsonBody(select) },
919
+ [failure]: validationFailed,
920
+ "404": missing,
921
+ // The primary key is not in the update schema, so a patch cannot collide on it. Only a
922
+ // unique constraint over other columns can.
923
+ ...table.unique.length ? {
924
+ "409": conflict(
925
+ table.unique.map((u) => `${u.name ? `${u.name} ` : ""}(${u.columns.join(", ")})`)
926
+ )
927
+ } : {}
928
+ }
929
+ });
930
+ byId.delete = operation(`delete${T}`, table, {
931
+ summary: `Delete one ${table.name} row.`,
932
+ responses: {
933
+ // No body. Handing back the deleted row is the alternative and it is not a true statement
934
+ // on every dialect DRZL supports: RETURNING is Postgres and SQLite, and MySQL has no such
935
+ // clause, so an implementation there has nothing to send.
936
+ "204": { description: `The ${table.name} row was deleted. No content is returned.` },
937
+ [failure]: validationFailed,
938
+ "404": missing
939
+ }
940
+ });
941
+ }
942
+ paths[itemPath] = byId;
943
+ if (!opts.includeRelations) continue;
944
+ for (const child of built) {
945
+ if (child.table === table) continue;
946
+ const matching = foreignKeysOf(child.table).filter(
947
+ (fk) => fk.foreignTable === table.name && fk.foreignColumns.length === key.length && fk.foreignColumns.every((c, i) => c === key[i].name)
948
+ );
949
+ if (matching.length !== 1) continue;
950
+ const subPath = `${itemPath}/${child.segment}`;
951
+ claim(
952
+ subPath,
953
+ `${table.tsName} -> ${child.table.tsName}`,
954
+ `${table.name} -> ${child.table.name}`
955
+ );
956
+ paths[subPath] = {
957
+ parameters,
958
+ get: operation(`list${T}${pascal(child.table.tsName)}`, child.table, {
959
+ summary: `List the ${child.table.name} rows belonging to one ${table.name} row.`,
960
+ responses: {
961
+ "200": {
962
+ description: `The ${child.table.name} rows whose ${matching[0].columns.join(", ")} names this ${table.name} row.`,
963
+ ...jsonBody({ type: "array", items: ref(componentName(child.table, "select")) })
964
+ },
965
+ [failure]: validationFailed,
966
+ "404": missing
967
+ }
968
+ })
969
+ };
970
+ }
971
+ }
972
+ return { paths, schemas, tags };
973
+ }
974
+ function openApiDocument(tables, opts = {}) {
975
+ const target = opts.target ?? "draft-2020-12";
976
+ const { paths, schemas, tags } = build(tables, opts);
977
+ if (ERROR_SCHEMA in schemas) {
978
+ throw new Error(
979
+ `@drzl/generator-json-schema: a table produced the component schema name "${ERROR_SCHEMA}", which the document already uses for its error responses.`
980
+ );
981
+ }
982
+ return {
983
+ openapi: target === "openapi-3.0" ? "3.0.3" : "3.1.1",
984
+ info: {
985
+ title: opts.info?.title ?? "API",
986
+ version: opts.info?.version ?? "0.0.0",
987
+ description: opts.info?.description ?? "Generated by DRZL from a Drizzle schema. Paths, request bodies and response bodies are derived from the schema alone; nothing here has been checked against a running server."
988
+ },
989
+ ...opts.servers?.length ? { servers: opts.servers } : {},
990
+ paths,
991
+ components: { schemas: { ...schemas, [ERROR_SCHEMA]: errorSchema() } },
992
+ tags
993
+ };
994
+ }
779
995
  function renderTableModule(table, affix, target, applyDefaults) {
780
996
  const T = table.tsName;
781
997
  const schemas = tableSchemas(table, { target, applyDefaults });
782
- const decl = (mode) => `export const ${(0, import_validation_core3.schemaName)(mode, T, affix)} = ${JSON.stringify(schemas[mode], null, 2)} as const;
998
+ const decl = (mode) => `export const ${(0, import_validation_core4.schemaName)(mode, T, affix)} = ${JSON.stringify(schemas[mode], null, 2)} as const;
783
999
 
784
- export type ${(0, import_validation_core3.typeName)(mode, T, affix)} = typeof ${(0, import_validation_core3.schemaName)(mode, T, affix)};`;
1000
+ export type ${(0, import_validation_core4.typeName)(mode, T, affix)} = typeof ${(0, import_validation_core4.schemaName)(mode, T, affix)};`;
785
1001
  return [decl("insert"), decl("update"), decl("select")].join("\n\n") + "\n";
786
1002
  }
1003
+ function resolveDocument(opt) {
1004
+ if (!opt) return null;
1005
+ const o = opt === true ? {} : opt;
1006
+ if (o.enabled === false) return null;
1007
+ return { ...o, format: o.format ?? "ts" };
1008
+ }
787
1009
  function buildHeader2(h) {
788
1010
  if (h?.enabled === false) return "";
789
1011
  const text = h?.text ?? "// Generated by DRZL. Do not edit by hand.";
@@ -791,14 +1013,38 @@ function buildHeader2(h) {
791
1013
 
792
1014
  `;
793
1015
  }
794
- var import_validation_core3, DEFAULT_FILE_SUFFIX, DRAFT, UUID_FORMAT, JsonSchemaGenerator, index_default2;
1016
+ var import_validation_core4, import_validation_core5, DRAFT, UUID_FORMAT, base64, ERROR_SCHEMA, componentName, ref, pascal, modesFor, resourceSegment, jsonBody, errorSchema, DEFAULT_FILE_SUFFIX, JsonSchemaGenerator, index_default2;
795
1017
  var init_dist2 = __esm({
796
1018
  "../generator-json-schema/dist/index.js"() {
797
1019
  "use strict";
798
- import_validation_core3 = require("@drzl/validation-core");
799
- DEFAULT_FILE_SUFFIX = ".schema.ts";
1020
+ import_validation_core4 = require("@drzl/validation-core");
1021
+ import_validation_core5 = require("@drzl/validation-core");
800
1022
  DRAFT = "https://json-schema.org/draft/2020-12/schema";
801
1023
  UUID_FORMAT = "uuid";
1024
+ base64 = (target) => target === "openapi-3.0" ? { type: "string", format: "byte" } : { type: "string", contentEncoding: "base64" };
1025
+ ERROR_SCHEMA = "Error";
1026
+ componentName = (table, mode) => `${table.tsName}${mode[0].toUpperCase()}${mode.slice(1)}`;
1027
+ ref = (name) => ({ $ref: `#/components/schemas/${name}` });
1028
+ pascal = (s) => s.charAt(0).toUpperCase() + s.slice(1);
1029
+ modesFor = (table, key) => [
1030
+ ...table.readOnly ? [] : ["insert"],
1031
+ ...table.readOnly || !key ? [] : ["update"],
1032
+ "select"
1033
+ ];
1034
+ resourceSegment = (table) => encodeURIComponent(table.name);
1035
+ jsonBody = (schema) => ({ content: { "application/json": { schema } } });
1036
+ errorSchema = () => ({
1037
+ title: "error",
1038
+ description: "What an operation returns when it does not return the row.",
1039
+ type: "object",
1040
+ properties: {
1041
+ message: { type: "string" },
1042
+ code: { type: "string" }
1043
+ },
1044
+ required: ["message"],
1045
+ additionalProperties: true
1046
+ });
1047
+ DEFAULT_FILE_SUFFIX = ".schema.ts";
802
1048
  JsonSchemaGenerator = class {
803
1049
  constructor(analysis) {
804
1050
  this.analysis = analysis;
@@ -810,13 +1056,14 @@ var init_dist2 = __esm({
810
1056
  const out = path6.resolve(process.cwd(), opts.outDir);
811
1057
  const files = [];
812
1058
  await fs3.mkdir(out, { recursive: true });
813
- const affix = (0, import_validation_core3.resolveAffix)(opts);
1059
+ const affix = (0, import_validation_core4.resolveAffix)(opts);
814
1060
  const fileSuffix = opts.fileSuffix ?? DEFAULT_FILE_SUFFIX;
815
1061
  const target = opts.target ?? "draft-2020-12";
1062
+ const document = resolveDocument(opts.document);
816
1063
  for (const table of this.analysis.tables) {
817
- const filePath = path6.join(out, (0, import_validation_core3.moduleFileName)(table.tsName, fileSuffix));
1064
+ const filePath = path6.join(out, (0, import_validation_core4.moduleFileName)(table.tsName, fileSuffix));
818
1065
  const code = renderTableModule(table, affix, target, !!opts.applyDefaults);
819
- const formatted = await (0, import_validation_core3.formatCode)(
1066
+ const formatted = await (0, import_validation_core4.formatCode)(
820
1067
  buildHeader2(opts.outputHeader) + code,
821
1068
  filePath,
822
1069
  opts.format
@@ -834,16 +1081,44 @@ var init_dist2 = __esm({
834
1081
  `;
835
1082
  await fs3.writeFile(
836
1083
  componentsPath,
837
- await (0, import_validation_core3.formatCode)(buildHeader2(opts.outputHeader) + code, componentsPath, opts.format),
1084
+ await (0, import_validation_core4.formatCode)(buildHeader2(opts.outputHeader) + code, componentsPath, opts.format),
838
1085
  "utf8"
839
1086
  );
840
1087
  files.push(componentsPath);
841
1088
  }
1089
+ if (document) {
1090
+ const built = openApiDocument(this.analysis.tables, {
1091
+ target,
1092
+ applyDefaults: !!opts.applyDefaults,
1093
+ includeRelations: !!opts.includeRelations,
1094
+ info: document.info,
1095
+ servers: document.servers,
1096
+ validationStatus: document.validationStatus
1097
+ });
1098
+ const body = JSON.stringify(built, null, 2);
1099
+ if (document.format !== "json") {
1100
+ const tsPath = path6.join(out, "openapi.ts");
1101
+ const code = `export const openapi = ${body} as const;
1102
+ `;
1103
+ await fs3.writeFile(
1104
+ tsPath,
1105
+ await (0, import_validation_core4.formatCode)(buildHeader2(opts.outputHeader) + code, tsPath, opts.format),
1106
+ "utf8"
1107
+ );
1108
+ files.push(tsPath);
1109
+ }
1110
+ if (document.format !== "ts") {
1111
+ const jsonPath = path6.join(out, "openapi.json");
1112
+ await fs3.writeFile(jsonPath, body + "\n", "utf8");
1113
+ files.push(jsonPath);
1114
+ }
1115
+ }
1116
+ const ext = opts.importExtension === "none" ? "" : ".js";
842
1117
  const indexPath = path6.join(out, "index.ts");
843
- const index = this.analysis.tables.map((t) => `export * from '${(0, import_validation_core3.moduleSpecifier)(t.tsName, fileSuffix, opts.importExtension)}';`).concat(
844
- opts.components ? [`export * from './components${opts.importExtension === "none" ? "" : ".js"}';`] : []
845
- ).join("\n") + "\n";
846
- const indexFormatted = await (0, import_validation_core3.formatCode)(
1118
+ const index = this.analysis.tables.map(
1119
+ (t) => `export * from '${(0, import_validation_core4.moduleSpecifier)(t.tsName, fileSuffix, opts.importExtension)}';`
1120
+ ).concat(opts.components ? [`export * from './components${ext}';`] : []).concat(document && document.format !== "json" ? [`export * from './openapi${ext}';`] : []).join("\n") + "\n";
1121
+ const indexFormatted = await (0, import_validation_core4.formatCode)(
847
1122
  buildHeader2(opts.outputHeader) + index,
848
1123
  indexPath,
849
1124
  opts.format
@@ -855,7 +1130,7 @@ var init_dist2 = __esm({
855
1130
  renderTable(table, opts) {
856
1131
  return renderTableModule(
857
1132
  table,
858
- (0, import_validation_core3.resolveAffix)(opts),
1133
+ (0, import_validation_core4.resolveAffix)(opts),
859
1134
  opts?.target ?? "draft-2020-12",
860
1135
  !!opts?.applyDefaults
861
1136
  );
@@ -868,13 +1143,53 @@ var init_dist2 = __esm({
868
1143
  // src/cli.ts
869
1144
  var import_analyzer = require("@drzl/analyzer");
870
1145
  var import_generator_orpc = require("@drzl/generator-orpc");
871
- var import_chalk2 = __toESM(require("chalk"), 1);
1146
+ var import_chalk3 = __toESM(require("chalk"), 1);
872
1147
  var import_chokidar = __toESM(require("chokidar"), 1);
873
1148
  var import_cli_progress = __toESM(require("cli-progress"), 1);
874
1149
  var import_commander = require("commander");
875
1150
  var path5 = __toESM(require("path"), 1);
876
1151
  var import_ora = __toESM(require("ora"), 1);
877
1152
 
1153
+ // src/validation-options.ts
1154
+ function validationOptions(g, cfg, outDir, caps = {}) {
1155
+ return {
1156
+ outDir,
1157
+ outputHeader: g.outputHeader,
1158
+ format: g.format,
1159
+ schemaSuffix: g.schemaSuffix,
1160
+ fileSuffix: g.fileSuffix,
1161
+ importExtension: g.importExtension,
1162
+ affix: g.affix,
1163
+ coerceDates: g.coerceDates,
1164
+ applyDefaults: g.applyDefaults,
1165
+ duplicateFinder: g.duplicateFinder,
1166
+ nestedSchemas: g.nestedSchemas,
1167
+ nestedDepth: g.nestedDepth,
1168
+ // Only where the generator can act on them, so an unsupported option is absent rather than
1169
+ // present and ignored.
1170
+ ...caps.schemaTypes ? {
1171
+ // Needed by both: the reference is resolved relative to the emitted file.
1172
+ schemaPath: cfg.schema,
1173
+ typedJson: g.typedJson,
1174
+ typedColumns: g.typedColumns
1175
+ } : {}
1176
+ };
1177
+ }
1178
+
1179
+ // src/json-schema-options.ts
1180
+ function jsonSchemaOptions(g, cfg, outDir) {
1181
+ return {
1182
+ // JSON Schema is data, so nothing it emits references a type from the schema module.
1183
+ ...validationOptions(g, cfg, outDir, { schemaTypes: false }),
1184
+ target: g.target,
1185
+ components: g.components,
1186
+ document: g.document,
1187
+ // Read only while emitting a document, where it adds `/users/{id}/posts`. The per-table
1188
+ // schemas are flat whatever it says.
1189
+ includeRelations: g.includeRelations
1190
+ };
1191
+ }
1192
+
878
1193
  // src/config.ts
879
1194
  var import_validation_core = require("@drzl/validation-core");
880
1195
  var fs = __toESM(require("fs"), 1);
@@ -988,6 +1303,35 @@ var GeneratorSchema = import_zod.z.object({
988
1303
  target: import_zod.z.enum(["draft-2020-12", "openapi-3.1", "openapi-3.0"]).optional(),
989
1304
  /** Also emit `components.ts` for the `json-schema` generator, ready for an OpenAPI document. */
990
1305
  components: import_zod.z.boolean().optional(),
1306
+ /**
1307
+ * Also emit the whole OpenAPI document for the `json-schema` generator: paths, verbs, request and
1308
+ * response bodies per table, with `components.schemas` embedded so the file stands alone.
1309
+ *
1310
+ * `true` is the short form. The object form carries the three things a Drizzle schema genuinely
1311
+ * cannot say: what the API is called, where it is served, and which status code that particular
1312
+ * server answers a request that fails its schema with.
1313
+ */
1314
+ document: import_zod.z.union([
1315
+ import_zod.z.boolean(),
1316
+ import_zod.z.object({
1317
+ enabled: import_zod.z.boolean().optional(),
1318
+ /** `ts` (default) writes a module, `json` the file OpenAPI tooling reads directly. */
1319
+ format: import_zod.z.enum(["ts", "json", "both"]).optional(),
1320
+ info: import_zod.z.object({
1321
+ title: import_zod.z.string().optional(),
1322
+ version: import_zod.z.string().optional(),
1323
+ description: import_zod.z.string().optional()
1324
+ }).strict().optional(),
1325
+ /**
1326
+ * Omitted by default, which the specification reads as a single server at `/`: the
1327
+ * document describes whatever is serving it. A placeholder host would be a fabrication
1328
+ * that tooling then follows.
1329
+ */
1330
+ servers: import_zod.z.array(import_zod.z.object({ url: import_zod.z.string(), description: import_zod.z.string().optional() }).strict()).optional(),
1331
+ /** 400 by default. 422 is the other defensible reading; exactly one is emitted. */
1332
+ validationStatus: import_zod.z.union([import_zod.z.literal(400), import_zod.z.literal(422)]).optional()
1333
+ }).strict()
1334
+ ]).optional(),
991
1335
  // service generator specific options
992
1336
  path: import_zod.z.string().optional(),
993
1337
  dataAccess: import_zod.z.enum(["stub", "drizzle"]).default("stub").optional(),
@@ -1291,31 +1635,263 @@ function trpcOptions(g, cfg, servicesDir) {
1291
1635
  };
1292
1636
  }
1293
1637
 
1294
- // src/validation-options.ts
1295
- function validationOptions(g, cfg, outDir, caps = {}) {
1638
+ // src/doctor.ts
1639
+ var import_validation_core2 = require("@drzl/validation-core");
1640
+ var import_chalk = __toESM(require("chalk"), 1);
1641
+ var HANDLED_CODES = /* @__PURE__ */ new Set(["DRZL_ANL_UNKNOWN_COLUMN"]);
1642
+ function splitPath(path6) {
1643
+ if (!path6) return {};
1644
+ const dot = path6.lastIndexOf(".");
1645
+ if (dot <= 0) return { table: path6 };
1646
+ return { table: path6.slice(0, dot), column: path6.slice(dot + 1) };
1647
+ }
1648
+ function namedColumns(parsed) {
1649
+ const out = [];
1650
+ for (const c of parsed.checks) out.push({ column: c.column, scalar: true });
1651
+ for (const s of parsed.sets ?? []) out.push({ column: s.column, scalar: true });
1652
+ for (const l of parsed.lengths ?? []) out.push({ column: l.column, scalar: false });
1653
+ for (const c of parsed.cardinalities ?? []) out.push({ column: c.column, scalar: false });
1654
+ for (const r of parsed.rows ?? []) {
1655
+ out.push({ column: r.left, scalar: false });
1656
+ out.push({ column: r.right, scalar: false });
1657
+ }
1658
+ return out;
1659
+ }
1660
+ function describeShape(c) {
1661
+ if (c.arrayDimensions) return "an array";
1662
+ switch (c.shape?.kind) {
1663
+ case "json":
1664
+ return "a JSON";
1665
+ case "buffer":
1666
+ return "a binary";
1667
+ case "tuple":
1668
+ case "numberObject":
1669
+ return "a structured";
1670
+ case "numberVector":
1671
+ return "a vector";
1672
+ case "bitstring":
1673
+ return "a bit-string";
1674
+ case "byteString":
1675
+ return "a byte-string";
1676
+ case "custom":
1677
+ return "a customType";
1678
+ default:
1679
+ return "a structured";
1680
+ }
1681
+ }
1682
+ function checkFindings(table) {
1683
+ const out = [];
1684
+ const byName = new Map(table.columns.map((c) => [c.name, c]));
1685
+ for (const k of table.checks ?? []) {
1686
+ const label = k.name ? `"${k.name}"` : "an unnamed constraint";
1687
+ const raw = k.expression ?? "";
1688
+ const expr = raw.trim() ? raw : "(empty)";
1689
+ const parsed = (0, import_validation_core2.parseCheck)(raw, k.name);
1690
+ if (!parsed.ok) {
1691
+ out.push({
1692
+ kind: "check-declined",
1693
+ level: "warn",
1694
+ table: table.tsName,
1695
+ constraint: k.name,
1696
+ message: `CHECK ${label} on "${table.tsName}" is not translated: ${parsed.reason}. Expression: ${expr}`,
1697
+ hint: "Only constraints whose meaning is unambiguous are translated, because a validator enforcing a guess rejects rows the database accepts. Your database still enforces this one; nothing DRZL emits does."
1698
+ });
1699
+ continue;
1700
+ }
1701
+ const seen = /* @__PURE__ */ new Set();
1702
+ for (const { column, scalar } of namedColumns(parsed)) {
1703
+ if (seen.has(column)) continue;
1704
+ seen.add(column);
1705
+ const col = byName.get(column);
1706
+ if (!col) {
1707
+ out.push({
1708
+ kind: "check-unknown-column",
1709
+ level: "warn",
1710
+ table: table.tsName,
1711
+ column,
1712
+ constraint: k.name,
1713
+ message: `CHECK ${label} on "${table.tsName}" names "${column}", which is not a column of that table, so nothing enforces it. Expression: ${expr}`,
1714
+ hint: "A constraint is attached to the field it names. Check the spelling, or move a constraint spanning two tables out of the schema."
1715
+ });
1716
+ continue;
1717
+ }
1718
+ if (scalar && (col.arrayDimensions || col.shape)) {
1719
+ out.push({
1720
+ kind: "check-not-scalar",
1721
+ level: "warn",
1722
+ table: table.tsName,
1723
+ column,
1724
+ constraint: k.name,
1725
+ message: `CHECK ${label} on "${table.tsName}" compares ${describeShape(col)} column "${column}" against a scalar literal, which does not describe it, so it is not translated. Expression: ${expr}`,
1726
+ hint: "On an array column only cardinality(col) is read, since it is the one comparison that is about the array rather than about an element."
1727
+ });
1728
+ }
1729
+ }
1730
+ }
1731
+ return out;
1732
+ }
1733
+ function primaryKeyFindings(table) {
1734
+ if (table.readOnly) return [];
1735
+ const pk = table.primaryKey?.columns ?? [];
1736
+ if (!pk.length) {
1737
+ const hasId = table.columns.some((c) => c.name === "id");
1738
+ return [
1739
+ {
1740
+ kind: "no-primary-key",
1741
+ level: "warn",
1742
+ table: table.tsName,
1743
+ message: hasId ? `Table "${table.tsName}" declares no primary key. The service and router generators fall back to a column named "id", which this table happens to have, so they work by coincidence.` : `Table "${table.tsName}" declares no primary key. The service and router generators fall back to a column named "id", which this table does not have, so the generated service will not compile.`,
1744
+ hint: "Declare a primary key, or leave this table out with the config table filter."
1745
+ }
1746
+ ];
1747
+ }
1748
+ if (pk.length > 1) {
1749
+ return [
1750
+ {
1751
+ kind: "partial-primary-key",
1752
+ level: "warn",
1753
+ table: table.tsName,
1754
+ message: `Table "${table.tsName}" has a composite primary key (${pk.join(", ")}). The service and router generators key getById, update and delete on "${pk[0]}" alone, so those operations match on part of the key.`,
1755
+ hint: "Treat the generated service as a starting point for this table and widen the key by hand."
1756
+ }
1757
+ ];
1758
+ }
1759
+ return [];
1760
+ }
1761
+ function buildDoctorReport(analysis, schemaPath) {
1762
+ const findings = [];
1763
+ const errors = analysis.issues.filter((i) => i.level === "error");
1764
+ for (const i of errors) {
1765
+ findings.push({
1766
+ kind: "analyzer",
1767
+ level: "error",
1768
+ ...splitPath(i.path),
1769
+ message: i.message,
1770
+ hint: i.hint
1771
+ });
1772
+ }
1773
+ for (const i of analysis.issues) {
1774
+ if (i.code !== "DRZL_ANL_UNKNOWN_COLUMN") continue;
1775
+ findings.push({
1776
+ kind: "unknown-column",
1777
+ level: "warn",
1778
+ ...splitPath(i.path),
1779
+ message: i.message,
1780
+ hint: i.hint
1781
+ });
1782
+ }
1783
+ for (const t of analysis.tables) findings.push(...checkFindings(t));
1784
+ for (const t of analysis.tables) findings.push(...primaryKeyFindings(t));
1785
+ for (const i of analysis.issues) {
1786
+ if (i.level === "error" || HANDLED_CODES.has(i.code)) continue;
1787
+ findings.push({
1788
+ kind: "analyzer",
1789
+ level: "warn",
1790
+ ...splitPath(i.path),
1791
+ message: i.message,
1792
+ hint: i.hint
1793
+ });
1794
+ }
1795
+ const columns = analysis.tables.reduce((n, t) => n + t.columns.length, 0);
1796
+ const checks = analysis.tables.reduce((n, t) => n + (t.checks?.length ?? 0), 0);
1296
1797
  return {
1297
- outDir,
1298
- outputHeader: g.outputHeader,
1299
- format: g.format,
1300
- schemaSuffix: g.schemaSuffix,
1301
- fileSuffix: g.fileSuffix,
1302
- importExtension: g.importExtension,
1303
- affix: g.affix,
1304
- coerceDates: g.coerceDates,
1305
- applyDefaults: g.applyDefaults,
1306
- duplicateFinder: g.duplicateFinder,
1307
- nestedSchemas: g.nestedSchemas,
1308
- nestedDepth: g.nestedDepth,
1309
- // Only where the generator can act on them, so an unsupported option is absent rather than
1310
- // present and ignored.
1311
- ...caps.schemaTypes ? {
1312
- // Needed by both: the reference is resolved relative to the emitted file.
1313
- schemaPath: cfg.schema,
1314
- typedJson: g.typedJson,
1315
- typedColumns: g.typedColumns
1316
- } : {}
1798
+ schema: schemaPath,
1799
+ dialect: analysis.dialect,
1800
+ ok: findings.length === 0,
1801
+ counts: { tables: analysis.tables.length, columns, checks, findings: findings.length },
1802
+ findings
1317
1803
  };
1318
1804
  }
1805
+ var SECTIONS = [
1806
+ {
1807
+ kinds: ["unknown-column"],
1808
+ title: "Columns DRZL cannot type",
1809
+ why: "These get a validator that accepts any value."
1810
+ },
1811
+ {
1812
+ kinds: ["check-declined", "check-unknown-column", "check-not-scalar"],
1813
+ title: "CHECK constraints DRZL does not enforce",
1814
+ why: "Your database still enforces these. Nothing DRZL generates does."
1815
+ },
1816
+ {
1817
+ kinds: ["no-primary-key", "partial-primary-key"],
1818
+ title: "Primary keys the generators cannot use",
1819
+ why: "The generated getById, update and delete are keyed on one column."
1820
+ },
1821
+ {
1822
+ kinds: ["analyzer"],
1823
+ title: "Other findings",
1824
+ why: "Reported by the analyzer while reading the schema."
1825
+ }
1826
+ ];
1827
+ function wrap(text, indent, first = indent, width = 96) {
1828
+ const lines = [];
1829
+ let line = "";
1830
+ for (const word of text.split(/\s+/)) {
1831
+ if (line && `${line} ${word}`.length + indent.length > width) {
1832
+ lines.push(line);
1833
+ line = word;
1834
+ } else {
1835
+ line = line ? `${line} ${word}` : word;
1836
+ }
1837
+ }
1838
+ if (line) lines.push(line);
1839
+ return lines.map((l, i) => (i === 0 ? first : indent) + l).join("\n");
1840
+ }
1841
+ function renderDoctorReport(report) {
1842
+ const out = [];
1843
+ const plural = (n, one) => `${n} ${one}${n === 1 ? "" : "s"}`;
1844
+ out.push(import_chalk.default.bold(`DRZL doctor ${report.schema}`));
1845
+ out.push(
1846
+ import_chalk.default.dim(
1847
+ `${report.dialect}, ${plural(report.counts.tables, "table")}, ${plural(report.counts.columns, "column")}, ${plural(report.counts.checks, "CHECK constraint")}`
1848
+ )
1849
+ );
1850
+ out.push("");
1851
+ if (report.ok) {
1852
+ out.push(import_chalk.default.green("Nothing to report."));
1853
+ out.push(import_chalk.default.dim(" Every column has a type DRZL can describe."));
1854
+ out.push(import_chalk.default.dim(" Every CHECK constraint is translated into the generated validators."));
1855
+ out.push(import_chalk.default.dim(" Every table has a primary key the generators can use."));
1856
+ return out.join("\n");
1857
+ }
1858
+ const fatal = report.findings.filter((f) => f.level === "error");
1859
+ if (fatal.length) {
1860
+ out.push(import_chalk.default.red("DRZL could not read this schema"));
1861
+ out.push(import_chalk.default.dim(" Nothing else could be checked."));
1862
+ out.push("");
1863
+ for (const f of fatal) {
1864
+ out.push(wrap(f.message, " ", ` ${import_chalk.default.dim("-")} `));
1865
+ if (f.hint) out.push(import_chalk.default.dim(wrap(f.hint, " ")));
1866
+ }
1867
+ out.push("");
1868
+ }
1869
+ for (const section of SECTIONS) {
1870
+ const mine = report.findings.filter(
1871
+ (f) => f.level !== "error" && section.kinds.includes(f.kind)
1872
+ );
1873
+ if (!mine.length) continue;
1874
+ out.push(import_chalk.default.yellow(`${section.title} (${mine.length})`));
1875
+ out.push(import_chalk.default.dim(` ${section.why}`));
1876
+ out.push("");
1877
+ const groups = /* @__PURE__ */ new Map();
1878
+ for (const f of mine) {
1879
+ const key = f.hint ?? "";
1880
+ groups.set(key, [...groups.get(key) ?? [], f]);
1881
+ }
1882
+ for (const [hint, items] of groups) {
1883
+ for (const f of items) out.push(wrap(f.message, " ", ` ${import_chalk.default.dim("-")} `));
1884
+ if (hint) out.push(import_chalk.default.dim(wrap(hint, " ")));
1885
+ out.push("");
1886
+ }
1887
+ }
1888
+ out.push(
1889
+ import_chalk.default.bold(`${plural(report.counts.findings, "finding")} in ${report.schema}.`) + import_chalk.default.dim(
1890
+ fatal.length ? " Fix the error above and run this again." : " None of these stop DRZL generating; they are what it will not check for you."
1891
+ )
1892
+ );
1893
+ return out.join("\n");
1894
+ }
1319
1895
 
1320
1896
  // src/drift.ts
1321
1897
  var import_node_fs = require("fs");
@@ -1393,7 +1969,7 @@ async function loadGenerator(specifier, load) {
1393
1969
  }
1394
1970
 
1395
1971
  // src/sponsor.ts
1396
- var import_chalk = __toESM(require("chalk"), 1);
1972
+ var import_chalk2 = __toESM(require("chalk"), 1);
1397
1973
  var import_node_fs2 = require("fs");
1398
1974
  var import_node_path2 = __toESM(require("path"), 1);
1399
1975
  var CACHE_DIR = import_node_path2.default.join(process.cwd(), "node_modules", ".cache", "@drzl");
@@ -1407,9 +1983,9 @@ var tips = [
1407
1983
  "Need JSON Schema or OpenAPI? The json-schema generator emits both, with no runtime dependency.",
1408
1984
  "Use output headers to track generated files and trim noisy diffs."
1409
1985
  ];
1410
- var green = (msg) => import_chalk.default.hex("#6ee7b7")(msg);
1411
- var cyan = (msg) => import_chalk.default.cyan(msg);
1412
- var gray = (msg) => import_chalk.default.gray(msg);
1986
+ var green = (msg) => import_chalk2.default.hex("#6ee7b7")(msg);
1987
+ var cyan = (msg) => import_chalk2.default.cyan(msg);
1988
+ var gray = (msg) => import_chalk2.default.gray(msg);
1413
1989
  function maybeShowSponsorMessage({
1414
1990
  reason = "generate",
1415
1991
  minIntervalMs = DEFAULT_INTERVAL_MS,
@@ -1498,13 +2074,13 @@ var CLI_VERSION = readCliVersion();
1498
2074
  function reportGeneratorFailure(kind, e) {
1499
2075
  if (e instanceof GeneratorNotInstalledError) {
1500
2076
  console.error(
1501
- import_chalk2.default.red(`The ${kind} generator is not installed.`),
1502
- import_chalk2.default.yellow(`
2077
+ import_chalk3.default.red(`The ${kind} generator is not installed.`),
2078
+ import_chalk3.default.yellow(`
1503
2079
  Install with: npm install ${e.specifier}`)
1504
2080
  );
1505
2081
  return;
1506
2082
  }
1507
- console.error(import_chalk2.default.red(`The ${kind} generator failed:`), e?.message ?? e);
2083
+ console.error(import_chalk3.default.red(`The ${kind} generator failed:`), e?.message ?? e);
1508
2084
  }
1509
2085
  var program = new import_commander.Command();
1510
2086
  program.name("drzl").description("DRZL - Drizzle Developer Toolkit").version(CLI_VERSION);
@@ -1531,9 +2107,9 @@ program.command("analyze").argument("<schema>", "path to drizzle schema (TS)").o
1531
2107
  } else if (opts.out) {
1532
2108
  const fs3 = await import("fs/promises");
1533
2109
  await fs3.writeFile(opts.out, json, "utf8");
1534
- spinner?.succeed(import_chalk2.default.green(`Analysis written to ${opts.out} in ${ms}ms`));
2110
+ spinner?.succeed(import_chalk3.default.green(`Analysis written to ${opts.out} in ${ms}ms`));
1535
2111
  } else {
1536
- spinner?.succeed(import_chalk2.default.green(`Analyzed in ${ms}ms`));
2112
+ spinner?.succeed(import_chalk3.default.green(`Analyzed in ${ms}ms`));
1537
2113
  console.log(json);
1538
2114
  }
1539
2115
  process.exit(res.issues.some((i) => i.level === "error") ? 2 : 0);
@@ -1543,7 +2119,48 @@ program.command("analyze").argument("<schema>", "path to drizzle schema (TS)").o
1543
2119
  console.log(JSON.stringify({ event: "error", code: "DRZL_CLI_ANALYZE", message: msg }));
1544
2120
  else
1545
2121
  console.error(
1546
- import_chalk2.default.red("Analyze failed (DRZL_CLI_ANALYZE):"),
2122
+ import_chalk3.default.red("Analyze failed (DRZL_CLI_ANALYZE):"),
2123
+ msg,
2124
+ "\nTip: run with --json for structured output."
2125
+ );
2126
+ process.exit(1);
2127
+ }
2128
+ });
2129
+ program.command("doctor").description("Report what DRZL cannot type or enforce in your schema, and why").argument("[schema]", "path to drizzle schema (TS); defaults to the schema in drzl.config").option("-c, --config <path>", "path to drzl.config, read when no schema argument is given").option("--json", "print the report as JSON instead of prose", false).option("--strict", "exit 2 when anything is reported", false).action(async (schema, opts) => {
2130
+ try {
2131
+ let target = schema;
2132
+ if (!target) {
2133
+ const cfg = await loadConfig(opts.config);
2134
+ target = cfg?.schema;
2135
+ }
2136
+ if (!target) {
2137
+ const msg = "No schema given. Pass a path, or run from a directory with a drzl.config.";
2138
+ if (opts.json)
2139
+ console.log(JSON.stringify({ event: "error", code: "DRZL_CLI_DOCTOR", message: msg }));
2140
+ else console.error(import_chalk3.default.red("Doctor failed (DRZL_CLI_DOCTOR):"), msg);
2141
+ process.exit(1);
2142
+ return;
2143
+ }
2144
+ const analyzer = new import_analyzer.SchemaAnalyzer(target);
2145
+ const analysis = await analyzer.analyze({
2146
+ includeRelations: true,
2147
+ validateConstraints: true
2148
+ });
2149
+ const report = buildDoctorReport(analysis, target);
2150
+ if (opts.json) console.log(JSON.stringify(report, null, 2));
2151
+ else console.log(renderDoctorReport(report));
2152
+ if (report.findings.some((f) => f.level === "error")) {
2153
+ process.exit(1);
2154
+ return;
2155
+ }
2156
+ process.exit(opts.strict && report.findings.length ? 2 : 0);
2157
+ } catch (e) {
2158
+ const msg = e?.message ?? String(e);
2159
+ if (opts.json)
2160
+ console.log(JSON.stringify({ event: "error", code: "DRZL_CLI_DOCTOR", message: msg }));
2161
+ else
2162
+ console.error(
2163
+ import_chalk3.default.red("Doctor failed (DRZL_CLI_DOCTOR):"),
1547
2164
  msg,
1548
2165
  "\nTip: run with --json for structured output."
1549
2166
  );
@@ -1558,7 +2175,7 @@ program.command("generate").description("Run configured generators (drzl.config.
1558
2175
  const cfg = await loadConfig(opts.config);
1559
2176
  if (!cfg) {
1560
2177
  console.error(
1561
- import_chalk2.default.red("No config found (DRZL_CFG_001). Create drzl.config.ts or pass --config.")
2178
+ import_chalk3.default.red("No config found (DRZL_CFG_001). Create drzl.config.ts or pass --config.")
1562
2179
  );
1563
2180
  process.exit(2);
1564
2181
  return;
@@ -1603,8 +2220,8 @@ program.command("generate").description("Run configured generators (drzl.config.
1603
2220
  onProgress: ({ index }) => progress.update(index)
1604
2221
  });
1605
2222
  progress.stop();
1606
- (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (${g.kind}): ${files.length} files`));
1607
- files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
2223
+ (0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (${g.kind}): ${files.length} files`));
2224
+ files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
1608
2225
  } else if (g.kind === "trpc") {
1609
2226
  try {
1610
2227
  const { TRPCGenerator: TRPCGenerator2 } = await loadGenerator(
@@ -1617,8 +2234,8 @@ program.command("generate").description("Run configured generators (drzl.config.
1617
2234
  onProgress: ({ index }) => progress.update(index)
1618
2235
  });
1619
2236
  progress.stop();
1620
- (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (trpc): ${files.length} files`));
1621
- files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
2237
+ (0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (trpc): ${files.length} files`));
2238
+ files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
1622
2239
  } catch (e) {
1623
2240
  progress.stop();
1624
2241
  reportGeneratorFailure(g.kind, e);
@@ -1647,8 +2264,8 @@ program.command("generate").description("Run configured generators (drzl.config.
1647
2264
  databaseInjection: g.databaseInjection
1648
2265
  });
1649
2266
  progress.stop();
1650
- (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (service): ${files.length} files`));
1651
- files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
2267
+ (0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (service): ${files.length} files`));
2268
+ files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
1652
2269
  } catch (e) {
1653
2270
  progress.stop();
1654
2271
  reportGeneratorFailure(g.kind, e);
@@ -1666,8 +2283,8 @@ program.command("generate").description("Run configured generators (drzl.config.
1666
2283
  validationOptions(g, cfg, target, { schemaTypes: true })
1667
2284
  );
1668
2285
  progress.stop();
1669
- (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (zod): ${files.length} files`));
1670
- files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
2286
+ (0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (zod): ${files.length} files`));
2287
+ files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
1671
2288
  } catch (e) {
1672
2289
  progress.stop();
1673
2290
  reportGeneratorFailure(g.kind, e);
@@ -1685,8 +2302,8 @@ program.command("generate").description("Run configured generators (drzl.config.
1685
2302
  validationOptions(g, cfg, target, { schemaTypes: true })
1686
2303
  );
1687
2304
  progress.stop();
1688
- (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (valibot): ${files.length} files`));
1689
- files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
2305
+ (0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (valibot): ${files.length} files`));
2306
+ files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
1690
2307
  } catch (e) {
1691
2308
  progress.stop();
1692
2309
  reportGeneratorFailure(g.kind, e);
@@ -1704,8 +2321,8 @@ program.command("generate").description("Run configured generators (drzl.config.
1704
2321
  validationOptions(g, cfg, target, { schemaTypes: false })
1705
2322
  );
1706
2323
  progress.stop();
1707
- (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (arktype): ${files.length} files`));
1708
- files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
2324
+ (0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (arktype): ${files.length} files`));
2325
+ files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
1709
2326
  } catch (e) {
1710
2327
  progress.stop();
1711
2328
  reportGeneratorFailure(g.kind, e);
@@ -1719,15 +2336,10 @@ program.command("generate").description("Run configured generators (drzl.config.
1719
2336
  );
1720
2337
  const gen = new JsonSchemaGenerator2(analysis);
1721
2338
  const target = g.path ?? "src/validators/json-schema";
1722
- const files = await gen.generate({
1723
- // JSON Schema is data, so nothing here references a type from the schema module.
1724
- ...validationOptions(g, cfg, target, { schemaTypes: false }),
1725
- target: g.target,
1726
- components: g.components
1727
- });
2339
+ const files = await gen.generate(jsonSchemaOptions(g, cfg, target));
1728
2340
  progress.stop();
1729
- (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (json-schema): ${files.length} files`));
1730
- files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
2341
+ (0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (json-schema): ${files.length} files`));
2342
+ files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
1731
2343
  } catch (e) {
1732
2344
  progress.stop();
1733
2345
  reportGeneratorFailure(g.kind, e);
@@ -1745,8 +2357,8 @@ program.command("generate").description("Run configured generators (drzl.config.
1745
2357
  validationOptions(g, cfg, target, { schemaTypes: true })
1746
2358
  );
1747
2359
  progress.stop();
1748
- (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (typebox): ${files.length} files`));
1749
- files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
2360
+ (0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (typebox): ${files.length} files`));
2361
+ files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
1750
2362
  } catch (e) {
1751
2363
  progress.stop();
1752
2364
  reportGeneratorFailure(g.kind, e);
@@ -1759,22 +2371,22 @@ program.command("generate").description("Run configured generators (drzl.config.
1759
2371
  const drift = diffSnapshots(driftBefore, after);
1760
2372
  await restoreSnapshot(driftBefore, after);
1761
2373
  if (drift.length) {
1762
- console.error(import_chalk2.default.red(`
2374
+ console.error(import_chalk3.default.red(`
1763
2375
  Generated output is out of date (${drift.length} file(s)):`));
1764
2376
  for (const d of drift) {
1765
2377
  const mark = d.status === "added" ? "+" : d.status === "removed" ? "-" : "~";
1766
2378
  console.error(
1767
- ` ${mark} ${import_chalk2.default.yellow(d.status.padEnd(8))} ${path5.relative(process.cwd(), d.file)}`
2379
+ ` ${mark} ${import_chalk3.default.yellow(d.status.padEnd(8))} ${path5.relative(process.cwd(), d.file)}`
1768
2380
  );
1769
2381
  }
1770
2382
  console.error(
1771
- import_chalk2.default.dim(
2383
+ import_chalk3.default.dim(
1772
2384
  "\nRun `drzl generate` and commit the result. Nothing was written by this check."
1773
2385
  )
1774
2386
  );
1775
2387
  process.exit(1);
1776
2388
  }
1777
- console.log(import_chalk2.default.green("Generated output is up to date."));
2389
+ console.log(import_chalk3.default.green("Generated output is up to date."));
1778
2390
  return;
1779
2391
  }
1780
2392
  if (cfg.generators.length) {
@@ -1782,7 +2394,7 @@ Generated output is out of date (${drift.length} file(s)):`));
1782
2394
  }
1783
2395
  } catch (e) {
1784
2396
  console.error(
1785
- import_chalk2.default.red("Generate failed (DRZL_GEN_001):"),
2397
+ import_chalk3.default.red("Generate failed (DRZL_GEN_001):"),
1786
2398
  e?.message ?? e,
1787
2399
  "\nTip: check your drzl.config.ts and template path."
1788
2400
  );
@@ -1802,10 +2414,10 @@ program.command("generate:orpc").argument("<schema>", "path to drizzle schema (T
1802
2414
  template: opts.template,
1803
2415
  includeRelations: !!opts.includeRelations
1804
2416
  });
1805
- console.log(import_chalk2.default.green(`Generated:`), files.map((f) => import_chalk2.default.cyan(f)).join(", "));
2417
+ console.log(import_chalk3.default.green(`Generated:`), files.map((f) => import_chalk3.default.cyan(f)).join(", "));
1806
2418
  maybeShowSponsorMessage({ reason: "generate:orpc" });
1807
2419
  } catch (e) {
1808
- console.error(import_chalk2.default.red("Generate orpc failed:"), e?.message ?? e);
2420
+ console.error(import_chalk3.default.red("Generate orpc failed:"), e?.message ?? e);
1809
2421
  process.exit(1);
1810
2422
  }
1811
2423
  });
@@ -1829,7 +2441,7 @@ program.command("generate:trpc").argument("<schema>", "path to drizzle schema (T
1829
2441
  // cannot become the branch that forgets it.
1830
2442
  servicesDir: opts.servicesDir
1831
2443
  });
1832
- console.log(import_chalk2.default.green(`Generated:`), files.map((f) => import_chalk2.default.cyan(f)).join(", "));
2444
+ console.log(import_chalk3.default.green(`Generated:`), files.map((f) => import_chalk3.default.cyan(f)).join(", "));
1833
2445
  maybeShowSponsorMessage({ reason: "generate:trpc" });
1834
2446
  } catch (e) {
1835
2447
  reportGeneratorFailure("trpc", e);
@@ -1839,7 +2451,7 @@ program.command("generate:trpc").argument("<schema>", "path to drizzle schema (T
1839
2451
  program.command("watch").description("Watch schema and regenerate on changes").option("-c, --config <path>", "path to drzl.config").option("--pipeline <name>", "all | analyze | generate-orpc | generate-trpc", "all").option("--debounce <ms>", "debounce ms", "200").option("--json", "emit JSON logs", false).option("--poll", "force polling (helps WSL/Docker/remote FS)", false).action(async (opts) => {
1840
2452
  let cfg = await loadConfig(opts.config);
1841
2453
  if (!cfg) {
1842
- console.error(import_chalk2.default.red("No config found. Create drzl.config.ts or pass --config."));
2454
+ console.error(import_chalk3.default.red("No config found. Create drzl.config.ts or pass --config."));
1843
2455
  process.exit(2);
1844
2456
  return;
1845
2457
  }
@@ -1931,7 +2543,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
1931
2543
  })
1932
2544
  );
1933
2545
  } else {
1934
- console.log(import_chalk2.default.green("Analyze complete."));
2546
+ console.log(import_chalk3.default.green("Analyze complete."));
1935
2547
  }
1936
2548
  return;
1937
2549
  }
@@ -1961,8 +2573,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
1961
2573
  servicesDir
1962
2574
  });
1963
2575
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
1964
- import_chalk2.default.green(`Generated (${g.kind}):`),
1965
- files.map((f) => import_chalk2.default.cyan(f)).join(", ")
2576
+ import_chalk3.default.green(`Generated (${g.kind}):`),
2577
+ files.map((f) => import_chalk3.default.cyan(f)).join(", ")
1966
2578
  );
1967
2579
  newFiles.push(...files);
1968
2580
  } else if (g.kind === "trpc") {
@@ -1974,8 +2586,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
1974
2586
  const gen = new TRPCGenerator2(analysis);
1975
2587
  const { files } = await gen.generate(trpcOptions(g, cfg, servicesDir));
1976
2588
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
1977
- import_chalk2.default.green(`Generated (trpc): ${files.length} files`),
1978
- files.map((f) => import_chalk2.default.cyan(f)).join(", ")
2589
+ import_chalk3.default.green(`Generated (trpc): ${files.length} files`),
2590
+ files.map((f) => import_chalk3.default.cyan(f)).join(", ")
1979
2591
  );
1980
2592
  newFiles.push(...files);
1981
2593
  } catch (e) {
@@ -2001,8 +2613,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
2001
2613
  databaseInjection: g.databaseInjection
2002
2614
  });
2003
2615
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
2004
- import_chalk2.default.green(`Generated (service): ${files.length} files`),
2005
- files.map((f) => import_chalk2.default.cyan(f)).join(", ")
2616
+ import_chalk3.default.green(`Generated (service): ${files.length} files`),
2617
+ files.map((f) => import_chalk3.default.cyan(f)).join(", ")
2006
2618
  );
2007
2619
  newFiles.push(...files);
2008
2620
  } catch (e) {
@@ -2021,8 +2633,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
2021
2633
  validationOptions(g, cfg, target, { schemaTypes: true })
2022
2634
  );
2023
2635
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
2024
- import_chalk2.default.green(`Generated (zod): ${files.length} files`),
2025
- files.map((f) => import_chalk2.default.cyan(f)).join(", ")
2636
+ import_chalk3.default.green(`Generated (zod): ${files.length} files`),
2637
+ files.map((f) => import_chalk3.default.cyan(f)).join(", ")
2026
2638
  );
2027
2639
  newFiles.push(...files);
2028
2640
  } catch (e) {
@@ -2041,8 +2653,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
2041
2653
  validationOptions(g, cfg, target, { schemaTypes: true })
2042
2654
  );
2043
2655
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
2044
- import_chalk2.default.green(`Generated (valibot): ${files.length} files`),
2045
- files.map((f) => import_chalk2.default.cyan(f)).join(", ")
2656
+ import_chalk3.default.green(`Generated (valibot): ${files.length} files`),
2657
+ files.map((f) => import_chalk3.default.cyan(f)).join(", ")
2046
2658
  );
2047
2659
  newFiles.push(...files);
2048
2660
  } catch (e) {
@@ -2061,8 +2673,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
2061
2673
  validationOptions(g, cfg, target, { schemaTypes: false })
2062
2674
  );
2063
2675
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
2064
- import_chalk2.default.green(`Generated (arktype): ${files.length} files`),
2065
- files.map((f) => import_chalk2.default.cyan(f)).join(", ")
2676
+ import_chalk3.default.green(`Generated (arktype): ${files.length} files`),
2677
+ files.map((f) => import_chalk3.default.cyan(f)).join(", ")
2066
2678
  );
2067
2679
  newFiles.push(...files);
2068
2680
  } catch (e) {
@@ -2081,8 +2693,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
2081
2693
  validationOptions(g, cfg, target, { schemaTypes: true })
2082
2694
  );
2083
2695
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
2084
- import_chalk2.default.green(`Generated (typebox): ${files.length} files`),
2085
- files.map((f) => import_chalk2.default.cyan(f)).join(", ")
2696
+ import_chalk3.default.green(`Generated (typebox): ${files.length} files`),
2697
+ files.map((f) => import_chalk3.default.cyan(f)).join(", ")
2086
2698
  );
2087
2699
  newFiles.push(...files);
2088
2700
  } catch (e) {
@@ -2097,15 +2709,10 @@ program.command("watch").description("Watch schema and regenerate on changes").o
2097
2709
  );
2098
2710
  const gen = new JsonSchemaGenerator2(analysis);
2099
2711
  const target = g.path ?? "src/validators/json-schema";
2100
- const files = await gen.generate({
2101
- // JSON Schema is data, so nothing here references a type from the schema module.
2102
- ...validationOptions(g, cfg, target, { schemaTypes: false }),
2103
- target: g.target,
2104
- components: g.components
2105
- });
2712
+ const files = await gen.generate(jsonSchemaOptions(g, cfg, target));
2106
2713
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
2107
- import_chalk2.default.green(`Generated (json-schema): ${files.length} files`),
2108
- files.map((f) => import_chalk2.default.cyan(f)).join(", ")
2714
+ import_chalk3.default.green(`Generated (json-schema): ${files.length} files`),
2715
+ files.map((f) => import_chalk3.default.cyan(f)).join(", ")
2109
2716
  );
2110
2717
  newFiles.push(...files);
2111
2718
  } catch (e) {
@@ -2117,8 +2724,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
2117
2724
  const added = newFiles.filter((f) => !lastFiles.includes(f));
2118
2725
  const removed = lastFiles.filter((f) => !newFiles.includes(f));
2119
2726
  opts.json ? console.log(JSON.stringify({ event: "diff", added, removed })) : (() => {
2120
- if (added.length) console.log(import_chalk2.default.blue(`Added: ${added.join(", ")}`));
2121
- if (removed.length) console.log(import_chalk2.default.yellow(`Removed: ${removed.join(", ")}`));
2727
+ if (added.length) console.log(import_chalk3.default.blue(`Added: ${added.join(", ")}`));
2728
+ if (removed.length) console.log(import_chalk3.default.yellow(`Removed: ${removed.join(", ")}`));
2122
2729
  })();
2123
2730
  if (newFiles.length && !opts.json) {
2124
2731
  const reason = opts.pipeline && opts.pipeline !== "all" ? `watch:${opts.pipeline}` : "watch";
@@ -2126,7 +2733,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
2126
2733
  }
2127
2734
  lastFiles = newFiles;
2128
2735
  } catch (e) {
2129
- opts.json ? console.log(JSON.stringify({ event: "error", message: String(e?.message ?? e) })) : console.error(import_chalk2.default.red("Watch pipeline failed:"), e?.message ?? e);
2736
+ opts.json ? console.log(JSON.stringify({ event: "error", message: String(e?.message ?? e) })) : console.error(import_chalk3.default.red("Watch pipeline failed:"), e?.message ?? e);
2130
2737
  }
2131
2738
  };
2132
2739
  const debounced = Number(opts.debounce) || 200;
@@ -2151,12 +2758,12 @@ program.command("watch").description("Watch schema and regenerate on changes").o
2151
2758
  );
2152
2759
  } else {
2153
2760
  console.log(
2154
- import_chalk2.default.gray(
2761
+ import_chalk3.default.gray(
2155
2762
  "Watching:\n " + Array.from(currentTargets).map((p) => path5.relative(process.cwd(), p)).join("\n ")
2156
2763
  )
2157
2764
  );
2158
2765
  }
2159
- watcher.on("add", (p) => trigger(p)).on("change", (p) => trigger(p)).on("unlink", (p) => trigger(p)).on("error", (err) => console.error(import_chalk2.default.red("Watcher error:"), err));
2766
+ watcher.on("add", (p) => trigger(p)).on("change", (p) => trigger(p)).on("unlink", (p) => trigger(p)).on("error", (err) => console.error(import_chalk3.default.red("Watcher error:"), err));
2160
2767
  await run();
2161
2768
  });
2162
2769
  program.command("init").description("Scaffold a drzl.config.ts").option("-y, --yes", "accept defaults").action(async (_opts) => {
@@ -2176,9 +2783,9 @@ program.command("init").description("Scaffold a drzl.config.ts").option("-y, --y
2176
2783
  `;
2177
2784
  try {
2178
2785
  await fs3.writeFile(target, template, { flag: "wx" });
2179
- console.log(import_chalk2.default.green(`Created ${target}`));
2786
+ console.log(import_chalk3.default.green(`Created ${target}`));
2180
2787
  } catch (e) {
2181
- console.error(import_chalk2.default.red("Init failed:"), e?.message ?? e);
2788
+ console.error(import_chalk3.default.red("Init failed:"), e?.message ?? e);
2182
2789
  process.exit(1);
2183
2790
  }
2184
2791
  });
@@ -2186,13 +2793,14 @@ function reportWideColumns(issues) {
2186
2793
  const wide = issues.filter((i) => i.code === "DRZL_ANL_UNKNOWN_COLUMN");
2187
2794
  if (!wide.length) return;
2188
2795
  console.warn(
2189
- import_chalk2.default.yellow(`
2796
+ import_chalk3.default.yellow(`
2190
2797
  ${wide.length} column${wide.length === 1 ? "" : "s"} could not be typed:`)
2191
2798
  );
2192
- for (const i of wide.slice(0, 10)) console.warn(import_chalk2.default.gray(` - ${i.message}`));
2193
- if (wide.length > 10) console.warn(import_chalk2.default.gray(` ... and ${wide.length - 10} more`));
2799
+ for (const i of wide.slice(0, 10)) console.warn(import_chalk3.default.gray(` - ${i.message}`));
2800
+ if (wide.length > 10) console.warn(import_chalk3.default.gray(` ... and ${wide.length - 10} more`));
2194
2801
  const hints = [...new Set(wide.map((i) => i.hint).filter(Boolean))];
2195
- for (const h of hints) console.warn(import_chalk2.default.gray(` ${h}`));
2802
+ for (const h of hints) console.warn(import_chalk3.default.gray(` ${h}`));
2803
+ console.warn(import_chalk3.default.gray(" Run `drzl doctor` for the full report."));
2196
2804
  }
2197
2805
  program.parseAsync(process.argv);
2198
2806
  //# sourceMappingURL=cli.cjs.map