@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.
- package/README.md +1 -0
- package/package.json +17 -15
- package/rulesets/library.ruleset.json +19 -0
- package/rulesets/package.ruleset.json +1 -5
- package/rulesets/tool-ts.ruleset.json +107 -0
- package/rulesets/tool.ruleset.json +19 -0
- package/src/cli.ts +322 -0
- package/src/constants.ts +8 -0
- package/{dist/esm/index.js → src/index.ts} +0 -1
- package/{dist/esm/lib/ajv.js → src/lib/ajv.ts} +47 -24
- package/src/lib/describeSchema.ts +59 -0
- package/src/lib/discoverAllRulesets.ts +18 -0
- package/src/lib/discoverRulesetsInDir.ts +41 -0
- package/src/lib/executeValidationRules.ts +54 -0
- package/{dist/esm/lib/index.js → src/lib/index.ts} +1 -1
- package/{dist/esm/lib/listDirectory.js → src/lib/listDirectory.ts} +12 -10
- package/{dist/esm/lib/loadFullSchema.js → src/lib/loadFullSchema.ts} +16 -12
- package/src/lib/resolveSchema.ts +128 -0
- package/src/lib/validateDirectoryRule.ts +158 -0
- package/src/lib/validateFileRule.ts +105 -0
- package/{dist/esm/lib/validateRule.js → src/lib/validateRule.ts} +18 -11
- package/src/schema.json +61 -0
- package/src/schemas/draft-2020-12.json +58 -0
- package/src/schemas/meta/applicator.json +45 -0
- package/src/schemas/meta/content.json +14 -0
- package/src/schemas/meta/core.json +48 -0
- package/src/schemas/meta/format-annotation.json +11 -0
- package/src/schemas/meta/meta-data.json +34 -0
- package/src/schemas/meta/unevaluated.json +12 -0
- package/src/schemas/meta/validation.json +95 -0
- package/src/types.ts +49 -0
- package/{dist/esm/validate.js → src/validate.ts} +8 -4
- package/dist/esm/cli.js +0 -277
- package/dist/esm/cli.js.map +0 -1
- package/dist/esm/constants.js +0 -3
- package/dist/esm/constants.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/lib/ajv.js.map +0 -1
- package/dist/esm/lib/describeSchema.js +0 -53
- package/dist/esm/lib/describeSchema.js.map +0 -1
- package/dist/esm/lib/discoverAllRulesets.js +0 -13
- package/dist/esm/lib/discoverAllRulesets.js.map +0 -1
- package/dist/esm/lib/discoverRulesetsInDir.js +0 -35
- package/dist/esm/lib/discoverRulesetsInDir.js.map +0 -1
- package/dist/esm/lib/executeValidationRules.js +0 -41
- package/dist/esm/lib/executeValidationRules.js.map +0 -1
- package/dist/esm/lib/index.js.map +0 -1
- package/dist/esm/lib/listDirectory.js.map +0 -1
- package/dist/esm/lib/loadFullSchema.js.map +0 -1
- package/dist/esm/lib/resolveSchema.js +0 -113
- package/dist/esm/lib/resolveSchema.js.map +0 -1
- package/dist/esm/lib/validateDirectoryRule.js +0 -138
- package/dist/esm/lib/validateDirectoryRule.js.map +0 -1
- package/dist/esm/lib/validateFileRule.js +0 -92
- package/dist/esm/lib/validateFileRule.js.map +0 -1
- package/dist/esm/lib/validateRule.js.map +0 -1
- package/dist/esm/schema.json +0 -61
- package/dist/esm/schemas/draft-2020-12.json +0 -57
- package/dist/esm/schemas/meta/applicator.json +0 -44
- package/dist/esm/schemas/meta/content.json +0 -12
- package/dist/esm/schemas/meta/core.json +0 -47
- package/dist/esm/schemas/meta/format-annotation.json +0 -10
- package/dist/esm/schemas/meta/meta-data.json +0 -32
- package/dist/esm/schemas/meta/unevaluated.json +0 -11
- package/dist/esm/schemas/meta/validation.json +0 -94
- package/dist/esm/types.js +0 -2
- package/dist/esm/types.js.map +0 -1
- package/dist/esm/validate.js.map +0 -1
- package/dist/types/cli.d.ts +0 -3
- package/dist/types/cli.d.ts.map +0 -1
- package/dist/types/constants.d.ts +0 -2
- package/dist/types/constants.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -2
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/lib/ajv.d.ts +0 -4
- package/dist/types/lib/ajv.d.ts.map +0 -1
- package/dist/types/lib/describeSchema.d.ts +0 -22
- package/dist/types/lib/describeSchema.d.ts.map +0 -1
- package/dist/types/lib/discoverAllRulesets.d.ts +0 -9
- package/dist/types/lib/discoverAllRulesets.d.ts.map +0 -1
- package/dist/types/lib/discoverRulesetsInDir.d.ts +0 -6
- package/dist/types/lib/discoverRulesetsInDir.d.ts.map +0 -1
- package/dist/types/lib/executeValidationRules.d.ts +0 -22
- package/dist/types/lib/executeValidationRules.d.ts.map +0 -1
- package/dist/types/lib/index.d.ts +0 -15
- package/dist/types/lib/index.d.ts.map +0 -1
- package/dist/types/lib/listDirectory.d.ts +0 -20
- package/dist/types/lib/listDirectory.d.ts.map +0 -1
- package/dist/types/lib/loadFullSchema.d.ts +0 -20
- package/dist/types/lib/loadFullSchema.d.ts.map +0 -1
- package/dist/types/lib/resolveSchema.d.ts +0 -29
- package/dist/types/lib/resolveSchema.d.ts.map +0 -1
- package/dist/types/lib/validateDirectoryRule.d.ts +0 -26
- package/dist/types/lib/validateDirectoryRule.d.ts.map +0 -1
- package/dist/types/lib/validateFileRule.d.ts +0 -29
- package/dist/types/lib/validateFileRule.d.ts.map +0 -1
- package/dist/types/lib/validateRule.d.ts +0 -21
- package/dist/types/lib/validateRule.d.ts.map +0 -1
- package/dist/types/types.d.ts +0 -42
- package/dist/types/types.d.ts.map +0 -1
- package/dist/types/validate.d.ts +0 -23
- 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.
|
|
4
|
+
"version": "0.11.0",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"module": "
|
|
7
|
-
"types": "
|
|
6
|
+
"module": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"webarchitect": "
|
|
9
|
+
"webarchitect": "src/cli.ts"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
-
"
|
|
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": "
|
|
30
|
-
"
|
|
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.
|
|
39
|
-
"@canonical/biome-config": "^0.
|
|
40
|
-
"@canonical/typescript-config-base": "^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.
|
|
44
|
+
"typescript": "^5.9.3"
|
|
43
45
|
},
|
|
44
46
|
"dependencies": {
|
|
45
47
|
"ajv": "^8.17.1",
|
|
46
|
-
"chalk": "^5.
|
|
47
|
-
"commander": "^14.0.
|
|
48
|
+
"chalk": "^5.6.2",
|
|
49
|
+
"commander": "^14.0.2"
|
|
48
50
|
},
|
|
49
|
-
"gitHead": "
|
|
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();
|
package/src/constants.ts
ADDED
|
@@ -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,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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13
|
+
type: "json",
|
|
14
14
|
};
|
|
15
15
|
import metaValidation from "../schemas/meta/validation.json" with {
|
|
16
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
ajv.addSchema(
|
|
33
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|