@herb-tools/config 0.8.2 → 0.8.4

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.
@@ -12,21 +12,30 @@ import { StringDecoder } from 'node:string_decoder';
12
12
  /** A special constant with type `never` */
13
13
  function $constructor(name, initializer, params) {
14
14
  function init(inst, def) {
15
- var _a;
16
- Object.defineProperty(inst, "_zod", {
17
- value: inst._zod ?? {},
18
- enumerable: false,
19
- });
20
- (_a = inst._zod).traits ?? (_a.traits = new Set());
15
+ if (!inst._zod) {
16
+ Object.defineProperty(inst, "_zod", {
17
+ value: {
18
+ def,
19
+ constr: _,
20
+ traits: new Set(),
21
+ },
22
+ enumerable: false,
23
+ });
24
+ }
25
+ if (inst._zod.traits.has(name)) {
26
+ return;
27
+ }
21
28
  inst._zod.traits.add(name);
22
29
  initializer(inst, def);
23
30
  // support prototype modifications
24
- for (const k in _.prototype) {
25
- if (!(k in inst))
26
- Object.defineProperty(inst, k, { value: _.prototype[k].bind(inst) });
31
+ const proto = _.prototype;
32
+ const keys = Object.keys(proto);
33
+ for (let i = 0; i < keys.length; i++) {
34
+ const k = keys[i];
35
+ if (!(k in inst)) {
36
+ inst[k] = proto[k].bind(inst);
37
+ }
27
38
  }
28
- inst._zod.constr = _;
29
- inst._zod.def = def;
30
39
  }
31
40
  // doesn't work if Parent has a constructor with arguments
32
41
  const Parent = params?.Parent ?? Object;
@@ -161,6 +170,14 @@ function mergeDefs(...defs) {
161
170
  function esc(str) {
162
171
  return JSON.stringify(str);
163
172
  }
173
+ function slugify(input) {
174
+ return input
175
+ .toLowerCase()
176
+ .trim()
177
+ .replace(/[^\w\s-]/g, "")
178
+ .replace(/[\s_-]+/g, "-")
179
+ .replace(/^-+|-+$/g, "");
180
+ }
164
181
  const captureStackTrace = ("captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => { });
165
182
  function isObject$1(data) {
166
183
  return typeof data === "object" && data !== null && !Array.isArray(data);
@@ -186,6 +203,8 @@ function isPlainObject(o) {
186
203
  const ctor = o.constructor;
187
204
  if (ctor === undefined)
188
205
  return true;
206
+ if (typeof ctor !== "function")
207
+ return true;
189
208
  // modified prototype
190
209
  const prot = ctor.prototype;
191
210
  if (isObject$1(prot) === false)
@@ -649,9 +668,6 @@ const cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?:
649
668
  // https://stackoverflow.com/questions/7860392/determine-if-string-is-in-base64-using-javascript
650
669
  const base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
651
670
  const base64url = /^[A-Za-z0-9_-]*$/;
652
- // based on https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
653
- // export const hostname: RegExp = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
654
- const hostname = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
655
671
  // https://blog.stevenlevithan.com/archives/validate-phone-number#r4-3 (regex sans spaces)
656
672
  const e164 = /^\+(?:[0-9]){6,14}[0-9]$/;
657
673
  // const dateSource = `((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))`;
@@ -1142,7 +1158,7 @@ class Doc {
1142
1158
  const version$1 = {
1143
1159
  major: 4,
1144
1160
  minor: 1,
1145
- patch: 12,
1161
+ patch: 13,
1146
1162
  };
1147
1163
 
1148
1164
  const $ZodType = /*@__PURE__*/ $constructor("$ZodType", (inst, def) => {
@@ -1345,7 +1361,7 @@ const $ZodURL = /*@__PURE__*/ $constructor("$ZodURL", (inst, def) => {
1345
1361
  code: "invalid_format",
1346
1362
  format: "url",
1347
1363
  note: "Invalid hostname",
1348
- pattern: hostname.source,
1364
+ pattern: def.hostname.source,
1349
1365
  input: payload.value,
1350
1366
  inst,
1351
1367
  continue: !def.abort,
@@ -1435,18 +1451,12 @@ const $ZodISODuration = /*@__PURE__*/ $constructor("$ZodISODuration", (inst, def
1435
1451
  const $ZodIPv4 = /*@__PURE__*/ $constructor("$ZodIPv4", (inst, def) => {
1436
1452
  def.pattern ?? (def.pattern = ipv4);
1437
1453
  $ZodStringFormat.init(inst, def);
1438
- inst._zod.onattach.push((inst) => {
1439
- const bag = inst._zod.bag;
1440
- bag.format = `ipv4`;
1441
- });
1454
+ inst._zod.bag.format = `ipv4`;
1442
1455
  });
1443
1456
  const $ZodIPv6 = /*@__PURE__*/ $constructor("$ZodIPv6", (inst, def) => {
1444
1457
  def.pattern ?? (def.pattern = ipv6);
1445
1458
  $ZodStringFormat.init(inst, def);
1446
- inst._zod.onattach.push((inst) => {
1447
- const bag = inst._zod.bag;
1448
- bag.format = `ipv6`;
1449
- });
1459
+ inst._zod.bag.format = `ipv6`;
1450
1460
  inst._zod.check = (payload) => {
1451
1461
  try {
1452
1462
  // @ts-ignore
@@ -1516,9 +1526,7 @@ function isValidBase64(data) {
1516
1526
  const $ZodBase64 = /*@__PURE__*/ $constructor("$ZodBase64", (inst, def) => {
1517
1527
  def.pattern ?? (def.pattern = base64);
1518
1528
  $ZodStringFormat.init(inst, def);
1519
- inst._zod.onattach.push((inst) => {
1520
- inst._zod.bag.contentEncoding = "base64";
1521
- });
1529
+ inst._zod.bag.contentEncoding = "base64";
1522
1530
  inst._zod.check = (payload) => {
1523
1531
  if (isValidBase64(payload.value))
1524
1532
  return;
@@ -1542,9 +1550,7 @@ function isValidBase64URL(data) {
1542
1550
  const $ZodBase64URL = /*@__PURE__*/ $constructor("$ZodBase64URL", (inst, def) => {
1543
1551
  def.pattern ?? (def.pattern = base64url);
1544
1552
  $ZodStringFormat.init(inst, def);
1545
- inst._zod.onattach.push((inst) => {
1546
- inst._zod.bag.contentEncoding = "base64url";
1547
- });
1553
+ inst._zod.bag.contentEncoding = "base64url";
1548
1554
  inst._zod.check = (payload) => {
1549
1555
  if (isValidBase64URL(payload.value))
1550
1556
  return;
@@ -1628,9 +1634,9 @@ const $ZodNumber = /*@__PURE__*/ $constructor("$ZodNumber", (inst, def) => {
1628
1634
  return payload;
1629
1635
  };
1630
1636
  });
1631
- const $ZodNumberFormat = /*@__PURE__*/ $constructor("$ZodNumber", (inst, def) => {
1637
+ const $ZodNumberFormat = /*@__PURE__*/ $constructor("$ZodNumberFormat", (inst, def) => {
1632
1638
  $ZodCheckNumberFormat.init(inst, def);
1633
- $ZodNumber.init(inst, def); // no format checksp
1639
+ $ZodNumber.init(inst, def); // no format checks
1634
1640
  });
1635
1641
  const $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def) => {
1636
1642
  $ZodType.init(inst, def);
@@ -1744,7 +1750,7 @@ function handleCatchall(proms, input, payload, ctx, def, inst) {
1744
1750
  const keySet = def.keySet;
1745
1751
  const _catchall = def.catchall._zod;
1746
1752
  const t = _catchall.def.type;
1747
- for (const key of Object.keys(input)) {
1753
+ for (const key in input) {
1748
1754
  if (keySet.has(key))
1749
1755
  continue;
1750
1756
  if (t === "never") {
@@ -2075,11 +2081,13 @@ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
2075
2081
  return payload;
2076
2082
  }
2077
2083
  const proms = [];
2078
- if (def.keyType._zod.values) {
2079
- const values = def.keyType._zod.values;
2084
+ const values = def.keyType._zod.values;
2085
+ if (values) {
2080
2086
  payload.value = {};
2087
+ const recordKeys = new Set();
2081
2088
  for (const key of values) {
2082
2089
  if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
2090
+ recordKeys.add(typeof key === "number" ? key.toString() : key);
2083
2091
  const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
2084
2092
  if (result instanceof Promise) {
2085
2093
  proms.push(result.then((result) => {
@@ -2099,7 +2107,7 @@ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
2099
2107
  }
2100
2108
  let unrecognized;
2101
2109
  for (const key in input) {
2102
- if (!values.has(key)) {
2110
+ if (!recordKeys.has(key)) {
2103
2111
  unrecognized = unrecognized ?? [];
2104
2112
  unrecognized.push(key);
2105
2113
  }
@@ -2394,8 +2402,8 @@ const $ZodReadonly = /*@__PURE__*/ $constructor("$ZodReadonly", (inst, def) => {
2394
2402
  $ZodType.init(inst, def);
2395
2403
  defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues);
2396
2404
  defineLazy(inst._zod, "values", () => def.innerType._zod.values);
2397
- defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
2398
- defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
2405
+ defineLazy(inst._zod, "optin", () => def.innerType?._zod?.optin);
2406
+ defineLazy(inst._zod, "optout", () => def.innerType?._zod?.optout);
2399
2407
  inst._zod.parse = (payload, ctx) => {
2400
2408
  if (ctx.direction === "backward") {
2401
2409
  return def.innerType._zod.run(payload, ctx);
@@ -2443,6 +2451,7 @@ function handleRefineResult(result, payload, input, inst) {
2443
2451
  }
2444
2452
  }
2445
2453
 
2454
+ var _a;
2446
2455
  class $ZodRegistry {
2447
2456
  constructor() {
2448
2457
  this._map = new WeakMap();
@@ -2492,7 +2501,8 @@ class $ZodRegistry {
2492
2501
  function registry() {
2493
2502
  return new $ZodRegistry();
2494
2503
  }
2495
- const globalRegistry = /*@__PURE__*/ registry();
2504
+ (_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
2505
+ const globalRegistry = globalThis.__zod_globalRegistry;
2496
2506
 
2497
2507
  function _string(Class, params) {
2498
2508
  return new Class({
@@ -2899,6 +2909,10 @@ function _toLowerCase() {
2899
2909
  function _toUpperCase() {
2900
2910
  return _overwrite((input) => input.toUpperCase());
2901
2911
  }
2912
+ // slugify
2913
+ function _slugify() {
2914
+ return _overwrite((input) => slugify(input));
2915
+ }
2902
2916
  function _array(Class, element, params) {
2903
2917
  return new Class({
2904
2918
  type: "array",
@@ -3142,6 +3156,7 @@ const _ZodString = /*@__PURE__*/ $constructor("_ZodString", (inst, def) => {
3142
3156
  inst.normalize = (...args) => inst.check(_normalize(...args));
3143
3157
  inst.toLowerCase = () => inst.check(_toLowerCase());
3144
3158
  inst.toUpperCase = () => inst.check(_toUpperCase());
3159
+ inst.slugify = () => inst.check(_slugify());
3145
3160
  });
3146
3161
  const ZodString = /*@__PURE__*/ $constructor("ZodString", (inst, def) => {
3147
3162
  $ZodString.init(inst, def);
@@ -11786,17 +11801,19 @@ function deepMerge(target, source) {
11786
11801
  return output;
11787
11802
  }
11788
11803
 
11789
- var version = "0.8.2";
11804
+ var version = "0.8.4";
11790
11805
  var packageJson = {
11791
11806
  version: version};
11792
11807
 
11793
- var configTemplate = "# This file configures Herb for your project and team.\n# Settings here take precedence over individual editor preferences.\n#\n# Herb is a suite of tools for HTML+ERB templates including:\n# - Linter: Validates templates and enforces best practices\n# - Formatter: Auto-formats templates with intelligent indentation\n# - Language Server: Provides IDE support (VS Code, Zed, Neovim, etc.)\n#\n# Website: https://herb-tools.dev\n# Configuration: https://herb-tools.dev/configuration\n# GitHub Repo: https://github.com/marcoroth/herb\n#\n\nversion: 0.8.2\n\n# files:\n# # Additional patterns beyond the defaults (**.html, **.rhtml, **.html.erb, etc.)\n# include:\n# - '**/*.xml.erb'\n# - 'custom/**/*.html'\n#\n# # Patterns to exclude (can exclude defaults too)\n# exclude:\n# - 'public/**/*'\n# - 'tmp/**/*'\n\nlinter:\n enabled: true\n\n # # Additional patterns beyond the defaults for linting\n # include:\n # - '**/*.xml.erb'\n #\n # # Patterns to exclude from linting\n # exclude:\n # - 'app/views/admin/**/*'\n\n # rules:\n # erb-no-extra-newline:\n # enabled: false\n #\n # # Rules can have 'include', 'only', and 'exclude' patterns\n # some-rule:\n # # Additional patterns to check (additive, ignored when 'only' is present)\n # include:\n # - 'app/components/**/*'\n # # Don't apply this rule to files matching these patterns\n # exclude:\n # - 'app/views/admin/**/*'\n #\n # another-rule:\n # # Only apply this rule to files matching these patterns (overrides all 'include')\n # only:\n # - 'app/views/**/*'\n # # Exclude still applies even with 'only'\n # exclude:\n # - 'app/views/admin/**/*'\n\nformatter:\n enabled: false\n indentWidth: 2\n maxLineLength: 80\n\n # # Additional patterns beyond the defaults for formatting\n # include:\n # - '**/*.xml.erb'\n #\n # # Patterns to exclude from formatting\n # exclude:\n # - 'app/views/admin/**/*'\n\n # # Rewriters modify templates during formatting\n # rewriter:\n # # Pre-format rewriters (modify AST before formatting)\n # pre:\n # - tailwind-class-sorter\n # # Post-format rewriters (modify formatted output string)\n # post: []\n";
11808
+ var configTemplate = "# This file configures Herb for your project and team.\n# Settings here take precedence over individual editor preferences.\n#\n# Herb is a suite of tools for HTML+ERB templates including:\n# - Linter: Validates templates and enforces best practices\n# - Formatter: Auto-formats templates with intelligent indentation\n# - Language Server: Provides IDE support (VS Code, Zed, Neovim, etc.)\n#\n# Website: https://herb-tools.dev\n# Configuration: https://herb-tools.dev/configuration\n# GitHub Repo: https://github.com/marcoroth/herb\n#\n\nversion: 0.8.4\n\n# files:\n# # Additional patterns beyond the defaults (**.html, **.rhtml, **.html.erb, etc.)\n# include:\n# - '**/*.xml.erb'\n# - 'custom/**/*.html'\n#\n# # Patterns to exclude (can exclude defaults too)\n# exclude:\n# - 'public/**/*'\n# - 'tmp/**/*'\n\nlinter:\n enabled: true\n\n # # Additional patterns beyond the defaults for linting\n # include:\n # - '**/*.xml.erb'\n #\n # # Patterns to exclude from linting\n # exclude:\n # - 'app/views/admin/**/*'\n\n # rules:\n # erb-no-extra-newline:\n # enabled: false\n #\n # # Rules can have 'include', 'only', and 'exclude' patterns\n # some-rule:\n # # Additional patterns to check (additive, ignored when 'only' is present)\n # include:\n # - 'app/components/**/*'\n # # Don't apply this rule to files matching these patterns\n # exclude:\n # - 'app/views/admin/**/*'\n #\n # another-rule:\n # # Only apply this rule to files matching these patterns (overrides all 'include')\n # only:\n # - 'app/views/**/*'\n # # Exclude still applies even with 'only'\n # exclude:\n # - 'app/views/admin/**/*'\n\nformatter:\n enabled: false\n indentWidth: 2\n maxLineLength: 80\n\n # # Additional patterns beyond the defaults for formatting\n # include:\n # - '**/*.xml.erb'\n #\n # # Patterns to exclude from formatting\n # exclude:\n # - 'app/views/admin/**/*'\n\n # # Rewriters modify templates during formatting\n # rewriter:\n # # Pre-format rewriters (modify AST before formatting)\n # pre:\n # - tailwind-class-sorter\n # # Post-format rewriters (modify formatted output string)\n # post: []\n";
11794
11809
 
11795
11810
  const DEFAULT_VERSION = packageJson.version;
11796
11811
  class Config {
11797
11812
  static configPath = ".herb.yml";
11798
11813
  static PROJECT_INDICATORS = [
11799
11814
  '.git',
11815
+ '.herb',
11816
+ '.herb.yml',
11800
11817
  'Gemfile',
11801
11818
  'package.json',
11802
11819
  'Rakefile',
@@ -11841,10 +11858,10 @@ class Config {
11841
11858
  }
11842
11859
  /**
11843
11860
  * Check if the formatter is enabled.
11844
- * @returns true if formatter is enabled (default), false if explicitly disabled
11861
+ * @returns true if formatter is explicitly enabled, false otherwise (default)
11845
11862
  */
11846
11863
  get isFormatterEnabled() {
11847
- return this.config.formatter?.enabled ?? Config.getDefaultConfig().formatter?.enabled ?? true;
11864
+ return this.config.formatter?.enabled ?? Config.getDefaultConfig().formatter?.enabled ?? false;
11848
11865
  }
11849
11866
  /**
11850
11867
  * Check if a specific rule is disabled.
@@ -12088,6 +12105,61 @@ class Config {
12088
12105
  return false;
12089
12106
  }
12090
12107
  }
12108
+ /**
12109
+ * Find the project root by walking up from a given path.
12110
+ * Looks for .herb.yml first, then falls back to project indicators
12111
+ * (.git, Gemfile, package.json, etc.)
12112
+ *
12113
+ * @param startPath - File or directory path to start searching from
12114
+ * @returns The project root directory path
12115
+ */
12116
+ static async findProjectRoot(startPath) {
12117
+ const { projectRoot } = await this.findConfigFile(startPath);
12118
+ return projectRoot;
12119
+ }
12120
+ /**
12121
+ * Synchronous version of findProjectRoot for use in CLIs.
12122
+ *
12123
+ * @param startPath - File or directory path to start searching from
12124
+ * @returns The project root directory path
12125
+ */
12126
+ static findProjectRootSync(startPath) {
12127
+ const fsSync = require('fs');
12128
+ let currentPath = path$1.resolve(startPath);
12129
+ try {
12130
+ const stats = fsSync.statSync(currentPath);
12131
+ if (stats.isFile()) {
12132
+ currentPath = path$1.dirname(currentPath);
12133
+ }
12134
+ }
12135
+ catch {
12136
+ currentPath = path$1.resolve(process.cwd());
12137
+ }
12138
+ while (true) {
12139
+ const configPath = path$1.join(currentPath, this.configPath);
12140
+ try {
12141
+ fsSync.accessSync(configPath);
12142
+ return currentPath;
12143
+ }
12144
+ catch {
12145
+ // Config not in this directory, continue
12146
+ }
12147
+ for (const indicator of this.PROJECT_INDICATORS) {
12148
+ try {
12149
+ fsSync.accessSync(path$1.join(currentPath, indicator));
12150
+ return currentPath;
12151
+ }
12152
+ catch {
12153
+ // Indicator not found, continue checking
12154
+ }
12155
+ }
12156
+ const parentPath = path$1.dirname(currentPath);
12157
+ if (parentPath === currentPath) {
12158
+ return process.cwd();
12159
+ }
12160
+ currentPath = parentPath;
12161
+ }
12162
+ }
12091
12163
  /**
12092
12164
  * Read raw YAML content from a config file.
12093
12165
  * Handles both explicit .herb.yml paths and directory paths.
@@ -12629,7 +12701,7 @@ class Config {
12629
12701
  rules: {}
12630
12702
  },
12631
12703
  formatter: {
12632
- enabled: true,
12704
+ enabled: false,
12633
12705
  indentWidth: 2,
12634
12706
  maxLineLength: 80
12635
12707
  }