aboutsummaryrefslogtreecommitdiff
path: root/validate.js
diff options
context:
space:
mode:
Diffstat (limited to 'validate.js')
-rw-r--r--validate.js234
1 files changed, 0 insertions, 234 deletions
diff --git a/validate.js b/validate.js
deleted file mode 100644
index 404a6f0..0000000
--- a/validate.js
+++ /dev/null
@@ -1,234 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * CLI validator for .gitinfo files
- * Usage: node validate.js [file]
- * node validate.js # validates .gitinfo in current directory
- * node validate.js path/to/.gitinfo
- */
-
-const fs = require("fs");
-const path = require("path");
-
-// Load the schema
-const SCHEMA_PATH = path.join(__dirname, "gitinfo.schema.json");
-
-/**
- * Strip JSONC comments (single-line // and multi-line /* *\/)
- */
-function stripJsonComments(jsonc) {
- let result = "";
- let i = 0;
- let inString = false;
- let stringChar = null;
-
- while (i < jsonc.length) {
- const char = jsonc[i];
- const next = jsonc[i + 1];
-
- // Track string state
- if (!inString && (char === '"' || char === "'")) {
- inString = true;
- stringChar = char;
- result += char;
- i++;
- continue;
- }
-
- if (inString) {
- if (char === "\\" && i + 1 < jsonc.length) {
- // Escape sequence
- result += char + jsonc[i + 1];
- i += 2;
- continue;
- }
- if (char === stringChar) {
- inString = false;
- stringChar = null;
- }
- result += char;
- i++;
- continue;
- }
-
- // Single-line comment
- if (char === "/" && next === "/") {
- while (i < jsonc.length && jsonc[i] !== "\n") {
- i++;
- }
- continue;
- }
-
- // Multi-line comment
- if (char === "/" && next === "*") {
- i += 2;
- while (
- i < jsonc.length - 1 &&
- !(jsonc[i] === "*" && jsonc[i + 1] === "/")
- ) {
- i++;
- }
- i += 2;
- continue;
- }
-
- result += char;
- i++;
- }
-
- return result;
-}
-
-/**
- * Simple JSON Schema validator (subset of draft 2020-12)
- */
-function validateSchema(data, schema, path = "") {
- const errors = [];
-
- if (schema.type === "object") {
- if (typeof data !== "object" || data === null || Array.isArray(data)) {
- errors.push(`${path || "root"}: expected object`);
- return errors;
- }
-
- // Check additionalProperties
- if (schema.additionalProperties === false && schema.properties) {
- const allowed = new Set(Object.keys(schema.properties));
- for (const key of Object.keys(data)) {
- if (!allowed.has(key)) {
- errors.push(`${path || "root"}: unknown property "${key}"`);
- }
- }
- }
-
- // Validate each property
- if (schema.properties) {
- for (const [key, propSchema] of Object.entries(schema.properties)) {
- if (data[key] !== undefined) {
- errors.push(
- ...validateSchema(data[key], propSchema, `${path}.${key}`),
- );
- }
- }
- }
- } else if (schema.type === "array") {
- if (!Array.isArray(data)) {
- errors.push(`${path}: expected array`);
- return errors;
- }
-
- if (schema.items) {
- if (Array.isArray(schema.items)) {
- // Tuple validation
- for (let i = 0; i < data.length; i++) {
- const itemSchema = schema.items[i] || {};
- errors.push(...validateSchema(data[i], itemSchema, `${path}[${i}]`));
- }
- if (schema.minItems && data.length < schema.minItems) {
- errors.push(`${path}: expected at least ${schema.minItems} items`);
- }
- if (schema.maxItems && data.length > schema.maxItems) {
- errors.push(`${path}: expected at most ${schema.maxItems} items`);
- }
- } else {
- // Array of same type
- for (let i = 0; i < data.length; i++) {
- errors.push(
- ...validateSchema(data[i], schema.items, `${path}[${i}]`),
- );
- }
- }
- }
- } else if (schema.type === "string") {
- if (typeof data !== "string") {
- errors.push(`${path}: expected string`);
- return errors;
- }
-
- if (schema.minLength && data.length < schema.minLength) {
- errors.push(`${path}: string too short (min ${schema.minLength})`);
- }
-
- if (schema.format === "uri") {
- try {
- new URL(data);
- } catch {
- errors.push(`${path}: invalid URI "${data}"`);
- }
- }
-
- if (schema.format === "email") {
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data)) {
- errors.push(`${path}: invalid email "${data}"`);
- }
- }
-
- if (schema.pattern) {
- if (!new RegExp(schema.pattern).test(data)) {
- errors.push(`${path}: does not match pattern ${schema.pattern}`);
- }
- }
- }
-
- return errors;
-}
-
-function main() {
- const args = process.argv.slice(2);
- const filePath = args[0] || ".gitinfo";
-
- // Check if file exists
- if (!fs.existsSync(filePath)) {
- console.error(`Error: File not found: ${filePath}`);
- process.exit(1);
- }
-
- // Check if schema exists
- if (!fs.existsSync(SCHEMA_PATH)) {
- console.error(`Error: Schema not found: ${SCHEMA_PATH}`);
- process.exit(1);
- }
-
- // Read and parse schema
- let schema;
- try {
- schema = JSON.parse(fs.readFileSync(SCHEMA_PATH, "utf-8"));
- } catch (err) {
- console.error(`Error parsing schema: ${err.message}`);
- process.exit(1);
- }
-
- // Read and parse .gitinfo file
- let content;
- try {
- content = fs.readFileSync(filePath, "utf-8");
- } catch (err) {
- console.error(`Error reading file: ${err.message}`);
- process.exit(1);
- }
-
- let data;
- try {
- const stripped = stripJsonComments(content);
- data = JSON.parse(stripped);
- } catch (err) {
- console.error(`Error parsing JSONC: ${err.message}`);
- process.exit(1);
- }
-
- // Validate against schema
- const errors = validateSchema(data, schema);
-
- if (errors.length > 0) {
- console.error(`Validation failed for ${filePath}:`);
- for (const error of errors) {
- console.error(` - ${error}`);
- }
- process.exit(1);
- }
-
- console.log(`✓ ${filePath} is valid`);
- process.exit(0);
-}
-
-main();