@boostdev/design-system-components 2.17.0 → 2.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.
Files changed (66) hide show
  1. package/AGENTS.md +2 -1
  2. package/dist/client.cjs +57 -57
  3. package/dist/client.css +633 -627
  4. package/dist/client.js +57 -57
  5. package/dist/index.cjs +57 -57
  6. package/dist/index.css +633 -627
  7. package/dist/index.js +57 -57
  8. package/dist/web-components/{chunk-XI2U6U26.js → chunk-2FNCV46J.js} +4 -5
  9. package/dist/web-components/{chunk-XJU7KITO.js → chunk-5QA7JOC3.js} +4 -5
  10. package/dist/web-components/{chunk-EYCW7TKB.js → chunk-7GEAT2TY.js} +4 -5
  11. package/dist/web-components/chunk-FYTISJN3.js +18 -0
  12. package/dist/web-components/{chunk-LHFUXZ52.js → chunk-ISV56UWR.js} +4 -8
  13. package/dist/web-components/{chunk-YLDQOESI.js → chunk-K3VK6VEL.js} +6 -5
  14. package/dist/web-components/{chunk-GQNQBEYR.js → chunk-QZMFNJSN.js} +4 -5
  15. package/dist/web-components/{chunk-GPJWLAKU.js → chunk-VGPJ5L6N.js} +6 -5
  16. package/dist/web-components/{chunk-AGI6YQRC.js → chunk-WOFGYU6E.js} +4 -5
  17. package/dist/web-components/index.js +9 -8
  18. package/dist/web-components/interaction/form/bds-checkbox-group.js +1 -1
  19. package/dist/web-components/interaction/form/bds-combobox.js +2 -1
  20. package/dist/web-components/interaction/form/bds-form-input.js +2 -1
  21. package/dist/web-components/interaction/form/bds-number-input.js +2 -1
  22. package/dist/web-components/interaction/form/bds-radio-group.js +1 -1
  23. package/dist/web-components/interaction/form/bds-select.js +2 -1
  24. package/dist/web-components/interaction/form/bds-slider.js +2 -1
  25. package/dist/web-components/interaction/form/bds-textarea.js +2 -1
  26. package/package.json +1 -1
  27. package/src/components/interaction/form/Checkbox/Checkbox.tsx +1 -1
  28. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +1 -1
  29. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +1 -1
  30. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +1 -1
  31. package/src/components/interaction/form/Combobox/Combobox.mdx +2 -0
  32. package/src/components/interaction/form/FormInput/FormInput.mdx +4 -0
  33. package/src/components/interaction/form/NumberInput/NumberInput.mdx +2 -0
  34. package/src/components/interaction/form/Radio/Radio.tsx +1 -1
  35. package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +1 -1
  36. package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +1 -1
  37. package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +1 -1
  38. package/src/components/interaction/form/Select/Select.mdx +2 -0
  39. package/src/components/interaction/form/Slider/Slider.mdx +2 -0
  40. package/src/components/interaction/form/Switch/Switch.tsx +1 -1
  41. package/src/components/interaction/form/Textarea/Textarea.mdx +2 -0
  42. package/src/components/interaction/form/atoms/Label.module.css +10 -0
  43. package/src/components/interaction/form/atoms/Label.spec.tsx +48 -0
  44. package/src/components/interaction/form/atoms/Label.stories.tsx +3 -0
  45. package/src/components/interaction/form/atoms/Label.tsx +4 -2
  46. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +14 -0
  47. package/src/web-components/interaction/form/BdsCombobox.mdx +4 -0
  48. package/src/web-components/interaction/form/BdsFormInput.mdx +4 -0
  49. package/src/web-components/interaction/form/BdsNumberInput.mdx +4 -0
  50. package/src/web-components/interaction/form/BdsRadioGroup.mdx +11 -1
  51. package/src/web-components/interaction/form/BdsSelect.mdx +4 -0
  52. package/src/web-components/interaction/form/BdsSlider.mdx +4 -0
  53. package/src/web-components/interaction/form/BdsTextarea.mdx +4 -0
  54. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +11 -1
  55. package/src/web-components/interaction/form/bds-checkbox-group.ts +6 -5
  56. package/src/web-components/interaction/form/bds-combobox.ts +2 -5
  57. package/src/web-components/interaction/form/bds-form-input.ts +2 -5
  58. package/src/web-components/interaction/form/bds-number-input.ts +2 -5
  59. package/src/web-components/interaction/form/bds-radio-group.spec.ts +11 -1
  60. package/src/web-components/interaction/form/bds-radio-group.ts +6 -5
  61. package/src/web-components/interaction/form/bds-select.ts +2 -5
  62. package/src/web-components/interaction/form/bds-slider.ts +2 -8
  63. package/src/web-components/interaction/form/bds-textarea.ts +2 -5
  64. package/src/web-components/interaction/form/field-label.spec.ts +42 -0
  65. package/src/web-components/interaction/form/field-label.ts +20 -0
  66. package/src/web-components/test/helpers.ts +20 -0
@@ -56,7 +56,17 @@ Use `<bds-radio-group>` whenever you have a set of mutually exclusive options. T
56
56
 
57
57
  ## Overridable CSS custom properties
58
58
 
59
- The legend uses `var(--bds-font_weight--medium)` (matching the React `RadioGroup`). `bds-radio-group` has no component-local override tokens of its own — style slotted `<bds-radio>` elements directly via their own tokens.
59
+ Legend hooks mirror the React `RadioGroup` (same names and defaults), so a theme carries over between the two implementations. Set them on the group or any ancestor.
60
+
61
+ | Variable | Default | Description |
62
+ |----------|---------|-------------|
63
+ | `--radioGroup_legend-color` | `var(--bds-color_on-bg)` | Legend text colour. |
64
+ | `--radioGroup_legend-font-family` | `var(--bds-font_family--body)` | Legend font family. |
65
+ | `--radioGroup_legend-font-size` | `var(--bds-font_size--body--s)` | Legend font size, the same as the field labels (`--formLabel_font-size`). |
66
+ | `--radioGroup_legend-font-weight` | `var(--bds-font_weight--medium)` | Legend font weight. |
67
+ | `--radioGroup_legend-gap` | `var(--bds-space_xs)` | Space below the legend before the items. |
68
+
69
+ Style slotted `<bds-radio>` elements via their own tokens.
60
70
 
61
71
  ## Usage in plain HTML
62
72
 
@@ -82,6 +82,10 @@ When `label` is omitted, set `aria-label` on the host element (`<bds-select aria
82
82
  ></bds-select>
83
83
  ```
84
84
 
85
+ ## Label
86
+
87
+ The `label` reads the shared label hooks (`--formLabel_*`: font-family, font-size, font-weight, color), listed on `<bds-form-input>`. The React field labels use the same hooks and defaults.
88
+
85
89
  ## Error state
86
90
 
87
91
  With `error`, the message gets a warning icon and the field ring widens to `--bdc-outline_width--error` (2px). Message hooks (`--formMessage_*`: font-size, gap, icon-size, color--error, font-weight--error, color--hint, font-style--hint) are listed on `<bds-form-input>`.
@@ -84,6 +84,10 @@ Use `<bds-slider>` for continuous or stepped numeric ranges — volume controls,
84
84
  <bds-slider name="volume" label="Volume" value="50" min="0" max="100" step="5" show-value></bds-slider>
85
85
  ```
86
86
 
87
+ ## Label
88
+
89
+ The `label` reads the shared label hooks (`--formLabel_*`: font-family, font-size, font-weight, color), listed on `<bds-form-input>`. The React field labels use the same hooks and defaults.
90
+
87
91
  ## Error state
88
92
 
89
93
  With `error`, the message gets a warning icon. Message hooks (`--formMessage_*`: font-size, gap, icon-size, color--error, font-weight--error, color--hint, font-style--hint) are listed on `<bds-form-input>`.
@@ -88,6 +88,10 @@ Use `<bds-textarea>` for multiline text entry — messages, comments, descriptio
88
88
  <bds-textarea name="feedback" label="Feedback" error="Feedback is required"></bds-textarea>
89
89
  ```
90
90
 
91
+ ## Label
92
+
93
+ The `label` reads the shared label hooks (`--formLabel_*`: font-family, font-size, font-weight, color), listed on `<bds-form-input>`. The React field labels use the same hooks and defaults.
94
+
91
95
  ## Error state
92
96
 
93
97
  With `error`, the message gets a warning icon and the field ring widens to `--bdc-outline_width--error` (2px). Message hooks (`--formMessage_*`: font-size, gap, icon-size, color--error, font-weight--error, color--hint, font-style--hint) are listed on `<bds-form-input>`.
@@ -1,4 +1,4 @@
1
- import { fixture, cleanup } from '../../test/helpers';
1
+ import { fixture, cleanup, readSource, ruleDeclarations } from '../../test/helpers';
2
2
  import { BdsCheckboxGroup } from './bds-checkbox-group';
3
3
 
4
4
  describe('bds-checkbox-group', () => {
@@ -136,6 +136,16 @@ describe('bds-checkbox-group', () => {
136
136
  cleanup(el);
137
137
  });
138
138
 
139
+ it('styles the legend through the same hooks and defaults as the React CheckboxGroup', () => {
140
+ const react = ruleDeclarations(readSource('src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css'), '.legend');
141
+ expect(ruleDeclarations(BdsCheckboxGroup.styles.toString(), '.legend')).toEqual(react);
142
+ });
143
+
144
+ it('sizes the legend like the field labels', () => {
145
+ expect(ruleDeclarations(BdsCheckboxGroup.styles.toString(), '.legend')['font-size'])
146
+ .toBe('var(--checkboxGroup_legend-font-size, var(--bds-font_size--body--s))');
147
+ });
148
+
139
149
  it('uses medium font-weight token for the legend (not semibold)', () => {
140
150
  const legendRule = BdsCheckboxGroup.styles.toString().match(/\.legend \{[^}]*\}/)?.[0] ?? '';
141
151
  expect(legendRule).toContain('--bds-font_weight--medium');
@@ -63,11 +63,12 @@ export class BdsCheckboxGroup extends LitElement {
63
63
  }
64
64
 
65
65
  .legend {
66
- font-size: var(--bds-font_size--body--s);
67
- font-family: var(--bds-font_family--body);
68
- font-weight: var(--bds-font_weight--medium);
69
- color: var(--bds-color_on-bg);
70
- margin-block-end: var(--bds-space_xs);
66
+ padding: 0;
67
+ font-family: var(--checkboxGroup_legend-font-family, var(--bds-font_family--body));
68
+ font-size: var(--checkboxGroup_legend-font-size, var(--bds-font_size--body--s));
69
+ font-weight: var(--checkboxGroup_legend-font-weight, var(--bds-font_weight--medium));
70
+ color: var(--checkboxGroup_legend-color, var(--bds-color_on-bg));
71
+ margin-block-end: var(--checkboxGroup_legend-gap, var(--bds-space_xs));
71
72
  }
72
73
 
73
74
  .required {
@@ -1,4 +1,5 @@
1
1
  import { LitElement, css, html, nothing } from 'lit';
2
+ import { fieldLabelStyles } from './field-label';
2
3
  import { fieldMessageStyles, renderFieldMessage } from './field-message';
3
4
 
4
5
  export interface ComboboxOption {
@@ -59,11 +60,6 @@ export class BdsCombobox extends LitElement {
59
60
  }
60
61
 
61
62
  .label {
62
- display: block;
63
- font-size: var(--bds-font_size--body--s);
64
- font-family: var(--bds-font_family--body);
65
- font-weight: var(--bds-font_weight--semibold);
66
- color: var(--bds-color_on-bg);
67
63
  margin-block-end: var(--bds-space_xxs);
68
64
  }
69
65
 
@@ -179,6 +175,7 @@ export class BdsCombobox extends LitElement {
179
175
  color: var(--bds-color_on-bg--subtle);
180
176
  }
181
177
 
178
+ ${fieldLabelStyles}
182
179
  ${fieldMessageStyles}
183
180
  `;
184
181
 
@@ -1,5 +1,6 @@
1
1
  import { LitElement, css, html } from 'lit';
2
2
  import { ifDefined } from 'lit/directives/if-defined.js';
3
+ import { fieldLabelStyles } from './field-label';
3
4
  import { fieldMessageStyles, renderFieldMessage } from './field-message';
4
5
 
5
6
  /**
@@ -53,11 +54,6 @@ export class BdsFormInput extends LitElement {
53
54
  }
54
55
 
55
56
  .label {
56
- display: block;
57
- font-size: var(--bds-font_size--body--s);
58
- font-family: var(--bds-font_family--body);
59
- font-weight: var(--bds-font_weight--semibold);
60
- color: var(--bds-color_on-bg);
61
57
  margin-block-end: var(--bds-space_xxs);
62
58
  }
63
59
 
@@ -143,6 +139,7 @@ export class BdsFormInput extends LitElement {
143
139
  display: none;
144
140
  }
145
141
 
142
+ ${fieldLabelStyles}
146
143
  ${fieldMessageStyles}
147
144
  `;
148
145
 
@@ -1,4 +1,5 @@
1
1
  import { LitElement, css, html } from 'lit';
2
+ import { fieldLabelStyles } from './field-label';
2
3
  import { fieldMessageStyles, renderFieldMessage } from './field-message';
3
4
 
4
5
  /**
@@ -46,11 +47,6 @@ export class BdsNumberInput extends LitElement {
46
47
  }
47
48
 
48
49
  .label {
49
- display: block;
50
- font-size: var(--bds-font_size--body--s);
51
- font-family: var(--bds-font_family--body);
52
- font-weight: var(--bds-font_weight--semibold);
53
- color: var(--bds-color_on-bg);
54
50
  margin-block-end: var(--bds-space_xxs);
55
51
  }
56
52
 
@@ -157,6 +153,7 @@ export class BdsNumberInput extends LitElement {
157
153
  color: var(--numberInput_stepper-color, var(--bds-color_interactive));
158
154
  }
159
155
 
156
+ ${fieldLabelStyles}
160
157
  ${fieldMessageStyles}
161
158
  `;
162
159
 
@@ -1,4 +1,4 @@
1
- import { fixture, cleanup } from '../../test/helpers';
1
+ import { fixture, cleanup, readSource, ruleDeclarations } from '../../test/helpers';
2
2
  import { BdsRadioGroup } from './bds-radio-group';
3
3
 
4
4
  describe('bds-radio-group', () => {
@@ -111,6 +111,16 @@ describe('bds-radio-group', () => {
111
111
  cleanup(el);
112
112
  });
113
113
 
114
+ it('styles the legend through the same hooks and defaults as the React RadioGroup', () => {
115
+ const react = ruleDeclarations(readSource('src/components/interaction/form/RadioGroup/RadioGroup.module.css'), '.legend');
116
+ expect(ruleDeclarations(BdsRadioGroup.styles.toString(), '.legend')).toEqual(react);
117
+ });
118
+
119
+ it('sizes the legend like the field labels', () => {
120
+ expect(ruleDeclarations(BdsRadioGroup.styles.toString(), '.legend')['font-size'])
121
+ .toBe('var(--radioGroup_legend-font-size, var(--bds-font_size--body--s))');
122
+ });
123
+
114
124
  it('uses medium font-weight token for the legend (not semibold)', () => {
115
125
  const legendRule = BdsRadioGroup.styles.toString().match(/\.legend \{[^}]*\}/)?.[0] ?? '';
116
126
  expect(legendRule).toContain('--bds-font_weight--medium');
@@ -50,11 +50,12 @@ export class BdsRadioGroup extends LitElement {
50
50
  }
51
51
 
52
52
  .legend {
53
- font-size: var(--bds-font_size--body--s);
54
- font-family: var(--bds-font_family--body);
55
- font-weight: var(--bds-font_weight--medium);
56
- color: var(--bds-color_on-bg);
57
- margin-block-end: var(--bds-space_xs);
53
+ padding: 0;
54
+ font-family: var(--radioGroup_legend-font-family, var(--bds-font_family--body));
55
+ font-size: var(--radioGroup_legend-font-size, var(--bds-font_size--body--s));
56
+ font-weight: var(--radioGroup_legend-font-weight, var(--bds-font_weight--medium));
57
+ color: var(--radioGroup_legend-color, var(--bds-color_on-bg));
58
+ margin-block-end: var(--radioGroup_legend-gap, var(--bds-space_xs));
58
59
  }
59
60
 
60
61
  .required {
@@ -1,4 +1,5 @@
1
1
  import { LitElement, css, html, nothing } from 'lit';
2
+ import { fieldLabelStyles } from './field-label';
2
3
  import { fieldMessageStyles, renderFieldMessage } from './field-message';
3
4
 
4
5
  export interface SelectOption {
@@ -62,11 +63,6 @@ export class BdsSelect extends LitElement {
62
63
  }
63
64
 
64
65
  .label {
65
- display: block;
66
- font-size: var(--bds-font_size--body--s);
67
- font-family: var(--bds-font_family--body);
68
- font-weight: var(--bds-font_weight--semibold);
69
- color: var(--bds-color_on-bg);
70
66
  margin-block-end: var(--bds-space_xxs);
71
67
  }
72
68
 
@@ -131,6 +127,7 @@ export class BdsSelect extends LitElement {
131
127
  block-size: 100%;
132
128
  }
133
129
 
130
+ ${fieldLabelStyles}
134
131
  ${fieldMessageStyles}
135
132
  `;
136
133
 
@@ -1,4 +1,5 @@
1
1
  import { LitElement, css, html } from 'lit';
2
+ import { fieldLabelStyles } from './field-label';
2
3
  import { fieldMessageStyles, renderFieldMessage } from './field-message';
3
4
 
4
5
  /**
@@ -60,14 +61,6 @@ export class BdsSlider extends LitElement {
60
61
  margin-block-end: var(--bds-space_xxs);
61
62
  }
62
63
 
63
- .label {
64
- display: block;
65
- font-size: var(--bds-font_size--body--s);
66
- font-family: var(--bds-font_family--body);
67
- font-weight: var(--bds-font_weight--semibold);
68
- color: var(--bds-color_on-bg);
69
- }
70
-
71
64
  .value {
72
65
  font-size: var(--bds-font_size--body--s);
73
66
  font-variant-numeric: tabular-nums;
@@ -157,6 +150,7 @@ export class BdsSlider extends LitElement {
157
150
  }
158
151
  }
159
152
 
153
+ ${fieldLabelStyles}
160
154
  ${fieldMessageStyles}
161
155
  `;
162
156
 
@@ -1,4 +1,5 @@
1
1
  import { LitElement, css, html } from 'lit';
2
+ import { fieldLabelStyles } from './field-label';
2
3
  import { fieldMessageStyles, renderFieldMessage } from './field-message';
3
4
 
4
5
  /**
@@ -49,11 +50,6 @@ export class BdsTextarea extends LitElement {
49
50
  }
50
51
 
51
52
  .label {
52
- display: block;
53
- font-size: var(--bds-font_size--body--s);
54
- font-family: var(--bds-font_family--body);
55
- font-weight: var(--bds-font_weight--semibold);
56
- color: var(--bds-color_on-bg);
57
53
  margin-block-end: var(--bds-space_xxs);
58
54
  }
59
55
 
@@ -108,6 +104,7 @@ export class BdsTextarea extends LitElement {
108
104
  --bdc-outline_width: var(--bdc-outline_width--error, 2px);
109
105
  }
110
106
 
107
+ ${fieldLabelStyles}
111
108
  ${fieldMessageStyles}
112
109
  `;
113
110
 
@@ -0,0 +1,42 @@
1
+ import { readSource, ruleDeclarations } from '../../test/helpers';
2
+ import { fieldLabelStyles } from './field-label';
3
+ import { BdsFormInput } from './bds-form-input';
4
+ import { BdsNumberInput } from './bds-number-input';
5
+ import { BdsCombobox } from './bds-combobox';
6
+ import { BdsSelect } from './bds-select';
7
+ import { BdsTextarea } from './bds-textarea';
8
+ import { BdsSlider } from './bds-slider';
9
+
10
+ const typographyProps = ['font-family', 'font-size', 'font-weight', 'color'];
11
+
12
+ describe('field-label', () => {
13
+ it('reads every label look from a --formLabel_* hook', () => {
14
+ expect(ruleDeclarations(fieldLabelStyles.toString(), '.label')).toEqual({
15
+ display: 'block',
16
+ 'font-family': 'var(--formLabel_font-family, var(--bds-font_family--body))',
17
+ 'font-size': 'var(--formLabel_font-size, var(--bds-font_size--body--s))',
18
+ 'font-weight': 'var(--formLabel_font-weight, var(--bds-font_weight--semibold))',
19
+ color: 'var(--formLabel_color, var(--bds-color_on-bg))',
20
+ });
21
+ });
22
+
23
+ it('matches the field variant of the React Label atom', () => {
24
+ const react = ruleDeclarations(readSource('src/components/interaction/form/atoms/Label.module.css'), '.--field');
25
+ const wc = ruleDeclarations(fieldLabelStyles.toString(), '.label');
26
+ expect(Object.keys(react).sort()).toEqual([...typographyProps].sort());
27
+ typographyProps.forEach(prop => expect(react[prop]).toBe(wc[prop]));
28
+ });
29
+
30
+ it.each([
31
+ ['bds-form-input', BdsFormInput],
32
+ ['bds-number-input', BdsNumberInput],
33
+ ['bds-combobox', BdsCombobox],
34
+ ['bds-select', BdsSelect],
35
+ ['bds-textarea', BdsTextarea],
36
+ ['bds-slider', BdsSlider],
37
+ ])('%s styles its label with the shared field label', (_tag, ctor) => {
38
+ const css = ctor.styles.toString();
39
+ expect(css).toContain(fieldLabelStyles.toString());
40
+ expect(css.replace(fieldLabelStyles.toString(), '')).not.toMatch(/\.label \{[^}]*font-(size|weight)/);
41
+ });
42
+ });
@@ -0,0 +1,20 @@
1
+ import { css } from 'lit';
2
+
3
+ /**
4
+ * Shared look for the label above a form control. Every `bds-*` field with a
5
+ * `label` attribute spreads `fieldLabelStyles` into its own styles, so the
6
+ * controls cannot drift. The React `Label` atom (field variant) reads the same
7
+ * hooks with the same defaults.
8
+ *
9
+ * Hooks: --formLabel_font-family, --formLabel_font-size, --formLabel_font-weight,
10
+ * --formLabel_color.
11
+ */
12
+ export const fieldLabelStyles = css`
13
+ .label {
14
+ display: block;
15
+ font-family: var(--formLabel_font-family, var(--bds-font_family--body));
16
+ font-size: var(--formLabel_font-size, var(--bds-font_size--body--s));
17
+ font-weight: var(--formLabel_font-weight, var(--bds-font_weight--semibold));
18
+ color: var(--formLabel_color, var(--bds-color_on-bg));
19
+ }
20
+ `;
@@ -1,3 +1,6 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+
1
4
  /** Render an HTML string, upgrade the custom element, and await first paint. */
2
5
  export async function fixture(html: string): Promise<HTMLElement> {
3
6
  const template = document.createElement('template');
@@ -20,3 +23,20 @@ export function makeCommandEvent(command: string): Event {
20
23
  Object.defineProperty(event, 'source', { value: null, configurable: true });
21
24
  return event;
22
25
  }
26
+
27
+ /**
28
+ * Declarations of the first `selector { ... }` rule in a stylesheet, keyed by property.
29
+ * Used to compare a web component's styles with its React counterpart's CSS module.
30
+ */
31
+ export function ruleDeclarations(css: string, selector: string): Record<string, string> {
32
+ const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
33
+ const body = css.match(new RegExp(`(?:^|[\\s,}])${escaped}\\s*\\{([^}]*)\\}`))?.[1] ?? '';
34
+ return Object.fromEntries(
35
+ [...body.matchAll(/([\w-]+)\s*:\s*([^;]+);/g)].map(([, prop, value]) => [prop, value.replace(/\s+/g, ' ').trim()]),
36
+ );
37
+ }
38
+
39
+ /** Reads a source file relative to the repo root. */
40
+ export function readSource(path: string): string {
41
+ return readFileSync(resolve(process.cwd(), path), 'utf8');
42
+ }