@drzl/validation-core 3.15.1 → 3.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -60,6 +60,11 @@ __export(index_exports, {
60
60
  validateAffix: () => validateAffix
61
61
  });
62
62
  module.exports = __toCommonJS(index_exports);
63
+ var import_node_child_process = require("child_process");
64
+ var import_node_fs2 = require("fs");
65
+ var import_node_module = require("module");
66
+ var import_node_path2 = __toESM(require("path"), 1);
67
+ var import_node_url = require("url");
63
68
 
64
69
  // src/checks.ts
65
70
  var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
@@ -394,12 +399,12 @@ var SUFFIX_RE = /^[A-Za-z0-9_$]+$/;
394
399
  function validateAffix(affix, schemaSuffix) {
395
400
  const issues = [];
396
401
  if (!affix) return issues;
397
- const checkOne = (value, path, kind) => {
402
+ const checkOne = (value, path2, kind) => {
398
403
  if (value === "") return;
399
404
  const ok = kind === "prefix" ? PREFIX_RE.test(value) : SUFFIX_RE.test(value);
400
405
  if (ok) return;
401
406
  issues.push({
402
- path,
407
+ path: path2,
403
408
  message: `${JSON.stringify(value)} cannot appear in a TypeScript identifier. Use only letters, digits, "_" and "$"` + (kind === "prefix" ? ", and do not start with a digit." : ".")
404
409
  });
405
410
  };
@@ -513,32 +518,98 @@ function updateColumns(table) {
513
518
  function selectColumns(table) {
514
519
  return table.columns;
515
520
  }
521
+ var reportedEngines = /* @__PURE__ */ new Set();
522
+ var ENGINE_PACKAGE = { prettier: "prettier", biome: "@biomejs/biome" };
523
+ function reportUnusableFormatter(engine, cause) {
524
+ if (reportedEngines.has(engine)) return;
525
+ reportedEngines.add(engine);
526
+ const pkg = ENGINE_PACKAGE[engine];
527
+ const remedy = engine === "prettier" ? 'Install prettier, which is an optional peer of @drzl/validation-core, or set format.engine to "auto" to accept whatever formatter is present.' : 'Install @biomejs/biome in the project being generated into, or set format.engine to "auto" or "prettier".';
528
+ const reason = cause instanceof Error ? cause.message : String(cause);
529
+ console.warn(
530
+ `[drzl] format.engine is "${engine}" but ${pkg} could not be used, so the generated files were left unformatted. ${remedy} Reason: ${reason}`
531
+ );
532
+ }
533
+ function nearestExistingDir(from) {
534
+ let dir = import_node_path2.default.dirname(import_node_path2.default.resolve(from));
535
+ for (; ; ) {
536
+ try {
537
+ if ((0, import_node_fs2.statSync)(dir).isDirectory()) return dir;
538
+ } catch {
539
+ }
540
+ const parent = import_node_path2.default.dirname(dir);
541
+ if (parent === dir) return dir;
542
+ dir = parent;
543
+ }
544
+ }
545
+ function biomeBinary(startDir) {
546
+ const require_ = (0, import_node_module.createRequire)((0, import_node_url.pathToFileURL)(import_node_path2.default.join(startDir, "noop.js")));
547
+ const manifestPath = require_.resolve("@biomejs/biome/package.json", {
548
+ paths: [startDir, process.cwd()]
549
+ });
550
+ const manifest = JSON.parse((0, import_node_fs2.readFileSync)(manifestPath, "utf8"));
551
+ const relative = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.biome;
552
+ if (typeof relative !== "string") {
553
+ throw new Error(`@biomejs/biome at ${manifestPath} declares no biome binary`);
554
+ }
555
+ const binary = import_node_path2.default.resolve(import_node_path2.default.dirname(manifestPath), relative);
556
+ if (!(0, import_node_fs2.existsSync)(binary)) {
557
+ throw new Error(`@biomejs/biome declares a binary at ${relative}, and there is nothing there`);
558
+ }
559
+ return binary;
560
+ }
561
+ function formatBiome(code, filePath, configAnchor) {
562
+ const cwd = nearestExistingDir(configAnchor);
563
+ const binary = biomeBinary(nearestExistingDir(filePath));
564
+ return new Promise((resolve, reject) => {
565
+ const child = (0, import_node_child_process.spawn)(
566
+ process.execPath,
567
+ [binary, "format", `--stdin-file-path=${import_node_path2.default.basename(filePath)}`],
568
+ { cwd, stdio: ["pipe", "pipe", "pipe"] }
569
+ );
570
+ let out = "";
571
+ let err = "";
572
+ child.stdout.setEncoding("utf8");
573
+ child.stdout.on("data", (chunk) => out += chunk);
574
+ child.stderr.setEncoding("utf8");
575
+ child.stderr.on("data", (chunk) => err += chunk);
576
+ child.on("error", reject);
577
+ child.stdin.on("error", () => {
578
+ });
579
+ child.on("close", (status) => {
580
+ if (status !== 0) {
581
+ const detail = err.trim().split("\n")[0] || out.trim().split("\n")[0] || "no output";
582
+ reject(new Error(`biome format exited with ${status}: ${detail}`));
583
+ return;
584
+ }
585
+ if (out === "" && code !== "") {
586
+ reject(new Error("biome format exited 0 but returned nothing"));
587
+ return;
588
+ }
589
+ resolve(out);
590
+ });
591
+ child.stdin.end(code);
592
+ });
593
+ }
516
594
  async function formatCode(code, filePath, fmt) {
517
595
  if (fmt && fmt.enabled === false) return code;
518
596
  const engine = fmt?.engine ?? "auto";
519
- try {
520
- if (engine === "prettier" || engine === "auto") {
597
+ if (engine === "prettier" || engine === "auto") {
598
+ try {
521
599
  const prettier = await import("prettier");
522
600
  const cfgRef = fmt?.configPath ?? filePath;
523
601
  const cfg = await prettier.resolveConfig(cfgRef).catch(() => null);
524
602
  return prettier.format(code, { ...cfg ?? {}, parser: "typescript", filepath: filePath });
603
+ } catch (err) {
604
+ if (engine === "prettier") reportUnusableFormatter("prettier", err);
525
605
  }
526
- } catch {
527
606
  }
528
- try {
529
- if (engine === "biome" || engine === "auto") {
530
- const dynamicImport = Function("s", "return import(s)");
531
- const biome = await dynamicImport("@biomejs/biome").catch(() => null);
532
- if (biome?.formatContent) {
533
- const res = await biome.formatContent(code, { filePath });
534
- return (res && (res.content || res.formatted)) ?? code;
535
- }
536
- if (biome?.format) {
537
- const res = await biome.format(code, { filePath });
538
- return res ?? code;
539
- }
607
+ if (engine === "biome" || engine === "auto") {
608
+ try {
609
+ return await formatBiome(code, filePath, fmt?.configPath ?? filePath);
610
+ } catch (err) {
611
+ if (engine === "biome") reportUnusableFormatter("biome", err);
540
612
  }
541
- } catch {
542
613
  }
543
614
  return code;
544
615
  }
package/dist/index.d.cts CHANGED
@@ -512,9 +512,21 @@ declare function selectColumns(table: Table): Column[];
512
512
  * anyone installing @drzl/cli. It is `--external` in every build script that can reach it, and
513
513
  * no-bundled-formatter.spec.ts builds those scripts and checks.
514
514
  *
515
- * Neither absence is an error. A consumer with no formatter gets the code as rendered, which is
516
- * valid TypeScript that merely looks worse, and losing generated files at the last step would be
517
- * a far worse trade than losing their whitespace.
515
+ * An absent formatter is never fatal. A consumer with no formatter gets the code as rendered,
516
+ * which is valid TypeScript that merely looks worse, and losing generated files at the last step
517
+ * would be a far worse trade than losing their whitespace.
518
+ *
519
+ * Whether it is worth saying so depends on what was asked for, which is the difference between the
520
+ * two branches below. `engine: 'auto'` asked for whatever happens to be installed, so finding
521
+ * nothing is an outcome and the code comes back unchanged in silence. Naming an engine is a
522
+ * request, and an unmet request that produces neither formatted output nor a message reads as
523
+ * "this is fine" when it is not: the consumer configured something, it did not happen, and nothing
524
+ * in the run says which. So a named engine that cannot be loaded warns on stderr and still returns
525
+ * the code, rather than throwing. Throwing would lose a whole generation over whitespace, which is
526
+ * a bad trade even now that the CLI reports the reason faithfully: it used to answer any throw at
527
+ * all with "<name> generator missing. Install with: npm install @drzl/generator-<name>", naming a
528
+ * package the consumer already had, and it now separates an unresolvable package from a generator
529
+ * that ran and failed.
518
530
  */
519
531
  declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
520
532
 
package/dist/index.d.ts CHANGED
@@ -512,9 +512,21 @@ declare function selectColumns(table: Table): Column[];
512
512
  * anyone installing @drzl/cli. It is `--external` in every build script that can reach it, and
513
513
  * no-bundled-formatter.spec.ts builds those scripts and checks.
514
514
  *
515
- * Neither absence is an error. A consumer with no formatter gets the code as rendered, which is
516
- * valid TypeScript that merely looks worse, and losing generated files at the last step would be
517
- * a far worse trade than losing their whitespace.
515
+ * An absent formatter is never fatal. A consumer with no formatter gets the code as rendered,
516
+ * which is valid TypeScript that merely looks worse, and losing generated files at the last step
517
+ * would be a far worse trade than losing their whitespace.
518
+ *
519
+ * Whether it is worth saying so depends on what was asked for, which is the difference between the
520
+ * two branches below. `engine: 'auto'` asked for whatever happens to be installed, so finding
521
+ * nothing is an outcome and the code comes back unchanged in silence. Naming an engine is a
522
+ * request, and an unmet request that produces neither formatted output nor a message reads as
523
+ * "this is fine" when it is not: the consumer configured something, it did not happen, and nothing
524
+ * in the run says which. So a named engine that cannot be loaded warns on stderr and still returns
525
+ * the code, rather than throwing. Throwing would lose a whole generation over whitespace, which is
526
+ * a bad trade even now that the CLI reports the reason faithfully: it used to answer any throw at
527
+ * all with "<name> generator missing. Install with: npm install @drzl/generator-<name>", naming a
528
+ * package the consumer already had, and it now separates an unresolvable package from a generator
529
+ * that ran and failed.
518
530
  */
519
531
  declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
520
532
 
package/dist/index.js CHANGED
@@ -1,3 +1,10 @@
1
+ // src/index.ts
2
+ import { spawn } from "child_process";
3
+ import { existsSync, readFileSync, statSync } from "fs";
4
+ import { createRequire } from "module";
5
+ import path from "path";
6
+ import { pathToFileURL } from "url";
7
+
1
8
  // src/checks.ts
2
9
  var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
3
10
  var IN_LIST = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.+)\)\s*$/i;
@@ -331,12 +338,12 @@ var SUFFIX_RE = /^[A-Za-z0-9_$]+$/;
331
338
  function validateAffix(affix, schemaSuffix) {
332
339
  const issues = [];
333
340
  if (!affix) return issues;
334
- const checkOne = (value, path, kind) => {
341
+ const checkOne = (value, path2, kind) => {
335
342
  if (value === "") return;
336
343
  const ok = kind === "prefix" ? PREFIX_RE.test(value) : SUFFIX_RE.test(value);
337
344
  if (ok) return;
338
345
  issues.push({
339
- path,
346
+ path: path2,
340
347
  message: `${JSON.stringify(value)} cannot appear in a TypeScript identifier. Use only letters, digits, "_" and "$"` + (kind === "prefix" ? ", and do not start with a digit." : ".")
341
348
  });
342
349
  };
@@ -450,32 +457,98 @@ function updateColumns(table) {
450
457
  function selectColumns(table) {
451
458
  return table.columns;
452
459
  }
460
+ var reportedEngines = /* @__PURE__ */ new Set();
461
+ var ENGINE_PACKAGE = { prettier: "prettier", biome: "@biomejs/biome" };
462
+ function reportUnusableFormatter(engine, cause) {
463
+ if (reportedEngines.has(engine)) return;
464
+ reportedEngines.add(engine);
465
+ const pkg = ENGINE_PACKAGE[engine];
466
+ const remedy = engine === "prettier" ? 'Install prettier, which is an optional peer of @drzl/validation-core, or set format.engine to "auto" to accept whatever formatter is present.' : 'Install @biomejs/biome in the project being generated into, or set format.engine to "auto" or "prettier".';
467
+ const reason = cause instanceof Error ? cause.message : String(cause);
468
+ console.warn(
469
+ `[drzl] format.engine is "${engine}" but ${pkg} could not be used, so the generated files were left unformatted. ${remedy} Reason: ${reason}`
470
+ );
471
+ }
472
+ function nearestExistingDir(from) {
473
+ let dir = path.dirname(path.resolve(from));
474
+ for (; ; ) {
475
+ try {
476
+ if (statSync(dir).isDirectory()) return dir;
477
+ } catch {
478
+ }
479
+ const parent = path.dirname(dir);
480
+ if (parent === dir) return dir;
481
+ dir = parent;
482
+ }
483
+ }
484
+ function biomeBinary(startDir) {
485
+ const require_ = createRequire(pathToFileURL(path.join(startDir, "noop.js")));
486
+ const manifestPath = require_.resolve("@biomejs/biome/package.json", {
487
+ paths: [startDir, process.cwd()]
488
+ });
489
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
490
+ const relative = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.biome;
491
+ if (typeof relative !== "string") {
492
+ throw new Error(`@biomejs/biome at ${manifestPath} declares no biome binary`);
493
+ }
494
+ const binary = path.resolve(path.dirname(manifestPath), relative);
495
+ if (!existsSync(binary)) {
496
+ throw new Error(`@biomejs/biome declares a binary at ${relative}, and there is nothing there`);
497
+ }
498
+ return binary;
499
+ }
500
+ function formatBiome(code, filePath, configAnchor) {
501
+ const cwd = nearestExistingDir(configAnchor);
502
+ const binary = biomeBinary(nearestExistingDir(filePath));
503
+ return new Promise((resolve, reject) => {
504
+ const child = spawn(
505
+ process.execPath,
506
+ [binary, "format", `--stdin-file-path=${path.basename(filePath)}`],
507
+ { cwd, stdio: ["pipe", "pipe", "pipe"] }
508
+ );
509
+ let out = "";
510
+ let err = "";
511
+ child.stdout.setEncoding("utf8");
512
+ child.stdout.on("data", (chunk) => out += chunk);
513
+ child.stderr.setEncoding("utf8");
514
+ child.stderr.on("data", (chunk) => err += chunk);
515
+ child.on("error", reject);
516
+ child.stdin.on("error", () => {
517
+ });
518
+ child.on("close", (status) => {
519
+ if (status !== 0) {
520
+ const detail = err.trim().split("\n")[0] || out.trim().split("\n")[0] || "no output";
521
+ reject(new Error(`biome format exited with ${status}: ${detail}`));
522
+ return;
523
+ }
524
+ if (out === "" && code !== "") {
525
+ reject(new Error("biome format exited 0 but returned nothing"));
526
+ return;
527
+ }
528
+ resolve(out);
529
+ });
530
+ child.stdin.end(code);
531
+ });
532
+ }
453
533
  async function formatCode(code, filePath, fmt) {
454
534
  if (fmt && fmt.enabled === false) return code;
455
535
  const engine = fmt?.engine ?? "auto";
456
- try {
457
- if (engine === "prettier" || engine === "auto") {
536
+ if (engine === "prettier" || engine === "auto") {
537
+ try {
458
538
  const prettier = await import("prettier");
459
539
  const cfgRef = fmt?.configPath ?? filePath;
460
540
  const cfg = await prettier.resolveConfig(cfgRef).catch(() => null);
461
541
  return prettier.format(code, { ...cfg ?? {}, parser: "typescript", filepath: filePath });
542
+ } catch (err) {
543
+ if (engine === "prettier") reportUnusableFormatter("prettier", err);
462
544
  }
463
- } catch {
464
545
  }
465
- try {
466
- if (engine === "biome" || engine === "auto") {
467
- const dynamicImport = Function("s", "return import(s)");
468
- const biome = await dynamicImport("@biomejs/biome").catch(() => null);
469
- if (biome?.formatContent) {
470
- const res = await biome.formatContent(code, { filePath });
471
- return (res && (res.content || res.formatted)) ?? code;
472
- }
473
- if (biome?.format) {
474
- const res = await biome.format(code, { filePath });
475
- return res ?? code;
476
- }
546
+ if (engine === "biome" || engine === "auto") {
547
+ try {
548
+ return await formatBiome(code, filePath, fmt?.configPath ?? filePath);
549
+ } catch (err) {
550
+ if (engine === "biome") reportUnusableFormatter("biome", err);
477
551
  }
478
- } catch {
479
552
  }
480
553
  return code;
481
554
  }
package/package.json CHANGED
@@ -1,17 +1,30 @@
1
1
  {
2
2
  "name": "@drzl/validation-core",
3
- "version": "3.15.1",
3
+ "version": "3.16.1",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
- "main": "dist/index.js",
8
- "types": "dist/index.d.ts",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
20
+ }
21
+ },
9
22
  "files": [
10
23
  "dist"
11
24
  ],
12
25
  "sideEffects": false,
13
26
  "dependencies": {
14
- "@drzl/analyzer": "^1.15.0"
27
+ "@drzl/analyzer": "^1.17.1"
15
28
  },
16
29
  "peerDependencies": {
17
30
  "prettier": ">=3"