check-rule-mate 0.5.6 → 0.5.9

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.
@@ -0,0 +1,435 @@
1
+ /**
2
+ * check-rule-mate — Auto Documentation Generator
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const process = require('process');
8
+ const { parseArgs } = require('../utils/args.js');
9
+
10
+ const generateDocs = function ({ rulesArg, schemasArg, errorsArg, outArg }) {
11
+ console.log(rulesArg)
12
+ const RULES_DIR = rulesArg;
13
+ const SCHEMAS_DIR = schemasArg;
14
+ const ERRORS_DIR = errorsArg;
15
+ const OUTPUT = outArg || "check-rule-mate-docs.html";
16
+
17
+ if (!RULES_DIR || !SCHEMAS_DIR) {
18
+ console.error(`
19
+ Usage:
20
+ npx check-rule-mate docs --rules ./rules --schemas ./schemas --errors ./errors --out docs.html
21
+ `);
22
+ process.exit(1);
23
+ }
24
+
25
+ /* ---------------------------------------
26
+ * HELPERS
27
+ * -------------------------------------*/
28
+ const readJSONFiles = (dir) => {
29
+ return fs.readdirSync(dir)
30
+ .filter(f => f.endsWith(".json"))
31
+ .map(file => ({
32
+ name: file.replace(".json", ""),
33
+ content: JSON.parse(fs.readFileSync(path.join(dir, file), "utf-8"))
34
+ }));
35
+ };
36
+
37
+ const rulesFiles = readJSONFiles(RULES_DIR);
38
+ const schemasFiles = readJSONFiles(SCHEMAS_DIR);
39
+ const errorsFiles = readJSONFiles(ERRORS_DIR);
40
+
41
+
42
+ /* ---------------------------------------
43
+ * INDEX RELATIONSHIPS
44
+ * -------------------------------------*/
45
+ const ruleUsageMap = {};
46
+ const errorUsageMap = {}
47
+
48
+ schemasFiles.forEach(schemaFile => {
49
+ Object.entries(schemaFile.content).forEach(([field, config]) => {
50
+ const ruleName = config.rule.split("--")[0];
51
+ if (!ruleUsageMap[ruleName]) ruleUsageMap[ruleName] = [];
52
+ ruleUsageMap[ruleName].push({
53
+ schema: schemaFile.name,
54
+ field,
55
+ rule: config.rule
56
+ });
57
+ });
58
+ });
59
+
60
+ rulesFiles.forEach(rulesFile => {
61
+ Object.entries(rulesFile.content).forEach(([field, config]) => {
62
+ Object.entries(config.error).forEach(([key, value]) => {
63
+ const errorName = value;
64
+ if (!errorUsageMap[errorName]) errorUsageMap[errorName] = [];
65
+ errorUsageMap[errorName].push({
66
+ file: rulesFile.name,
67
+ field,
68
+ error: value
69
+ });
70
+ });
71
+ if (config?.modifier) {
72
+ Object.entries(config.modifier).forEach(([modifierKey, modifier]) => {
73
+ if (modifier?.error) {
74
+ Object.entries(modifier.error).forEach(([key, value]) => {
75
+ const errorName = value;
76
+ if (!errorUsageMap[errorName]) errorUsageMap[errorName] = [];
77
+ errorUsageMap[errorName].push({
78
+ file: rulesFile.name,
79
+ field,
80
+ error: value
81
+ });
82
+ });
83
+ }
84
+ });
85
+ }
86
+ });
87
+ });
88
+
89
+
90
+ /* ---------------------------------------
91
+ * HTML GENERATION
92
+ * -------------------------------------*/
93
+ const html = `
94
+ <!DOCTYPE html>
95
+ <html lang="en">
96
+ <head>
97
+ <meta charset="UTF-8" />
98
+ <title>check-rule-mate — Documentation</title>
99
+ <style>
100
+ ${generateCSS()}
101
+ </style>
102
+ </head>
103
+ <body>
104
+
105
+ <aside class="sidebar">
106
+ <input id="search" placeholder="Search..." />
107
+
108
+ <h3>Schemas</h3>
109
+ ${schemasFiles.map(s => `
110
+ <a href="#schema-${s.name}">${s.name}</a>
111
+ `).join("")}
112
+
113
+ <h3>Rules</h3>
114
+ ${rulesFiles.map(rf =>
115
+ Object.keys(rf.content).map(rule => `
116
+ <a href="#rule-${rule}">${rf.name} - ${rule}</a>
117
+ `).join("")
118
+ ).join("")}
119
+
120
+ <h3>Errors</h3>
121
+ ${errorsFiles.map(errorFile =>
122
+ Object.keys(errorFile.content).map(error => `
123
+ <a href="#error-${error}">${errorFile.name} - ${error}</a>
124
+ `).join("")
125
+ ).join("")}
126
+ </aside>
127
+
128
+ <main>
129
+ <section>
130
+ <h1>check-rule-mate</h1>
131
+ <p class="muted">
132
+ Visual documentation of validation rules and schemas.
133
+ </p>
134
+ </section>
135
+
136
+ ${renderSchemas(schemasFiles)}
137
+ ${renderRules(rulesFiles, ruleUsageMap)}
138
+ ${renderErrors(errorsFiles, errorUsageMap)}
139
+ </main>
140
+
141
+ <script>
142
+ ${generateClientJS()}
143
+ </script>
144
+
145
+ </body>
146
+ </html>
147
+ `;
148
+
149
+ fs.writeFileSync(OUTPUT, html);
150
+ console.log(`✔ Documentation generated at ${OUTPUT}`);
151
+
152
+ /* ---------------------------------------
153
+ * RENDERERS
154
+ * -------------------------------------*/
155
+ function renderSchemas(schemas) {
156
+ return schemas.map(schema => `
157
+ <section id="schema-${schema.name}" class="card">
158
+ <h2>Schema: ${schema.name}</h2>
159
+
160
+ <table>
161
+ <thead>
162
+ <tr>
163
+ <th>Field</th>
164
+ <th>Rule</th>
165
+ <th class="text-center">Required</th>
166
+ <th class="text-center">Cache</th>
167
+ </tr>
168
+ </thead>
169
+ <tbody>
170
+ ${Object.entries(schema.content).map(([field, cfg]) => `
171
+ <tr>
172
+ <td>${field}</td>
173
+ <td>
174
+ <a href="#rule-${cfg.rule.split("--")[0]}">
175
+ ${cfg.rule}
176
+ </a>
177
+ </td>
178
+ <td class="text-center">${cfg.required ? "✔" : "optional"}</td>
179
+ <td class="text-center">${cfg.cache === false ? "off" : "✔"}</td>
180
+ </tr>
181
+ `).join("")}
182
+ </tbody>
183
+ </table>
184
+ </section>
185
+ `).join("");
186
+ }
187
+
188
+ function renderRules(rulesFiles, usageMap) {
189
+ return rulesFiles.map(file =>
190
+ Object.entries(file.content).map(([ruleName, rule]) => `
191
+ <section id="rule-${ruleName}" class="card">
192
+ <h2>Rule: ${ruleName} (${file.name})</h2>
193
+ ${rule?.docs?.description ?
194
+ `<p>${rule.docs.description}</p>`
195
+ : ''}
196
+
197
+ <h3>Validation Flow</h3>
198
+ <div class="flow">
199
+ ${rule.validate.map(v => `
200
+ <div class="flow-step">${v}</div>
201
+ <div class="flow-arrow">→</div>
202
+ `).join("")}
203
+ <div class="flow-step success">valid</div>
204
+ </div>
205
+
206
+ <h3>Error Codes</h3>
207
+ ${renderRulesErrors(rule.error)}
208
+
209
+ ${rule.modifier ? `
210
+ <h3>Modifiers</h3>
211
+ ${Object.entries(rule.modifier).map(([mod, modRule]) => `
212
+ <div class="modifier">
213
+ <span class="tag modifier">${mod}</span>
214
+ ${modRule?.docs?.description ? `<p>${modRule.docs.description}</p>` : ''}
215
+
216
+ <div class="flow">
217
+ ${modRule.validate.map(v => `
218
+ <div class="flow-step">${v}</div>
219
+ <div class="flow-arrow">→</div>
220
+ `).join("")}
221
+ <div class="flow-step success">valid</div>
222
+ </div>
223
+
224
+ <h4>Error Codes</h4>
225
+ ${renderRulesErrors(modRule.error)}
226
+ </div>
227
+ `).join("")}
228
+ ` : ""}
229
+
230
+ <h3>Used by Schemas</h3>
231
+ <ul>
232
+ ${(usageMap[ruleName] || []).map(u =>
233
+ `<li>${u.schema} → <strong>${u.field}</strong> (${u.rule})</li>`
234
+ ).join("") || "<li>Not used</li>"}
235
+ </ul>
236
+
237
+ ${rule?.docs?.notes ?
238
+ `
239
+ <h3>Notes</h3>
240
+ <div class="rule-notes">${rule?.docs?.notes}</div>
241
+ `
242
+ : ''}
243
+ </section>
244
+ `).join("")
245
+ ).join("");
246
+ }
247
+
248
+ function renderRulesErrors(errors = {}) {
249
+ return `
250
+ <ul>
251
+ ${Object.entries(errors).map(([k, v]) =>
252
+ `<li><a href="#error-${v.split('.')[0]}" class="tag error">${k}</a> ${v}</li>`
253
+ ).join("")}
254
+ </ul>
255
+ `;
256
+ }
257
+
258
+ function renderErrors(errorFiles, usageMap) {
259
+ return errorFiles.map(file =>
260
+ Object.entries(file.content).map(([errorName, errors]) => `
261
+ <section id="error-${errorName}" class="card">
262
+ <h2>Error: ${errorName} (${file.name})</h2>
263
+ <ul>
264
+ ${Object.entries(errors).map(([key, value]) => `
265
+ <li><span class="tag error">${key}</span> ${value} </li>
266
+ `).join('')}
267
+ </ul>
268
+
269
+ <h3>Used by Rules</h3>
270
+ <ul>
271
+ ${Object.entries(errors).map(([key, value]) => `
272
+ ${(usageMap[`${errorName}.${key}`] || []).map(u =>
273
+ `<li>${u.file} → <strong>${u.field}</strong> (${u.error})</li>`
274
+ ).join("") || "<li>Not used</li>"}
275
+ `).join('')}
276
+ </ul>
277
+ </section>
278
+ `).join('')).join('');
279
+ }
280
+
281
+ /* ---------------------------------------
282
+ * CSS
283
+ * -------------------------------------*/
284
+ function generateCSS() {
285
+ return `
286
+ body {
287
+ margin: 0;
288
+ font-family: Inter, system-ui, sans-serif;
289
+ display: grid;
290
+ grid-template-columns: 280px 1fr;
291
+ background: #0f172a;
292
+ color: #e5e7eb;
293
+ }
294
+
295
+ .sidebar {
296
+ padding: 16px;
297
+ background: #020617;
298
+ overflow-y: auto;
299
+ }
300
+
301
+ .sidebar input {
302
+ width: 100%;
303
+ padding: 8px;
304
+ border-radius: 6px;
305
+ border: none;
306
+ margin-bottom: 16px;
307
+ }
308
+
309
+ .sidebar a {
310
+ display: block;
311
+ padding: 6px 10px;
312
+ color: #e5e7eb;
313
+ text-decoration: none;
314
+ border-radius: 6px;
315
+ }
316
+
317
+ .sidebar a:hover {
318
+ background: rgba(56,189,248,0.2);
319
+ }
320
+
321
+ a {
322
+ color: #e5e7eb;
323
+ }
324
+
325
+ main {
326
+ padding: 32px;
327
+ overflow-y: auto;
328
+ }
329
+
330
+ .card {
331
+ background: #020617;
332
+ border-radius: 12px;
333
+ padding: 24px;
334
+ margin-bottom: 32px;
335
+ border: 1px solid rgba(255,255,255,0.05);
336
+ }
337
+
338
+ .flow {
339
+ display: flex;
340
+ align-items: center;
341
+ gap: 12px;
342
+ flex-wrap: wrap;
343
+ }
344
+
345
+ .flow-step {
346
+ padding: 10px 14px;
347
+ border-radius: 8px;
348
+ background: rgba(56,189,248,0.15);
349
+ }
350
+
351
+ .flow-arrow {
352
+ opacity: 0.5;
353
+ }
354
+
355
+ .success {
356
+ background: rgba(34,197,94,0.2);
357
+ }
358
+
359
+ .tag {
360
+ padding: 4px 8px;
361
+ border-radius: 999px;
362
+ font-size: 14px;
363
+ }
364
+
365
+ .tag.error {
366
+ display: inline-block;
367
+ margin-bottom: 8px;
368
+ background: rgba(239,68,68,0.2);
369
+ text-decoration: none;
370
+ }
371
+
372
+ .tag.modifier {
373
+ display: inline-block;
374
+ font-size: 16px;
375
+ margin-bottom: 8px;
376
+ font-weight: 600;
377
+ background: rgba(167,139,250,0.2);
378
+ }
379
+
380
+ table {
381
+ width: 100%;
382
+ border-collapse: collapse;
383
+ }
384
+
385
+ td, th {
386
+ padding: 8px;
387
+ border-bottom: 1px solid rgba(255,255,255,0.05);
388
+ }
389
+
390
+ th {
391
+ text-align: left;
392
+ border-bottom: 1px solid #f4f4f4;
393
+ }
394
+
395
+ .rule-notes {
396
+ padding: 16px;
397
+ color: black;
398
+ background: #f4f4f4;
399
+ border-radius: 12px;
400
+ }
401
+
402
+ .text-center {
403
+ text-align: center;
404
+ }
405
+ `;
406
+ }
407
+
408
+ /* ---------------------------------------
409
+ * CLIENT JS
410
+ * -------------------------------------*/
411
+ function generateClientJS() {
412
+ return `
413
+ const search = document.getElementById("search");
414
+ search.addEventListener("input", e => {
415
+ const value = e.target.value.toLowerCase();
416
+ document.querySelectorAll("section.card").forEach(card => {
417
+ card.style.display = card.innerText.toLowerCase().includes(value)
418
+ ? ""
419
+ : "none";
420
+ });
421
+ });
422
+ `;
423
+ }
424
+ }
425
+
426
+ module.exports = function docs(argv) {
427
+ const args = parseArgs(argv);
428
+
429
+ return generateDocs({
430
+ rulesArg: args.rules,
431
+ schemasArg: args.schemas,
432
+ errorsArg: args.errors,
433
+ outArg: args.out
434
+ });
435
+ }
@@ -1,12 +1,7 @@
1
- #!/usr/bin/env node
2
-
3
- // import fs from 'fs';
4
- // import path from 'path';
5
- // import process from 'process';
6
-
7
1
  const fs = require('fs');
8
2
  const path = require('path');
9
3
  const process = require('process');
4
+ const { parseArgs } = require('../utils/args.js');
10
5
 
11
6
  /**
12
7
  * ---------------------------
@@ -144,40 +139,17 @@ function findUnusedErrors({ rules, errors }) {
144
139
  return issues;
145
140
  }
146
141
 
147
- /**
148
- * ---------------------------
149
- * CLI Argument Parsing
150
- * ---------------------------
151
- */
152
-
153
- function parseArgs() {
154
- const args = process.argv.slice(2);
155
- const options = {};
156
-
157
- for (let i = 0; i < args.length; i++) {
158
- if (args[i].startsWith('--')) {
159
- const key = args[i].replace('--', '');
160
- options[key] = args[i + 1];
161
- i++;
162
- }
163
- }
164
-
165
- return options;
166
- }
167
-
168
142
  /**
169
143
  * ---------------------------
170
144
  * Runner
171
145
  * ---------------------------
172
146
  */
173
147
 
174
- async function runVerify() {
148
+ async function runVerify(args) {
175
149
  try {
176
- const args = parseArgs();
177
-
178
150
  if (!args.schemas || !args.rules || !args.errors) {
179
151
  console.error(
180
- 'Usage: check-rule-mate-verify-templates --schemas <path> --rules <path> --errors <path>'
152
+ 'Usage: check-rule-mate verify --schemas <path> --rules <path> --errors <path>'
181
153
  );
182
154
  process.exit(1);
183
155
  }
@@ -223,4 +195,8 @@ async function runVerify() {
223
195
  }
224
196
  }
225
197
 
226
- runVerify();
198
+ module.exports = function verify(argv) {
199
+ const args = parseArgs(argv);
200
+
201
+ return runVerify(args);
202
+ }
package/bin/index.js ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ const docs = require('./commands/generate-docs');
4
+ const docsPlayground = require('./commands/generate-docs-playground-experimental');
5
+ const verify = require('./commands/verify-templates');
6
+
7
+ run(process.argv.slice(2));
8
+
9
+
10
+ function run(args) {
11
+ const [command, ...rest] = args;
12
+
13
+ switch (command) {
14
+ case 'docs':
15
+ return docs(rest);
16
+
17
+ case 'docs:playground':
18
+ return docsPlayground(rest);
19
+
20
+ case 'verify':
21
+ return verify(rest);
22
+
23
+ case '--help':
24
+ case undefined:
25
+ return showHelp();
26
+
27
+ default:
28
+ console.error(`Unknown command: ${command}`);
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ function showHelp() {
34
+ console.log(`
35
+ check-rule-mate CLI
36
+
37
+ Usage:
38
+ check-rule-mate <command> [options]
39
+
40
+ Commands:
41
+ docs Generate HTML documentation
42
+ docs:playground Generate docs with interactive playground (experimental)
43
+ verify Verify schemas, rules and error templates
44
+
45
+ Examples:
46
+ check-rule-mate docs --rules ./rules --schemas ./schemas --errors ./errors --out ./output.html
47
+ check-rule-mate docs:pĺayground --rules ./rules --schemas ./schemas --errors ./errors --options ./options.json --out ./output.html
48
+ check-rule-mate verify --rules ./rules --schemas ./schemas --errors ./errors
49
+ `);
50
+ }
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ parseArgs: function parseArgs(argv) {
3
+ const args = {};
4
+ for (let i = 0; i < argv.length; i += 2) {
5
+ const key = argv[i].replace(/^--/, '');
6
+ args[key] = argv[i + 1];
7
+ }
8
+ return args;
9
+ }
10
+ }
@@ -153,37 +153,35 @@ export interface ValidatorHooks {
153
153
  onValidateEnd?: onValidateEnd
154
154
  }
155
155
 
156
- export interface onValidateStart {
157
- /** Returns the payload of hook */
156
+ export type onValidateStart = (
158
157
  payload: onValidateStartPayload
159
- }
158
+ ) => void | Promise<void>
160
159
 
161
- export interface onValidateFieldStart {
162
- /** Returns the payload of hook */
160
+ export type onValidateFieldStart = (
163
161
  payload: onValidateFieldStartPayload
164
- }
162
+ ) => void | Promise<void>
165
163
 
166
- export interface onValidateFieldError {
167
- /** Returns the payload of hook */
164
+ export type onValidateFieldError = (
168
165
  payload: onValidateFieldErrorPayload
169
- }
166
+ ) => void | Promise<void>
170
167
 
171
- export interface onValidateFieldSuccess {
172
- /** Returns the payload of hook */
168
+ export type onValidateFieldSuccess = (
173
169
  payload: onValidateFieldSuccessPayload
174
- }
170
+ ) => void | Promise<void>
175
171
 
176
- export interface onValidateEnd {
177
- /** Returns the payload of hook */
172
+ export type onValidateEnd = (
178
173
  payload: onValidateEndPayload
179
- }
174
+ ) => void | Promise<void>
180
175
 
176
+ /**
177
+ * Hook payloads
178
+ */
181
179
  export interface onValidateStartPayload {
182
180
  /** Form data object */
183
181
  data: Object
184
182
  }
185
183
 
186
- export interface onValidateFieldStart {
184
+ export interface onValidateFieldStartPayload {
187
185
  /** Field name */
188
186
  field: string
189
187
 
@@ -194,7 +192,7 @@ export interface onValidateFieldStart {
194
192
  schemaField: SchemaRuleField
195
193
  }
196
194
 
197
- export interface onValidateFieldError {
195
+ export interface onValidateFieldErrorPayload {
198
196
  /** Field name */
199
197
  field: string
200
198
 
@@ -208,7 +206,7 @@ export interface onValidateFieldError {
208
206
  error: CheckError
209
207
  }
210
208
 
211
- export interface onValidateFieldSuccess {
209
+ export interface onValidateFieldSuccessPayload {
212
210
  /** Field name */
213
211
  field: string
214
212
 
@@ -226,3 +224,23 @@ export interface onValidateEndPayload {
226
224
  /** Form errors */
227
225
  errors?: Record<string, CheckError>
228
226
  }
227
+
228
+ export function createValidator(
229
+ data: Record<string, any>,
230
+ options: {
231
+ validationHelpers?: Record<string, any>
232
+ rules: Record<string, any>
233
+ schema: Record<string, any>
234
+ errorMessages?: Record<string, string>
235
+ hooks?: ValidatorHooks
236
+ options?: {
237
+ propertiesMustMatch?: boolean
238
+ abortEarly?: boolean
239
+ cache?: boolean
240
+ }
241
+ }
242
+ ): {
243
+ validate(): Promise<{ ok?: true; error?: true; errors?: any }>
244
+ validateField(field: string): Promise<any>
245
+ setData(data: Record<string, any>): void
246
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "check-rule-mate",
3
- "version": "0.5.6",
3
+ "version": "0.5.9",
4
4
  "description": "",
5
5
  "main": "./dist/main.cjs.js",
6
6
  "type": "commonjs",
@@ -18,9 +18,7 @@
18
18
  "check rule mate"
19
19
  ],
20
20
  "bin": {
21
- "check-rule-mate-auto-docs": "./bin/generate-docs.js",
22
- "check-rule-mate-auto-docs-playground-experimental": "./bin/generate-docs-playground-experimental.js",
23
- "check-rule-mate-verify-templates": "./bin/verify-templates.js"
21
+ "check-rule-mate": "./bin/index.js"
24
22
  },
25
23
  "scripts": {
26
24
  "start": "node ./examples/vanilla/src/index.js",