From 358955a9fb0c4b579a08c6c0854d2e9e9ddef354 Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Mon, 16 Feb 2026 09:58:58 -0600 Subject: [PATCH 1/2] Make validation script self-contained with GAP directory discovery The script now handles directory discovery internally using Node.js's built-in globSync. It takes a required directory path argument and discovers all GAP-* directories within that path using glob patterns, then validates each one. Usage: ./scripts/validate-structure.js This keeps all logic self-contained in the script and eliminates the need for the find command in the npm task. Co-Authored-By: Claude Sonnet 4.5 --- package.json | 2 +- scripts/validate-structure.js | 54 +++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 125acfb..df2a4ac 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "suggest:format": "echo \"\nTo resolve this, run: $(tput bold)npm run format$(tput sgr0)\" && exit 1", "test:format": "prettier --check . || npm run suggest:format", "test:spelling": "cspell \"spec/**/*.md\" README.md LICENSE.md", - "test:structure": "find . -maxdepth 1 -type d -name 'GAP-*' -exec ./scripts/validate-structure.js {} \\;" + "test:structure": "./scripts/validate-structure.js ." }, "devDependencies": { "ajv": "^8.17.1", diff --git a/scripts/validate-structure.js b/scripts/validate-structure.js index e1a88b6..a38c8cb 100755 --- a/scripts/validate-structure.js +++ b/scripts/validate-structure.js @@ -1,15 +1,14 @@ #!/usr/bin/env node /** - * Validates the structure of a GAP directory. + * Validates the structure of GAP directories. * - * Usage: ./scripts/validate-structure.js + * Usage: ./scripts/validate-structure.js * - * Can be xarg'd over all GAP directories: - * find . -maxdepth 1 -type d -name 'GAP-*' | xargs -I{} node scripts/validate-structure.js {} + * Discovers and validates all GAP-* directories within the specified directory. */ -import { existsSync, readFileSync, statSync } from "node:fs"; +import { existsSync, readFileSync, statSync, globSync } from "node:fs"; import { basename, join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { parseArgs } from "node:util"; @@ -122,34 +121,53 @@ function validateMetadata(dirPath, gapName) { } } +function validateGapDirectory(dirPath) { + if (!existsSync(dirPath) || !statSync(dirPath).isDirectory()) { + console.error(`Must be a directory: ${dirPath}`); + process.exit(1); + } + + // Validate directory naming + const gapName = validateDirectoryNaming(dirPath); + + // Validate README.md exists + validateReadmeExists(dirPath, gapName); + + // Validate metadata.yml + validateMetadata(dirPath, gapName); + + console.log(`✓ ${gapName} validated successfully`); +} + function main() { const { positionals } = parseArgs({ allowPositionals: true, strict: true }); if (positionals.length !== 1) { - console.error("Usage: ./scripts/validate-structure.js "); + console.error("Usage: ./scripts/validate-structure.js "); process.exit(1); } - const dirPath = positionals[0]; + const searchDir = positionals[0]; - if (!existsSync(dirPath)) { - console.error(`Directory does not exist: ${dirPath}`); + if (!existsSync(searchDir) || !statSync(searchDir).isDirectory()) { + console.error(`Must be a directory: ${searchDir}`); process.exit(1); } - if (!statSync(dirPath).isDirectory()) { - console.error(`Not a directory: ${dirPath}`); + // Discover and validate all GAP directories using glob + const pattern = join(searchDir, "GAP-*"); + const gapDirs = globSync(pattern).filter((path) => statSync(path).isDirectory()); + + if (gapDirs.length === 0) { + console.error(`No GAP directories found in ${searchDir}`); process.exit(1); } - // Validate directory naming - const gapName = validateDirectoryNaming(dirPath); + console.log(`Found ${gapDirs.length} GAP directories`); - // Validate README.md exists - validateReadmeExists(dirPath, gapName); - - // Validate metadata.yml - validateMetadata(dirPath, gapName); + for (const dir of gapDirs) { + validateGapDirectory(dir); + } } main(); From 5170b14a9e41f10fadf0b283e812efb4fe36d269 Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Mon, 16 Feb 2026 10:12:56 -0600 Subject: [PATCH 2/2] Implement Benjie's suggestion to make scripts/validate-structure.js self contained using glob --- scripts/validate-structure.js | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/scripts/validate-structure.js b/scripts/validate-structure.js index a38c8cb..69694ba 100755 --- a/scripts/validate-structure.js +++ b/scripts/validate-structure.js @@ -26,7 +26,10 @@ const metadataSchema = JSON.parse(readFileSync(schemaPath, "utf8")); const ajv = new Ajv({ allErrors: true }); const validateMetadataSchema = ajv.compile(metadataSchema); +let errorSeen = 0; + function error(gapName, message) { + errorSeen += 1; console.error(`${gapName}: ${message}`); process.exit(1); } @@ -123,8 +126,7 @@ function validateMetadata(dirPath, gapName) { function validateGapDirectory(dirPath) { if (!existsSync(dirPath) || !statSync(dirPath).isDirectory()) { - console.error(`Must be a directory: ${dirPath}`); - process.exit(1); + throw new Error(`Not a directory: ${dirPath}`); } // Validate directory naming @@ -143,31 +145,24 @@ function main() { const { positionals } = parseArgs({ allowPositionals: true, strict: true }); if (positionals.length !== 1) { - console.error("Usage: ./scripts/validate-structure.js "); - process.exit(1); + throw new Error("Usage: ./scripts/validate-structure.js "); } const searchDir = positionals[0]; if (!existsSync(searchDir) || !statSync(searchDir).isDirectory()) { - console.error(`Must be a directory: ${searchDir}`); - process.exit(1); - } - - // Discover and validate all GAP directories using glob - const pattern = join(searchDir, "GAP-*"); - const gapDirs = globSync(pattern).filter((path) => statSync(path).isDirectory()); - - if (gapDirs.length === 0) { - console.error(`No GAP directories found in ${searchDir}`); - process.exit(1); + throw new Error(`Not a directory: ${searchDir}`); } + const gapDirs = globSync("GAP-*", { cwd: searchDir }); console.log(`Found ${gapDirs.length} GAP directories`); for (const dir of gapDirs) { validateGapDirectory(dir); } + + // return an exit code + return errorSeen > 0 ? 1 : 0; } -main(); +process.exit(main())