@canonical/webarchitect 0.10.0 → 0.11.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 (102) hide show
  1. package/README.md +1 -0
  2. package/package.json +17 -15
  3. package/rulesets/library.ruleset.json +19 -0
  4. package/rulesets/package.ruleset.json +1 -5
  5. package/rulesets/tool-ts.ruleset.json +107 -0
  6. package/rulesets/tool.ruleset.json +19 -0
  7. package/src/cli.ts +322 -0
  8. package/src/constants.ts +8 -0
  9. package/{dist/esm/index.js → src/index.ts} +0 -1
  10. package/{dist/esm/lib/ajv.js → src/lib/ajv.ts} +47 -24
  11. package/src/lib/describeSchema.ts +59 -0
  12. package/src/lib/discoverAllRulesets.ts +18 -0
  13. package/src/lib/discoverRulesetsInDir.ts +41 -0
  14. package/src/lib/executeValidationRules.ts +54 -0
  15. package/{dist/esm/lib/index.js → src/lib/index.ts} +1 -1
  16. package/{dist/esm/lib/listDirectory.js → src/lib/listDirectory.ts} +12 -10
  17. package/{dist/esm/lib/loadFullSchema.js → src/lib/loadFullSchema.ts} +16 -12
  18. package/src/lib/resolveSchema.ts +128 -0
  19. package/src/lib/validateDirectoryRule.ts +158 -0
  20. package/src/lib/validateFileRule.ts +105 -0
  21. package/{dist/esm/lib/validateRule.js → src/lib/validateRule.ts} +18 -11
  22. package/src/schema.json +61 -0
  23. package/src/schemas/draft-2020-12.json +58 -0
  24. package/src/schemas/meta/applicator.json +45 -0
  25. package/src/schemas/meta/content.json +14 -0
  26. package/src/schemas/meta/core.json +48 -0
  27. package/src/schemas/meta/format-annotation.json +11 -0
  28. package/src/schemas/meta/meta-data.json +34 -0
  29. package/src/schemas/meta/unevaluated.json +12 -0
  30. package/src/schemas/meta/validation.json +95 -0
  31. package/src/types.ts +49 -0
  32. package/{dist/esm/validate.js → src/validate.ts} +8 -4
  33. package/dist/esm/cli.js +0 -277
  34. package/dist/esm/cli.js.map +0 -1
  35. package/dist/esm/constants.js +0 -3
  36. package/dist/esm/constants.js.map +0 -1
  37. package/dist/esm/index.js.map +0 -1
  38. package/dist/esm/lib/ajv.js.map +0 -1
  39. package/dist/esm/lib/describeSchema.js +0 -53
  40. package/dist/esm/lib/describeSchema.js.map +0 -1
  41. package/dist/esm/lib/discoverAllRulesets.js +0 -13
  42. package/dist/esm/lib/discoverAllRulesets.js.map +0 -1
  43. package/dist/esm/lib/discoverRulesetsInDir.js +0 -35
  44. package/dist/esm/lib/discoverRulesetsInDir.js.map +0 -1
  45. package/dist/esm/lib/executeValidationRules.js +0 -41
  46. package/dist/esm/lib/executeValidationRules.js.map +0 -1
  47. package/dist/esm/lib/index.js.map +0 -1
  48. package/dist/esm/lib/listDirectory.js.map +0 -1
  49. package/dist/esm/lib/loadFullSchema.js.map +0 -1
  50. package/dist/esm/lib/resolveSchema.js +0 -113
  51. package/dist/esm/lib/resolveSchema.js.map +0 -1
  52. package/dist/esm/lib/validateDirectoryRule.js +0 -138
  53. package/dist/esm/lib/validateDirectoryRule.js.map +0 -1
  54. package/dist/esm/lib/validateFileRule.js +0 -92
  55. package/dist/esm/lib/validateFileRule.js.map +0 -1
  56. package/dist/esm/lib/validateRule.js.map +0 -1
  57. package/dist/esm/schema.json +0 -61
  58. package/dist/esm/schemas/draft-2020-12.json +0 -57
  59. package/dist/esm/schemas/meta/applicator.json +0 -44
  60. package/dist/esm/schemas/meta/content.json +0 -12
  61. package/dist/esm/schemas/meta/core.json +0 -47
  62. package/dist/esm/schemas/meta/format-annotation.json +0 -10
  63. package/dist/esm/schemas/meta/meta-data.json +0 -32
  64. package/dist/esm/schemas/meta/unevaluated.json +0 -11
  65. package/dist/esm/schemas/meta/validation.json +0 -94
  66. package/dist/esm/types.js +0 -2
  67. package/dist/esm/types.js.map +0 -1
  68. package/dist/esm/validate.js.map +0 -1
  69. package/dist/types/cli.d.ts +0 -3
  70. package/dist/types/cli.d.ts.map +0 -1
  71. package/dist/types/constants.d.ts +0 -2
  72. package/dist/types/constants.d.ts.map +0 -1
  73. package/dist/types/index.d.ts +0 -2
  74. package/dist/types/index.d.ts.map +0 -1
  75. package/dist/types/lib/ajv.d.ts +0 -4
  76. package/dist/types/lib/ajv.d.ts.map +0 -1
  77. package/dist/types/lib/describeSchema.d.ts +0 -22
  78. package/dist/types/lib/describeSchema.d.ts.map +0 -1
  79. package/dist/types/lib/discoverAllRulesets.d.ts +0 -9
  80. package/dist/types/lib/discoverAllRulesets.d.ts.map +0 -1
  81. package/dist/types/lib/discoverRulesetsInDir.d.ts +0 -6
  82. package/dist/types/lib/discoverRulesetsInDir.d.ts.map +0 -1
  83. package/dist/types/lib/executeValidationRules.d.ts +0 -22
  84. package/dist/types/lib/executeValidationRules.d.ts.map +0 -1
  85. package/dist/types/lib/index.d.ts +0 -15
  86. package/dist/types/lib/index.d.ts.map +0 -1
  87. package/dist/types/lib/listDirectory.d.ts +0 -20
  88. package/dist/types/lib/listDirectory.d.ts.map +0 -1
  89. package/dist/types/lib/loadFullSchema.d.ts +0 -20
  90. package/dist/types/lib/loadFullSchema.d.ts.map +0 -1
  91. package/dist/types/lib/resolveSchema.d.ts +0 -29
  92. package/dist/types/lib/resolveSchema.d.ts.map +0 -1
  93. package/dist/types/lib/validateDirectoryRule.d.ts +0 -26
  94. package/dist/types/lib/validateDirectoryRule.d.ts.map +0 -1
  95. package/dist/types/lib/validateFileRule.d.ts +0 -29
  96. package/dist/types/lib/validateFileRule.d.ts.map +0 -1
  97. package/dist/types/lib/validateRule.d.ts +0 -21
  98. package/dist/types/lib/validateRule.d.ts.map +0 -1
  99. package/dist/types/types.d.ts +0 -42
  100. package/dist/types/types.d.ts.map +0 -1
  101. package/dist/types/validate.d.ts +0 -23
  102. package/dist/types/validate.d.ts.map +0 -1
package/README.md CHANGED
@@ -249,3 +249,4 @@ The programmatic API makes it easy to integrate webarchitect into existing build
249
249
  - **IDE Integration** - No real-time validation feedback is available yet in code editors. Developers must run webarchitect manually or through build scripts to see validation results. Real-time diagnostics would require Language Server Protocol implementation or editor-specific plugins.
250
250
  - **Svelte support** - No svelte-specific ruleset has been developed yet.
251
251
  - **Error Code Granularity** - All validation failures return the same exit code (1) regardless of failure type. Missing files, invalid JSON syntax, and schema validation failures are not distinguished programmatically. This limits automated error handling and reporting capabilities.
252
+
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@canonical/webarchitect",
3
3
  "description": "A tool to test the compliance with architecture specifications for packages and applications.",
4
- "version": "0.10.0",
4
+ "version": "0.11.0",
5
5
  "type": "module",
6
- "module": "dist/esm/index.js",
7
- "types": "dist/types/index.d.ts",
6
+ "module": "src/index.ts",
7
+ "types": "src/index.ts",
8
8
  "bin": {
9
- "webarchitect": "dist/esm/cli.js"
9
+ "webarchitect": "src/cli.ts"
10
10
  },
11
11
  "files": [
12
- "dist",
12
+ "src",
13
13
  "rulesets"
14
14
  ],
15
15
  "author": {
@@ -26,25 +26,27 @@
26
26
  },
27
27
  "homepage": "https://github.com/canonical/ds25#readme",
28
28
  "scripts": {
29
- "build": "tsc -p tsconfig.build.json",
30
- "check": "bun run check:biome && bun run check:ts",
29
+ "build": "echo 'No build needed - runs directly from TypeScript'",
30
+ "build:all": "bun run build",
31
+ "check": "bun run check:biome && bun run check:ts && bun run check:webarchitect",
31
32
  "check:fix": "bun run check:biome:fix && bun run check:ts",
32
33
  "check:biome": "biome check",
33
34
  "check:biome:fix": "biome check --write",
34
35
  "check:ts": "tsc --noEmit",
35
- "test": "echo 'No tests defined yet'"
36
+ "test": "echo 'No tests defined yet'",
37
+ "check:webarchitect": "webarchitect tool-ts"
36
38
  },
37
39
  "devDependencies": {
38
- "@biomejs/biome": "2.2.4",
39
- "@canonical/biome-config": "^0.10.0",
40
- "@canonical/typescript-config-base": "^0.10.0",
40
+ "@biomejs/biome": "2.3.11",
41
+ "@canonical/biome-config": "^0.11.0",
42
+ "@canonical/typescript-config-base": "^0.11.0",
41
43
  "@types/json-schema": "^7.0.15",
42
- "typescript": "^5.8.2"
44
+ "typescript": "^5.9.3"
43
45
  },
44
46
  "dependencies": {
45
47
  "ajv": "^8.17.1",
46
- "chalk": "^5.4.1",
47
- "commander": "^14.0.0"
48
+ "chalk": "^5.6.2",
49
+ "commander": "^14.0.2"
48
50
  },
49
- "gitHead": "49cd1f8973f269cc6ced61c27c137e379ccb032c"
51
+ "gitHead": "29125736059d1880b3d0fc277b218a63a2574f28"
50
52
  }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://github.com/webarchitect/schemas/main/schema.json",
3
+ "name": "library",
4
+ "extends": ["package"],
5
+ "package-license": {
6
+ "file": {
7
+ "name": "package.json",
8
+ "contains": {
9
+ "type": "object",
10
+ "properties": {
11
+ "license": {
12
+ "const": "LGPL-3.0"
13
+ }
14
+ },
15
+ "required": ["license"]
16
+ }
17
+ }
18
+ }
19
+ }
@@ -34,7 +34,7 @@
34
34
  }
35
35
  }
36
36
  },
37
- "package": {
37
+ "package-structure": {
38
38
  "file": {
39
39
  "name": "package.json",
40
40
  "contains": {
@@ -60,9 +60,6 @@
60
60
  "type": "array",
61
61
  "contains": { "const": "dist" }
62
62
  },
63
- "license": {
64
- "const": "GPL-3.0"
65
- },
66
63
  "scripts": {
67
64
  "type": "object",
68
65
  "properties": {
@@ -88,7 +85,6 @@
88
85
  "module",
89
86
  "types",
90
87
  "files",
91
- "license",
92
88
  "scripts"
93
89
  ]
94
90
  }
@@ -0,0 +1,107 @@
1
+ {
2
+ "$schema": "https://github.com/webarchitect/schemas/main/schema.json",
3
+ "name": "tool-ts",
4
+ "extends": ["base"],
5
+ "biome": {
6
+ "file": {
7
+ "name": "biome.json",
8
+ "contains": {
9
+ "type": "object",
10
+ "properties": {
11
+ "$schema": {
12
+ "type": "string"
13
+ },
14
+ "extends": {
15
+ "type": "array",
16
+ "items": { "const": "@canonical/biome-config" },
17
+ "minItems": 1,
18
+ "maxItems": 1
19
+ },
20
+ "files": {
21
+ "type": "object",
22
+ "properties": {
23
+ "includes": {
24
+ "type": "array",
25
+ "items": { "type": "string" }
26
+ }
27
+ },
28
+ "additionalProperties": false,
29
+ "description": "File inclusion patterns only"
30
+ }
31
+ },
32
+ "required": ["extends"],
33
+ "additionalProperties": false
34
+ }
35
+ }
36
+ },
37
+ "package-structure": {
38
+ "file": {
39
+ "name": "package.json",
40
+ "contains": {
41
+ "type": "object",
42
+ "properties": {
43
+ "name": {
44
+ "type": "string",
45
+ "pattern": "^@canonical/"
46
+ },
47
+ "version": {
48
+ "type": "string"
49
+ },
50
+ "type": {
51
+ "const": "module"
52
+ },
53
+ "module": {
54
+ "const": "src/index.ts"
55
+ },
56
+ "types": {
57
+ "const": "src/index.ts"
58
+ },
59
+ "files": {
60
+ "type": "array",
61
+ "contains": { "const": "src" }
62
+ },
63
+ "scripts": {
64
+ "type": "object",
65
+ "properties": {
66
+ "build": {
67
+ "type": "string",
68
+ "description": "Build command (can be no-op for TS-only packages)"
69
+ },
70
+ "test": {
71
+ "type": "string",
72
+ "description": "Test command (implementation flexible)"
73
+ },
74
+ "check:ts": {
75
+ "const": "tsc --noEmit"
76
+ }
77
+ },
78
+ "required": ["build", "test", "check:ts"]
79
+ }
80
+ },
81
+ "required": [
82
+ "name",
83
+ "version",
84
+ "type",
85
+ "module",
86
+ "types",
87
+ "files",
88
+ "scripts"
89
+ ]
90
+ }
91
+ }
92
+ },
93
+ "package-license": {
94
+ "file": {
95
+ "name": "package.json",
96
+ "contains": {
97
+ "type": "object",
98
+ "properties": {
99
+ "license": {
100
+ "const": "GPL-3.0"
101
+ }
102
+ },
103
+ "required": ["license"]
104
+ }
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://github.com/webarchitect/schemas/main/schema.json",
3
+ "name": "tool",
4
+ "extends": ["package"],
5
+ "package-license": {
6
+ "file": {
7
+ "name": "package.json",
8
+ "contains": {
9
+ "type": "object",
10
+ "properties": {
11
+ "license": {
12
+ "const": "GPL-3.0"
13
+ }
14
+ },
15
+ "required": ["license"]
16
+ }
17
+ }
18
+ }
19
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env bun
2
+ import type { ErrorObject } from "ajv";
3
+ import chalk from "chalk";
4
+ import { Command } from "commander";
5
+ import { discoverAllRulesets } from "./lib/index.js";
6
+ import type { ValidationResult } from "./types.js";
7
+ import validate from "./validate.js";
8
+
9
+ const program = new Command();
10
+
11
+ /**
12
+ * Formats AJV validation errors into human-readable messages.
13
+ * Groups errors by their property path and removes duplicate messages.
14
+ *
15
+ * @param errorsJson - JSON string containing an array of AJV ErrorObject instances
16
+ * @returns Array of formatted error messages, one per property path
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const errors = '[{"instancePath": "/name", "message": "must be string"}]';
21
+ * const formatted = formatAjvErrors(errors);
22
+ * // Returns: ["name: must be string"]
23
+ * ```
24
+ */
25
+ function formatAjvErrors(errorsJson: string): string[] {
26
+ try {
27
+ const errors = JSON.parse(errorsJson) as ErrorObject[];
28
+ const errorsByPath = new Map<string, Set<string>>();
29
+
30
+ for (const error of errors) {
31
+ const path = error.instancePath || "root";
32
+ const message = error.message || "validation failed";
33
+
34
+ if (!errorsByPath.has(path)) {
35
+ errorsByPath.set(path, new Set());
36
+ }
37
+ // Use optional chaining for safety
38
+ errorsByPath.get(path)?.add(message);
39
+ }
40
+
41
+ return Array.from(errorsByPath.entries()).map(([path, messages]) => {
42
+ const property = path === "root" ? "file" : path.replace("/", "");
43
+ const messageList = Array.from(messages).join(", ");
44
+ return `${property}: ${messageList}`;
45
+ });
46
+ } catch {
47
+ // Fallback if JSON parsing fails
48
+ return [errorsJson];
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Formats validation results for terminal output with color coding.
54
+ * Displays failed validations first, followed by passed validations,
55
+ * and ends with a summary. In verbose mode, shows additional context
56
+ * including file contents and validation rules.
57
+ *
58
+ * @param results - Array of validation results to format
59
+ * @param verbose - Whether to show detailed information including file contents and rules
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const results = [
64
+ * { rule: "package.json", passed: true },
65
+ * { rule: "biome.json", passed: false, message: "File not found" }
66
+ * ];
67
+ * formatTerminalOutput(results, true);
68
+ * ```
69
+ */
70
+ function formatTerminalOutput(
71
+ results: ValidationResult[],
72
+ verbose = false,
73
+ ): void {
74
+ console.log(); // Empty line for spacing
75
+
76
+ const passedResults = results.filter((r) => r.passed);
77
+ const failedResults = results.filter((r) => !r.passed);
78
+
79
+ if (failedResults.length > 0) {
80
+ console.log(chalk.red.bold("✗ FAILED VALIDATIONS"));
81
+ console.log(chalk.gray("─".repeat(50)));
82
+
83
+ for (const result of failedResults) {
84
+ console.log(chalk.red(`✗ ${result.rule}`));
85
+
86
+ if (verbose && result.context) {
87
+ console.log(chalk.gray(` Target: ${result.context.target}`));
88
+ if (result.context.description) {
89
+ console.log(chalk.gray(` Rule: ${result.context.description}`));
90
+ }
91
+ }
92
+
93
+ if (result.message) {
94
+ // Check if message contains JSON (AJV errors)
95
+ if (result.message.includes("Validation failed: [")) {
96
+ const jsonStart = result.message.indexOf("[");
97
+ const prefix = result.message.substring(0, jsonStart).trim();
98
+ const jsonPart = result.message.substring(jsonStart);
99
+
100
+ if (prefix) {
101
+ console.log(chalk.gray(` ${prefix}`));
102
+ }
103
+
104
+ const formattedErrors = formatAjvErrors(jsonPart);
105
+ for (const error of formattedErrors) {
106
+ console.log(chalk.gray(` • ${error}`));
107
+ }
108
+ } else {
109
+ console.log(chalk.gray(` ${result.message}`));
110
+ }
111
+ }
112
+
113
+ if (
114
+ verbose &&
115
+ result.context?.value &&
116
+ result.context.value !== "[File not found]"
117
+ ) {
118
+ // Use regular string instead of template literal where no interpolation needed
119
+ console.log(chalk.gray(" Found:"));
120
+ if (typeof result.context.value === "object") {
121
+ const preview = JSON.stringify(result.context.value, null, 2)
122
+ .split("\n")
123
+ .slice(0, 10) // Show first 10 lines
124
+ .map((line) => ` ${line}`)
125
+ .join("\n");
126
+ console.log(chalk.gray(preview));
127
+ if (JSON.stringify(result.context.value).split("\n").length > 10) {
128
+ console.log(chalk.gray(" ... (truncated)"));
129
+ }
130
+ } else {
131
+ console.log(chalk.gray(` ${result.context.value}`));
132
+ }
133
+ }
134
+ console.log(); // Empty line between failures
135
+ }
136
+ }
137
+
138
+ if (passedResults.length > 0) {
139
+ console.log(chalk.green.bold("✓ PASSED VALIDATIONS"));
140
+ console.log(chalk.gray("─".repeat(50)));
141
+
142
+ for (const result of passedResults) {
143
+ console.log(chalk.green(`✓ ${result.rule}`));
144
+
145
+ if (verbose && result.context) {
146
+ console.log(chalk.gray(` Target: ${result.context.target}`));
147
+ if (result.context.description) {
148
+ console.log(chalk.gray(` Rule: ${result.context.description}`));
149
+ }
150
+
151
+ if (result.context.value && typeof result.context.value === "object") {
152
+ // Use regular string instead of template literal
153
+ console.log(chalk.gray(" Validated content:"));
154
+ const preview = JSON.stringify(result.context.value, null, 2)
155
+ .split("\n")
156
+ .slice(0, 5) // Show fewer lines for passed validations
157
+ .map((line) => ` ${line}`)
158
+ .join("\n");
159
+ console.log(chalk.gray(preview));
160
+ if (JSON.stringify(result.context.value).split("\n").length > 5) {
161
+ console.log(chalk.gray(" ... (content validated successfully)"));
162
+ }
163
+ }
164
+ }
165
+ }
166
+ console.log();
167
+ }
168
+
169
+ // Summary
170
+ const total = results.length;
171
+ const failed = failedResults.length;
172
+ const passed = passedResults.length;
173
+
174
+ if (failed > 0) {
175
+ console.log(
176
+ chalk.red.bold(
177
+ `Summary: ${failed}/${total} validations failed (${passed} passed)`,
178
+ ),
179
+ );
180
+ } else {
181
+ console.log(chalk.green.bold(`Summary: All ${total} validations passed`));
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Formats validation results as JSON for programmatic consumption.
187
+ * Outputs a structured object with summary statistics and detailed results.
188
+ *
189
+ * @param results - Array of validation results to format
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * const results = [
194
+ * { rule: "package.json", passed: true },
195
+ * { rule: "biome.json", passed: false, message: "File not found" }
196
+ * ];
197
+ * formatJsonOutput(results);
198
+ * // Outputs: {
199
+ * // "summary": { "total": 2, "passed": 1, "failed": 1 },
200
+ * // "results": [...]
201
+ * // }
202
+ * ```
203
+ */
204
+ function formatJsonOutput(results: ValidationResult[]): void {
205
+ const output = {
206
+ summary: {
207
+ total: results.length,
208
+ passed: results.filter((r) => r.passed).length,
209
+ failed: results.filter((r) => !r.passed).length,
210
+ },
211
+ results: results.map((result) => ({
212
+ rule: result.rule,
213
+ passed: result.passed,
214
+ ...(result.message && { message: result.message }),
215
+ })),
216
+ };
217
+
218
+ console.log(JSON.stringify(output, null, 2));
219
+ }
220
+
221
+ /**
222
+ * Displays available rulesets in a tree-like format.
223
+ * Groups rulesets by their location (bundled vs current directory).
224
+ */
225
+ async function displayRulesetTree(): Promise<void> {
226
+ const { bundled, local } = await discoverAllRulesets();
227
+
228
+ if (bundled.length === 0 && local.length === 0) {
229
+ console.log(chalk.yellow("No rulesets found"));
230
+ return;
231
+ }
232
+
233
+ console.log(chalk.bold("\nAvailable Webarchitect Rulesets:"));
234
+ console.log(chalk.gray("─".repeat(50)));
235
+
236
+ // Display bundled rulesets
237
+ if (bundled.length > 0) {
238
+ console.log(chalk.cyan("Bundled Rulesets:"));
239
+ console.log(chalk.gray(`└── ${bundled[0].path.replace(/\/[^/]+$/, "")}/`));
240
+ for (let i = 0; i < bundled.length; i++) {
241
+ const isLast = i === bundled.length - 1;
242
+ const prefix = isLast ? "└──" : "├──";
243
+ const filename = `${bundled[i].name}.ruleset.json`;
244
+ console.log(
245
+ ` ${prefix} ${chalk.bold(bundled[i].name)} ${chalk.gray(`(${filename})`)}`,
246
+ );
247
+ }
248
+ }
249
+
250
+ // Display current directory rulesets
251
+ if (bundled.length > 0) console.log(); // Add spacing between sections
252
+ console.log(chalk.green("Current Directory:"));
253
+ console.log(chalk.gray(`└── ${process.cwd()}/`));
254
+ if (local.length > 0) {
255
+ for (let i = 0; i < local.length; i++) {
256
+ const isLast = i === local.length - 1;
257
+ const prefix = isLast ? "└──" : "├──";
258
+ const filename = `${local[i].name}.ruleset.json`;
259
+ console.log(
260
+ ` ${prefix} ${chalk.bold(local[i].name)} ${chalk.gray(`(${filename})`)}`,
261
+ );
262
+ }
263
+ } else {
264
+ console.log(chalk.gray(` └── (no rulesets found at this path)`));
265
+ }
266
+ }
267
+
268
+ program
269
+ .name("webarchitect")
270
+ .argument("[ruleset]", "ruleset identifier, local path, or URL")
271
+ .option("-v, --verbose", "show all validation results")
272
+ .option("--json", "output results in JSON format")
273
+ .option("--list", "list all available rulesets")
274
+ .action(async (schemaArg, options) => {
275
+ // Handle --list option
276
+ if (options.list) {
277
+ await displayRulesetTree();
278
+ return;
279
+ }
280
+
281
+ // Require ruleset argument if not listing
282
+ if (!schemaArg) {
283
+ console.error(chalk.red("Error: Missing required argument 'ruleset'"));
284
+ console.error(
285
+ chalk.gray("Use 'webarchitect --list' to see available rulesets"),
286
+ );
287
+ process.exit(1);
288
+ }
289
+ try {
290
+ const results = await validate(process.cwd(), schemaArg);
291
+
292
+ if (options.json) {
293
+ formatJsonOutput(results);
294
+ } else {
295
+ formatTerminalOutput(results, options.verbose);
296
+
297
+ // Exit with error code if any validations failed
298
+ const hasFailures = results.some((r) => !r.passed);
299
+ if (hasFailures) {
300
+ process.exit(1);
301
+ }
302
+ }
303
+ } catch (e) {
304
+ if (options.json) {
305
+ console.log(
306
+ JSON.stringify(
307
+ {
308
+ error: (e as Error).message,
309
+ success: false,
310
+ },
311
+ null,
312
+ 2,
313
+ ),
314
+ );
315
+ } else {
316
+ console.error(chalk.red(`Error: ${(e as Error).message}`));
317
+ }
318
+ process.exit(1);
319
+ }
320
+ });
321
+
322
+ program.parse();
@@ -0,0 +1,8 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ // Handle both source (src/) and built (dist/esm/) paths
5
+ const srcPath = join(import.meta.dirname, "../rulesets");
6
+ const distPath = join(import.meta.dirname, "../../rulesets");
7
+
8
+ export const BUNDLED_RULESETS_DIR = existsSync(srcPath) ? srcPath : distPath;
@@ -1,2 +1 @@
1
1
  export { default as validate } from "./validate.js";
2
- //# sourceMappingURL=index.js.map
@@ -1,42 +1,66 @@
1
1
  import ajvPackage from "ajv";
2
2
  import draft202012 from "../schemas/draft-2020-12.json" with { type: "json" };
3
3
  import metaApplicator from "../schemas/meta/applicator.json" with {
4
- type: "json"
4
+ type: "json",
5
5
  };
6
6
  import metaContent from "../schemas/meta/content.json" with { type: "json" };
7
7
  import metaCore from "../schemas/meta/core.json" with { type: "json" };
8
8
  import metaFormatAnnotation from "../schemas/meta/format-annotation.json" with {
9
- type: "json"
9
+ type: "json",
10
10
  };
11
11
  import metaMetaData from "../schemas/meta/meta-data.json" with { type: "json" };
12
12
  import metaUnevaluated from "../schemas/meta/unevaluated.json" with {
13
- type: "json"
13
+ type: "json",
14
14
  };
15
15
  import metaValidation from "../schemas/meta/validation.json" with {
16
- type: "json"
16
+ type: "json",
17
17
  };
18
+
18
19
  const Ajv = ajvPackage.default;
20
+
19
21
  // Create AJV instance with configuration optimized for our use case
20
22
  const ajv = new Ajv({
21
- strict: false, // Allow unknown keywords without throwing errors
22
- allErrors: true, // Collect all validation errors, not just the first one
23
- validateSchema: false, // Temporarily disable automatic schema validation during setup
23
+ strict: false, // Allow unknown keywords without throwing errors
24
+ allErrors: true, // Collect all validation errors, not just the first one
25
+ validateSchema: false, // Temporarily disable automatic schema validation during setup
24
26
  });
27
+
25
28
  // Step 1: Register all the foundational meta-schemas first
26
29
  // These are the building blocks that the main Draft 2020-12 schema depends on
27
30
  ajv.addSchema(metaCore, "https://json-schema.org/draft/2020-12/meta/core");
28
- ajv.addSchema(metaApplicator, "https://json-schema.org/draft/2020-12/meta/applicator");
29
- ajv.addSchema(metaUnevaluated, "https://json-schema.org/draft/2020-12/meta/unevaluated");
30
- ajv.addSchema(metaValidation, "https://json-schema.org/draft/2020-12/meta/validation");
31
- ajv.addSchema(metaMetaData, "https://json-schema.org/draft/2020-12/meta/meta-data");
32
- ajv.addSchema(metaFormatAnnotation, "https://json-schema.org/draft/2020-12/meta/format-annotation");
33
- ajv.addSchema(metaContent, "https://json-schema.org/draft/2020-12/meta/content");
31
+ ajv.addSchema(
32
+ metaApplicator,
33
+ "https://json-schema.org/draft/2020-12/meta/applicator",
34
+ );
35
+ ajv.addSchema(
36
+ metaUnevaluated,
37
+ "https://json-schema.org/draft/2020-12/meta/unevaluated",
38
+ );
39
+ ajv.addSchema(
40
+ metaValidation,
41
+ "https://json-schema.org/draft/2020-12/meta/validation",
42
+ );
43
+ ajv.addSchema(
44
+ metaMetaData,
45
+ "https://json-schema.org/draft/2020-12/meta/meta-data",
46
+ );
47
+ ajv.addSchema(
48
+ metaFormatAnnotation,
49
+ "https://json-schema.org/draft/2020-12/meta/format-annotation",
50
+ );
51
+ ajv.addSchema(
52
+ metaContent,
53
+ "https://json-schema.org/draft/2020-12/meta/content",
54
+ );
55
+
34
56
  // Step 2: Now register the main Draft 2020-12 schema
35
57
  // This schema composes all the meta-schemas above into the complete specification
36
58
  ajv.addSchema(draft202012, "https://json-schema.org/draft/2020-12/schema");
37
59
  ajv.addSchema(draft202012, "http://json-schema.org/draft/2020-12/schema");
60
+
38
61
  // Step 3: Re-enable schema validation now that all pieces are in place
39
62
  ajv.opts.validateSchema = true;
63
+
40
64
  // Step 4: Add format validation support manually to avoid type issues
41
65
  // Instead of using ajv-formats, we'll add the most common formats we need
42
66
  ajv.addFormat("email", /^[^\s@]+@[^\s@]+\.[^\s@]+$/);
@@ -44,16 +68,15 @@ ajv.addFormat("uri", /^https?:\/\/[^\s]+$/);
44
68
  ajv.addFormat("uri-reference", /^[^\s]*$/);
45
69
  // Add regex format validation - validates that a string is a valid regular expression
46
70
  ajv.addFormat("regex", {
47
- type: "string",
48
- validate: (data) => {
49
- try {
50
- new RegExp(data);
51
- return true;
52
- }
53
- catch {
54
- return false;
55
- }
56
- },
71
+ type: "string",
72
+ validate: (data: string) => {
73
+ try {
74
+ new RegExp(data);
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ },
57
80
  });
81
+
58
82
  export default ajv;
59
- //# sourceMappingURL=ajv.js.map