@cxtms/cx-schema 1.0.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 +384 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1523 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +111 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/schemaLoader.d.ts +17 -0
- package/dist/utils/schemaLoader.d.ts.map +1 -0
- package/dist/utils/schemaLoader.js +134 -0
- package/dist/utils/schemaLoader.js.map +1 -0
- package/dist/validator.d.ts +64 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +380 -0
- package/dist/validator.js.map +1 -0
- package/dist/workflowValidator.d.ts +64 -0
- package/dist/workflowValidator.d.ts.map +1 -0
- package/dist/workflowValidator.js +410 -0
- package/dist/workflowValidator.js.map +1 -0
- package/package.json +50 -0
- package/schemas/actions/all.json +26 -0
- package/schemas/actions/confirm.json +21 -0
- package/schemas/actions/consoleLog.json +16 -0
- package/schemas/actions/dialog.json +25 -0
- package/schemas/actions/fileDownload.json +16 -0
- package/schemas/actions/forEach.json +31 -0
- package/schemas/actions/if.json +12 -0
- package/schemas/actions/mutation.json +25 -0
- package/schemas/actions/navigate.json +18 -0
- package/schemas/actions/navigateBack.json +22 -0
- package/schemas/actions/navigateBackOrClose.json +21 -0
- package/schemas/actions/notification.json +19 -0
- package/schemas/actions/openBarcodeScanner.json +104 -0
- package/schemas/actions/query.json +32 -0
- package/schemas/actions/refresh.json +13 -0
- package/schemas/actions/resetDirtyState.json +22 -0
- package/schemas/actions/setFields.json +21 -0
- package/schemas/actions/setStore.json +13 -0
- package/schemas/actions/validateForm.json +15 -0
- package/schemas/actions/workflow.json +24 -0
- package/schemas/components/README.md +147 -0
- package/schemas/components/appComponent.json +50 -0
- package/schemas/components/barcodeScanner.json +69 -0
- package/schemas/components/button.json +123 -0
- package/schemas/components/calendar.json +489 -0
- package/schemas/components/card.json +176 -0
- package/schemas/components/collection.json +54 -0
- package/schemas/components/dataGrid.json +119 -0
- package/schemas/components/datasource.json +151 -0
- package/schemas/components/dropdown.json +57 -0
- package/schemas/components/field-collection.json +618 -0
- package/schemas/components/field.json +265 -0
- package/schemas/components/form.json +234 -0
- package/schemas/components/index.json +68 -0
- package/schemas/components/layout.json +69 -0
- package/schemas/components/module.json +138 -0
- package/schemas/components/navDropdown.json +36 -0
- package/schemas/components/navbar.json +78 -0
- package/schemas/components/navbarItem.json +28 -0
- package/schemas/components/navbarLink.json +36 -0
- package/schemas/components/row.json +31 -0
- package/schemas/components/tab.json +34 -0
- package/schemas/components/tabs.json +35 -0
- package/schemas/components/timeline.json +172 -0
- package/schemas/components/timelineGrid.json +324 -0
- package/schemas/fields/README.md +66 -0
- package/schemas/fields/attachment.json +156 -0
- package/schemas/fields/autocomplete-googleplaces.json +130 -0
- package/schemas/fields/checkbox.json +82 -0
- package/schemas/fields/date.json +88 -0
- package/schemas/fields/datetime.json +75 -0
- package/schemas/fields/email.json +75 -0
- package/schemas/fields/index.json +53 -0
- package/schemas/fields/number.json +91 -0
- package/schemas/fields/password.json +70 -0
- package/schemas/fields/radio.json +94 -0
- package/schemas/fields/rangedatetime.json +56 -0
- package/schemas/fields/select-async.json +334 -0
- package/schemas/fields/select.json +115 -0
- package/schemas/fields/tel.json +79 -0
- package/schemas/fields/text.json +86 -0
- package/schemas/fields/textarea.json +95 -0
- package/schemas/fields/time.json +91 -0
- package/schemas/fields/url.json +74 -0
- package/schemas/schema.graphql +10492 -0
- package/schemas/schemas.json +598 -0
- package/schemas/workflows/activity.json +111 -0
- package/schemas/workflows/common/condition.json +48 -0
- package/schemas/workflows/common/expression.json +76 -0
- package/schemas/workflows/common/mapping.json +134 -0
- package/schemas/workflows/input.json +76 -0
- package/schemas/workflows/output.json +41 -0
- package/schemas/workflows/schedule.json +26 -0
- package/schemas/workflows/tasks/accounting-transaction.json +95 -0
- package/schemas/workflows/tasks/all.json +34 -0
- package/schemas/workflows/tasks/attachment.json +94 -0
- package/schemas/workflows/tasks/charge.json +90 -0
- package/schemas/workflows/tasks/commodity.json +89 -0
- package/schemas/workflows/tasks/contact.json +82 -0
- package/schemas/workflows/tasks/csv.json +79 -0
- package/schemas/workflows/tasks/document-render.json +105 -0
- package/schemas/workflows/tasks/document-send.json +84 -0
- package/schemas/workflows/tasks/email-send.json +110 -0
- package/schemas/workflows/tasks/error.json +72 -0
- package/schemas/workflows/tasks/export.json +90 -0
- package/schemas/workflows/tasks/foreach.json +69 -0
- package/schemas/workflows/tasks/generic.json +47 -0
- package/schemas/workflows/tasks/graphql.json +78 -0
- package/schemas/workflows/tasks/httpRequest.json +119 -0
- package/schemas/workflows/tasks/job.json +88 -0
- package/schemas/workflows/tasks/log.json +73 -0
- package/schemas/workflows/tasks/map.json +58 -0
- package/schemas/workflows/tasks/order.json +87 -0
- package/schemas/workflows/tasks/payment.json +85 -0
- package/schemas/workflows/tasks/setVariable.json +76 -0
- package/schemas/workflows/tasks/switch.json +75 -0
- package/schemas/workflows/tasks/template.json +73 -0
- package/schemas/workflows/tasks/validation.json +90 -0
- package/schemas/workflows/tasks/while.json +53 -0
- package/schemas/workflows/tasks/workflow-execute.json +82 -0
- package/schemas/workflows/trigger.json +86 -0
- package/schemas/workflows/variable.json +46 -0
- package/schemas/workflows/workflow.json +172 -0
- package/scripts/postinstall.js +161 -0
- package/scripts/setup-vscode.js +80 -0
- package/templates/module.yaml +83 -0
- package/templates/workflow.yaml +100 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1523 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* CX Schema Validator CLI - Unified validation for YAML modules and workflows
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
46
|
+
const yaml = __importStar(require("js-yaml"));
|
|
47
|
+
const validator_1 = require("./validator");
|
|
48
|
+
const workflowValidator_1 = require("./workflowValidator");
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Constants
|
|
51
|
+
// ============================================================================
|
|
52
|
+
const VERSION = require('../package.json').version;
|
|
53
|
+
const PROGRAM_NAME = 'cx-validate';
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Help Text
|
|
56
|
+
// ============================================================================
|
|
57
|
+
const HELP_TEXT = `
|
|
58
|
+
${chalk_1.default.bold.cyan('╔═══════════════════════════════════════════════════════════════════════════╗')}
|
|
59
|
+
${chalk_1.default.bold.cyan('║')} ${chalk_1.default.bold.white('CX SCHEMA VALIDATOR')} ${chalk_1.default.bold.cyan('║')}
|
|
60
|
+
${chalk_1.default.bold.cyan('║')} ${chalk_1.default.gray('Unified validation for CargoXplorer YAML files')} ${chalk_1.default.bold.cyan('║')}
|
|
61
|
+
${chalk_1.default.bold.cyan('╚═══════════════════════════════════════════════════════════════════════════╝')}
|
|
62
|
+
|
|
63
|
+
${chalk_1.default.bold.yellow('DESCRIPTION:')}
|
|
64
|
+
Validates CargoXplorer YAML module and workflow files against JSON Schema
|
|
65
|
+
definitions. Provides detailed error feedback with examples and schema
|
|
66
|
+
references to help fix validation issues.
|
|
67
|
+
|
|
68
|
+
${chalk_1.default.bold.yellow('USAGE:')}
|
|
69
|
+
${chalk_1.default.cyan(PROGRAM_NAME)} [command] [options] <files...>
|
|
70
|
+
|
|
71
|
+
${chalk_1.default.bold.yellow('COMMANDS:')}
|
|
72
|
+
${chalk_1.default.green('validate')} Validate YAML file(s) ${chalk_1.default.gray('(default command)')}
|
|
73
|
+
${chalk_1.default.green('report')} Generate validation report for multiple files
|
|
74
|
+
${chalk_1.default.green('init')} Initialize a new CX project with app.yaml, README.md, AGENTS.md
|
|
75
|
+
${chalk_1.default.green('create')} Create a new module or workflow from template
|
|
76
|
+
${chalk_1.default.green('schema')} Show JSON schema for a component or task
|
|
77
|
+
${chalk_1.default.green('example')} Show example YAML for a component or task
|
|
78
|
+
${chalk_1.default.green('list')} List available schemas (modules, workflows, tasks)
|
|
79
|
+
${chalk_1.default.green('help')} Show this help message
|
|
80
|
+
|
|
81
|
+
${chalk_1.default.bold.yellow('OPTIONS:')}
|
|
82
|
+
${chalk_1.default.green('-h, --help')} Show this help message
|
|
83
|
+
${chalk_1.default.green('-v, --version')} Show version number
|
|
84
|
+
${chalk_1.default.green('-t, --type <type>')} Validation type: ${chalk_1.default.cyan('module')}, ${chalk_1.default.cyan('workflow')}, or ${chalk_1.default.cyan('auto')} ${chalk_1.default.gray('(default: auto)')}
|
|
85
|
+
${chalk_1.default.green('-f, --format <format>')} Output format: ${chalk_1.default.cyan('pretty')}, ${chalk_1.default.cyan('json')}, or ${chalk_1.default.cyan('compact')} ${chalk_1.default.gray('(default: pretty)')}
|
|
86
|
+
${chalk_1.default.green('-s, --schemas <path>')} Path to schemas directory
|
|
87
|
+
${chalk_1.default.green('--verbose')} Show detailed output with schema paths
|
|
88
|
+
${chalk_1.default.green('--quiet')} Only show errors, suppress other output
|
|
89
|
+
${chalk_1.default.green('-r, --report <file>')} Generate report to file (html, md, or json)
|
|
90
|
+
${chalk_1.default.green('--report-format <fmt>')} Report format: ${chalk_1.default.cyan('html')}, ${chalk_1.default.cyan('markdown')}, or ${chalk_1.default.cyan('json')} ${chalk_1.default.gray('(default: auto from extension)')}
|
|
91
|
+
|
|
92
|
+
${chalk_1.default.bold.yellow('VALIDATION EXAMPLES:')}
|
|
93
|
+
${chalk_1.default.gray('# Validate a module YAML file')}
|
|
94
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} modules/countries-module.yaml`)}
|
|
95
|
+
|
|
96
|
+
${chalk_1.default.gray('# Validate a workflow YAML file')}
|
|
97
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} workflows/my-workflow.yaml`)}
|
|
98
|
+
|
|
99
|
+
${chalk_1.default.gray('# Auto-detect file type and validate')}
|
|
100
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} --type auto my-file.yaml`)}
|
|
101
|
+
|
|
102
|
+
${chalk_1.default.gray('# Validate with custom schemas directory')}
|
|
103
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} --schemas ./custom-schemas file.yaml`)}
|
|
104
|
+
|
|
105
|
+
${chalk_1.default.gray('# Output validation results as JSON')}
|
|
106
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} --format json file.yaml > results.json`)}
|
|
107
|
+
|
|
108
|
+
${chalk_1.default.gray('# Validate multiple files')}
|
|
109
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} module1.yaml module2.yaml workflow1.yaml`)}
|
|
110
|
+
|
|
111
|
+
${chalk_1.default.bold.yellow('PROJECT COMMANDS:')}
|
|
112
|
+
${chalk_1.default.gray('# Initialize a new project')}
|
|
113
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} init`)}
|
|
114
|
+
|
|
115
|
+
${chalk_1.default.gray('# Create a new module from template')}
|
|
116
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} create module my-module`)}
|
|
117
|
+
|
|
118
|
+
${chalk_1.default.gray('# Create a new workflow from template')}
|
|
119
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} create workflow my-workflow`)}
|
|
120
|
+
|
|
121
|
+
${chalk_1.default.bold.yellow('SCHEMA COMMANDS:')}
|
|
122
|
+
${chalk_1.default.gray('# Show schema for a component')}
|
|
123
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} schema form`)}
|
|
124
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} schema dataGrid`)}
|
|
125
|
+
|
|
126
|
+
${chalk_1.default.gray('# Show schema for a workflow task')}
|
|
127
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} schema foreach`)}
|
|
128
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} schema graphql`)}
|
|
129
|
+
|
|
130
|
+
${chalk_1.default.gray('# Show example YAML for a component')}
|
|
131
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} example form`)}
|
|
132
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} example workflow`)}
|
|
133
|
+
|
|
134
|
+
${chalk_1.default.gray('# List all available schemas')}
|
|
135
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} list`)}
|
|
136
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} list --type workflow`)}
|
|
137
|
+
|
|
138
|
+
${chalk_1.default.bold.yellow('VALIDATION TYPES:')}
|
|
139
|
+
${chalk_1.default.bold('module')} - CargoXplorer UI module definitions (components, routes, entities)
|
|
140
|
+
${chalk_1.default.bold('workflow')} - CargoXplorer workflow definitions (activities, tasks, triggers)
|
|
141
|
+
${chalk_1.default.bold('auto')} - Auto-detect based on file content (checks for 'workflow:' vs 'module:')
|
|
142
|
+
|
|
143
|
+
${chalk_1.default.bold.yellow('OUTPUT FORMATS:')}
|
|
144
|
+
${chalk_1.default.bold('pretty')} - Colorized, human-readable output with detailed error info
|
|
145
|
+
${chalk_1.default.bold('json')} - JSON output suitable for CI/CD pipelines
|
|
146
|
+
${chalk_1.default.bold('compact')} - Minimal output showing only pass/fail and error count
|
|
147
|
+
|
|
148
|
+
${chalk_1.default.bold.yellow('EXIT CODES:')}
|
|
149
|
+
${chalk_1.default.green('0')} - Validation passed (no errors)
|
|
150
|
+
${chalk_1.default.red('1')} - Validation failed (errors found)
|
|
151
|
+
${chalk_1.default.red('2')} - CLI error (invalid arguments, file not found, etc.)
|
|
152
|
+
|
|
153
|
+
${chalk_1.default.bold.yellow('ENVIRONMENT VARIABLES:')}
|
|
154
|
+
${chalk_1.default.green('CX_SCHEMA_PATH')} - Default path to schemas directory
|
|
155
|
+
${chalk_1.default.green('NO_COLOR')} - Disable colored output
|
|
156
|
+
|
|
157
|
+
${chalk_1.default.bold.yellow('MORE INFORMATION:')}
|
|
158
|
+
Documentation: ${chalk_1.default.underline.cyan('https://github.com/cxtms/cx-schema')}
|
|
159
|
+
Report issues: ${chalk_1.default.underline.cyan('https://github.com/cxtms/cx-schema/issues')}
|
|
160
|
+
`;
|
|
161
|
+
const SCHEMA_HELP = `
|
|
162
|
+
${chalk_1.default.bold.yellow('SCHEMA COMMAND')}
|
|
163
|
+
|
|
164
|
+
Show the JSON Schema definition for a component, field, action, or task.
|
|
165
|
+
|
|
166
|
+
${chalk_1.default.bold.yellow('USAGE:')}
|
|
167
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} schema <name>`)}
|
|
168
|
+
|
|
169
|
+
${chalk_1.default.bold.yellow('AVAILABLE SCHEMAS:')}
|
|
170
|
+
|
|
171
|
+
${chalk_1.default.bold('Module Components:')}
|
|
172
|
+
form, dataGrid, layout, tabs, tab, field, button, collection,
|
|
173
|
+
dropdown, datasource, calendar, card, navbar, timeline
|
|
174
|
+
|
|
175
|
+
${chalk_1.default.bold('Workflow Core:')}
|
|
176
|
+
workflow, activity, input, output, variable, trigger, schedule
|
|
177
|
+
|
|
178
|
+
${chalk_1.default.bold('Workflow Tasks:')}
|
|
179
|
+
foreach, switch, while, validation, graphql, httpRequest,
|
|
180
|
+
setVariable, map, log, error, csv, export, template,
|
|
181
|
+
order, contact, commodity, job, attachment,
|
|
182
|
+
email-send, document-render, charge, workflow-execute
|
|
183
|
+
|
|
184
|
+
${chalk_1.default.bold.yellow('EXAMPLES:')}
|
|
185
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} schema form`)}
|
|
186
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} schema foreach`)}
|
|
187
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} schema workflow`)}
|
|
188
|
+
`;
|
|
189
|
+
const LIST_HELP = `
|
|
190
|
+
${chalk_1.default.bold.yellow('LIST COMMAND')}
|
|
191
|
+
|
|
192
|
+
List all available schemas for validation.
|
|
193
|
+
|
|
194
|
+
${chalk_1.default.bold.yellow('USAGE:')}
|
|
195
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} list [options]`)}
|
|
196
|
+
|
|
197
|
+
${chalk_1.default.bold.yellow('OPTIONS:')}
|
|
198
|
+
${chalk_1.default.green('--type <type>')} Filter by type: module, workflow, or all ${chalk_1.default.gray('(default: all)')}
|
|
199
|
+
|
|
200
|
+
${chalk_1.default.bold.yellow('EXAMPLES:')}
|
|
201
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} list`)}
|
|
202
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} list --type module`)}
|
|
203
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} list --type workflow`)}
|
|
204
|
+
`;
|
|
205
|
+
const INIT_HELP = `
|
|
206
|
+
${chalk_1.default.bold.yellow('INIT COMMAND')}
|
|
207
|
+
|
|
208
|
+
Initialize a new CX project with configuration files.
|
|
209
|
+
|
|
210
|
+
${chalk_1.default.bold.yellow('USAGE:')}
|
|
211
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} init`)}
|
|
212
|
+
|
|
213
|
+
${chalk_1.default.bold.yellow('FILES CREATED:')}
|
|
214
|
+
${chalk_1.default.green('app.yaml')} - Project configuration
|
|
215
|
+
${chalk_1.default.green('README.md')} - Project documentation
|
|
216
|
+
${chalk_1.default.green('AGENTS.md')} - AI assistant instructions for validation
|
|
217
|
+
`;
|
|
218
|
+
const CREATE_HELP = `
|
|
219
|
+
${chalk_1.default.bold.yellow('CREATE COMMAND')}
|
|
220
|
+
|
|
221
|
+
Create a new module or workflow from template.
|
|
222
|
+
|
|
223
|
+
${chalk_1.default.bold.yellow('USAGE:')}
|
|
224
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} create <type> <name>`)}
|
|
225
|
+
|
|
226
|
+
${chalk_1.default.bold.yellow('TYPES:')}
|
|
227
|
+
${chalk_1.default.green('module')} - Create a new UI module YAML file
|
|
228
|
+
${chalk_1.default.green('workflow')} - Create a new workflow YAML file
|
|
229
|
+
|
|
230
|
+
${chalk_1.default.bold.yellow('EXAMPLES:')}
|
|
231
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} create module orders`)}
|
|
232
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} create workflow invoice-generator`)}
|
|
233
|
+
`;
|
|
234
|
+
// ============================================================================
|
|
235
|
+
// Templates
|
|
236
|
+
// ============================================================================
|
|
237
|
+
function generateAppYaml() {
|
|
238
|
+
return `# CargoXplorer Application Configuration
|
|
239
|
+
# Generated by cx-validate init
|
|
240
|
+
|
|
241
|
+
app:
|
|
242
|
+
name: "My CX Application"
|
|
243
|
+
version: "1.0.0"
|
|
244
|
+
description: "CargoXplorer application"
|
|
245
|
+
|
|
246
|
+
# Module directories
|
|
247
|
+
modules:
|
|
248
|
+
- path: "./modules"
|
|
249
|
+
pattern: "*.yaml"
|
|
250
|
+
|
|
251
|
+
# Workflow directories
|
|
252
|
+
workflows:
|
|
253
|
+
- path: "./workflows"
|
|
254
|
+
pattern: "*.yaml"
|
|
255
|
+
|
|
256
|
+
# Validation settings
|
|
257
|
+
validation:
|
|
258
|
+
strict: true
|
|
259
|
+
failOnWarnings: false
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
function generateReadme() {
|
|
263
|
+
return `# CargoXplorer Application
|
|
264
|
+
|
|
265
|
+
This project contains CargoXplorer modules and workflows.
|
|
266
|
+
|
|
267
|
+
## Project Structure
|
|
268
|
+
|
|
269
|
+
\`\`\`
|
|
270
|
+
├── app.yaml # Application configuration
|
|
271
|
+
├── modules/ # UI module definitions
|
|
272
|
+
│ └── *.yaml
|
|
273
|
+
├── workflows/ # Workflow definitions
|
|
274
|
+
│ └── *.yaml
|
|
275
|
+
├── README.md # This file
|
|
276
|
+
└── AGENTS.md # AI assistant instructions
|
|
277
|
+
\`\`\`
|
|
278
|
+
|
|
279
|
+
## Validation
|
|
280
|
+
|
|
281
|
+
### Install the validator
|
|
282
|
+
|
|
283
|
+
\`\`\`bash
|
|
284
|
+
npm install @cxtms/cx-schema
|
|
285
|
+
\`\`\`
|
|
286
|
+
|
|
287
|
+
### Validate files
|
|
288
|
+
|
|
289
|
+
\`\`\`bash
|
|
290
|
+
# Validate all modules
|
|
291
|
+
npx cx-validate modules/*.yaml
|
|
292
|
+
|
|
293
|
+
# Validate all workflows
|
|
294
|
+
npx cx-validate workflows/*.yaml
|
|
295
|
+
|
|
296
|
+
# Validate with detailed output
|
|
297
|
+
npx cx-validate --verbose modules/my-module.yaml
|
|
298
|
+
|
|
299
|
+
# Generate validation report
|
|
300
|
+
npx cx-validate report modules/*.yaml workflows/*.yaml --report report.html
|
|
301
|
+
\`\`\`
|
|
302
|
+
|
|
303
|
+
### Create new files
|
|
304
|
+
|
|
305
|
+
\`\`\`bash
|
|
306
|
+
# Create a new module
|
|
307
|
+
npx cx-validate create module my-module
|
|
308
|
+
|
|
309
|
+
# Create a new workflow
|
|
310
|
+
npx cx-validate create workflow my-workflow
|
|
311
|
+
\`\`\`
|
|
312
|
+
|
|
313
|
+
### View schemas and examples
|
|
314
|
+
|
|
315
|
+
\`\`\`bash
|
|
316
|
+
# List available schemas
|
|
317
|
+
npx cx-validate list
|
|
318
|
+
|
|
319
|
+
# View schema for a component
|
|
320
|
+
npx cx-validate schema form
|
|
321
|
+
|
|
322
|
+
# View example YAML
|
|
323
|
+
npx cx-validate example workflow
|
|
324
|
+
\`\`\`
|
|
325
|
+
|
|
326
|
+
## Documentation
|
|
327
|
+
|
|
328
|
+
- [CX Schema CLI Documentation](https://docs.cargoxplorer.com/docs/documents/cx-schema-cli)
|
|
329
|
+
- [Module Development Guide](https://docs.cargoxplorer.com/docs/development/app-modules)
|
|
330
|
+
- [Workflow Development Guide](https://docs.cargoxplorer.com/docs/development/workflows)
|
|
331
|
+
`;
|
|
332
|
+
}
|
|
333
|
+
function generateAgentsMd() {
|
|
334
|
+
return `# AI Assistant Instructions for CargoXplorer Development
|
|
335
|
+
|
|
336
|
+
This file provides instructions for AI assistants (like Claude, GPT, Copilot) when working with this CargoXplorer project.
|
|
337
|
+
|
|
338
|
+
## Validation Commands
|
|
339
|
+
|
|
340
|
+
When making changes to YAML files, always validate them:
|
|
341
|
+
|
|
342
|
+
\`\`\`bash
|
|
343
|
+
# Validate a specific module file
|
|
344
|
+
npx cx-validate modules/<module-name>.yaml
|
|
345
|
+
|
|
346
|
+
# Validate a specific workflow file
|
|
347
|
+
npx cx-validate workflows/<workflow-name>.yaml
|
|
348
|
+
|
|
349
|
+
# Validate all files with a report
|
|
350
|
+
npx cx-validate report modules/*.yaml workflows/*.yaml --report validation-report.md
|
|
351
|
+
\`\`\`
|
|
352
|
+
|
|
353
|
+
## Schema Reference
|
|
354
|
+
|
|
355
|
+
Before editing components or tasks, check the schema:
|
|
356
|
+
|
|
357
|
+
\`\`\`bash
|
|
358
|
+
# View schema for components
|
|
359
|
+
npx cx-validate schema form
|
|
360
|
+
npx cx-validate schema dataGrid
|
|
361
|
+
npx cx-validate schema layout
|
|
362
|
+
|
|
363
|
+
# View schema for workflow tasks
|
|
364
|
+
npx cx-validate schema foreach
|
|
365
|
+
npx cx-validate schema graphql
|
|
366
|
+
npx cx-validate schema switch
|
|
367
|
+
\`\`\`
|
|
368
|
+
|
|
369
|
+
## Creating New Files
|
|
370
|
+
|
|
371
|
+
Use templates to create properly structured files:
|
|
372
|
+
|
|
373
|
+
\`\`\`bash
|
|
374
|
+
# Create a new module
|
|
375
|
+
npx cx-validate create module <name>
|
|
376
|
+
|
|
377
|
+
# Create a new workflow
|
|
378
|
+
npx cx-validate create workflow <name>
|
|
379
|
+
\`\`\`
|
|
380
|
+
|
|
381
|
+
## Module Structure
|
|
382
|
+
|
|
383
|
+
Modules contain UI component definitions:
|
|
384
|
+
|
|
385
|
+
- **Components**: form, dataGrid, layout, tabs, card, etc.
|
|
386
|
+
- **Fields**: text, number, select, date, checkbox, etc.
|
|
387
|
+
- **Actions**: navigate, mutation, query, setFields, etc.
|
|
388
|
+
- **Routes**: Define navigation paths
|
|
389
|
+
|
|
390
|
+
## Workflow Structure
|
|
391
|
+
|
|
392
|
+
Workflows contain automation definitions:
|
|
393
|
+
|
|
394
|
+
- **workflow**: Metadata (workflowId, name, executionMode)
|
|
395
|
+
- **inputs/outputs**: Parameter definitions
|
|
396
|
+
- **variables**: Internal state
|
|
397
|
+
- **activities**: Ordered steps containing tasks
|
|
398
|
+
- **triggers**: Manual, Entity, or Scheduled triggers
|
|
399
|
+
|
|
400
|
+
### Common Task Types
|
|
401
|
+
|
|
402
|
+
- **Control flow**: foreach, switch, while, validation
|
|
403
|
+
- **Data**: Query/GraphQL, Map@1, SetVariable@1
|
|
404
|
+
- **Entity operations**: Order/Create@1, Contact/Update@1, etc.
|
|
405
|
+
- **Communication**: Email/Send@1, Document/Render@1
|
|
406
|
+
|
|
407
|
+
## Best Practices
|
|
408
|
+
|
|
409
|
+
1. **Always validate** after making changes to YAML files
|
|
410
|
+
2. **Use verbose mode** (\`--verbose\`) for detailed error information
|
|
411
|
+
3. **Check schemas** before adding new properties
|
|
412
|
+
4. **Use templates** when creating new files
|
|
413
|
+
5. **Generate reports** for batch validation of multiple files
|
|
414
|
+
`;
|
|
415
|
+
}
|
|
416
|
+
function findTemplatesPath() {
|
|
417
|
+
// Check for templates in node_modules
|
|
418
|
+
const nodeModulesTemplates = path.join(process.cwd(), 'node_modules', '@cxtms/cx-schema', 'templates');
|
|
419
|
+
if (fs.existsSync(nodeModulesTemplates)) {
|
|
420
|
+
return nodeModulesTemplates;
|
|
421
|
+
}
|
|
422
|
+
// Check in package directory (for development)
|
|
423
|
+
const packageTemplates = path.join(__dirname, '../templates');
|
|
424
|
+
if (fs.existsSync(packageTemplates)) {
|
|
425
|
+
return packageTemplates;
|
|
426
|
+
}
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
function loadTemplate(templateName) {
|
|
430
|
+
const templatesPath = findTemplatesPath();
|
|
431
|
+
if (!templatesPath) {
|
|
432
|
+
throw new Error('Could not find templates directory');
|
|
433
|
+
}
|
|
434
|
+
const templateFile = path.join(templatesPath, `${templateName}.yaml`);
|
|
435
|
+
if (!fs.existsSync(templateFile)) {
|
|
436
|
+
throw new Error(`Template not found: ${templateName}`);
|
|
437
|
+
}
|
|
438
|
+
return fs.readFileSync(templateFile, 'utf-8');
|
|
439
|
+
}
|
|
440
|
+
function processTemplate(template, variables) {
|
|
441
|
+
let result = template;
|
|
442
|
+
// Replace all {{variableName}} placeholders (but not \{{...}} which are escaped)
|
|
443
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
444
|
+
// Match {{key}} but not \{{key}}
|
|
445
|
+
const regex = new RegExp(`(?<!\\\\)\\{\\{${key}\\}\\}`, 'g');
|
|
446
|
+
result = result.replace(regex, value);
|
|
447
|
+
}
|
|
448
|
+
// Unescape \{{ to {{ (for runtime expressions like {{inputs.entityId}})
|
|
449
|
+
result = result.replace(/\\(\{\{)/g, '$1');
|
|
450
|
+
return result;
|
|
451
|
+
}
|
|
452
|
+
function generateTemplateContent(type, name, fileName) {
|
|
453
|
+
const template = loadTemplate(type);
|
|
454
|
+
const displayName = name
|
|
455
|
+
.split('-')
|
|
456
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
457
|
+
.join(' ');
|
|
458
|
+
const variables = {
|
|
459
|
+
name,
|
|
460
|
+
displayName,
|
|
461
|
+
displayNameNoSpaces: displayName.replace(/\s/g, ''),
|
|
462
|
+
uuid: generateUUID(),
|
|
463
|
+
fileName
|
|
464
|
+
};
|
|
465
|
+
return processTemplate(template, variables);
|
|
466
|
+
}
|
|
467
|
+
function generateUUID() {
|
|
468
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
469
|
+
const r = Math.random() * 16 | 0;
|
|
470
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
471
|
+
return v.toString(16);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
// ============================================================================
|
|
475
|
+
// Init and Create Commands
|
|
476
|
+
// ============================================================================
|
|
477
|
+
function runInit() {
|
|
478
|
+
console.log(chalk_1.default.bold.cyan('\n╔═══════════════════════════════════════════════════════════════════╗'));
|
|
479
|
+
console.log(chalk_1.default.bold.cyan('║ CX PROJECT INITIALIZATION ║'));
|
|
480
|
+
console.log(chalk_1.default.bold.cyan('╚═══════════════════════════════════════════════════════════════════╝\n'));
|
|
481
|
+
const files = [
|
|
482
|
+
{ name: 'app.yaml', content: generateAppYaml() },
|
|
483
|
+
{ name: 'README.md', content: generateReadme() },
|
|
484
|
+
{ name: 'AGENTS.md', content: generateAgentsMd() }
|
|
485
|
+
];
|
|
486
|
+
const createdDirs = [];
|
|
487
|
+
const createdFiles = [];
|
|
488
|
+
const skippedFiles = [];
|
|
489
|
+
// Create directories
|
|
490
|
+
for (const dir of ['modules', 'workflows']) {
|
|
491
|
+
const dirPath = path.join(process.cwd(), dir);
|
|
492
|
+
if (!fs.existsSync(dirPath)) {
|
|
493
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
494
|
+
createdDirs.push(dir);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Create files
|
|
498
|
+
for (const file of files) {
|
|
499
|
+
const filePath = path.join(process.cwd(), file.name);
|
|
500
|
+
if (fs.existsSync(filePath)) {
|
|
501
|
+
skippedFiles.push(file.name);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
fs.writeFileSync(filePath, file.content, 'utf-8');
|
|
505
|
+
createdFiles.push(file.name);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// Output summary
|
|
509
|
+
if (createdDirs.length > 0) {
|
|
510
|
+
console.log(chalk_1.default.bold(' Created directories:'));
|
|
511
|
+
for (const dir of createdDirs) {
|
|
512
|
+
console.log(chalk_1.default.green(` ✓ ${dir}/`));
|
|
513
|
+
}
|
|
514
|
+
console.log('');
|
|
515
|
+
}
|
|
516
|
+
if (createdFiles.length > 0) {
|
|
517
|
+
console.log(chalk_1.default.bold(' Created files:'));
|
|
518
|
+
for (const file of createdFiles) {
|
|
519
|
+
console.log(chalk_1.default.green(` ✓ ${file}`));
|
|
520
|
+
}
|
|
521
|
+
console.log('');
|
|
522
|
+
}
|
|
523
|
+
if (skippedFiles.length > 0) {
|
|
524
|
+
console.log(chalk_1.default.bold(' Skipped (already exist):'));
|
|
525
|
+
for (const file of skippedFiles) {
|
|
526
|
+
console.log(chalk_1.default.yellow(` - ${file}`));
|
|
527
|
+
}
|
|
528
|
+
console.log('');
|
|
529
|
+
}
|
|
530
|
+
console.log(chalk_1.default.gray(' Next steps:'));
|
|
531
|
+
console.log(chalk_1.default.gray(` 1. Edit ${chalk_1.default.white('app.yaml')} to configure your project`));
|
|
532
|
+
console.log(chalk_1.default.gray(` 2. Create modules: ${chalk_1.default.white(`${PROGRAM_NAME} create module <name>`)}`));
|
|
533
|
+
console.log(chalk_1.default.gray(` 3. Create workflows: ${chalk_1.default.white(`${PROGRAM_NAME} create workflow <name>`)}`));
|
|
534
|
+
console.log(chalk_1.default.gray(` 4. Validate files: ${chalk_1.default.white(`${PROGRAM_NAME} modules/*.yaml`)}`));
|
|
535
|
+
console.log('');
|
|
536
|
+
}
|
|
537
|
+
function runCreate(type, name) {
|
|
538
|
+
if (!type || !['module', 'workflow'].includes(type)) {
|
|
539
|
+
console.error(chalk_1.default.red('Error: Invalid or missing type. Use: module or workflow'));
|
|
540
|
+
console.error(chalk_1.default.gray(`Example: ${PROGRAM_NAME} create module my-module`));
|
|
541
|
+
process.exit(2);
|
|
542
|
+
}
|
|
543
|
+
if (!name) {
|
|
544
|
+
console.error(chalk_1.default.red(`Error: Missing name for ${type}`));
|
|
545
|
+
console.error(chalk_1.default.gray(`Example: ${PROGRAM_NAME} create ${type} my-${type}`));
|
|
546
|
+
process.exit(2);
|
|
547
|
+
}
|
|
548
|
+
// Sanitize name
|
|
549
|
+
const safeName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
550
|
+
// Determine output directory and file
|
|
551
|
+
const dir = type === 'module' ? 'modules' : 'workflows';
|
|
552
|
+
const fileName = `${safeName}.yaml`;
|
|
553
|
+
const filePath = path.join(process.cwd(), dir, fileName);
|
|
554
|
+
// Check if file already exists
|
|
555
|
+
if (fs.existsSync(filePath)) {
|
|
556
|
+
console.error(chalk_1.default.red(`Error: File already exists: ${filePath}`));
|
|
557
|
+
process.exit(2);
|
|
558
|
+
}
|
|
559
|
+
// Create directory if needed
|
|
560
|
+
const dirPath = path.join(process.cwd(), dir);
|
|
561
|
+
if (!fs.existsSync(dirPath)) {
|
|
562
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
563
|
+
}
|
|
564
|
+
// Generate content from template
|
|
565
|
+
const relativeFileName = path.join(dir, fileName);
|
|
566
|
+
let content;
|
|
567
|
+
try {
|
|
568
|
+
content = generateTemplateContent(type, safeName, relativeFileName);
|
|
569
|
+
}
|
|
570
|
+
catch (error) {
|
|
571
|
+
console.error(chalk_1.default.red(`Error loading template: ${error.message}`));
|
|
572
|
+
process.exit(2);
|
|
573
|
+
}
|
|
574
|
+
// Write file
|
|
575
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
576
|
+
console.log(chalk_1.default.green(`\n✓ Created ${type}: ${path.join(dir, fileName)}`));
|
|
577
|
+
console.log(chalk_1.default.gray(`\n Next steps:`));
|
|
578
|
+
console.log(chalk_1.default.gray(` 1. Edit ${chalk_1.default.white(filePath)} to customize`));
|
|
579
|
+
console.log(chalk_1.default.gray(` 2. Validate: ${chalk_1.default.white(`${PROGRAM_NAME} ${filePath}`)}`));
|
|
580
|
+
console.log(chalk_1.default.gray(` 3. View schema: ${chalk_1.default.white(`${PROGRAM_NAME} schema ${type}`)}`));
|
|
581
|
+
console.log('');
|
|
582
|
+
}
|
|
583
|
+
// ============================================================================
|
|
584
|
+
// Argument Parsing
|
|
585
|
+
// ============================================================================
|
|
586
|
+
function parseArgs(args) {
|
|
587
|
+
const files = [];
|
|
588
|
+
let command = null;
|
|
589
|
+
const options = {
|
|
590
|
+
help: false,
|
|
591
|
+
version: false,
|
|
592
|
+
type: 'auto',
|
|
593
|
+
format: 'pretty',
|
|
594
|
+
verbose: false,
|
|
595
|
+
showSchema: null,
|
|
596
|
+
showExample: null,
|
|
597
|
+
listSchemas: false,
|
|
598
|
+
listTasks: false,
|
|
599
|
+
quiet: false,
|
|
600
|
+
reportFormat: 'json'
|
|
601
|
+
};
|
|
602
|
+
// Check for commands
|
|
603
|
+
const commands = ['validate', 'schema', 'example', 'list', 'help', 'report', 'init', 'create'];
|
|
604
|
+
if (args.length > 0 && commands.includes(args[0])) {
|
|
605
|
+
command = args[0];
|
|
606
|
+
args = args.slice(1);
|
|
607
|
+
}
|
|
608
|
+
for (let i = 0; i < args.length; i++) {
|
|
609
|
+
const arg = args[i];
|
|
610
|
+
if (arg === '--help' || arg === '-h') {
|
|
611
|
+
options.help = true;
|
|
612
|
+
}
|
|
613
|
+
else if (arg === '--version' || arg === '-v') {
|
|
614
|
+
options.version = true;
|
|
615
|
+
}
|
|
616
|
+
else if (arg === '--schemas' || arg === '-s') {
|
|
617
|
+
options.schemasPath = args[++i];
|
|
618
|
+
}
|
|
619
|
+
else if (arg === '--type' || arg === '-t') {
|
|
620
|
+
const typeArg = args[++i];
|
|
621
|
+
if (['module', 'workflow', 'auto'].includes(typeArg)) {
|
|
622
|
+
options.type = typeArg;
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
console.error(chalk_1.default.red(`Invalid type: ${typeArg}. Use: module, workflow, or auto`));
|
|
626
|
+
process.exit(2);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
else if (arg === '--format' || arg === '-f') {
|
|
630
|
+
const formatArg = args[++i];
|
|
631
|
+
if (['pretty', 'json', 'compact'].includes(formatArg)) {
|
|
632
|
+
options.format = formatArg;
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
console.error(chalk_1.default.red(`Invalid format: ${formatArg}. Use: pretty, json, or compact`));
|
|
636
|
+
process.exit(2);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
else if (arg === '--verbose') {
|
|
640
|
+
options.verbose = true;
|
|
641
|
+
}
|
|
642
|
+
else if (arg === '--quiet' || arg === '-q') {
|
|
643
|
+
options.quiet = true;
|
|
644
|
+
}
|
|
645
|
+
else if (arg === '--json') {
|
|
646
|
+
options.format = 'json';
|
|
647
|
+
}
|
|
648
|
+
else if (arg === '--report' || arg === '-r') {
|
|
649
|
+
options.report = args[++i];
|
|
650
|
+
}
|
|
651
|
+
else if (arg === '--report-format') {
|
|
652
|
+
const reportFormatArg = args[++i];
|
|
653
|
+
if (['html', 'markdown', 'json'].includes(reportFormatArg)) {
|
|
654
|
+
options.reportFormat = reportFormatArg;
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
console.error(chalk_1.default.red(`Invalid report format: ${reportFormatArg}. Use: html, markdown, or json`));
|
|
658
|
+
process.exit(2);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
else if (!arg.startsWith('-')) {
|
|
662
|
+
files.push(arg);
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
console.error(chalk_1.default.red(`Unknown option: ${arg}`));
|
|
666
|
+
console.error(`Use ${chalk_1.default.cyan(`${PROGRAM_NAME} --help`)} for usage information`);
|
|
667
|
+
process.exit(2);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
// Handle schema command
|
|
671
|
+
if (command === 'schema' && files.length > 0) {
|
|
672
|
+
options.showSchema = files[0];
|
|
673
|
+
}
|
|
674
|
+
// Handle example command
|
|
675
|
+
if (command === 'example' && files.length > 0) {
|
|
676
|
+
options.showExample = files[0];
|
|
677
|
+
}
|
|
678
|
+
// Handle list command
|
|
679
|
+
if (command === 'list') {
|
|
680
|
+
options.listSchemas = true;
|
|
681
|
+
}
|
|
682
|
+
// Handle help command
|
|
683
|
+
if (command === 'help') {
|
|
684
|
+
options.help = true;
|
|
685
|
+
}
|
|
686
|
+
return { command, files, options };
|
|
687
|
+
}
|
|
688
|
+
// ============================================================================
|
|
689
|
+
// Schema Path Finding
|
|
690
|
+
// ============================================================================
|
|
691
|
+
function findSchemasPath() {
|
|
692
|
+
// Check environment variable
|
|
693
|
+
if (process.env.CX_SCHEMA_PATH && fs.existsSync(process.env.CX_SCHEMA_PATH)) {
|
|
694
|
+
return process.env.CX_SCHEMA_PATH;
|
|
695
|
+
}
|
|
696
|
+
// Check for .cx-schema in current directory
|
|
697
|
+
const localSchemas = path.join(process.cwd(), '.cx-schema');
|
|
698
|
+
if (fs.existsSync(localSchemas)) {
|
|
699
|
+
return localSchemas;
|
|
700
|
+
}
|
|
701
|
+
// Check for schemas in node_modules
|
|
702
|
+
const nodeModulesSchemas = path.join(process.cwd(), 'node_modules', '@cxtms/cx-schema', 'schemas');
|
|
703
|
+
if (fs.existsSync(nodeModulesSchemas)) {
|
|
704
|
+
return nodeModulesSchemas;
|
|
705
|
+
}
|
|
706
|
+
// Check in package directory (for development)
|
|
707
|
+
const packageSchemas = path.join(__dirname, '../schemas');
|
|
708
|
+
if (fs.existsSync(packageSchemas)) {
|
|
709
|
+
return packageSchemas;
|
|
710
|
+
}
|
|
711
|
+
return undefined;
|
|
712
|
+
}
|
|
713
|
+
// ============================================================================
|
|
714
|
+
// Auto-detection
|
|
715
|
+
// ============================================================================
|
|
716
|
+
function detectFileType(filePath) {
|
|
717
|
+
try {
|
|
718
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
719
|
+
const data = yaml.load(content);
|
|
720
|
+
if (data && typeof data === 'object') {
|
|
721
|
+
if ('workflow' in data) {
|
|
722
|
+
return 'workflow';
|
|
723
|
+
}
|
|
724
|
+
if ('module' in data || 'components' in data) {
|
|
725
|
+
return 'module';
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
// Check file path for hints
|
|
729
|
+
if (filePath.includes('workflow')) {
|
|
730
|
+
return 'workflow';
|
|
731
|
+
}
|
|
732
|
+
if (filePath.includes('module')) {
|
|
733
|
+
return 'module';
|
|
734
|
+
}
|
|
735
|
+
// Default to module
|
|
736
|
+
return 'module';
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
return 'module';
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// ============================================================================
|
|
743
|
+
// Schema Display
|
|
744
|
+
// ============================================================================
|
|
745
|
+
function findSchemaFile(schemasPath, name, preferWorkflow = false) {
|
|
746
|
+
// Normalize name
|
|
747
|
+
const normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
748
|
+
// Workflow schema names - these should match workflow schemas first
|
|
749
|
+
const workflowCoreNames = ['workflow', 'activity', 'input', 'output', 'variable', 'trigger', 'schedule'];
|
|
750
|
+
const workflowTaskNames = [
|
|
751
|
+
'foreach', 'switch', 'while', 'validation', 'map', 'setvariable', 'httprequest',
|
|
752
|
+
'log', 'error', 'csv', 'export', 'template', 'graphql', 'order', 'contact',
|
|
753
|
+
'commodity', 'job', 'attachment', 'email-send', 'document-render', 'document-send',
|
|
754
|
+
'charge', 'accounting-transaction', 'payment', 'workflow-execute', 'condition',
|
|
755
|
+
'expression', 'mapping'
|
|
756
|
+
];
|
|
757
|
+
const isWorkflowSchema = workflowCoreNames.includes(normalizedName) ||
|
|
758
|
+
workflowTaskNames.includes(normalizedName);
|
|
759
|
+
// Search patterns in order of priority
|
|
760
|
+
const searchPaths = preferWorkflow || isWorkflowSchema
|
|
761
|
+
? [
|
|
762
|
+
// Workflow schemas first for workflow-related names
|
|
763
|
+
path.join(schemasPath, 'workflows', `${name}.json`),
|
|
764
|
+
path.join(schemasPath, 'workflows', 'tasks', `${name}.json`),
|
|
765
|
+
path.join(schemasPath, 'workflows', 'common', `${name}.json`),
|
|
766
|
+
// Then module schemas
|
|
767
|
+
path.join(schemasPath, 'components', `${name}.json`),
|
|
768
|
+
path.join(schemasPath, 'fields', `${name}.json`),
|
|
769
|
+
path.join(schemasPath, 'actions', `${name}.json`)
|
|
770
|
+
]
|
|
771
|
+
: [
|
|
772
|
+
// Module schemas first
|
|
773
|
+
path.join(schemasPath, 'components', `${name}.json`),
|
|
774
|
+
path.join(schemasPath, 'fields', `${name}.json`),
|
|
775
|
+
path.join(schemasPath, 'actions', `${name}.json`),
|
|
776
|
+
// Then workflow schemas
|
|
777
|
+
path.join(schemasPath, 'workflows', `${name}.json`),
|
|
778
|
+
path.join(schemasPath, 'workflows', 'tasks', `${name}.json`),
|
|
779
|
+
path.join(schemasPath, 'workflows', 'common', `${name}.json`)
|
|
780
|
+
];
|
|
781
|
+
for (const schemaPath of searchPaths) {
|
|
782
|
+
if (fs.existsSync(schemaPath)) {
|
|
783
|
+
return schemaPath;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
// Try fuzzy matching
|
|
787
|
+
const allSchemas = getAllSchemas(schemasPath);
|
|
788
|
+
for (const schema of allSchemas) {
|
|
789
|
+
const schemaBaseName = path.basename(schema, '.json').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
790
|
+
if (schemaBaseName === normalizedName || schemaBaseName.includes(normalizedName)) {
|
|
791
|
+
return schema;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
function getAllSchemas(schemasPath) {
|
|
797
|
+
const schemas = [];
|
|
798
|
+
function scanDir(dir) {
|
|
799
|
+
if (!fs.existsSync(dir))
|
|
800
|
+
return;
|
|
801
|
+
const files = fs.readdirSync(dir);
|
|
802
|
+
for (const file of files) {
|
|
803
|
+
const filePath = path.join(dir, file);
|
|
804
|
+
const stat = fs.statSync(filePath);
|
|
805
|
+
if (stat.isDirectory()) {
|
|
806
|
+
scanDir(filePath);
|
|
807
|
+
}
|
|
808
|
+
else if (file.endsWith('.json')) {
|
|
809
|
+
schemas.push(filePath);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
scanDir(schemasPath);
|
|
814
|
+
return schemas;
|
|
815
|
+
}
|
|
816
|
+
function showSchema(schemasPath, name) {
|
|
817
|
+
const schemaFile = findSchemaFile(schemasPath, name);
|
|
818
|
+
if (!schemaFile) {
|
|
819
|
+
console.error(chalk_1.default.red(`Schema not found: ${name}`));
|
|
820
|
+
console.error(chalk_1.default.gray(`Use '${PROGRAM_NAME} list' to see available schemas`));
|
|
821
|
+
process.exit(2);
|
|
822
|
+
}
|
|
823
|
+
const schema = JSON.parse(fs.readFileSync(schemaFile, 'utf-8'));
|
|
824
|
+
const relativePath = path.relative(schemasPath, schemaFile);
|
|
825
|
+
console.log(chalk_1.default.bold.cyan(`\nSchema: ${relativePath}\n`));
|
|
826
|
+
console.log(chalk_1.default.gray('─'.repeat(70)));
|
|
827
|
+
console.log(JSON.stringify(schema, null, 2));
|
|
828
|
+
console.log(chalk_1.default.gray('─'.repeat(70)));
|
|
829
|
+
}
|
|
830
|
+
function showExample(schemasPath, name) {
|
|
831
|
+
const schemaFile = findSchemaFile(schemasPath, name);
|
|
832
|
+
if (!schemaFile) {
|
|
833
|
+
console.error(chalk_1.default.red(`Schema not found: ${name}`));
|
|
834
|
+
console.error(chalk_1.default.gray(`Use '${PROGRAM_NAME} list' to see available schemas`));
|
|
835
|
+
process.exit(2);
|
|
836
|
+
}
|
|
837
|
+
const schema = JSON.parse(fs.readFileSync(schemaFile, 'utf-8'));
|
|
838
|
+
const relativePath = path.relative(schemasPath, schemaFile);
|
|
839
|
+
console.log(chalk_1.default.bold.cyan(`\nExample for: ${relativePath}\n`));
|
|
840
|
+
console.log(chalk_1.default.gray('─'.repeat(70)));
|
|
841
|
+
// Generate example from schema
|
|
842
|
+
const example = generateExampleFromSchema(schema, name);
|
|
843
|
+
console.log(yaml.dump(example, { indent: 2, lineWidth: 100 }));
|
|
844
|
+
console.log(chalk_1.default.gray('─'.repeat(70)));
|
|
845
|
+
}
|
|
846
|
+
function generateExampleFromSchema(schema, name) {
|
|
847
|
+
// Check for x-example or examples in schema
|
|
848
|
+
if (schema['x-example']) {
|
|
849
|
+
return schema['x-example'];
|
|
850
|
+
}
|
|
851
|
+
if (schema['x-examples'] && Array.isArray(schema['x-examples'])) {
|
|
852
|
+
return schema['x-examples'][0];
|
|
853
|
+
}
|
|
854
|
+
if (schema.examples && Array.isArray(schema.examples)) {
|
|
855
|
+
return schema.examples[0];
|
|
856
|
+
}
|
|
857
|
+
// Generate basic example from properties
|
|
858
|
+
const example = {};
|
|
859
|
+
if (schema.properties) {
|
|
860
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
861
|
+
if (prop['x-example'] !== undefined) {
|
|
862
|
+
example[key] = prop['x-example'];
|
|
863
|
+
}
|
|
864
|
+
else if (prop.const !== undefined) {
|
|
865
|
+
example[key] = prop.const;
|
|
866
|
+
}
|
|
867
|
+
else if (prop.enum && prop.enum.length > 0) {
|
|
868
|
+
example[key] = prop.enum[0];
|
|
869
|
+
}
|
|
870
|
+
else if (prop.type === 'string') {
|
|
871
|
+
example[key] = prop.description ? `<${key}>` : 'example';
|
|
872
|
+
}
|
|
873
|
+
else if (prop.type === 'number' || prop.type === 'integer') {
|
|
874
|
+
example[key] = 1;
|
|
875
|
+
}
|
|
876
|
+
else if (prop.type === 'boolean') {
|
|
877
|
+
example[key] = true;
|
|
878
|
+
}
|
|
879
|
+
else if (prop.type === 'array') {
|
|
880
|
+
example[key] = [];
|
|
881
|
+
}
|
|
882
|
+
else if (prop.type === 'object') {
|
|
883
|
+
example[key] = {};
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return example;
|
|
888
|
+
}
|
|
889
|
+
function listSchemas(schemasPath, type) {
|
|
890
|
+
console.log(chalk_1.default.bold.cyan('\n╔═══════════════════════════════════════════════════════════╗'));
|
|
891
|
+
console.log(chalk_1.default.bold.cyan('║ AVAILABLE SCHEMAS ║'));
|
|
892
|
+
console.log(chalk_1.default.bold.cyan('╚═══════════════════════════════════════════════════════════╝\n'));
|
|
893
|
+
if (type === 'auto' || type === 'module') {
|
|
894
|
+
console.log(chalk_1.default.bold.yellow('MODULE SCHEMAS:'));
|
|
895
|
+
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
896
|
+
// Components
|
|
897
|
+
const componentsDir = path.join(schemasPath, 'components');
|
|
898
|
+
if (fs.existsSync(componentsDir)) {
|
|
899
|
+
console.log(chalk_1.default.bold('\n Components:'));
|
|
900
|
+
const components = fs.readdirSync(componentsDir)
|
|
901
|
+
.filter(f => f.endsWith('.json'))
|
|
902
|
+
.map(f => f.replace('.json', ''));
|
|
903
|
+
console.log(chalk_1.default.green(' ' + components.join(', ')));
|
|
904
|
+
}
|
|
905
|
+
// Fields
|
|
906
|
+
const fieldsDir = path.join(schemasPath, 'fields');
|
|
907
|
+
if (fs.existsSync(fieldsDir)) {
|
|
908
|
+
console.log(chalk_1.default.bold('\n Fields:'));
|
|
909
|
+
const fields = fs.readdirSync(fieldsDir)
|
|
910
|
+
.filter(f => f.endsWith('.json'))
|
|
911
|
+
.map(f => f.replace('.json', ''));
|
|
912
|
+
console.log(chalk_1.default.green(' ' + fields.join(', ')));
|
|
913
|
+
}
|
|
914
|
+
// Actions
|
|
915
|
+
const actionsDir = path.join(schemasPath, 'actions');
|
|
916
|
+
if (fs.existsSync(actionsDir)) {
|
|
917
|
+
console.log(chalk_1.default.bold('\n Actions:'));
|
|
918
|
+
const actions = fs.readdirSync(actionsDir)
|
|
919
|
+
.filter(f => f.endsWith('.json'))
|
|
920
|
+
.map(f => f.replace('.json', ''));
|
|
921
|
+
console.log(chalk_1.default.green(' ' + actions.join(', ')));
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (type === 'auto' || type === 'workflow') {
|
|
925
|
+
console.log(chalk_1.default.bold.yellow('\nWORKFLOW SCHEMAS:'));
|
|
926
|
+
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
927
|
+
// Workflow core
|
|
928
|
+
const workflowsDir = path.join(schemasPath, 'workflows');
|
|
929
|
+
if (fs.existsSync(workflowsDir)) {
|
|
930
|
+
console.log(chalk_1.default.bold('\n Core:'));
|
|
931
|
+
const core = fs.readdirSync(workflowsDir)
|
|
932
|
+
.filter(f => f.endsWith('.json'))
|
|
933
|
+
.map(f => f.replace('.json', ''));
|
|
934
|
+
console.log(chalk_1.default.green(' ' + core.join(', ')));
|
|
935
|
+
// Tasks
|
|
936
|
+
const tasksDir = path.join(workflowsDir, 'tasks');
|
|
937
|
+
if (fs.existsSync(tasksDir)) {
|
|
938
|
+
console.log(chalk_1.default.bold('\n Tasks:'));
|
|
939
|
+
const tasks = fs.readdirSync(tasksDir)
|
|
940
|
+
.filter(f => f.endsWith('.json'))
|
|
941
|
+
.map(f => f.replace('.json', ''));
|
|
942
|
+
console.log(chalk_1.default.green(' ' + tasks.join(', ')));
|
|
943
|
+
}
|
|
944
|
+
// Common
|
|
945
|
+
const commonDir = path.join(workflowsDir, 'common');
|
|
946
|
+
if (fs.existsSync(commonDir)) {
|
|
947
|
+
console.log(chalk_1.default.bold('\n Common Definitions:'));
|
|
948
|
+
const common = fs.readdirSync(commonDir)
|
|
949
|
+
.filter(f => f.endsWith('.json'))
|
|
950
|
+
.map(f => f.replace('.json', ''));
|
|
951
|
+
console.log(chalk_1.default.green(' ' + common.join(', ')));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
console.log(chalk_1.default.gray('\n─'.repeat(50)));
|
|
956
|
+
console.log(chalk_1.default.gray(`\nUse '${PROGRAM_NAME} schema <name>' to view a specific schema`));
|
|
957
|
+
console.log(chalk_1.default.gray(`Use '${PROGRAM_NAME} example <name>' to see an example\n`));
|
|
958
|
+
}
|
|
959
|
+
// ============================================================================
|
|
960
|
+
// Error Formatting
|
|
961
|
+
// ============================================================================
|
|
962
|
+
function getSchemaSnippet(schemasPath, error) {
|
|
963
|
+
if (!error.schemaPath)
|
|
964
|
+
return null;
|
|
965
|
+
// Try to extract component type from path
|
|
966
|
+
const pathMatch = error.path.match(/components?\[?\d*\]?\.?(\w+)?/);
|
|
967
|
+
if (pathMatch && pathMatch[1]) {
|
|
968
|
+
const schemaFile = findSchemaFile(schemasPath, pathMatch[1]);
|
|
969
|
+
if (schemaFile) {
|
|
970
|
+
try {
|
|
971
|
+
const schema = JSON.parse(fs.readFileSync(schemaFile, 'utf-8'));
|
|
972
|
+
return JSON.stringify(schema, null, 2).slice(0, 500) + '...';
|
|
973
|
+
}
|
|
974
|
+
catch {
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
function formatErrorPretty(error, index, schemasPath, verbose) {
|
|
982
|
+
const lines = [];
|
|
983
|
+
// Error header
|
|
984
|
+
lines.push(chalk_1.default.red(`\n┌─ Error #${index + 1}: ${error.type.toUpperCase().replace(/_/g, ' ')}`));
|
|
985
|
+
lines.push(chalk_1.default.red('│'));
|
|
986
|
+
// Path
|
|
987
|
+
lines.push(chalk_1.default.red('│ ') + chalk_1.default.bold('Path: ') + chalk_1.default.yellow(error.path || '/'));
|
|
988
|
+
// Message
|
|
989
|
+
lines.push(chalk_1.default.red('│ ') + chalk_1.default.bold('Message: ') + error.message);
|
|
990
|
+
// Schema path (verbose mode)
|
|
991
|
+
if (verbose && error.schemaPath) {
|
|
992
|
+
lines.push(chalk_1.default.red('│ ') + chalk_1.default.bold('Schema: ') + chalk_1.default.gray(error.schemaPath));
|
|
993
|
+
}
|
|
994
|
+
// Example (if available)
|
|
995
|
+
if (error.example !== undefined) {
|
|
996
|
+
lines.push(chalk_1.default.red('│'));
|
|
997
|
+
lines.push(chalk_1.default.red('│ ') + chalk_1.default.bold('Example:'));
|
|
998
|
+
const exampleLines = JSON.stringify(error.example, null, 2).split('\n');
|
|
999
|
+
exampleLines.forEach(line => {
|
|
1000
|
+
lines.push(chalk_1.default.red('│ ') + chalk_1.default.green(line));
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
// Suggestion based on error type
|
|
1004
|
+
const suggestion = getSuggestion(error);
|
|
1005
|
+
if (suggestion) {
|
|
1006
|
+
lines.push(chalk_1.default.red('│'));
|
|
1007
|
+
lines.push(chalk_1.default.red('│ ') + chalk_1.default.bold('Suggestion: ') + chalk_1.default.cyan(suggestion));
|
|
1008
|
+
}
|
|
1009
|
+
lines.push(chalk_1.default.red('│'));
|
|
1010
|
+
lines.push(chalk_1.default.red('└' + '─'.repeat(60)));
|
|
1011
|
+
return lines.join('\n');
|
|
1012
|
+
}
|
|
1013
|
+
function getSuggestion(error) {
|
|
1014
|
+
switch (error.type) {
|
|
1015
|
+
case 'missing_property':
|
|
1016
|
+
const propMatch = error.message.match(/property:\s*(\w+)/i) || error.path.match(/\.(\w+)$/);
|
|
1017
|
+
if (propMatch) {
|
|
1018
|
+
return `Add the required property '${propMatch[1]}' to your YAML`;
|
|
1019
|
+
}
|
|
1020
|
+
return 'Check required properties in the schema';
|
|
1021
|
+
case 'schema_violation':
|
|
1022
|
+
if (error.message.includes('enum')) {
|
|
1023
|
+
return 'The value must be one of the allowed values. Check the schema for valid options.';
|
|
1024
|
+
}
|
|
1025
|
+
if (error.message.includes('type')) {
|
|
1026
|
+
return 'Check that the value type matches the expected type (string, number, boolean, etc.)';
|
|
1027
|
+
}
|
|
1028
|
+
if (error.message.includes('additionalProperties')) {
|
|
1029
|
+
return 'Remove unrecognized properties. Use `cx-validate schema <type>` to see allowed properties.';
|
|
1030
|
+
}
|
|
1031
|
+
return 'Review the schema requirements for this property';
|
|
1032
|
+
case 'yaml_syntax_error':
|
|
1033
|
+
return 'Check YAML indentation and syntax. Use a YAML linter to identify issues.';
|
|
1034
|
+
case 'invalid_task_type':
|
|
1035
|
+
return `Use 'cx-validate list --type workflow' to see available task types`;
|
|
1036
|
+
case 'invalid_activity':
|
|
1037
|
+
return 'Each activity must have a "name" and "steps" array';
|
|
1038
|
+
default:
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function formatWarningPretty(warning, index) {
|
|
1043
|
+
const lines = [];
|
|
1044
|
+
lines.push(chalk_1.default.yellow(`\n⚠ Warning #${index + 1}: ${warning.type.toUpperCase().replace(/_/g, ' ')}`));
|
|
1045
|
+
lines.push(chalk_1.default.gray(` Path: ${warning.path}`));
|
|
1046
|
+
lines.push(` ${warning.message}`);
|
|
1047
|
+
return lines.join('\n');
|
|
1048
|
+
}
|
|
1049
|
+
// ============================================================================
|
|
1050
|
+
// Result Output
|
|
1051
|
+
// ============================================================================
|
|
1052
|
+
function printResultPretty(result, fileType, schemasPath, verbose) {
|
|
1053
|
+
const { summary, errors, warnings } = result;
|
|
1054
|
+
// Header
|
|
1055
|
+
console.log('\n' + chalk_1.default.bold.cyan('╔═══════════════════════════════════════════════════════════════════╗'));
|
|
1056
|
+
console.log(chalk_1.default.bold.cyan('║') + chalk_1.default.bold.white(' CX SCHEMA VALIDATION REPORT ') + chalk_1.default.bold.cyan('║'));
|
|
1057
|
+
console.log(chalk_1.default.bold.cyan('╚═══════════════════════════════════════════════════════════════════╝\n'));
|
|
1058
|
+
// Summary
|
|
1059
|
+
console.log(chalk_1.default.bold(' File: ') + summary.file);
|
|
1060
|
+
console.log(chalk_1.default.bold(' Type: ') + chalk_1.default.cyan(fileType === 'auto' ? 'auto-detected' : fileType));
|
|
1061
|
+
console.log(chalk_1.default.bold(' Time: ') + chalk_1.default.gray(summary.timestamp));
|
|
1062
|
+
console.log(chalk_1.default.bold(' Status: ') +
|
|
1063
|
+
(summary.status === 'PASSED'
|
|
1064
|
+
? chalk_1.default.green.bold('✓ PASSED')
|
|
1065
|
+
: chalk_1.default.red.bold('✗ FAILED')));
|
|
1066
|
+
console.log(chalk_1.default.bold(' Errors: ') + (summary.errorCount > 0 ? chalk_1.default.red(summary.errorCount) : chalk_1.default.green('0')));
|
|
1067
|
+
console.log(chalk_1.default.bold(' Warnings:') + (summary.warningCount > 0 ? chalk_1.default.yellow(summary.warningCount) : chalk_1.default.green('0')));
|
|
1068
|
+
// Error breakdown
|
|
1069
|
+
if (summary.errorCount > 0) {
|
|
1070
|
+
console.log('\n' + chalk_1.default.bold(' Errors by Type:'));
|
|
1071
|
+
for (const [type, count] of Object.entries(summary.errorsByType)) {
|
|
1072
|
+
console.log(chalk_1.default.gray(` ${type}: `) + chalk_1.default.red(count));
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
// Errors
|
|
1076
|
+
if (errors.length > 0) {
|
|
1077
|
+
console.log('\n' + chalk_1.default.bold.red('═══════════════════════════════════════════════════════════════════'));
|
|
1078
|
+
console.log(chalk_1.default.bold.red(' ERRORS'));
|
|
1079
|
+
console.log(chalk_1.default.bold.red('═══════════════════════════════════════════════════════════════════'));
|
|
1080
|
+
errors.forEach((error, index) => {
|
|
1081
|
+
console.log(formatErrorPretty(error, index, schemasPath, verbose));
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
// Warnings
|
|
1085
|
+
if (warnings.length > 0) {
|
|
1086
|
+
console.log('\n' + chalk_1.default.bold.yellow('═══════════════════════════════════════════════════════════════════'));
|
|
1087
|
+
console.log(chalk_1.default.bold.yellow(' WARNINGS'));
|
|
1088
|
+
console.log(chalk_1.default.bold.yellow('═══════════════════════════════════════════════════════════════════'));
|
|
1089
|
+
warnings.forEach((warning, index) => {
|
|
1090
|
+
console.log(formatWarningPretty(warning, index));
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
// Footer with help
|
|
1094
|
+
if (errors.length > 0) {
|
|
1095
|
+
console.log('\n' + chalk_1.default.gray('─'.repeat(70)));
|
|
1096
|
+
console.log(chalk_1.default.gray(' Tips:'));
|
|
1097
|
+
console.log(chalk_1.default.gray(` • Use '${PROGRAM_NAME} schema <name>' to view schema requirements`));
|
|
1098
|
+
console.log(chalk_1.default.gray(` • Use '${PROGRAM_NAME} example <name>' to see example YAML`));
|
|
1099
|
+
console.log(chalk_1.default.gray(` • Use '${PROGRAM_NAME} list' to see all available schemas`));
|
|
1100
|
+
console.log(chalk_1.default.gray('─'.repeat(70)));
|
|
1101
|
+
}
|
|
1102
|
+
console.log('');
|
|
1103
|
+
}
|
|
1104
|
+
function printResultCompact(result, filePath) {
|
|
1105
|
+
const status = result.isValid ? chalk_1.default.green('PASS') : chalk_1.default.red('FAIL');
|
|
1106
|
+
const errorInfo = result.summary.errorCount > 0 ? chalk_1.default.red(` (${result.summary.errorCount} errors)`) : '';
|
|
1107
|
+
console.log(`${status} ${filePath}${errorInfo}`);
|
|
1108
|
+
}
|
|
1109
|
+
function printResultJson(result) {
|
|
1110
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1111
|
+
}
|
|
1112
|
+
// ============================================================================
|
|
1113
|
+
// Report Generation
|
|
1114
|
+
// ============================================================================
|
|
1115
|
+
function buildReportData(results) {
|
|
1116
|
+
const errorsByType = {};
|
|
1117
|
+
const errorsByFile = {};
|
|
1118
|
+
let totalErrors = 0;
|
|
1119
|
+
let totalWarnings = 0;
|
|
1120
|
+
for (const fileResult of results) {
|
|
1121
|
+
const errorCount = fileResult.result.errors.length;
|
|
1122
|
+
totalErrors += errorCount;
|
|
1123
|
+
totalWarnings += fileResult.result.warnings.length;
|
|
1124
|
+
if (errorCount > 0) {
|
|
1125
|
+
errorsByFile[fileResult.file] = errorCount;
|
|
1126
|
+
}
|
|
1127
|
+
for (const error of fileResult.result.errors) {
|
|
1128
|
+
const type = error.type || 'unknown';
|
|
1129
|
+
errorsByType[type] = (errorsByType[type] || 0) + 1;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
return {
|
|
1133
|
+
timestamp: new Date().toISOString(),
|
|
1134
|
+
totalFiles: results.length,
|
|
1135
|
+
passedFiles: results.filter(r => r.result.isValid).length,
|
|
1136
|
+
failedFiles: results.filter(r => !r.result.isValid).length,
|
|
1137
|
+
totalErrors,
|
|
1138
|
+
totalWarnings,
|
|
1139
|
+
errorsByType,
|
|
1140
|
+
errorsByFile,
|
|
1141
|
+
files: results
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
function generateJsonReport(data) {
|
|
1145
|
+
return JSON.stringify(data, null, 2);
|
|
1146
|
+
}
|
|
1147
|
+
function generateMarkdownReport(data) {
|
|
1148
|
+
const lines = [];
|
|
1149
|
+
// Header
|
|
1150
|
+
lines.push('# CX Schema Validation Report');
|
|
1151
|
+
lines.push('');
|
|
1152
|
+
lines.push(`Generated: ${data.timestamp}`);
|
|
1153
|
+
lines.push('');
|
|
1154
|
+
// Summary
|
|
1155
|
+
lines.push('## Summary');
|
|
1156
|
+
lines.push('');
|
|
1157
|
+
lines.push('| Metric | Value |');
|
|
1158
|
+
lines.push('|--------|-------|');
|
|
1159
|
+
lines.push(`| Total Files | ${data.totalFiles} |`);
|
|
1160
|
+
lines.push(`| Passed | ${data.passedFiles} |`);
|
|
1161
|
+
lines.push(`| Failed | ${data.failedFiles} |`);
|
|
1162
|
+
lines.push(`| Total Errors | ${data.totalErrors} |`);
|
|
1163
|
+
lines.push(`| Total Warnings | ${data.totalWarnings} |`);
|
|
1164
|
+
lines.push(`| Pass Rate | ${((data.passedFiles / data.totalFiles) * 100).toFixed(1)}% |`);
|
|
1165
|
+
lines.push('');
|
|
1166
|
+
// Errors by Type
|
|
1167
|
+
if (Object.keys(data.errorsByType).length > 0) {
|
|
1168
|
+
lines.push('## Errors by Type');
|
|
1169
|
+
lines.push('');
|
|
1170
|
+
lines.push('| Error Type | Count |');
|
|
1171
|
+
lines.push('|------------|-------|');
|
|
1172
|
+
for (const [type, count] of Object.entries(data.errorsByType).sort((a, b) => b[1] - a[1])) {
|
|
1173
|
+
lines.push(`| ${type} | ${count} |`);
|
|
1174
|
+
}
|
|
1175
|
+
lines.push('');
|
|
1176
|
+
}
|
|
1177
|
+
// Failed Files
|
|
1178
|
+
const failedFiles = data.files.filter(f => !f.result.isValid);
|
|
1179
|
+
if (failedFiles.length > 0) {
|
|
1180
|
+
lines.push('## Failed Files');
|
|
1181
|
+
lines.push('');
|
|
1182
|
+
for (const fileResult of failedFiles) {
|
|
1183
|
+
lines.push(`### ${fileResult.file}`);
|
|
1184
|
+
lines.push('');
|
|
1185
|
+
lines.push(`- **Type:** ${fileResult.fileType}`);
|
|
1186
|
+
lines.push(`- **Errors:** ${fileResult.result.errors.length}`);
|
|
1187
|
+
lines.push(`- **Warnings:** ${fileResult.result.warnings.length}`);
|
|
1188
|
+
lines.push('');
|
|
1189
|
+
if (fileResult.result.errors.length > 0) {
|
|
1190
|
+
lines.push('**Errors:**');
|
|
1191
|
+
lines.push('');
|
|
1192
|
+
for (const error of fileResult.result.errors) {
|
|
1193
|
+
lines.push(`- **${error.type}** at \`${error.path || '/'}\`: ${error.message}`);
|
|
1194
|
+
}
|
|
1195
|
+
lines.push('');
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
// Passed Files (summary)
|
|
1200
|
+
const passedFiles = data.files.filter(f => f.result.isValid);
|
|
1201
|
+
if (passedFiles.length > 0) {
|
|
1202
|
+
lines.push('## Passed Files');
|
|
1203
|
+
lines.push('');
|
|
1204
|
+
for (const fileResult of passedFiles) {
|
|
1205
|
+
const warnings = fileResult.result.warnings.length;
|
|
1206
|
+
lines.push(`- ✓ ${fileResult.file}${warnings > 0 ? ` (${warnings} warnings)` : ''}`);
|
|
1207
|
+
}
|
|
1208
|
+
lines.push('');
|
|
1209
|
+
}
|
|
1210
|
+
return lines.join('\n');
|
|
1211
|
+
}
|
|
1212
|
+
function generateHtmlReport(data) {
|
|
1213
|
+
const passRate = ((data.passedFiles / data.totalFiles) * 100).toFixed(1);
|
|
1214
|
+
const passRateColor = data.failedFiles === 0 ? '#22c55e' : data.passedFiles > data.failedFiles ? '#eab308' : '#ef4444';
|
|
1215
|
+
const errorsByTypeRows = Object.entries(data.errorsByType)
|
|
1216
|
+
.sort((a, b) => b[1] - a[1])
|
|
1217
|
+
.map(([type, count]) => `<tr><td>${type}</td><td>${count}</td></tr>`)
|
|
1218
|
+
.join('\n');
|
|
1219
|
+
const failedFilesHtml = data.files
|
|
1220
|
+
.filter(f => !f.result.isValid)
|
|
1221
|
+
.map(f => {
|
|
1222
|
+
const errorsHtml = f.result.errors
|
|
1223
|
+
.map(e => `<li><strong>${e.type}</strong> at <code>${e.path || '/'}</code>: ${escapeHtml(e.message)}</li>`)
|
|
1224
|
+
.join('\n');
|
|
1225
|
+
return `
|
|
1226
|
+
<div class="file-card failed">
|
|
1227
|
+
<h3>✗ ${escapeHtml(f.file)}</h3>
|
|
1228
|
+
<p><strong>Type:</strong> ${f.fileType} | <strong>Errors:</strong> ${f.result.errors.length} | <strong>Warnings:</strong> ${f.result.warnings.length}</p>
|
|
1229
|
+
<ul class="error-list">${errorsHtml}</ul>
|
|
1230
|
+
</div>
|
|
1231
|
+
`;
|
|
1232
|
+
})
|
|
1233
|
+
.join('\n');
|
|
1234
|
+
const passedFilesHtml = data.files
|
|
1235
|
+
.filter(f => f.result.isValid)
|
|
1236
|
+
.map(f => {
|
|
1237
|
+
const warnings = f.result.warnings.length;
|
|
1238
|
+
return `<div class="file-card passed"><span>✓</span> ${escapeHtml(f.file)}${warnings > 0 ? ` <span class="warning-badge">${warnings} warnings</span>` : ''}</div>`;
|
|
1239
|
+
})
|
|
1240
|
+
.join('\n');
|
|
1241
|
+
return `<!DOCTYPE html>
|
|
1242
|
+
<html lang="en">
|
|
1243
|
+
<head>
|
|
1244
|
+
<meta charset="UTF-8">
|
|
1245
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1246
|
+
<title>CX Schema Validation Report</title>
|
|
1247
|
+
<style>
|
|
1248
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1249
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background: #f5f5f5; color: #333; line-height: 1.6; }
|
|
1250
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
1251
|
+
h1 { color: #1e40af; margin-bottom: 10px; }
|
|
1252
|
+
h2 { color: #374151; margin: 30px 0 15px; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px; }
|
|
1253
|
+
h3 { color: #4b5563; margin-bottom: 10px; }
|
|
1254
|
+
.timestamp { color: #6b7280; margin-bottom: 30px; }
|
|
1255
|
+
.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin-bottom: 30px; }
|
|
1256
|
+
.stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); text-align: center; }
|
|
1257
|
+
.stat-card .value { font-size: 2rem; font-weight: bold; }
|
|
1258
|
+
.stat-card .label { color: #6b7280; font-size: 0.9rem; }
|
|
1259
|
+
.stat-card.passed .value { color: #22c55e; }
|
|
1260
|
+
.stat-card.failed .value { color: #ef4444; }
|
|
1261
|
+
.stat-card.rate .value { color: ${passRateColor}; }
|
|
1262
|
+
table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 20px; }
|
|
1263
|
+
th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #e5e7eb; }
|
|
1264
|
+
th { background: #f9fafb; font-weight: 600; }
|
|
1265
|
+
tr:last-child td { border-bottom: none; }
|
|
1266
|
+
.file-card { background: white; padding: 15px 20px; border-radius: 8px; margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
1267
|
+
.file-card.failed { border-left: 4px solid #ef4444; }
|
|
1268
|
+
.file-card.passed { border-left: 4px solid #22c55e; }
|
|
1269
|
+
.file-card.passed span { color: #22c55e; font-weight: bold; }
|
|
1270
|
+
.error-list { margin-top: 10px; padding-left: 20px; }
|
|
1271
|
+
.error-list li { margin-bottom: 8px; font-size: 0.9rem; }
|
|
1272
|
+
.error-list code { background: #f3f4f6; padding: 2px 6px; border-radius: 4px; font-size: 0.85rem; }
|
|
1273
|
+
.warning-badge { background: #fef3c7; color: #92400e; padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; margin-left: 10px; }
|
|
1274
|
+
</style>
|
|
1275
|
+
</head>
|
|
1276
|
+
<body>
|
|
1277
|
+
<div class="container">
|
|
1278
|
+
<h1>CX Schema Validation Report</h1>
|
|
1279
|
+
<p class="timestamp">Generated: ${data.timestamp}</p>
|
|
1280
|
+
|
|
1281
|
+
<div class="summary-grid">
|
|
1282
|
+
<div class="stat-card">
|
|
1283
|
+
<div class="value">${data.totalFiles}</div>
|
|
1284
|
+
<div class="label">Total Files</div>
|
|
1285
|
+
</div>
|
|
1286
|
+
<div class="stat-card passed">
|
|
1287
|
+
<div class="value">${data.passedFiles}</div>
|
|
1288
|
+
<div class="label">Passed</div>
|
|
1289
|
+
</div>
|
|
1290
|
+
<div class="stat-card failed">
|
|
1291
|
+
<div class="value">${data.failedFiles}</div>
|
|
1292
|
+
<div class="label">Failed</div>
|
|
1293
|
+
</div>
|
|
1294
|
+
<div class="stat-card">
|
|
1295
|
+
<div class="value">${data.totalErrors}</div>
|
|
1296
|
+
<div class="label">Total Errors</div>
|
|
1297
|
+
</div>
|
|
1298
|
+
<div class="stat-card rate">
|
|
1299
|
+
<div class="value">${passRate}%</div>
|
|
1300
|
+
<div class="label">Pass Rate</div>
|
|
1301
|
+
</div>
|
|
1302
|
+
</div>
|
|
1303
|
+
|
|
1304
|
+
${Object.keys(data.errorsByType).length > 0 ? `
|
|
1305
|
+
<h2>Errors by Type</h2>
|
|
1306
|
+
<table>
|
|
1307
|
+
<thead><tr><th>Error Type</th><th>Count</th></tr></thead>
|
|
1308
|
+
<tbody>${errorsByTypeRows}</tbody>
|
|
1309
|
+
</table>
|
|
1310
|
+
` : ''}
|
|
1311
|
+
|
|
1312
|
+
${data.failedFiles > 0 ? `
|
|
1313
|
+
<h2>Failed Files (${data.failedFiles})</h2>
|
|
1314
|
+
${failedFilesHtml}
|
|
1315
|
+
` : ''}
|
|
1316
|
+
|
|
1317
|
+
${data.passedFiles > 0 ? `
|
|
1318
|
+
<h2>Passed Files (${data.passedFiles})</h2>
|
|
1319
|
+
${passedFilesHtml}
|
|
1320
|
+
` : ''}
|
|
1321
|
+
</div>
|
|
1322
|
+
</body>
|
|
1323
|
+
</html>`;
|
|
1324
|
+
}
|
|
1325
|
+
function escapeHtml(text) {
|
|
1326
|
+
return text
|
|
1327
|
+
.replace(/&/g, '&')
|
|
1328
|
+
.replace(/</g, '<')
|
|
1329
|
+
.replace(/>/g, '>')
|
|
1330
|
+
.replace(/"/g, '"')
|
|
1331
|
+
.replace(/'/g, ''');
|
|
1332
|
+
}
|
|
1333
|
+
function detectReportFormat(filePath) {
|
|
1334
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
1335
|
+
switch (ext) {
|
|
1336
|
+
case '.html':
|
|
1337
|
+
case '.htm':
|
|
1338
|
+
return 'html';
|
|
1339
|
+
case '.md':
|
|
1340
|
+
case '.markdown':
|
|
1341
|
+
return 'markdown';
|
|
1342
|
+
case '.json':
|
|
1343
|
+
default:
|
|
1344
|
+
return 'json';
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
function generateReport(data, format) {
|
|
1348
|
+
switch (format) {
|
|
1349
|
+
case 'html':
|
|
1350
|
+
return generateHtmlReport(data);
|
|
1351
|
+
case 'markdown':
|
|
1352
|
+
return generateMarkdownReport(data);
|
|
1353
|
+
case 'json':
|
|
1354
|
+
default:
|
|
1355
|
+
return generateJsonReport(data);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
// ============================================================================
|
|
1359
|
+
// Validation
|
|
1360
|
+
// ============================================================================
|
|
1361
|
+
async function validateFile(filePath, options, schemasPath) {
|
|
1362
|
+
// Determine file type
|
|
1363
|
+
let fileType = options.type;
|
|
1364
|
+
if (fileType === 'auto') {
|
|
1365
|
+
fileType = detectFileType(filePath);
|
|
1366
|
+
}
|
|
1367
|
+
// Create appropriate validator
|
|
1368
|
+
if (fileType === 'workflow') {
|
|
1369
|
+
const validator = new workflowValidator_1.WorkflowValidator({
|
|
1370
|
+
schemasPath: path.join(schemasPath, 'workflows')
|
|
1371
|
+
});
|
|
1372
|
+
return validator.validateWorkflow(filePath);
|
|
1373
|
+
}
|
|
1374
|
+
else {
|
|
1375
|
+
const validator = new validator_1.ModuleValidator({ schemasPath });
|
|
1376
|
+
return validator.validateModule(filePath);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
// ============================================================================
|
|
1380
|
+
// Main
|
|
1381
|
+
// ============================================================================
|
|
1382
|
+
async function main() {
|
|
1383
|
+
const args = process.argv.slice(2);
|
|
1384
|
+
const { command, files, options } = parseArgs(args);
|
|
1385
|
+
// Handle help
|
|
1386
|
+
if (options.help) {
|
|
1387
|
+
if (command === 'schema') {
|
|
1388
|
+
console.log(SCHEMA_HELP);
|
|
1389
|
+
}
|
|
1390
|
+
else if (command === 'list') {
|
|
1391
|
+
console.log(LIST_HELP);
|
|
1392
|
+
}
|
|
1393
|
+
else if (command === 'init') {
|
|
1394
|
+
console.log(INIT_HELP);
|
|
1395
|
+
}
|
|
1396
|
+
else if (command === 'create') {
|
|
1397
|
+
console.log(CREATE_HELP);
|
|
1398
|
+
}
|
|
1399
|
+
else {
|
|
1400
|
+
console.log(HELP_TEXT);
|
|
1401
|
+
}
|
|
1402
|
+
process.exit(0);
|
|
1403
|
+
}
|
|
1404
|
+
// Handle version
|
|
1405
|
+
if (options.version) {
|
|
1406
|
+
console.log(`cx-validate v${VERSION}`);
|
|
1407
|
+
process.exit(0);
|
|
1408
|
+
}
|
|
1409
|
+
// Find schemas path
|
|
1410
|
+
const schemasPath = options.schemasPath || findSchemasPath();
|
|
1411
|
+
if (!schemasPath) {
|
|
1412
|
+
console.error(chalk_1.default.red('Error: Could not find schemas directory.'));
|
|
1413
|
+
console.error(chalk_1.default.gray('Please run npm install first or specify --schemas <path>'));
|
|
1414
|
+
process.exit(2);
|
|
1415
|
+
}
|
|
1416
|
+
// Handle schema command
|
|
1417
|
+
if (options.showSchema) {
|
|
1418
|
+
showSchema(schemasPath, options.showSchema);
|
|
1419
|
+
process.exit(0);
|
|
1420
|
+
}
|
|
1421
|
+
// Handle example command
|
|
1422
|
+
if (options.showExample) {
|
|
1423
|
+
showExample(schemasPath, options.showExample);
|
|
1424
|
+
process.exit(0);
|
|
1425
|
+
}
|
|
1426
|
+
// Handle list command
|
|
1427
|
+
if (options.listSchemas) {
|
|
1428
|
+
listSchemas(schemasPath, options.type);
|
|
1429
|
+
process.exit(0);
|
|
1430
|
+
}
|
|
1431
|
+
// Handle init command
|
|
1432
|
+
if (command === 'init') {
|
|
1433
|
+
runInit();
|
|
1434
|
+
process.exit(0);
|
|
1435
|
+
}
|
|
1436
|
+
// Handle create command
|
|
1437
|
+
if (command === 'create') {
|
|
1438
|
+
runCreate(files[0], files[1]);
|
|
1439
|
+
process.exit(0);
|
|
1440
|
+
}
|
|
1441
|
+
// Validate files
|
|
1442
|
+
if (files.length === 0) {
|
|
1443
|
+
console.error(chalk_1.default.red('Error: No input file specified'));
|
|
1444
|
+
console.error(chalk_1.default.gray(`Use '${PROGRAM_NAME} --help' for usage information`));
|
|
1445
|
+
process.exit(2);
|
|
1446
|
+
}
|
|
1447
|
+
let hasErrors = false;
|
|
1448
|
+
const allResults = [];
|
|
1449
|
+
const isReportMode = command === 'report' || options.report;
|
|
1450
|
+
for (const file of files) {
|
|
1451
|
+
// Check file exists
|
|
1452
|
+
if (!fs.existsSync(file)) {
|
|
1453
|
+
console.error(chalk_1.default.red(`Error: File not found: ${file}`));
|
|
1454
|
+
hasErrors = true;
|
|
1455
|
+
continue;
|
|
1456
|
+
}
|
|
1457
|
+
try {
|
|
1458
|
+
const fileType = options.type === 'auto' ? detectFileType(file) : options.type;
|
|
1459
|
+
const result = await validateFile(file, options, schemasPath);
|
|
1460
|
+
// Collect results for report
|
|
1461
|
+
if (isReportMode) {
|
|
1462
|
+
allResults.push({ file, fileType, result });
|
|
1463
|
+
}
|
|
1464
|
+
// Output individual results (unless quiet mode for reports)
|
|
1465
|
+
if (!isReportMode || !options.quiet) {
|
|
1466
|
+
if (options.format === 'json' && !isReportMode) {
|
|
1467
|
+
printResultJson(result);
|
|
1468
|
+
}
|
|
1469
|
+
else if (options.format === 'compact' || isReportMode) {
|
|
1470
|
+
printResultCompact(result, file);
|
|
1471
|
+
}
|
|
1472
|
+
else {
|
|
1473
|
+
printResultPretty(result, fileType, schemasPath, options.verbose);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (!result.isValid) {
|
|
1477
|
+
hasErrors = true;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
catch (error) {
|
|
1481
|
+
console.error(chalk_1.default.red(`Error validating ${file}:`), error.message);
|
|
1482
|
+
if (options.verbose) {
|
|
1483
|
+
console.error(chalk_1.default.gray(error.stack));
|
|
1484
|
+
}
|
|
1485
|
+
hasErrors = true;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
// Generate report if requested
|
|
1489
|
+
if (isReportMode && allResults.length > 0) {
|
|
1490
|
+
const reportData = buildReportData(allResults);
|
|
1491
|
+
const reportPath = options.report || 'validation-report.json';
|
|
1492
|
+
// Determine report format (auto-detect from extension if not specified explicitly)
|
|
1493
|
+
let reportFormat = options.reportFormat;
|
|
1494
|
+
if (!options.reportFormat || options.reportFormat === 'json') {
|
|
1495
|
+
// If reportFormat wasn't explicitly set, auto-detect from file extension
|
|
1496
|
+
const detectedFormat = detectReportFormat(reportPath);
|
|
1497
|
+
if (detectedFormat !== 'json' || !options.reportFormat) {
|
|
1498
|
+
reportFormat = detectedFormat;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const reportContent = generateReport(reportData, reportFormat);
|
|
1502
|
+
fs.writeFileSync(reportPath, reportContent, 'utf-8');
|
|
1503
|
+
// Print summary
|
|
1504
|
+
console.log('');
|
|
1505
|
+
console.log(chalk_1.default.bold.cyan('═══════════════════════════════════════════════════════════════════'));
|
|
1506
|
+
console.log(chalk_1.default.bold.cyan(' VALIDATION SUMMARY'));
|
|
1507
|
+
console.log(chalk_1.default.bold.cyan('═══════════════════════════════════════════════════════════════════'));
|
|
1508
|
+
console.log('');
|
|
1509
|
+
console.log(chalk_1.default.bold(' Total Files: ') + reportData.totalFiles);
|
|
1510
|
+
console.log(chalk_1.default.bold(' Passed: ') + chalk_1.default.green(reportData.passedFiles));
|
|
1511
|
+
console.log(chalk_1.default.bold(' Failed: ') + (reportData.failedFiles > 0 ? chalk_1.default.red(reportData.failedFiles) : '0'));
|
|
1512
|
+
console.log(chalk_1.default.bold(' Pass Rate: ') + chalk_1.default.cyan(`${((reportData.passedFiles / reportData.totalFiles) * 100).toFixed(1)}%`));
|
|
1513
|
+
console.log('');
|
|
1514
|
+
console.log(chalk_1.default.gray(` Report saved to: ${chalk_1.default.white(reportPath)}`));
|
|
1515
|
+
console.log('');
|
|
1516
|
+
}
|
|
1517
|
+
process.exit(hasErrors ? 1 : 0);
|
|
1518
|
+
}
|
|
1519
|
+
main().catch(error => {
|
|
1520
|
+
console.error(chalk_1.default.red('Unexpected error:'), error.message);
|
|
1521
|
+
process.exit(2);
|
|
1522
|
+
});
|
|
1523
|
+
//# sourceMappingURL=cli.js.map
|