@principal-ai/principal-view-cli 0.1.13
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 +157 -0
- package/dist/commands/create.d.ts +6 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +50 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +274 -0
- package/dist/commands/hooks.d.ts +9 -0
- package/dist/commands/hooks.d.ts.map +1 -0
- package/dist/commands/hooks.js +295 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +290 -0
- package/dist/commands/lint.d.ts +6 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +375 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +80 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +333 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +389 -0
- package/dist/index.cjs +17286 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Visual Validation CLI
|
|
2
|
+
|
|
3
|
+
A command-line tool for validating and managing `.canvas` configuration files for the Visual Validation Framework.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @principal-ai/visual-validation-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
The CLI provides two command aliases: `vv` (primary) and `visual-validation`.
|
|
14
|
+
|
|
15
|
+
### Commands
|
|
16
|
+
|
|
17
|
+
#### `init` - Initialize Project Structure
|
|
18
|
+
|
|
19
|
+
Set up a new `.vgc` folder with template files:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
vv init
|
|
23
|
+
vv init --name my-architecture
|
|
24
|
+
vv init --force # Overwrite existing files
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### `validate` - Validate Canvas Files
|
|
28
|
+
|
|
29
|
+
Strict validation of `.canvas` configuration files:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
vv validate # Validates all .vgc/*.canvas files
|
|
33
|
+
vv validate path/to/file.canvas
|
|
34
|
+
vv validate "**/*.canvas" # Glob pattern
|
|
35
|
+
vv validate --quiet # Only output errors
|
|
36
|
+
vv validate --json # Output as JSON
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Validation checks:**
|
|
40
|
+
- Required `vv` extension with name and version
|
|
41
|
+
- All nodes have required fields (id, type, x, y, width, height)
|
|
42
|
+
- Custom node types must have `vv.nodeType` and valid `vv.shape`
|
|
43
|
+
- Edge references point to existing nodes
|
|
44
|
+
- Edge types reference defined edge type definitions
|
|
45
|
+
|
|
46
|
+
#### `list` (alias: `ls`) - List Canvas Files
|
|
47
|
+
|
|
48
|
+
Display all canvas files in the project with metadata:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
vv list
|
|
52
|
+
vv ls --all # Search all directories
|
|
53
|
+
vv ls --json # Output as JSON
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### `schema` - Display Format Documentation
|
|
57
|
+
|
|
58
|
+
Show detailed documentation about the canvas format:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
vv schema # Overview
|
|
62
|
+
vv schema nodes # Node types, shapes, colors
|
|
63
|
+
vv schema edges # Edge properties and styles
|
|
64
|
+
vv schema vv # Visual Validation extension fields
|
|
65
|
+
vv schema examples # Complete examples
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### `doctor` - Configuration Health Check
|
|
69
|
+
|
|
70
|
+
Check configuration staleness and validate source patterns:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
vv doctor
|
|
74
|
+
vv doctor --quiet # Only show errors and warnings
|
|
75
|
+
vv doctor --errors-only # For pre-commit hooks
|
|
76
|
+
vv doctor --json # Output as JSON
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Canvas Format
|
|
80
|
+
|
|
81
|
+
Canvas files follow the [JSON Canvas](https://jsoncanvas.org/) specification with Visual Validation extensions that maintain compatibility with standard tools like Obsidian.
|
|
82
|
+
|
|
83
|
+
### Required Structure
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"nodes": [...],
|
|
88
|
+
"edges": [...],
|
|
89
|
+
"vv": {
|
|
90
|
+
"name": "my-architecture",
|
|
91
|
+
"version": "1.0.0"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Node Types
|
|
97
|
+
|
|
98
|
+
**Standard types** (no additional metadata required):
|
|
99
|
+
- `text` - Text content
|
|
100
|
+
- `group` - Container for other nodes
|
|
101
|
+
- `file` - File reference
|
|
102
|
+
- `link` - URL link
|
|
103
|
+
|
|
104
|
+
**Custom types** require `vv` extension:
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"id": "node-1",
|
|
108
|
+
"type": "custom",
|
|
109
|
+
"x": 0, "y": 0,
|
|
110
|
+
"width": 200, "height": 100,
|
|
111
|
+
"vv": {
|
|
112
|
+
"nodeType": "service",
|
|
113
|
+
"shape": "rectangle"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Available shapes:** `circle`, `rectangle`, `hexagon`, `diamond`, `custom`
|
|
119
|
+
|
|
120
|
+
### Edge Types
|
|
121
|
+
|
|
122
|
+
Define reusable edge styles at the canvas level:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"vv": {
|
|
127
|
+
"edgeTypes": {
|
|
128
|
+
"data-flow": {
|
|
129
|
+
"style": "dashed",
|
|
130
|
+
"color": "#3498db",
|
|
131
|
+
"width": 2
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Use in edges:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"id": "edge-1",
|
|
143
|
+
"fromNode": "node-1",
|
|
144
|
+
"toNode": "node-2",
|
|
145
|
+
"vv": {
|
|
146
|
+
"edgeType": "data-flow"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Requirements
|
|
152
|
+
|
|
153
|
+
- Node.js >= 18
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,mBAAmB,IAAI,OAAO,CA2C7C"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create command - Create a new canvas file in the .principal-views folder
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
const TEMPLATE_CANVAS = {
|
|
9
|
+
nodes: [],
|
|
10
|
+
edges: [],
|
|
11
|
+
};
|
|
12
|
+
export function createCreateCommand() {
|
|
13
|
+
const command = new Command('create');
|
|
14
|
+
command
|
|
15
|
+
.description('Create a new canvas file in the .principal-views folder')
|
|
16
|
+
.requiredOption('-n, --name <name>', 'Name for the canvas file (e.g., "cache-sync-architecture")')
|
|
17
|
+
.option('-f, --force', 'Overwrite existing file')
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
try {
|
|
20
|
+
const vgcDir = join(process.cwd(), '.principal-views');
|
|
21
|
+
const canvasFile = join(vgcDir, `${options.name}.canvas`);
|
|
22
|
+
// Check if .principal-views directory exists
|
|
23
|
+
if (!existsSync(vgcDir)) {
|
|
24
|
+
mkdirSync(vgcDir, { recursive: true });
|
|
25
|
+
console.log(chalk.green(`Created directory: .principal-views/`));
|
|
26
|
+
}
|
|
27
|
+
// Check if canvas file already exists
|
|
28
|
+
if (existsSync(canvasFile) && !options.force) {
|
|
29
|
+
console.error(chalk.red(`Error: Canvas file already exists: .principal-views/${options.name}.canvas`));
|
|
30
|
+
console.log(chalk.yellow(`Use ${chalk.cyan('--force')} to overwrite`));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
// Create the canvas file
|
|
34
|
+
writeFileSync(canvasFile, JSON.stringify(TEMPLATE_CANVAS, null, 2));
|
|
35
|
+
console.log(chalk.green(`✓ Created canvas file: .principal-views/${options.name}.canvas`));
|
|
36
|
+
// Show next steps
|
|
37
|
+
console.log('');
|
|
38
|
+
console.log(chalk.bold('Next steps:'));
|
|
39
|
+
console.log(` 1. Open ${chalk.cyan(`.principal-views/${options.name}.canvas`)} in your editor`);
|
|
40
|
+
console.log(` 2. Add nodes and edges to define your architecture`);
|
|
41
|
+
console.log(` 3. Run ${chalk.cyan('privu validate')} to check your configuration`);
|
|
42
|
+
console.log(` 4. Run ${chalk.cyan('privu doctor')} to verify source mappings`);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error(chalk.red('Error:'), error.message);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return command;
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor command - Check configuration staleness and source pattern validity
|
|
3
|
+
*
|
|
4
|
+
* This command performs two types of checks:
|
|
5
|
+
* 1. Pattern validation: Ensures source patterns in .principal-views/*.yaml configs match actual files
|
|
6
|
+
* 2. Freshness check: Compares config modification times vs source file changes
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
export declare function createDoctorCommand(): Command;
|
|
10
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+KpC,wBAAgB,mBAAmB,IAAI,OAAO,CAgK7C"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor command - Check configuration staleness and source pattern validity
|
|
3
|
+
*
|
|
4
|
+
* This command performs two types of checks:
|
|
5
|
+
* 1. Pattern validation: Ensures source patterns in .principal-views/*.yaml configs match actual files
|
|
6
|
+
* 2. Freshness check: Compares config modification times vs source file changes
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
10
|
+
import { resolve, relative } from 'node:path';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { globby } from 'globby';
|
|
13
|
+
import yaml from 'js-yaml';
|
|
14
|
+
/**
|
|
15
|
+
* Format a time difference in human-readable form
|
|
16
|
+
*/
|
|
17
|
+
function formatTimeDiff(ms) {
|
|
18
|
+
const seconds = Math.floor(ms / 1000);
|
|
19
|
+
const minutes = Math.floor(seconds / 60);
|
|
20
|
+
const hours = Math.floor(minutes / 60);
|
|
21
|
+
const days = Math.floor(hours / 24);
|
|
22
|
+
if (days > 0)
|
|
23
|
+
return `${days} day${days > 1 ? 's' : ''}`;
|
|
24
|
+
if (hours > 0)
|
|
25
|
+
return `${hours} hour${hours > 1 ? 's' : ''}`;
|
|
26
|
+
if (minutes > 0)
|
|
27
|
+
return `${minutes} minute${minutes > 1 ? 's' : ''}`;
|
|
28
|
+
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check a single .principal-views config file for staleness issues
|
|
32
|
+
*/
|
|
33
|
+
async function checkConfig(configPath, projectRoot) {
|
|
34
|
+
const absolutePath = resolve(configPath);
|
|
35
|
+
const relativePath = relative(projectRoot, absolutePath);
|
|
36
|
+
const issues = [];
|
|
37
|
+
const stats = {
|
|
38
|
+
nodeTypesChecked: 0,
|
|
39
|
+
patternsChecked: 0,
|
|
40
|
+
filesMatched: 0,
|
|
41
|
+
staleConfigs: 0,
|
|
42
|
+
};
|
|
43
|
+
let configName = 'Unknown';
|
|
44
|
+
try {
|
|
45
|
+
const content = readFileSync(absolutePath, 'utf8');
|
|
46
|
+
const config = yaml.load(content);
|
|
47
|
+
const configStats = statSync(absolutePath);
|
|
48
|
+
const configMtime = configStats.mtime.getTime();
|
|
49
|
+
configName = config.metadata?.name || relativePath;
|
|
50
|
+
// Check if config has nodeTypes with sources
|
|
51
|
+
if (!config.nodeTypes || Object.keys(config.nodeTypes).length === 0) {
|
|
52
|
+
issues.push({
|
|
53
|
+
type: 'info',
|
|
54
|
+
configFile: relativePath,
|
|
55
|
+
message: 'No node types defined in configuration',
|
|
56
|
+
});
|
|
57
|
+
return { configFile: relativePath, configName, issues, stats };
|
|
58
|
+
}
|
|
59
|
+
// Check each node type's source patterns
|
|
60
|
+
for (const [nodeTypeName, nodeType] of Object.entries(config.nodeTypes)) {
|
|
61
|
+
stats.nodeTypesChecked++;
|
|
62
|
+
if (!nodeType.sources || nodeType.sources.length === 0) {
|
|
63
|
+
issues.push({
|
|
64
|
+
type: 'info',
|
|
65
|
+
configFile: relativePath,
|
|
66
|
+
nodeType: nodeTypeName,
|
|
67
|
+
message: `Node type "${nodeTypeName}" has no source patterns defined`,
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
for (const pattern of nodeType.sources) {
|
|
72
|
+
stats.patternsChecked++;
|
|
73
|
+
// Find files matching this pattern
|
|
74
|
+
const matchedFiles = await globby(pattern, {
|
|
75
|
+
cwd: projectRoot,
|
|
76
|
+
gitignore: true,
|
|
77
|
+
ignore: ['node_modules/**', 'dist/**', '.git/**'],
|
|
78
|
+
});
|
|
79
|
+
if (matchedFiles.length === 0) {
|
|
80
|
+
// Pattern doesn't match any files - this is a warning
|
|
81
|
+
issues.push({
|
|
82
|
+
type: 'warning',
|
|
83
|
+
configFile: relativePath,
|
|
84
|
+
nodeType: nodeTypeName,
|
|
85
|
+
message: `Source pattern "${pattern}" doesn't match any files`,
|
|
86
|
+
details: 'This pattern may be outdated or the files may have been moved/deleted',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
stats.filesMatched += matchedFiles.length;
|
|
91
|
+
// Check if any matched files are newer than the config
|
|
92
|
+
let newestFile = null;
|
|
93
|
+
let newestMtime = 0;
|
|
94
|
+
for (const file of matchedFiles) {
|
|
95
|
+
const filePath = resolve(projectRoot, file);
|
|
96
|
+
try {
|
|
97
|
+
const fileStats = statSync(filePath);
|
|
98
|
+
const fileMtime = fileStats.mtime.getTime();
|
|
99
|
+
if (fileMtime > newestMtime) {
|
|
100
|
+
newestMtime = fileMtime;
|
|
101
|
+
newestFile = file;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// File may have been deleted between glob and stat
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Check staleness (with 5-second buffer for build tools)
|
|
109
|
+
const STALE_THRESHOLD_MS = 5000;
|
|
110
|
+
if (newestFile && newestMtime > configMtime + STALE_THRESHOLD_MS) {
|
|
111
|
+
const timeDiff = newestMtime - configMtime;
|
|
112
|
+
stats.staleConfigs++;
|
|
113
|
+
issues.push({
|
|
114
|
+
type: 'warning',
|
|
115
|
+
configFile: relativePath,
|
|
116
|
+
nodeType: nodeTypeName,
|
|
117
|
+
message: `Config may be stale: "${newestFile}" was modified ${formatTimeDiff(timeDiff)} after the config`,
|
|
118
|
+
details: `Pattern: ${pattern} (matched ${matchedFiles.length} file${matchedFiles.length > 1 ? 's' : ''})`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
issues.push({
|
|
127
|
+
type: 'error',
|
|
128
|
+
configFile: relativePath,
|
|
129
|
+
message: `Failed to parse config: ${error.message}`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return { configFile: relativePath, configName, issues, stats };
|
|
133
|
+
}
|
|
134
|
+
export function createDoctorCommand() {
|
|
135
|
+
const command = new Command('doctor');
|
|
136
|
+
command
|
|
137
|
+
.description('Check configuration staleness and source pattern validity')
|
|
138
|
+
.option('-q, --quiet', 'Only show errors and warnings')
|
|
139
|
+
.option('--errors-only', 'Only show errors (for pre-commit hooks)')
|
|
140
|
+
.option('--json', 'Output results as JSON')
|
|
141
|
+
.option('-d, --dir <path>', 'Project directory (defaults to current directory)')
|
|
142
|
+
.action(async (options) => {
|
|
143
|
+
try {
|
|
144
|
+
const projectRoot = resolve(options.dir || process.cwd());
|
|
145
|
+
const vgcDir = resolve(projectRoot, '.principal-views');
|
|
146
|
+
if (!existsSync(vgcDir)) {
|
|
147
|
+
if (options.json) {
|
|
148
|
+
console.log(JSON.stringify({ error: 'No .principal-views directory found', results: [] }));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
console.log(chalk.yellow('No .principal-views directory found.'));
|
|
152
|
+
console.log(chalk.dim('Run "privu init" to create a configuration.'));
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Find all .yaml config files
|
|
157
|
+
const configFiles = await globby(['*.yaml', '*.yml'], {
|
|
158
|
+
cwd: vgcDir,
|
|
159
|
+
absolute: true,
|
|
160
|
+
ignore: ['README.md'],
|
|
161
|
+
});
|
|
162
|
+
if (configFiles.length === 0) {
|
|
163
|
+
if (options.json) {
|
|
164
|
+
console.log(JSON.stringify({ error: 'No config files found in .principal-views', results: [] }));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.log(chalk.yellow('No configuration files found in .principal-views/'));
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Check each config
|
|
172
|
+
const results = [];
|
|
173
|
+
for (const configFile of configFiles) {
|
|
174
|
+
const result = await checkConfig(configFile, projectRoot);
|
|
175
|
+
results.push(result);
|
|
176
|
+
}
|
|
177
|
+
// Aggregate stats
|
|
178
|
+
const totalStats = results.reduce((acc, r) => ({
|
|
179
|
+
nodeTypesChecked: acc.nodeTypesChecked + r.stats.nodeTypesChecked,
|
|
180
|
+
patternsChecked: acc.patternsChecked + r.stats.patternsChecked,
|
|
181
|
+
filesMatched: acc.filesMatched + r.stats.filesMatched,
|
|
182
|
+
staleConfigs: acc.staleConfigs + r.stats.staleConfigs,
|
|
183
|
+
}), { nodeTypesChecked: 0, patternsChecked: 0, filesMatched: 0, staleConfigs: 0 });
|
|
184
|
+
// Filter issues based on options
|
|
185
|
+
const filterIssues = (issues) => {
|
|
186
|
+
if (options.errorsOnly) {
|
|
187
|
+
return issues.filter(i => i.type === 'error');
|
|
188
|
+
}
|
|
189
|
+
if (options.quiet) {
|
|
190
|
+
return issues.filter(i => i.type === 'error' || i.type === 'warning');
|
|
191
|
+
}
|
|
192
|
+
return issues;
|
|
193
|
+
};
|
|
194
|
+
// Count issues
|
|
195
|
+
const allIssues = results.flatMap(r => filterIssues(r.issues));
|
|
196
|
+
const errorCount = allIssues.filter(i => i.type === 'error').length;
|
|
197
|
+
const warningCount = allIssues.filter(i => i.type === 'warning').length;
|
|
198
|
+
// Output results
|
|
199
|
+
if (options.json) {
|
|
200
|
+
console.log(JSON.stringify({
|
|
201
|
+
results: results.map(r => ({
|
|
202
|
+
...r,
|
|
203
|
+
issues: filterIssues(r.issues),
|
|
204
|
+
})),
|
|
205
|
+
summary: {
|
|
206
|
+
configs: results.length,
|
|
207
|
+
errors: errorCount,
|
|
208
|
+
warnings: warningCount,
|
|
209
|
+
...totalStats,
|
|
210
|
+
},
|
|
211
|
+
}, null, 2));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
if (!options.quiet && !options.errorsOnly) {
|
|
215
|
+
console.log(chalk.bold(`\nChecking ${results.length} configuration file(s)...\n`));
|
|
216
|
+
}
|
|
217
|
+
for (const result of results) {
|
|
218
|
+
const issues = filterIssues(result.issues);
|
|
219
|
+
if (issues.length === 0 && !options.quiet && !options.errorsOnly) {
|
|
220
|
+
console.log(chalk.green(`✓ ${result.configFile}`) + chalk.dim(` (${result.configName})`));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (issues.length > 0) {
|
|
224
|
+
const hasErrors = issues.some(i => i.type === 'error');
|
|
225
|
+
const icon = hasErrors ? chalk.red('✗') : chalk.yellow('⚠');
|
|
226
|
+
console.log(`${icon} ${result.configFile}` + chalk.dim(` (${result.configName})`));
|
|
227
|
+
for (const issue of issues) {
|
|
228
|
+
const prefix = issue.nodeType ? `[${issue.nodeType}] ` : '';
|
|
229
|
+
if (issue.type === 'error') {
|
|
230
|
+
console.log(chalk.red(` ✗ ${prefix}${issue.message}`));
|
|
231
|
+
}
|
|
232
|
+
else if (issue.type === 'warning') {
|
|
233
|
+
console.log(chalk.yellow(` ⚠ ${prefix}${issue.message}`));
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
console.log(chalk.dim(` ℹ ${prefix}${issue.message}`));
|
|
237
|
+
}
|
|
238
|
+
if (issue.details) {
|
|
239
|
+
console.log(chalk.dim(` → ${issue.details}`));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
console.log('');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Summary
|
|
246
|
+
if (!options.errorsOnly) {
|
|
247
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
248
|
+
console.log(chalk.dim(`Checked ${totalStats.nodeTypesChecked} node types, ` +
|
|
249
|
+
`${totalStats.patternsChecked} patterns, ` +
|
|
250
|
+
`matched ${totalStats.filesMatched} files`));
|
|
251
|
+
}
|
|
252
|
+
if (errorCount > 0) {
|
|
253
|
+
console.log(chalk.red(`\n✗ ${errorCount} error(s) found`));
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
else if (warningCount > 0 && options.errorsOnly) {
|
|
257
|
+
// In errors-only mode, don't fail on warnings
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
else if (warningCount > 0) {
|
|
261
|
+
console.log(chalk.yellow(`\n⚠ ${warningCount} warning(s) found`));
|
|
262
|
+
}
|
|
263
|
+
else if (!options.quiet && !options.errorsOnly) {
|
|
264
|
+
console.log(chalk.green(`\n✓ All configurations are up to date`));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
console.error(chalk.red('Error:'), error.message);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
return command;
|
|
274
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks command - Manage husky pre-commit hooks for Principal View
|
|
3
|
+
*
|
|
4
|
+
* This command installs/removes pre-commit hooks into a target project
|
|
5
|
+
* that will run `privu doctor` and `privu validate` before each commit.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
export declare function createHooksCommand(): Command;
|
|
9
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/commands/hooks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0NpC,wBAAgB,kBAAkB,IAAI,OAAO,CA6F5C"}
|