wrec 0.29.2 → 0.29.3

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.
@@ -286,13 +286,13 @@ var W = class e extends m {
286
286
  constructor() {
287
287
  super(), this.attachShadow({ mode: "open" });
288
288
  let e = this.#n;
289
- this.#C("attrToPropMap") || (e.attrToPropMap = /* @__PURE__ */ new Map()), this.#C("computedGraph") || (e.computedGraph = null), this.#C("properties") || (e.properties = {}), this.#C("propToAttrMap") || (e.propToAttrMap = /* @__PURE__ */ new Map()), this.#C("propToComputedMap") || (e.propToComputedMap = /* @__PURE__ */ new Map()), this.#C("propToExprsMap") || (e.propToExprsMap = /* @__PURE__ */ new Map()), this.#C("registeredComputedProps") || (e.registeredComputedProps = /* @__PURE__ */ new Set());
289
+ this.#C("attrToPropMap") || (e.attrToPropMap = /* @__PURE__ */ new Map()), this.#C("computedGraph") || (e.computedGraph = null), this.#C("computedPropsRegistered") || (e.computedPropsRegistered = !1), this.#C("properties") || (e.properties = {}), this.#C("propToAttrMap") || (e.propToAttrMap = /* @__PURE__ */ new Map()), this.#C("propToComputedMap") || (e.propToComputedMap = /* @__PURE__ */ new Map()), this.#C("propToExprsMap") || (e.propToExprsMap = /* @__PURE__ */ new Map()), this.#C("registeredComputedProps") || (e.registeredComputedProps = /* @__PURE__ */ new Set());
290
290
  }
291
291
  attributeChangedCallback(t, n, r) {
292
292
  t === "disabled" && this.#h();
293
293
  let i = e.getPropName(t);
294
294
  if (!this.#T(i) && this.#w(i)) {
295
- let e = this.#L(i, r);
295
+ let e = this.#R(i, r);
296
296
  this[i] = e;
297
297
  let t = this.#i[i];
298
298
  t && this.setFormValue(t, String(e));
@@ -308,7 +308,7 @@ var W = class e extends m {
308
308
  }
309
309
  let r = this.#x(Object.keys(e));
310
310
  for (let [e, i] of r) {
311
- this.#j(e, this.#v(i));
311
+ this.#M(e, this.#v(i));
312
312
  let r = t.get(e) ?? [];
313
313
  for (let e of r) n.add(e);
314
314
  }
@@ -329,36 +329,37 @@ var W = class e extends m {
329
329
  this[t] = n;
330
330
  }
331
331
  async connectedCallback() {
332
- this.#U(), this.#p(), await this.#d(), this.hasAttribute("disabled") && this.#h(), this.#q(this.shadowRoot), this.#E(this.shadowRoot), this.#H(), this.#f(), this.ready();
332
+ this.#W(), this.#p(), await this.#d(), this.hasAttribute("disabled") && this.#h(), this.#J(this.shadowRoot), this.#E(this.shadowRoot), this.#U(), this.#f(), this.ready();
333
333
  }
334
334
  #f() {
335
335
  let { properties: e } = this.#n;
336
- for (let [t, { computed: n }] of Object.entries(e)) n && this.#j(t, this.#v(n));
336
+ for (let [t, { computed: n }] of Object.entries(e)) n && this.#M(t, this.#v(n));
337
337
  }
338
338
  #p() {
339
339
  let { observedAttributes: e, properties: t } = this.#n;
340
340
  for (let [n, r] of Object.entries(t)) r.computed || this.#m(n, r, e);
341
341
  for (let [n, r] of Object.entries(t)) r.computed && this.#m(n, r, e);
342
+ this.#A();
342
343
  }
343
344
  #m(t, n, r) {
344
345
  if (t === "class" || t === "style") throw new g(`"${t}" is a reserved property`);
345
346
  let i = e.getAttrName(t), a = this.hasAttribute(i);
346
- n.required && !a && this.#P(this, i, "is a required attribute");
347
+ n.required && !a && this.#F(this, i, "is a required attribute");
347
348
  let o = n.value;
348
349
  this.hasOwnProperty(t) && (o = this[t], delete this[t]);
349
- let { type: c } = n, l = c === Boolean ? o || a : r.includes(i) && a ? this.#I(t, i) : o ?? k(n), u = "#" + t;
350
- this[u] = l, n.computed && this.#A(t, n), Object.defineProperty(this, t, {
350
+ let { type: c } = n, l = c === Boolean ? o || a : r.includes(i) && a ? this.#L(t, i) : o ?? k(n), u = "#" + t;
351
+ this[u] = l, Object.defineProperty(this, t, {
351
352
  enumerable: !0,
352
353
  get() {
353
354
  return this[u];
354
355
  },
355
356
  set(e) {
356
- n.computed && !this.#t.has(t) && this.#P(null, t, "is a computed property and cannot be set directly"), c === Number && typeof e == "string" && (e = z(e));
357
+ n.computed && !this.#t.has(t) && this.#F(null, t, "is a computed property and cannot be set directly"), c === Number && typeof e == "string" && (e = z(e));
357
358
  let r = this[u];
358
359
  if (e === r) return;
359
- this.#K(t, c, e), this[u] = e;
360
+ this.#q(t, c, e), this[u] = e;
360
361
  let a = this.#l.get(t);
361
- a && s(a.state, a.stateProp, e), this.#R(t, c, e, i), this.#e || (this.#z(t), this.#k(t)), this.#V(t, e);
362
+ a && s(a.state, a.stateProp, e), this.#z(t, c, e, i), this.#e || (this.#B(t), this.#k(t)), this.#H(t, e);
362
363
  let o = this.#i[t];
363
364
  o && this.setFormValue(o, String(e)), this.propertyChangedCallback(t, r, e), n.dispatch && this.dispatch("change", {
364
365
  tagName: this.localName,
@@ -392,17 +393,17 @@ var W = class e extends m {
392
393
  for (let r of t.getAttributeNames()) {
393
394
  let i = t.getAttribute(r);
394
395
  if (r === "ref") {
395
- this.#N(t, i);
396
+ this.#P(t, i);
396
397
  continue;
397
398
  }
398
399
  let a = this.#O(t, i);
399
400
  if (a) {
400
401
  let i = this[a];
401
- i === void 0 && this.#F(t, r, a);
402
+ i === void 0 && this.#I(t, r, a);
402
403
  let [o, s] = r.split(":"), c = e.getPropName(o), l = this.#T(a);
403
- n && t.#T(c) || (t[c] = i), o === "value" && (s ? (t["on" + s] === void 0 && this.#P(t, r, "refers to an unsupported event name"), t.setAttribute(o, this[a])) : s = "change"), n && !l && t.#c.set(e.getPropName(o), a);
404
+ n && t.#T(c) || (t[c] = i), o === "value" && (s ? (t["on" + s] === void 0 && this.#F(t, r, "refers to an unsupported event name"), t.setAttribute(o, this[a])) : s = "change"), n && !l && t.#c.set(e.getPropName(o), a);
404
405
  }
405
- this.#M(i, t, r);
406
+ this.#N(i, t, r);
406
407
  }
407
408
  }
408
409
  #_(e) {
@@ -414,7 +415,7 @@ var W = class e extends m {
414
415
  r.add(t);
415
416
  continue;
416
417
  }
417
- if (t instanceof HTMLElement) this.#B(t, e);
418
+ if (t instanceof HTMLElement) this.#V(t, e);
418
419
  else if (!(t instanceof CSSStyleRule)) {
419
420
  let { element: n, attrName: r } = t;
420
421
  n instanceof CSSStyleRule ? n.style.setProperty(r, e) : V(n, r, e);
@@ -438,13 +439,13 @@ var W = class e extends m {
438
439
  let t = Array.from(e.style);
439
440
  for (let n of t) if (n.startsWith("--")) {
440
441
  let t = e.style.getPropertyValue(n);
441
- this.#M(t, e, n);
442
+ this.#N(t, e, n);
442
443
  }
443
444
  }
444
445
  } else {
445
446
  let t = "";
446
447
  if (F(e)) {
447
- this.#M(e.textContent, e);
448
+ this.#N(e.textContent, e);
448
449
  let n = e.textContent?.match(x);
449
450
  n && (t = n[1]);
450
451
  } else {
@@ -453,7 +454,7 @@ var W = class e extends m {
453
454
  }
454
455
  if (t) {
455
456
  let n = this.#O(e, t);
456
- n && F(e) ? e.textContent = this[n] : this.#M(t, e);
457
+ n && F(e) ? e.textContent = this[n] : this.#N(t, e);
457
458
  }
458
459
  }
459
460
  }
@@ -541,7 +542,7 @@ var W = class e extends m {
541
542
  if (!C.test(r)) return;
542
543
  let i = I(e) || F(e), [a, o] = (t ?? "").split(":");
543
544
  if (!(i && a === "value" || F(e))) return;
544
- o ? e["on" + o] === void 0 && this.#P(e, t, "refers to an unsupported event name") : o = "change";
545
+ o ? e["on" + o] === void 0 && this.#F(e, t, "refers to an unsupported event name") : o = "change";
545
546
  let s = M(r);
546
547
  e.addEventListener(o, (e) => {
547
548
  let { target: t } = e;
@@ -566,7 +567,7 @@ var W = class e extends m {
566
567
  #D() {
567
568
  if (this.#n.formAssociated || this.closest("form") === null) return;
568
569
  let e = this.#n.name;
569
- this.#P(this, void 0, `inside form, class ${e} requires "static formAssociated = true;"`);
570
+ this.#F(this, void 0, `inside form, class ${e} requires "static formAssociated = true;"`);
570
571
  }
571
572
  static get observedAttributes() {
572
573
  let t = Object.entries(this.properties || {}).filter(([e, t]) => !t.computed).map(([t]) => e.getAttrName(t));
@@ -576,26 +577,32 @@ var W = class e extends m {
576
577
  #O(e, t) {
577
578
  if (!t || !C.test(t)) return;
578
579
  let n = M(t);
579
- return this[n] === void 0 && this.#F(e, "", n), n;
580
+ return this[n] === void 0 && this.#I(e, "", n), n;
580
581
  }
581
582
  #k(e) {
582
583
  let t = this.#n.propToExprsMap.get(e) || [];
583
584
  this.#_(t);
584
585
  }
585
586
  ready() {}
586
- #A(e, t) {
587
+ #A() {
588
+ let e = this.#n;
589
+ if (!e.computedPropsRegistered) {
590
+ e.computedPropsRegistered = !0, e.computedGraph = null;
591
+ for (let [t, n] of Object.entries(e.properties)) n.computed && this.#j(t, n);
592
+ }
593
+ }
594
+ #j(e, t) {
587
595
  let n = this.#n;
588
- if (n.registeredComputedProps.has(e)) return;
589
- n.registeredComputedProps.add(e), n.computedGraph = null;
596
+ n.registeredComputedProps.add(e);
590
597
  let r = n.propToComputedMap;
591
598
  function i(t, n) {
592
599
  let i = r.get(t);
593
600
  i || (i = [], r.set(t, i)), i.push([e, n]);
594
601
  }
595
- let { computed: a } = t, o = a.match(w) || [];
596
- for (let t of o) {
597
- let n = M(t);
598
- this[n] === void 0 && this.#F(null, e, n), typeof this[n] != "function" && i(n, a);
602
+ let a = t.computed;
603
+ for (let t of a.matchAll(w)) {
604
+ let n = M(t[0]);
605
+ this[n] === void 0 && this.#I(null, e, n), typeof this[n] != "function" && i(n, a);
599
606
  }
600
607
  for (let t of a.matchAll(b)) {
601
608
  let r = t[1];
@@ -603,7 +610,7 @@ var W = class e extends m {
603
610
  for (let [e, t] of Object.entries(n.properties)) H(t.usedBy)?.includes(r) && i(e, a);
604
611
  }
605
612
  }
606
- #j(e, t) {
613
+ #M(e, t) {
607
614
  this.#t.add(e);
608
615
  try {
609
616
  this[e] = t;
@@ -611,9 +618,9 @@ var W = class e extends m {
611
618
  this.#t.delete(e);
612
619
  }
613
620
  }
614
- #M(e, t, n = void 0) {
621
+ #N(e, t, n = void 0) {
615
622
  if (!e) return;
616
- let r = this.#W(t, n, e);
623
+ let r = this.#G(t, n, e);
617
624
  if (!r) {
618
625
  let r = e.replaceAll("this..", "this.");
619
626
  n ? V(t, n, r) : "textContent" in t && (t.textContent = r);
@@ -636,11 +643,11 @@ var W = class e extends m {
636
643
  attrName: n
637
644
  } : t), t instanceof HTMLElement && this.#S(t, n, r);
638
645
  let o = this.#v(e);
639
- n ? V(t, n, o) : this.#B(t, o);
646
+ n ? V(t, n, o) : this.#V(t, o);
640
647
  }
641
- #N(e, t) {
648
+ #P(e, t) {
642
649
  let n = t?.trim() ?? "", r = this.#n.properties[n];
643
- r || this.#F(e, "ref", n), r.type !== m && this.#P(e, "ref", `refers to property "${n}" whose type is not HTMLElement`), this[n] && this.#P(e, "ref", `is a duplicate reference to the property "${n}"`), this[n] = e, e.removeAttribute("ref");
650
+ r || this.#I(e, "ref", n), r.type !== m && this.#F(e, "ref", `refers to property "${n}" whose type is not HTMLElement`), this[n] && this.#F(e, "ref", `is a duplicate reference to the property "${n}"`), this[n] = e, e.removeAttribute("ref");
644
651
  }
645
652
  setAttributeSafe(e, t) {
646
653
  this.hasAttribute(e) || this.setAttribute(e, t);
@@ -651,24 +658,24 @@ var W = class e extends m {
651
658
  static ssr(e = {}) {
652
659
  throw new g("SSR is not available in the browser build.");
653
660
  }
654
- #P(e, t, n) {
661
+ #F(e, t, n) {
655
662
  let r = e instanceof HTMLElement ? e.localName : "CSS rule";
656
663
  throw new g(`component ${this.#n.elementName}` + (e ? `, element "${r}"` : "") + (t ? `, attribute "${t}"` : "") + ` ${n}`);
657
664
  }
658
- #F(e, t, n) {
659
- this.#P(e, t, `refers to missing property "${n}"`);
665
+ #I(e, t, n) {
666
+ this.#F(e, t, `refers to missing property "${n}"`);
660
667
  }
661
- #I(e, t) {
662
- return this.#L(e, this.getAttribute(t));
668
+ #L(e, t) {
669
+ return this.#R(e, this.getAttribute(t));
663
670
  }
664
- #L(t, n) {
671
+ #R(t, n) {
665
672
  if (n?.match(w)) return n;
666
673
  let r = this.#n.properties[t], { type: i, values: a } = r;
667
- if (i || this.#P(null, t, "does not specify its type"), n === null) return i === Boolean ? !1 : k(r);
674
+ if (i || this.#F(null, t, "does not specify its type"), n === null) return i === Boolean ? !1 : k(r);
668
675
  if (i === String) {
669
676
  if (a && !a.includes(n)) {
670
677
  let e = a.map((e) => `"${e}"`).join(", ");
671
- this.#P(null, t, `must be one of ${e}`);
678
+ this.#F(null, t, `must be one of ${e}`);
672
679
  }
673
680
  return n;
674
681
  }
@@ -677,25 +684,25 @@ var W = class e extends m {
677
684
  if (n === "true") return !0;
678
685
  if (n === "false" || n === "null") return !1;
679
686
  let r = e.getAttrName(t);
680
- return n && n !== r && this.#P(null, t, "is a Boolean attribute, so its value must match attribute name or be missing"), n === "" || n === r;
687
+ return n && n !== r && this.#F(null, t, "is a Boolean attribute, so its value must match attribute name or be missing"), n === "" || n === r;
681
688
  }
682
689
  }
683
- #R(e, t, n, r) {
684
- P(n) && !this.#T(e) && n !== (t === Boolean ? this.hasAttribute(r) : this.#I(e, r)) && B(this, r || e, n);
690
+ #z(e, t, n, r) {
691
+ P(n) && !this.#T(e) && n !== (t === Boolean ? this.hasAttribute(r) : this.#L(e, r)) && B(this, r || e, n);
685
692
  }
686
- #z(e) {
687
- for (let [t, n] of this.#x([e])) this.#j(t, this.#v(n));
693
+ #B(e) {
694
+ for (let [t, n] of this.#x([e])) this.#M(t, this.#v(n));
688
695
  }
689
- #B(e, t) {
696
+ #V(e, t) {
690
697
  if (t === void 0) return;
691
698
  let n = e instanceof HTMLElement;
692
699
  Array.isArray(t) && (t = t.join(""));
693
700
  let r = typeof t;
694
- r !== "string" && r !== "number" && this.#P(e, void 0, " computed content is not a string or number");
701
+ r !== "string" && r !== "number" && this.#F(e, void 0, " computed content is not a string or number");
695
702
  let i = String(t);
696
- e instanceof HTMLElement && F(e) ? e.value = i : n && r === "string" && i.trim().startsWith("<") ? (e.innerHTML = d(i), this.#q(e), this.#E(e)) : n && (e.textContent = i);
703
+ e instanceof HTMLElement && F(e) ? e.value = i : n && r === "string" && i.trim().startsWith("<") ? (e.innerHTML = d(i), this.#J(e), this.#E(e)) : n && (e.textContent = i);
697
704
  }
698
- #V(e, t) {
705
+ #H(e, t) {
699
706
  let n = this.#c.get(e);
700
707
  if (!n) return;
701
708
  let r = this.getRootNode();
@@ -705,7 +712,7 @@ var W = class e extends m {
705
712
  let a = i;
706
713
  a[n] = t;
707
714
  }
708
- #H() {
715
+ #U() {
709
716
  let e = this.#n;
710
717
  function t() {
711
718
  let t = f();
@@ -735,7 +742,7 @@ var W = class e extends m {
735
742
  t = {};
736
743
  for (let n of Object.keys(e)) t[n] = n;
737
744
  }
738
- this.#G(e, t);
745
+ this.#K(e, t);
739
746
  for (let [n, r] of Object.entries(t)) if (this.#w(r)) {
740
747
  let t = o(e, n);
741
748
  t !== void 0 && (this[r] = t), this.#l.set(r, {
@@ -757,7 +764,7 @@ var W = class e extends m {
757
764
  unsubscribe: i
758
765
  });
759
766
  }
760
- #U() {
767
+ #W() {
761
768
  let t = new Set(Object.keys(this.#n.properties));
762
769
  for (let n of this.getAttributeNames()) if (!p.has(n) && !n.startsWith("on") && n !== "ref") {
763
770
  if (n === "form-assoc") {
@@ -769,38 +776,38 @@ var W = class e extends m {
769
776
  this.#D();
770
777
  continue;
771
778
  }
772
- this.#P(null, n, "is not a supported attribute");
779
+ this.#F(null, n, "is not a supported attribute");
773
780
  }
774
781
  }
775
782
  }
776
- #W(e, t, n) {
783
+ #G(e, t, n) {
777
784
  let r = n.match(w);
778
785
  if (r) return r.forEach((n) => {
779
786
  let r = M(n);
780
- this[r] === void 0 && this.#F(e, t, r);
787
+ this[r] === void 0 && this.#I(e, t, r);
781
788
  }), r;
782
789
  }
783
- #G(e, t) {
790
+ #K(e, t) {
784
791
  for (let [n, r] of Object.entries(t)) {
785
792
  let t = o(e, n);
786
- t === void 0 && this.#P(this, void 0, `invalid state path "${n}"`), t = this[r], this.#w(r) || this.#P(null, r, "refers to missing property in useState map");
793
+ t === void 0 && this.#F(this, void 0, `invalid state path "${n}"`), t = this[r], this.#w(r) || this.#F(null, r, "refers to missing property in useState map");
787
794
  }
788
795
  }
789
- #K(e, t, n) {
796
+ #q(e, t, n) {
790
797
  let { values: r } = this.#n.properties[e];
791
798
  if (r) {
792
799
  let i;
793
- t === String ? typeof n == "string" ? r.includes(n) || (i = `must be one of ${r.map((e) => `"${e}"`).join(", ")}`) : i = `value is a ${typeof n}, but type is String` : i = "declares allowed values, but its type is not String", i && this.#P(null, e, i);
800
+ t === String ? typeof n == "string" ? r.includes(n) || (i = `must be one of ${r.map((e) => `"${e}"`).join(", ")}`) : i = `value is a ${typeof n}, but type is String` : i = "declares allowed values, but its type is not String", i && this.#F(null, e, i);
794
801
  }
795
802
  if (n instanceof t) return;
796
803
  let i = typeof n;
797
804
  if (i === "object") {
798
805
  let { constructor: r } = n;
799
- i = r.name, r !== t && this.#P(null, e, `was set to a ${i}, but must be a ${t.name}`);
806
+ i = r.name, r !== t && this.#F(null, e, `was set to a ${i}, but must be a ${t.name}`);
800
807
  }
801
- i !== t.name.toLowerCase() && this.#P(null, e, `was set to a ${i}, but must be a ${t.name}`);
808
+ i !== t.name.toLowerCase() && this.#F(null, e, `was set to a ${i}, but must be a ${t.name}`);
802
809
  }
803
- #q(e) {
810
+ #J(e) {
804
811
  let t = Array.from(e.querySelectorAll("*"));
805
812
  for (let e of t) {
806
813
  let t = [];
@@ -810,9 +817,9 @@ var W = class e extends m {
810
817
  let i = r.slice(2);
811
818
  i = i[0].toLowerCase() + i.slice(1).toLowerCase();
812
819
  let a = n.value;
813
- this.#W(e, r, a);
820
+ this.#G(e, r, a);
814
821
  let o;
815
- typeof this[a] == "function" ? o = (e) => this[a](e) : (this.#W(e, r, a), o = () => this.#v(a)), e.addEventListener(i, o), t.push(r);
822
+ typeof this[a] == "function" ? o = (e) => this[a](e) : (this.#G(e, r, a), o = () => this.#v(a)), e.addEventListener(i, o), t.push(r);
816
823
  }
817
824
  }
818
825
  for (let n of t) e.removeAttribute(n);
@@ -51,6 +51,7 @@ export declare abstract class Wrec extends HTMLElementBase {
51
51
  private static methodToExprsMap;
52
52
  private static propToExprsMap;
53
53
  private static registeredComputedProps;
54
+ private static computedPropsRegistered;
54
55
  private static template;
55
56
  [key: string]: any;
56
57
  static define(elementName: string): void;
@@ -1,4 +1,4 @@
1
- import { a as e, i as t, n, r, t as i } from "./wrec-DHp2V7DA.js";
1
+ import { a as e, i as t, n, r, t as i } from "./wrec-DEac2MyH.js";
2
2
  //#region \0rolldown/runtime.js
3
3
  var a = Object.defineProperty, o = Object.getOwnPropertyDescriptor, s = Object.getOwnPropertyNames, c = Object.prototype.hasOwnProperty, l = (e, t) => () => (e && (t = e(e = 0)), t), u = (e, t) => () => (t || e((t = { exports: {} }).exports, t), t.exports), d = (e, t) => {
4
4
  let n = {};
package/dist/wrec.d.ts CHANGED
@@ -51,6 +51,7 @@ export declare abstract class Wrec extends HTMLElementBase {
51
51
  private static methodToExprsMap;
52
52
  private static propToExprsMap;
53
53
  private static registeredComputedProps;
54
+ private static computedPropsRegistered;
54
55
  private static template;
55
56
  [key: string]: any;
56
57
  static define(elementName: string): void;
package/dist/wrec.es.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, t as i } from "./wrec-DHp2V7DA.js";
1
+ import { a as e, i as t, n, r, t as i } from "./wrec-DEac2MyH.js";
2
2
  export { i as Wrec, e as WrecState, n as createElement, r as css, t as html };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "wrec",
3
3
  "description": "a small library that greatly simplifies building web components",
4
4
  "author": "R. Mark Volkmann",
5
- "version": "0.29.2",
5
+ "version": "0.29.3",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
@@ -46,7 +46,7 @@
46
46
  "preview": "vite preview",
47
47
  "test": "node port-check.js && playwright test",
48
48
  "testui": "playwright test --ui",
49
- "unittest": "vitest"
49
+ "unittest": "vitest run"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@playwright/test": "^1.59.1",
package/scripts/lint.js CHANGED
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // This lints a given source file that defines a wrec component.
4
+ // To run this, enter `npx wrec-lint [--verbose] {file-path}`
5
+ //
6
+ // Include --verbose to output lists of the
7
+ // properties and expressions that are found.
8
+
3
9
  // This linter checks Wrec components for these issues:
4
10
  // - undefined properties accessed in expressions
5
11
  // - undefined instance methods called in expressions
@@ -1380,7 +1386,7 @@ function main() {
1380
1386
  const positionalArgs = args.filter(arg => arg !== '--verbose');
1381
1387
 
1382
1388
  if (positionalArgs.length > 1) {
1383
- fail('usage: node scripts/wrec-lint.js [file.js|file.ts]');
1389
+ fail('usage: node scripts/wrec-lint.js [--verbose] {file-path}');
1384
1390
  }
1385
1391
 
1386
1392
  const [filePath] = positionalArgs;
@@ -1,50 +1,62 @@
1
1
  #!/usr/bin/env node
2
+ // This script inspects a given Wrec component source file and
3
+ // determines the proper values for property config `usedBy` properties.
4
+ // Each value is a list of methods that use the property
5
+ // or a single method name.
6
+ // It uses the TypeScript compiler to parse the file,
7
+ // discover which expressions call which methods,
8
+ // trace property usage through those call chains, and
9
+ // output or update the `usedBy` properties`.
10
+ //
11
+ // To run this, enter `npx wrec-usedby [--dry] [file-path]`
12
+ // If no file-path is specified, the script runs on
13
+ // all .js and .ts files in and below the current directory.
14
+ //
15
+ // Include the --dry flag for a dry run where `usedBy` values are output,
16
+ // but no files are modified.
2
17
 
3
18
  import fs from 'node:fs';
4
19
  import path from 'node:path';
20
+ import {fileURLToPath} from 'node:url';
5
21
  import ts from 'typescript';
6
22
 
7
- const args = process.argv.slice(2);
8
- const dry = args.includes('--dry');
9
- const verbose = args.includes('--verbose');
10
- const inputPaths = args.filter(arg => !arg.startsWith('--'));
11
-
12
- if (args.includes('--write')) {
13
- console.error('--write is no longer supported; writing is now the default.');
14
- process.exit(1);
15
- }
16
-
17
- if (args.includes('--check')) {
18
- console.error('Use --dry instead of --check.');
19
- process.exit(1);
20
- }
21
-
22
- if (inputPaths.length !== 1) {
23
- console.error(
24
- 'Specify a single source file, e.g. npx wrec-usedby src/examples/radio-group.js'
23
+ function buildConfigText(sourceFile, member, methodNames, quote) {
24
+ const {text} = sourceFile;
25
+ const configObject = member.initializer;
26
+ const existingMembers = configObject.properties.filter(
27
+ property =>
28
+ !(
29
+ ts.isPropertyAssignment(property) &&
30
+ getNameText(property.name) === 'usedBy'
31
+ )
25
32
  );
26
- process.exit(1);
27
- }
28
-
29
- const cwd = process.cwd();
30
- const targets = inputPaths.map(file => path.resolve(cwd, file));
31
-
32
- for (const target of targets) {
33
- if (!fs.existsSync(target)) {
34
- console.error(`File not found: ${path.relative(cwd, target)}`);
35
- process.exit(1);
36
- }
33
+ const existingTexts = existingMembers.map(property =>
34
+ text.slice(property.getStart(sourceFile), property.end).trim()
35
+ );
36
+ if (methodNames.length > 0)
37
+ existingTexts.push(createUsedByProperty(methodNames, quote));
37
38
 
38
- const stat = fs.statSync(target);
39
- if (!stat.isFile()) {
40
- console.error(`Not a file: ${path.relative(cwd, target)}`);
41
- process.exit(1);
39
+ const original = text.slice(
40
+ configObject.getStart(sourceFile),
41
+ configObject.end
42
+ );
43
+ const multiline = original.includes('\n');
44
+ if (!multiline) {
45
+ const openMatch = original.match(/^\{(\s*)/);
46
+ const closeMatch = original.match(/(\s*)\}$/);
47
+ const openSpacing = openMatch ? openMatch[1] : ' ';
48
+ const closeSpacing = closeMatch ? closeMatch[1] : ' ';
49
+ return `{${openSpacing}${existingTexts.join(', ')}${closeSpacing}}`;
42
50
  }
43
51
 
44
- if (!/\.(js|ts)$/.test(target) || /\.d\.ts$/.test(target)) {
45
- console.error(`Unsupported file type: ${path.relative(cwd, target)}`);
46
- process.exit(1);
47
- }
52
+ // Preserve the surrounding formatting style so the update feels like a
53
+ // minimal edit instead of reformatting the whole object.
54
+ const memberIndent = getIndent(text, member.getStart(sourceFile));
55
+ const firstExisting = existingMembers[0];
56
+ const innerIndent = firstExisting
57
+ ? getIndent(text, firstExisting.getStart(sourceFile))
58
+ : memberIndent + ' ';
59
+ return `{\n${existingTexts.map(part => `${innerIndent}${part}`).join(',\n')}\n${memberIndent}}`;
48
60
  }
49
61
 
50
62
  function collectFiles(startPath, files = []) {
@@ -52,9 +64,7 @@ function collectFiles(startPath, files = []) {
52
64
 
53
65
  const stat = fs.statSync(startPath);
54
66
  if (stat.isFile()) {
55
- if (/\.(js|ts)$/.test(startPath) && !/\.d\.ts$/.test(startPath)) {
56
- files.push(startPath);
57
- }
67
+ if (isSupportedSourceFile(startPath)) files.push(startPath);
58
68
  return files;
59
69
  }
60
70
 
@@ -63,12 +73,7 @@ function collectFiles(startPath, files = []) {
63
73
  if (entry.isDirectory()) {
64
74
  if (entry.name === 'node_modules' || entry.name === 'dist') continue;
65
75
  collectFiles(fullPath, files);
66
- } else if (
67
- entry.isFile() &&
68
- /\.(js|ts)$/.test(entry.name) &&
69
- !entry.name.endsWith('.d.ts') &&
70
- !entry.name.includes('.test.')
71
- ) {
76
+ } else if (entry.isFile() && isSupportedSourceFile(entry.name, true)) {
72
77
  files.push(fullPath);
73
78
  }
74
79
  }
@@ -76,63 +81,11 @@ function collectFiles(startPath, files = []) {
76
81
  return files;
77
82
  }
78
83
 
79
- const files = targets.flatMap(target => collectFiles(target));
80
-
81
- function getNameText(name) {
82
- if (
83
- ts.isIdentifier(name) ||
84
- ts.isStringLiteral(name) ||
85
- ts.isPrivateIdentifier(name)
86
- ) {
87
- return name.text;
88
- }
89
- return null;
90
- }
91
-
92
- function hasStaticModifier(node) {
93
- return Boolean(
94
- node.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword)
95
- );
96
- }
97
-
98
- function getWrecImportInfo(sourceFile) {
99
- const names = new Set(['Wrec']);
100
- let quote = "'";
101
-
102
- for (const statement of sourceFile.statements) {
103
- if (
104
- !ts.isImportDeclaration(statement) ||
105
- !statement.importClause ||
106
- !ts.isStringLiteral(statement.moduleSpecifier)
107
- ) {
108
- continue;
109
- }
110
-
111
- const moduleName = statement.moduleSpecifier.text;
112
- const isWrecModule =
113
- moduleName === 'wrec' ||
114
- moduleName === 'wrec/ssr' ||
115
- moduleName.endsWith('/wrec') ||
116
- moduleName.endsWith('/wrec-ssr');
117
- if (!isWrecModule) continue;
118
-
119
- const namedBindings = statement.importClause.namedBindings;
120
- if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;
121
-
122
- for (const element of namedBindings.elements) {
123
- const importedName = element.propertyName?.text ?? element.name.text;
124
- if (importedName === 'Wrec') {
125
- names.add(element.name.text);
126
-
127
- const moduleText = statement.moduleSpecifier.getText(sourceFile);
128
- if (moduleText.startsWith('"') || moduleText.startsWith("'")) {
129
- quote = moduleText[0];
130
- }
131
- }
132
- }
84
+ function createUsedByProperty(methodNames, quote) {
85
+ if (methodNames.length === 1) {
86
+ return `usedBy: ${quote}${methodNames[0]}${quote}`;
133
87
  }
134
-
135
- return {names, quote};
88
+ return `usedBy: [${methodNames.map(name => `${quote}${name}${quote}`).join(', ')}]`;
136
89
  }
137
90
 
138
91
  function extendsWrec(node, wrecNames) {
@@ -150,51 +103,6 @@ function extendsWrec(node, wrecNames) {
150
103
  );
151
104
  }
152
105
 
153
- function getTemplateCalledMethods(classNode) {
154
- const methodNames = new Set();
155
- const CALL_RE = /this\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g;
156
-
157
- function visit(node) {
158
- if (
159
- ts.isPropertyAccessExpression(node) &&
160
- node.expression.kind === ts.SyntaxKind.ThisKeyword &&
161
- ts.isCallExpression(node.parent) &&
162
- node.parent.expression === node
163
- ) {
164
- methodNames.add(node.name.text);
165
- }
166
-
167
- ts.forEachChild(node, visit);
168
- }
169
-
170
- function addTemplateTextMethods(template) {
171
- const text = template.getText();
172
- for (const match of text.matchAll(CALL_RE)) {
173
- methodNames.add(match[1]);
174
- }
175
- }
176
-
177
- for (const member of classNode.members) {
178
- if (
179
- ts.isPropertyDeclaration(member) &&
180
- hasStaticModifier(member) &&
181
- getNameText(member.name) === 'html' &&
182
- member.initializer
183
- ) {
184
- if (
185
- ts.isTaggedTemplateExpression(member.initializer) &&
186
- ts.isIdentifier(member.initializer.tag) &&
187
- member.initializer.tag.text === 'html'
188
- ) {
189
- addTemplateTextMethods(member.initializer.template);
190
- }
191
- ts.forEachChild(member.initializer, visit);
192
- }
193
- }
194
-
195
- return methodNames;
196
- }
197
-
198
106
  function getComputedCalledMethods(classNode) {
199
107
  const methodNames = new Set();
200
108
  const CALL_RE = /this\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g;
@@ -238,6 +146,12 @@ function getComputedCalledMethods(classNode) {
238
146
  return methodNames;
239
147
  }
240
148
 
149
+ function getIndent(text, pos) {
150
+ const lineStart = text.lastIndexOf('\n', pos - 1) + 1;
151
+ const match = /^[ \t]*/.exec(text.slice(lineStart));
152
+ return match ? match[0] : '';
153
+ }
154
+
241
155
  function getMethodUsages(classNode, propertyNames) {
242
156
  const methodInfo = new Map();
243
157
  for (const member of classNode.members) {
@@ -283,6 +197,9 @@ function getMethodUsages(classNode, propertyNames) {
283
197
  }
284
198
 
285
199
  function visit(child) {
200
+ // Record both direct property reads like `this.foo` and method calls
201
+ // like `this.renderFoo()` so we can later propagate property usage
202
+ // through method-to-method call chains.
286
203
  if (
287
204
  ts.isPropertyAccessExpression(child) &&
288
205
  child.expression.kind === ts.SyntaxKind.ThisKeyword
@@ -354,6 +271,9 @@ function getMethodUsages(classNode, propertyNames) {
354
271
  const memo = new Map();
355
272
 
356
273
  function getTransitiveProps(methodName, seen = new Set()) {
274
+ // Starting from methods that are reachable from the template/computed
275
+ // properties, walk through nested method calls and accumulate every
276
+ // component property touched along the way.
357
277
  if (memo.has(methodName)) return memo.get(methodName);
358
278
  if (seen.has(methodName)) return new Set();
359
279
 
@@ -395,55 +315,129 @@ function getMethodUsages(classNode, propertyNames) {
395
315
  return propToMethods;
396
316
  }
397
317
 
398
- function createUsedByProperty(methodNames, quote) {
399
- if (methodNames.length === 1) {
400
- return `usedBy: ${quote}${methodNames[0]}${quote}`;
318
+ function getNameText(name) {
319
+ if (
320
+ ts.isIdentifier(name) ||
321
+ ts.isStringLiteral(name) ||
322
+ ts.isPrivateIdentifier(name)
323
+ ) {
324
+ return name.text;
401
325
  }
402
- return `usedBy: [${methodNames.map(name => `${quote}${name}${quote}`).join(', ')}]`;
326
+ return null;
403
327
  }
404
328
 
405
- function getIndent(text, pos) {
406
- const lineStart = text.lastIndexOf('\n', pos - 1) + 1;
407
- const match = /^[ \t]*/.exec(text.slice(lineStart));
408
- return match ? match[0] : '';
329
+ function getTemplateCalledMethods(classNode) {
330
+ const methodNames = new Set();
331
+ const CALL_RE = /this\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g;
332
+
333
+ function visit(node) {
334
+ if (
335
+ ts.isPropertyAccessExpression(node) &&
336
+ node.expression.kind === ts.SyntaxKind.ThisKeyword &&
337
+ ts.isCallExpression(node.parent) &&
338
+ node.parent.expression === node
339
+ ) {
340
+ methodNames.add(node.name.text);
341
+ }
342
+
343
+ ts.forEachChild(node, visit);
344
+ }
345
+
346
+ function addTemplateTextMethods(template) {
347
+ // Template expressions can hide method calls inside raw template text,
348
+ // so use a regex in addition to AST traversal to catch those names.
349
+ const text = template.getText();
350
+ for (const match of text.matchAll(CALL_RE)) {
351
+ methodNames.add(match[1]);
352
+ }
353
+ }
354
+
355
+ for (const member of classNode.members) {
356
+ if (
357
+ ts.isPropertyDeclaration(member) &&
358
+ hasStaticModifier(member) &&
359
+ getNameText(member.name) === 'html' &&
360
+ member.initializer
361
+ ) {
362
+ if (
363
+ ts.isTaggedTemplateExpression(member.initializer) &&
364
+ ts.isIdentifier(member.initializer.tag) &&
365
+ member.initializer.tag.text === 'html'
366
+ ) {
367
+ addTemplateTextMethods(member.initializer.template);
368
+ }
369
+ ts.forEachChild(member.initializer, visit);
370
+ }
371
+ }
372
+
373
+ return methodNames;
409
374
  }
410
375
 
411
- function buildConfigText(sourceFile, member, methodNames, quote) {
412
- const {text} = sourceFile;
413
- const configObject = member.initializer;
414
- const existingMembers = configObject.properties.filter(
415
- property =>
416
- !(
417
- ts.isPropertyAssignment(property) &&
418
- getNameText(property.name) === 'usedBy'
419
- )
420
- );
421
- const existingTexts = existingMembers.map(property =>
422
- text.slice(property.getStart(sourceFile), property.end).trim()
423
- );
424
- if (methodNames.length > 0)
425
- existingTexts.push(createUsedByProperty(methodNames, quote));
376
+ function getWrecImportInfo(sourceFile) {
377
+ const names = new Set(['Wrec']);
378
+ let quote = "'";
426
379
 
427
- const original = text.slice(
428
- configObject.getStart(sourceFile),
429
- configObject.end
380
+ // Support aliased imports such as `import {Wrec as Base} from 'wrec'`
381
+ // so subclass detection still works and generated text matches the
382
+ // file's existing quote style.
383
+ for (const statement of sourceFile.statements) {
384
+ if (
385
+ !ts.isImportDeclaration(statement) ||
386
+ !statement.importClause ||
387
+ !ts.isStringLiteral(statement.moduleSpecifier)
388
+ ) {
389
+ continue;
390
+ }
391
+
392
+ const moduleName = statement.moduleSpecifier.text;
393
+ const isWrecModule =
394
+ moduleName === 'wrec' ||
395
+ moduleName === 'wrec/ssr' ||
396
+ moduleName.endsWith('/wrec') ||
397
+ moduleName.endsWith('/wrec-ssr');
398
+ if (!isWrecModule) continue;
399
+
400
+ const namedBindings = statement.importClause.namedBindings;
401
+ if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;
402
+
403
+ for (const element of namedBindings.elements) {
404
+ const importedName = element.propertyName?.text ?? element.name.text;
405
+ if (importedName === 'Wrec') {
406
+ names.add(element.name.text);
407
+
408
+ const moduleText = statement.moduleSpecifier.getText(sourceFile);
409
+ if (moduleText.startsWith('"') || moduleText.startsWith("'")) {
410
+ quote = moduleText[0];
411
+ }
412
+ }
413
+ }
414
+ }
415
+
416
+ return {names, quote};
417
+ }
418
+
419
+ function hasStaticModifier(node) {
420
+ return Boolean(
421
+ node.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword)
430
422
  );
431
- const multiline = original.includes('\n');
432
- if (!multiline) return `{ ${existingTexts.join(', ')} }`;
423
+ }
433
424
 
434
- const memberIndent = getIndent(text, member.getStart(sourceFile));
435
- const firstExisting = existingMembers[0];
436
- const innerIndent = firstExisting
437
- ? getIndent(text, firstExisting.getStart(sourceFile))
438
- : memberIndent + ' ';
439
- return `{\n${existingTexts.map(part => `${innerIndent}${part}`).join(',\n')}\n${memberIndent}}`;
425
+ function isSupportedSourceFile(filePath, excludeTests = false) {
426
+ return (
427
+ /\.(js|ts)$/.test(filePath) &&
428
+ !/\.d\.ts$/.test(filePath) &&
429
+ (!excludeTests || !filePath.includes('.test.'))
430
+ );
440
431
  }
441
432
 
442
433
  function transformSourceFile(sourceFile) {
443
434
  const edits = [];
444
435
  const {names: wrecNames, quote} = getWrecImportInfo(sourceFile);
436
+ const suggestions = [];
445
437
  let foundWrecSubclass = false;
446
438
 
439
+ // Each matching class contributes text replacements for the specific
440
+ // property config objects that need `usedBy` added, updated, or removed.
447
441
  for (const node of sourceFile.statements) {
448
442
  if (!ts.isClassDeclaration(node) || !extendsWrec(node, wrecNames)) continue;
449
443
  foundWrecSubclass = true;
@@ -492,6 +486,15 @@ function transformSourceFile(sourceFile) {
492
486
  );
493
487
  const hadUsedBy =
494
488
  existingMembers.length !== configObject.properties.length;
489
+ if (methodNames.length > 0 || hadUsedBy) {
490
+ suggestions.push({
491
+ propName,
492
+ suggestion:
493
+ methodNames.length > 0
494
+ ? createUsedByProperty(methodNames, quote)
495
+ : 'remove usedBy'
496
+ });
497
+ }
495
498
  const needsUsedBy = methodNames.length > 0;
496
499
  if (!hadUsedBy && !needsUsedBy) continue;
497
500
 
@@ -517,6 +520,7 @@ function transformSourceFile(sourceFile) {
517
520
  changed: false,
518
521
  edits: [],
519
522
  foundWrecSubclass: false,
523
+ suggestions,
520
524
  text: sourceFile.text
521
525
  };
522
526
  }
@@ -526,6 +530,7 @@ function transformSourceFile(sourceFile) {
526
530
  changed: false,
527
531
  edits: [],
528
532
  foundWrecSubclass: true,
533
+ suggestions,
529
534
  text: sourceFile.text
530
535
  };
531
536
  }
@@ -537,49 +542,123 @@ function transformSourceFile(sourceFile) {
537
542
  nextSource.slice(0, edit.start) + edit.text + nextSource.slice(edit.end);
538
543
  }
539
544
 
540
- return {changed: true, edits, foundWrecSubclass: true, text: nextSource};
545
+ return {
546
+ changed: true,
547
+ edits,
548
+ foundWrecSubclass: true,
549
+ suggestions,
550
+ text: nextSource
551
+ };
541
552
  }
542
553
 
543
- let changedCount = 0;
554
+ function validateTargetFile(target, cwd = process.cwd()) {
555
+ if (!fs.existsSync(target)) {
556
+ throw new Error(`File not found: ${path.relative(cwd, target)}`);
557
+ }
558
+
559
+ const stat = fs.statSync(target);
560
+ if (!stat.isFile()) {
561
+ throw new Error(`Not a file: ${path.relative(cwd, target)}`);
562
+ }
563
+
564
+ if (!/\.(js|ts)$/.test(target) || /\.d\.ts$/.test(target)) {
565
+ throw new Error(`Unsupported file type: ${path.relative(cwd, target)}`);
566
+ }
567
+ }
544
568
 
545
- for (const file of files) {
546
- const text = fs.readFileSync(file, 'utf8');
547
- const scriptKind = file.endsWith('.ts') ? ts.ScriptKind.TS : ts.ScriptKind.JS;
569
+ export function updateUsedBySource(filePath, text) {
570
+ const scriptKind = filePath.endsWith('.ts')
571
+ ? ts.ScriptKind.TS
572
+ : ts.ScriptKind.JS;
548
573
  const sourceFile = ts.createSourceFile(
549
- file,
574
+ filePath,
550
575
  text,
551
576
  ts.ScriptTarget.Latest,
552
577
  true,
553
578
  scriptKind
554
579
  );
555
580
 
581
+ return transformSourceFile(sourceFile);
582
+ }
583
+
584
+ export function updateUsedByFile(filePath, options = {}) {
585
+ const {dry = false, quiet = false} = options;
586
+ const cwd = process.cwd();
587
+ const resolved = path.resolve(cwd, filePath);
588
+ validateTargetFile(resolved, cwd);
589
+
590
+ const text = fs.readFileSync(resolved, 'utf8');
556
591
  const {
557
592
  changed,
558
- edits,
559
593
  foundWrecSubclass,
594
+ suggestions,
560
595
  text: nextText
561
- } = transformSourceFile(sourceFile);
596
+ } = updateUsedBySource(resolved, text);
562
597
  if (!foundWrecSubclass) {
563
- console.error('No class extending Wrec was found.');
564
- process.exit(1);
598
+ throw new Error('No class extending Wrec was found.');
599
+ }
600
+ if (dry) {
601
+ return {changed, foundWrecSubclass, suggestions, text: nextText};
602
+ }
603
+
604
+ if (changed) {
605
+ // Otherwise, apply the rewritten source text back to disk.
606
+ fs.writeFileSync(resolved, nextText);
607
+ }
608
+ return {changed, foundWrecSubclass, suggestions: [], text: nextText, quiet};
609
+ }
610
+
611
+ function fail(message) {
612
+ console.error(message);
613
+ process.exit(1);
614
+ }
615
+
616
+ function main() {
617
+ const args = process.argv.slice(2);
618
+ const dry = args.includes('--dry');
619
+ const quiet = args.includes('--quiet');
620
+ const inputPaths = args.filter(arg => !arg.startsWith('--'));
621
+
622
+ if (args.includes('--check')) {
623
+ throw new Error('Use --dry instead of --check.');
624
+ }
625
+
626
+ if (inputPaths.length !== 1) {
627
+ throw new Error(
628
+ 'Specify a single source file, e.g. npx wrec-usedby src/examples/radio-group.js'
629
+ );
565
630
  }
566
- if (!changed) continue;
567
631
 
568
- changedCount++;
632
+ const result = updateUsedByFile(inputPaths[0], {dry, quiet});
569
633
  if (dry) {
570
- for (const edit of edits.toReversed()) {
571
- const match = edit.text.match(/usedBy:\s*(?:['"][^'"]*['"]|\[[^\]]*\])/);
572
- const suggestion = match ? match[0] : 'remove usedBy';
573
- console.log(`${edit.propName} - ${suggestion}`);
634
+ // In dry mode, report the inferred change and exit non-zero when at
635
+ // least one update would be needed so the script can be used in checks.
636
+ for (const {propName, suggestion} of result.suggestions) {
637
+ console.log(`${propName} - ${suggestion}`);
574
638
  }
575
- } else {
576
- fs.writeFileSync(file, nextText);
639
+ if (!result.changed) return;
640
+ process.exit(1);
577
641
  }
578
- if (verbose && !dry) console.log('updated');
642
+
643
+ if (result.changed) console.log('updated');
579
644
  }
580
645
 
581
- if (dry && changedCount > 0) process.exit(1);
646
+ const isCliEntry = (() => {
647
+ if (!process.argv[1]) return false;
648
+ try {
649
+ return (
650
+ fs.realpathSync(process.argv[1]) ===
651
+ fs.realpathSync(fileURLToPath(import.meta.url))
652
+ );
653
+ } catch {
654
+ return path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
655
+ }
656
+ })();
582
657
 
583
- if (dry && changedCount === 0) {
584
- console.log('usedBy is already up to date.');
658
+ if (isCliEntry) {
659
+ try {
660
+ main();
661
+ } catch (error) {
662
+ fail(error instanceof Error ? error.message : String(error));
663
+ }
585
664
  }