aria-ease 6.5.1 → 6.7.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 (29) hide show
  1. package/README.md +88 -24
  2. package/bin/{chunk-AUJAN4RK.js → chunk-2TOYEY5L.js} +87 -40
  3. package/bin/chunk-VPBHLMAS.js +127 -0
  4. package/bin/cli.cjs +403 -237
  5. package/bin/cli.js +8 -123
  6. package/bin/configLoader-XRF6VM4J.js +7 -0
  7. package/{dist/contractTestRunnerPlaywright-7F756CFB.js → bin/contractTestRunnerPlaywright-UAOFNS7Z.js} +121 -60
  8. package/bin/{test-C3CMRHSI.js → test-WRIJHN6H.js} +65 -24
  9. package/dist/{chunk-AUJAN4RK.js → chunk-2TOYEY5L.js} +87 -40
  10. package/dist/configLoader-IT4PWCJB.js +128 -0
  11. package/{bin/contractTestRunnerPlaywright-7F756CFB.js → dist/contractTestRunnerPlaywright-UAOFNS7Z.js} +121 -60
  12. package/dist/index.cjs +471 -137
  13. package/dist/index.d.cts +6 -1
  14. package/dist/index.d.ts +6 -1
  15. package/dist/index.js +127 -35
  16. package/dist/src/menu/index.cjs +62 -11
  17. package/dist/src/menu/index.js +62 -11
  18. package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +8 -8
  19. package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +4 -4
  20. package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +172 -34
  21. package/dist/src/utils/test/aria-contracts/tabs/tabs.contract.json +10 -10
  22. package/dist/src/utils/test/{chunk-AUJAN4RK.js → chunk-2TOYEY5L.js} +85 -41
  23. package/dist/src/utils/test/configLoader-LD4RV2WQ.js +126 -0
  24. package/dist/src/utils/test/{contractTestRunnerPlaywright-HL73FADJ.js → contractTestRunnerPlaywright-IRJOAEMT.js} +117 -59
  25. package/dist/src/utils/test/index.cjs +403 -125
  26. package/dist/src/utils/test/index.d.cts +7 -1
  27. package/dist/src/utils/test/index.d.ts +7 -1
  28. package/dist/src/utils/test/index.js +61 -23
  29. package/package.json +1 -1
package/README.md CHANGED
@@ -13,19 +13,15 @@ Stop treating accessibility as an afterthought. Aria-Ease engineers accessibilit
13
13
 
14
14
  Aria-Ease isn't a utility library. **It's an accessibility infrastructure** that integrates into every phase of your frontend engineering lifecycle:
15
15
 
16
- | Phase | Feature | Status | Impact |
17
- | ------------------ | --------------------------------------------- | ------------ | --------------------------------------- |
18
- | **🔧 Development** | Component utilities for accessible patterns | ✅ Available | Build it right from the start |
19
- | **⚡ Linting** | ESLint rules to enforce accessible coding | 🚧 Roadmap | Catch mistakes as you type |
20
- | **🔍 Pre-Deploy** | Axe-core powered static accessibility audit | ✅ Available | Verify before it ships |
21
- | **🧪 Testing** | WAI-ARIA APG contract testing with Playwright | ✅ Available | 26 combobox assertions in ~4 seconds |
22
- | **🚀 CI/CD** | Accessibility as deployment gatekeeper | ✅ Available | Block inaccessible code from production |
23
- | **📊 Production** | Real user signal monitoring and replay | 🚧 Roadmap | Understand how users actually interact |
24
- | **📈 Insights** | Dashboard for reporting and analytics | 🚧 Roadmap | Visualize accessibility health |
25
-
26
- **The philosophy:** By the time your app reaches manual testing, there should only be minute, non-automatable aspects left to verify.
27
-
28
- **The reality:** Code that fails accessibility checks cannot reach production. Period.
16
+ | Phase | Feature | Status | Impact |
17
+ | ------------------ | --------------------------------------------- | ------------ | --------------------------------------------- |
18
+ | **🔧 Development** | Component utilities for accessible patterns | ✅ Available | Build it right from the start |
19
+ | **⚡ Linting** | ESLint rules to enforce accessible coding | 🚧 Roadmap | Catch mistakes as you type |
20
+ | **🔍 Pre-Deploy** | Axe-core powered static accessibility audit | ✅ Available | Verify before it ships |
21
+ | **🧪 Testing** | WAI-ARIA APG contract testing with Playwright | ✅ Available | Fast, determinic component accessibility test |
22
+ | **🚀 CI/CD** | Accessibility as deployment gatekeeper | ✅ Available | Block inaccessible code from production |
23
+ | **📊 Production** | Real user signal monitoring and replay | 🚧 Roadmap | Understand how users actually interact |
24
+ | **📈 Insights** | Dashboard for reporting and analytics | 🚧 Roadmap | Visualize accessibility health |
29
25
 
30
26
  ---
31
27
 
@@ -35,7 +31,7 @@ Aria-Ease isn't a utility library. **It's an accessibility infrastructure** that
35
31
 
36
32
  **Traditional approach:** Build features → Manual testing → Find accessibility issues → Fix them → Manual testing again → Ship (maybe)
37
33
 
38
- **Aria-Ease approach:** Build with accessible utilities → Automated audits catch issues → Contract tests verify behavior → CI/CD gates deployment → Ship with confidence
34
+ **Aria-Ease approach:** Build with accessible utilities → Automated audits catch issues → Contract tests verify deterministic component behaviors → CI/CD gates deployment → Ship with confidence
39
35
 
40
36
  ### What Makes This Different?
41
37
 
@@ -70,11 +66,11 @@ This is the game-changer. We encoded the WAI-ARIA APG into deterministic JSON "c
70
66
 
71
67
  ```bash
72
68
  npx aria-ease test
73
- # ✓ 26 assertions in ~4 seconds
69
+ # ✓ 26 assertions in ~2 seconds
74
70
  # ✓ 26 assertions in ~1 second in CI
75
71
  ```
76
72
 
77
- **Why this matters:** Before, verifying a combobox meant manual keyboard testing across browsers. Now, it's automated, fast, and repeatable. You can boast about executing 26 combobox interaction assertions in ~4 seconds.
73
+ **Why this matters:** Before, verifying a combobox meant manual keyboard testing across browsers. Now, it's automated, fast, and repeatable. You can boast about executing 26 combobox interaction assertions in ~2 seconds.
78
74
 
79
75
  #### 4. **CI/CD Integration** (Available Now)
80
76
 
@@ -134,7 +130,7 @@ pnpm add aria-ease
134
130
 
135
131
  ### Automated Accessibility Audits (CLI)
136
132
 
137
- Run automated accessibility audits on your website with one command:
133
+ Run axe-core powered automated accessibility audits on your website with one command:
138
134
 
139
135
  ```bash
140
136
  npx aria-ease audit --url https://yoursite.com
@@ -158,6 +154,9 @@ export default {
158
154
  out: "./accessibility-reports",
159
155
  },
160
156
  },
157
+ test: {
158
+ strictness: "balanced", // 'minimal' | 'balanced' | 'strict' | 'paranoid'
159
+ },
161
160
  };
162
161
  ```
163
162
 
@@ -662,6 +661,67 @@ describe("Shopify User Menu Accessibility Test", () => {
662
661
  });
663
662
  ```
664
663
 
664
+ ### Strictness Modes
665
+
666
+ Aria-Ease supports strictness policies to define how contract levels are enforced.
667
+
668
+ - `minimal`
669
+ - `required` -> error
670
+ - `recommended` -> ignore
671
+ - `optional` -> ignore
672
+ - `balanced` (default)
673
+ - `required` -> error
674
+ - `recommended` -> warning
675
+ - `optional` -> ignore
676
+ - `strict`
677
+ - `required` -> error
678
+ - `recommended` -> error
679
+ - `optional` -> warning
680
+ - `paranoid`
681
+ - `required` -> error
682
+ - `recommended` -> error
683
+ - `optional` -> error
684
+
685
+ Configure strictness globally in `ariaease.config.*`:
686
+
687
+ ```javascript
688
+ export default {
689
+ test: {
690
+ strictness: "balanced",
691
+ },
692
+ };
693
+ ```
694
+
695
+ Or define strictness per component (recommended when components have different maturity levels):
696
+
697
+ ```javascript
698
+ export default {
699
+ test: {
700
+ strictness: "balanced", // fallback
701
+ components: [
702
+ {
703
+ name: "menu",
704
+ strictness: "strict",
705
+ },
706
+ {
707
+ name: "accordion",
708
+ strictness: "minimal",
709
+ },
710
+ ],
711
+ },
712
+ };
713
+ ```
714
+
715
+ `path` is optional and not required for strictness resolution.
716
+
717
+ Or override per test call:
718
+
719
+ ```javascript
720
+ await testUiComponent("menu", null, "http://localhost:5173/test-harness?component=menu", {
721
+ strictness: "strict",
722
+ });
723
+ ```
724
+
665
725
  ---
666
726
 
667
727
  ## 📦 Bundle Size
@@ -754,7 +814,7 @@ For older browser support, use a polyfill service or transpile with appropriate
754
814
 
755
815
  ---
756
816
 
757
- ## CI/CD Integration: Accessibility as a Deployment Gatekeeper
817
+ ## 🚀 CI/CD Integration: Accessibility as a Deployment Gatekeeper
758
818
 
759
819
  **The game-changer:** Turn accessibility into a deployment invariant. Code that fails accessibility checks cannot reach production.
760
820
 
@@ -876,10 +936,14 @@ Create `ariaease.config.js` in your project root:
876
936
  ```javascript
877
937
  export default {
878
938
  audit: {
879
- urls: ["http://localhost:5173/", "http://localhost:5173/changelog"],
939
+ urls: [
940
+ "http://localhost:5173", // Homepage
941
+ "http://localhost:5173/docs", // Docs
942
+ "http://localhost:5173/examples", // Examples
943
+ ],
880
944
  output: {
881
- format: "html",
882
- out: "./accessibility-reports/audit",
945
+ format: "all", // Generate JSON, CSV, and HTML reports
946
+ out: "./accessibility-reports",
883
947
  },
884
948
  },
885
949
  };
@@ -985,7 +1049,7 @@ npx aria-ease test
985
1049
  - Deterministic JSON contracts (no flaky selectors)
986
1050
  - Runs headless in CI
987
1051
 
988
- **Compare to manual testing:** Manually verifying keyboard interactions for a combobox (arrow keys, typing, Enter, Escape, Home/End, etc.) across browsers, verifying WAI-ARIA roles, states and properties = 20-30 minutes. Aria-Ease = 4 seconds.
1052
+ **Compare to manual testing:** Manually verifying keyboard interactions for a combobox listbox (arrow keys, typing, Enter, Escape, Home/End, etc.) across browsers, verifying WAI-ARIA roles, states and properties = 20-30 minutes. Aria-Ease = ~2 seconds.
989
1053
 
990
1054
  ### The Moment of Truth
991
1055
 
@@ -993,11 +1057,11 @@ The first time you see that green check mark in your CI/CD pipeline—knowing th
993
1057
 
994
1058
  **No one has any excuse to ship inaccessible code anymore.**
995
1059
 
996
- You've shifted accessibility left (into development), automated the verification, and made it a deployment gatekeeper. By the time code reaches manual testing, there should only be minute, non-automatable aspects left to verify.
1060
+ You've shifted accessibility left (into development), automated the verification, and made it a deployment gatekeeper.
997
1061
 
998
1062
  ---
999
1063
 
1000
- ## �📖 More Resources
1064
+ ## 📖 More Resources
1001
1065
 
1002
1066
  - [Full Documentation](https://ariaease.site/docs)
1003
1067
  - [GitHub Repository](https://github.com/aria-ease/aria-ease)
@@ -24,10 +24,11 @@ var ContractReporter = class {
24
24
  componentName = "";
25
25
  staticPasses = 0;
26
26
  staticFailures = 0;
27
+ staticWarnings = 0;
27
28
  dynamicResults = [];
28
29
  totalTests = 0;
29
30
  skipped = 0;
30
- optionalSuggestions = 0;
31
+ warnings = 0;
31
32
  isPlaywright = false;
32
33
  apgUrl = "https://www.w3.org/WAI/ARIA/apg/";
33
34
  hasPrintedStaticSection = false;
@@ -54,23 +55,27 @@ ${"\u2550".repeat(60)}`);
54
55
  this.log(`${"\u2550".repeat(60)}
55
56
  `);
56
57
  }
57
- reportStatic(passes, failures) {
58
+ reportStatic(passes, failures, warnings = 0) {
58
59
  this.staticPasses = passes;
59
60
  this.staticFailures = failures;
61
+ this.staticWarnings = warnings;
60
62
  }
61
63
  /**
62
64
  * Report individual static test pass
63
65
  */
64
- reportStaticTest(description, passed, failureMessage) {
66
+ reportStaticTest(description, status, failureMessage, level) {
65
67
  if (!this.hasPrintedStaticSection) {
66
68
  this.log(`${"\u2500".repeat(60)}`);
67
69
  this.log(`\u{1F9EA} Static Assertions`);
68
70
  this.log(`${"\u2500".repeat(60)}`);
69
71
  this.hasPrintedStaticSection = true;
70
72
  }
71
- const icon = passed ? "\u2713" : "\u2717";
73
+ const icon = status === "pass" ? "\u2713" : status === "warn" ? "\u26A0" : status === "skip" ? "\u25CB" : "\u2717";
72
74
  this.log(` ${icon} ${description}`);
73
- if (!passed && failureMessage) {
75
+ if (level) {
76
+ this.log(` \u21B3 level=${level}`);
77
+ }
78
+ if ((status === "fail" || status === "warn" || status === "skip") && failureMessage) {
74
79
  this.log(` \u21B3 ${failureMessage}`);
75
80
  }
76
81
  }
@@ -89,23 +94,26 @@ ${"\u2550".repeat(60)}`);
89
94
  description: test.description,
90
95
  status,
91
96
  failureMessage,
92
- isOptional: test.isOptional
97
+ level: test.level
93
98
  };
94
99
  if (status === "skip") {
95
100
  result.skipReason = "Requires real browser (addEventListener events)";
96
101
  }
97
102
  this.dynamicResults.push(result);
98
- const icons = { pass: "\u2713", fail: "\u2717", skip: "\u25CB", "optional-fail": "\u25CB" };
99
- const prefix = test.isOptional ? "[OPTIONAL] " : "";
100
- this.log(` ${icons[status]} ${prefix}${test.description}`);
103
+ const icons = { pass: "\u2713", fail: "\u2717", warn: "\u26A0", skip: "\u25CB" };
104
+ const levelPrefix = test.level ? `[${test.level.toUpperCase()}] ` : "";
105
+ this.log(` ${icons[status]} ${levelPrefix}${test.description}`);
101
106
  if (status === "skip" && !this.isPlaywright) {
102
107
  this.log(` \u21B3 Skipped in jsdom (runs in Playwright)`);
103
108
  }
104
- if (status === "fail" && failureMessage && !test.isOptional) {
109
+ if (status === "fail" && failureMessage) {
105
110
  this.log(` \u21B3 ${failureMessage}`);
106
111
  }
107
- if (status === "optional-fail") {
108
- this.log(` \u21B3 Not implemented (recommended for enhanced UX)`);
112
+ if (status === "warn" && failureMessage) {
113
+ this.log(` \u21B3 ${failureMessage}`);
114
+ }
115
+ if (status === "skip" && failureMessage) {
116
+ this.log(` \u21B3 ${failureMessage}`);
109
117
  }
110
118
  }
111
119
  /**
@@ -129,29 +137,29 @@ ${"\u2500".repeat(60)}`);
129
137
  this.log("");
130
138
  });
131
139
  }
132
- /**
133
- * Report optional features that aren't implemented
134
- */
135
- reportOptionalSuggestions() {
136
- const suggestions = this.dynamicResults.filter((r) => r.status === "optional-fail");
137
- if (suggestions.length === 0) return;
140
+ reportWarnings() {
141
+ const warnings = this.dynamicResults.filter((r) => r.status === "warn");
142
+ if (warnings.length === 0 && this.staticWarnings === 0) return;
138
143
  this.log(`
139
144
  ${"\u2500".repeat(60)}`);
140
- this.log(`\u{1F4A1} Optional Enhancements (${suggestions.length}):
145
+ this.log(`\u26A0\uFE0F Warnings (${this.staticWarnings + warnings.length}):
141
146
  `);
142
- this.log(`These features are optional per APG guidelines but recommended`);
143
- this.log(`for improved user experience and keyboard interaction:
147
+ this.log(`These checks are failing but treated as warnings under the active strictness mode.
144
148
  `);
145
- suggestions.forEach((test, index) => {
149
+ warnings.forEach((test, index) => {
146
150
  this.log(`${index + 1}. ${test.description}`);
147
151
  if (test.failureMessage) {
148
152
  this.log(` \u21B3 ${test.failureMessage}`);
149
153
  }
154
+ if (test.level) {
155
+ this.log(` \u21B3 level=${test.level}`);
156
+ }
150
157
  });
151
- this.log(`
152
- \u2728 Consider implementing these for better accessibility`);
153
- this.log(` Reference: ${this.apgUrl}
158
+ if (this.apgUrl) {
159
+ this.log(`
160
+ Reference: ${this.apgUrl}
154
161
  `);
162
+ }
155
163
  }
156
164
  /**
157
165
  * Report skipped tests with helpful context
@@ -181,46 +189,42 @@ ${"\u2500".repeat(60)}`);
181
189
  const duration = Date.now() - this.startTime;
182
190
  const dynamicPasses = this.dynamicResults.filter((r) => r.status === "pass").length;
183
191
  const dynamicFailures = this.dynamicResults.filter((r) => r.status === "fail").length;
192
+ const dynamicWarnings = this.dynamicResults.filter((r) => r.status === "warn").length;
184
193
  this.skipped = this.dynamicResults.filter((r) => r.status === "skip").length;
185
- this.optionalSuggestions = this.dynamicResults.filter((r) => r.status === "optional-fail").length;
194
+ this.warnings = this.staticWarnings + dynamicWarnings;
186
195
  const totalPasses = this.staticPasses + dynamicPasses;
187
196
  const totalFailures = this.staticFailures + dynamicFailures;
188
- const totalRun = totalPasses + totalFailures;
197
+ const totalRun = totalPasses + totalFailures + this.warnings;
189
198
  if (failures.length > 0) {
190
199
  this.reportFailures(failures);
191
200
  }
192
- this.reportOptionalSuggestions();
201
+ this.reportWarnings();
193
202
  this.reportSkipped();
194
203
  this.log(`
195
204
  ${"\u2550".repeat(60)}`);
196
205
  this.log(`\u{1F4CA} Summary
197
206
  `);
198
- const staticIcon = this.staticFailures === 0 ? "\u2705" : "\u274C";
199
- const staticStatus = this.staticFailures === 0 ? "PASS" : "FAIL";
200
- this.log(`${staticIcon} Static ARIA Tests: ${staticStatus}`);
201
- this.log(` ${this.staticPasses}/${this.staticPasses + this.staticFailures} required attributes present`);
202
- this.log("");
203
- if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
207
+ if (totalFailures === 0 && this.skipped === 0 && this.warnings === 0) {
204
208
  this.log(`\u2705 All ${totalRun} tests passed!`);
205
209
  this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
206
210
  } else if (totalFailures === 0) {
207
- this.log(`\u2705 ${totalPasses}/${totalRun} required tests passed`);
211
+ this.log(`\u2705 ${totalPasses}/${totalRun} tests passed`);
208
212
  if (this.skipped > 0) {
209
213
  this.log(`\u25CB ${this.skipped} tests skipped`);
210
214
  }
211
- if (this.optionalSuggestions > 0) {
212
- this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
215
+ if (this.warnings > 0) {
216
+ this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings > 1 ? "s" : ""}`);
213
217
  }
214
218
  this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
215
219
  } else {
216
220
  this.log(`\u274C ${totalFailures} test${totalFailures > 1 ? "s" : ""} failed`);
217
221
  this.log(`\u2705 ${totalPasses} test${totalPasses > 1 ? "s" : ""} passed`);
222
+ if (this.warnings > 0) {
223
+ this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings > 1 ? "s" : ""}`);
224
+ }
218
225
  if (this.skipped > 0) {
219
226
  this.log(`\u25CB ${this.skipped} test${this.skipped > 1 ? "s" : ""} skipped`);
220
227
  }
221
- if (this.optionalSuggestions > 0) {
222
- this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
223
- }
224
228
  }
225
229
  this.log(`\u23F1\uFE0F Duration: ${duration}ms`);
226
230
  this.log(`${"\u2550".repeat(60)}
@@ -255,6 +259,46 @@ ${"\u2550".repeat(60)}`);
255
259
  }
256
260
  };
257
261
 
262
+ // src/utils/test/src/strictness.ts
263
+ var FALLBACK_LEVEL = "required";
264
+ function normalizeLevel(level) {
265
+ if (level === "required" || level === "recommended" || level === "optional") {
266
+ return level;
267
+ }
268
+ return FALLBACK_LEVEL;
269
+ }
270
+ function normalizeStrictness(strictness) {
271
+ if (strictness === "minimal" || strictness === "balanced" || strictness === "strict" || strictness === "paranoid") {
272
+ return strictness;
273
+ }
274
+ return "balanced";
275
+ }
276
+ function resolveEnforcement(level, strictness) {
277
+ const matrix = {
278
+ minimal: {
279
+ required: "error",
280
+ recommended: "ignore",
281
+ optional: "ignore"
282
+ },
283
+ balanced: {
284
+ required: "error",
285
+ recommended: "warning",
286
+ optional: "ignore"
287
+ },
288
+ strict: {
289
+ required: "error",
290
+ recommended: "error",
291
+ optional: "warning"
292
+ },
293
+ paranoid: {
294
+ required: "error",
295
+ recommended: "error",
296
+ optional: "error"
297
+ }
298
+ };
299
+ return matrix[strictness][level];
300
+ }
301
+
258
302
  // src/utils/test/src/playwrightTestHarness.ts
259
303
  import { chromium } from "playwright";
260
304
  var sharedBrowser = null;
@@ -302,6 +346,9 @@ async function closeSharedBrowser() {
302
346
  export {
303
347
  contract_default,
304
348
  ContractReporter,
349
+ normalizeLevel,
350
+ normalizeStrictness,
351
+ resolveEnforcement,
305
352
  createTestPage,
306
353
  closeSharedBrowser
307
354
  };
@@ -0,0 +1,127 @@
1
+ // src/utils/cli/configLoader.ts
2
+ import path from "path";
3
+ import fs from "fs-extra";
4
+ function validateConfig(config) {
5
+ const errors = [];
6
+ if (!config || typeof config !== "object") {
7
+ errors.push("Config must be an object");
8
+ return { valid: false, errors };
9
+ }
10
+ const cfg = config;
11
+ if (cfg.audit !== void 0) {
12
+ if (typeof cfg.audit !== "object" || cfg.audit === null) {
13
+ errors.push("audit must be an object");
14
+ } else {
15
+ if (cfg.audit.urls !== void 0) {
16
+ if (!Array.isArray(cfg.audit.urls)) {
17
+ errors.push("audit.urls must be an array");
18
+ } else if (cfg.audit.urls.some((url) => typeof url !== "string")) {
19
+ errors.push("audit.urls must contain only strings");
20
+ }
21
+ }
22
+ if (cfg.audit.output !== void 0) {
23
+ if (typeof cfg.audit.output !== "object") {
24
+ errors.push("audit.output must be an object");
25
+ } else {
26
+ const output = cfg.audit.output;
27
+ if (output.format !== void 0) {
28
+ if (!["json", "csv", "html", "all"].includes(output.format)) {
29
+ errors.push("audit.output.format must be one of: json, csv, html, all");
30
+ }
31
+ }
32
+ if (output.out !== void 0 && typeof output.out !== "string") {
33
+ errors.push("audit.output.out must be a string");
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ if (cfg.test !== void 0) {
40
+ if (typeof cfg.test !== "object" || cfg.test === null) {
41
+ errors.push("test must be an object");
42
+ } else {
43
+ if (cfg.test.components !== void 0) {
44
+ if (!Array.isArray(cfg.test.components)) {
45
+ errors.push("test.components must be an array");
46
+ } else {
47
+ cfg.test.components.forEach((comp, idx) => {
48
+ if (typeof comp !== "object" || comp === null) {
49
+ errors.push(`test.components[${idx}] must be an object`);
50
+ } else {
51
+ if (typeof comp.name !== "string") {
52
+ errors.push(`test.components[${idx}].name must be a string`);
53
+ }
54
+ if (comp.path !== void 0 && typeof comp.path !== "string") {
55
+ errors.push(`test.components[${idx}].path must be a string when provided`);
56
+ }
57
+ if (comp.strictness !== void 0 && !["minimal", "balanced", "strict", "paranoid"].includes(comp.strictness)) {
58
+ errors.push(`test.components[${idx}].strictness must be one of: minimal, balanced, strict, paranoid`);
59
+ }
60
+ }
61
+ });
62
+ }
63
+ }
64
+ if (cfg.test.strictness !== void 0) {
65
+ if (!["minimal", "balanced", "strict", "paranoid"].includes(cfg.test.strictness)) {
66
+ errors.push("test.strictness must be one of: minimal, balanced, strict, paranoid");
67
+ }
68
+ }
69
+ }
70
+ }
71
+ return { valid: errors.length === 0, errors };
72
+ }
73
+ async function loadConfigFile(filePath) {
74
+ try {
75
+ const ext = path.extname(filePath);
76
+ if (ext === ".json") {
77
+ const content = await fs.readFile(filePath, "utf-8");
78
+ return JSON.parse(content);
79
+ } else if ([".js", ".mjs", ".cjs", ".ts"].includes(ext)) {
80
+ const imported = await import(filePath);
81
+ return imported.default || imported;
82
+ }
83
+ return null;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+ async function loadConfig(cwd = process.cwd()) {
89
+ const configNames = [
90
+ "ariaease.config.js",
91
+ "ariaease.config.mjs",
92
+ "ariaease.config.cjs",
93
+ "ariaease.config.json",
94
+ "ariaease.config.ts"
95
+ ];
96
+ let loadedConfig = null;
97
+ let foundPath = null;
98
+ const errors = [];
99
+ for (const name of configNames) {
100
+ const configPath = path.resolve(cwd, name);
101
+ if (await fs.pathExists(configPath)) {
102
+ foundPath = configPath;
103
+ loadedConfig = await loadConfigFile(configPath);
104
+ if (loadedConfig === null) {
105
+ errors.push(`Found config at ${name} but failed to load it. Check for syntax errors.`);
106
+ continue;
107
+ }
108
+ const validation = validateConfig(loadedConfig);
109
+ if (!validation.valid) {
110
+ errors.push(`Config validation failed in ${name}:`);
111
+ errors.push(...validation.errors.map((err) => ` - ${err}`));
112
+ loadedConfig = null;
113
+ continue;
114
+ }
115
+ break;
116
+ }
117
+ }
118
+ return {
119
+ config: loadedConfig || {},
120
+ configPath: loadedConfig ? foundPath : null,
121
+ errors
122
+ };
123
+ }
124
+
125
+ export {
126
+ loadConfig
127
+ };