Skip to content

Commit 20b4b82

Browse files
committed
refactor: extract validateAgentFile to shared paths.mjs module
Signed-off-by: leocavalcante <leo@cavalcante.dev>
1 parent b20dbef commit 20b4b82

File tree

4 files changed

+428
-57
lines changed

4 files changed

+428
-57
lines changed

postinstall.mjs

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,13 @@ import { join } from "node:path"
1212

1313
import {
1414
AGENTS_TARGET_DIR,
15-
checkVersionCompatibility,
1615
createLogger,
1716
getAgentsSourceDir,
1817
getErrorMessage,
1918
getPackageRoot,
20-
OPENCODE_VERSION,
2119
parseCliFlags,
22-
parseFrontmatter,
2320
retryOnTransientError,
24-
validateAgentContent,
21+
validateAgentFile,
2522
} from "./src/paths.mjs"
2623

2724
const packageRoot = getPackageRoot(import.meta.url)
@@ -54,59 +51,6 @@ Examples:
5451
const logger = createLogger(VERBOSE)
5552
const verbose = logger.verbose
5653

57-
/**
58-
* Validates an agent file by reading and validating its content,
59-
* including version compatibility checking.
60-
*
61-
* Performs the following validations:
62-
* 1. Content structure validation (frontmatter, headers, keywords)
63-
* 2. Version compatibility checking against current OpenCode version
64-
*
65-
* @param {string} filePath - Path to the agent file to validate
66-
* @returns {{ valid: boolean, error?: string }} Validation result with optional error message
67-
* @throws {Error} If the file does not exist (ENOENT)
68-
* @throws {Error} If permission is denied reading the file (EACCES)
69-
* @throws {Error} If the file is a directory (EISDIR)
70-
*
71-
* @example
72-
* // Validate an agent file
73-
* const result = validateAgentFile('/path/to/agent.md')
74-
* if (!result.valid) {
75-
* console.error(`Validation failed: ${result.error}`)
76-
* }
77-
*
78-
* @example
79-
* // Use in a file copy loop
80-
* for (const file of agentFiles) {
81-
* const validation = validateAgentFile(join(sourceDir, file))
82-
* if (validation.valid) {
83-
* copyFileSync(join(sourceDir, file), join(targetDir, file))
84-
* }
85-
* }
86-
*/
87-
function validateAgentFile(filePath) {
88-
const content = readFileSync(filePath, "utf-8")
89-
const contentValidation = validateAgentContent(content)
90-
if (!contentValidation.valid) {
91-
return contentValidation
92-
}
93-
94-
// Check version compatibility from frontmatter
95-
const frontmatter = parseFrontmatter(content)
96-
if (frontmatter.found && frontmatter.fields.requires) {
97-
const requiresVersion = frontmatter.fields.requires
98-
const isCompatible = checkVersionCompatibility(requiresVersion, OPENCODE_VERSION)
99-
if (!isCompatible) {
100-
return {
101-
valid: false,
102-
error: `Incompatible OpenCode version: requires ${requiresVersion}, but current version is ${OPENCODE_VERSION}`,
103-
}
104-
}
105-
}
106-
107-
return { valid: true }
108-
}
109-
11054
/**
11155
* Main entry point for the postinstall script.
11256
*

src/paths.d.mts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,3 +365,51 @@ export interface Logger {
365365
* logger.verbose("Source: /path/to/src") // Does nothing (verbose disabled)
366366
*/
367367
export function createLogger(verbose: boolean): Logger
368+
369+
/**
370+
* Result of validating an agent file.
371+
*/
372+
export interface ValidateAgentFileResult {
373+
/** Whether the file is valid */
374+
valid: boolean
375+
/** Error message if validation failed */
376+
error?: string
377+
}
378+
379+
/**
380+
* Validates an agent file by reading and validating its content,
381+
* including version compatibility checking.
382+
*
383+
* Performs the following validations:
384+
* 1. Content structure validation (frontmatter, headers, keywords)
385+
* 2. Version compatibility checking against current OpenCode version
386+
*
387+
* @param filePath - Path to the agent file to validate
388+
* @param currentVersion - The current OpenCode version to check against (defaults to OPENCODE_VERSION)
389+
* @returns Validation result with valid status and optional error message
390+
* @throws {Error} If the file does not exist (ENOENT)
391+
* @throws {Error} If permission is denied reading the file (EACCES)
392+
* @throws {Error} If the file is a directory (EISDIR)
393+
* @throws {TypeError} If filePath is not a non-empty string
394+
*
395+
* @example
396+
* // Validate an agent file
397+
* const result = validateAgentFile('/path/to/agent.md')
398+
* if (!result.valid) {
399+
* console.error(`Validation failed: ${result.error}`)
400+
* }
401+
*
402+
* @example
403+
* // Use in a file copy loop
404+
* for (const file of agentFiles) {
405+
* const validation = validateAgentFile(join(sourceDir, file))
406+
* if (validation.valid) {
407+
* copyFileSync(join(sourceDir, file), join(targetDir, file))
408+
* }
409+
* }
410+
*
411+
* @example
412+
* // Validate against a specific version
413+
* const result = validateAgentFile('/path/to/agent.md', '1.0.0')
414+
*/
415+
export function validateAgentFile(filePath: string, currentVersion?: string): ValidateAgentFileResult

src/paths.mjs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
* and preuninstall.mjs to locate agent files.
66
*/
77

8+
import { readFileSync } from "node:fs"
89
import { homedir } from "node:os"
910
import { dirname, join } from "node:path"
1011
import { fileURLToPath } from "node:url"
1112

13+
// Import checkVersionCompatibility for internal use
14+
import { checkVersionCompatibility as _checkVersionCompatibility } from "./semver.mjs"
15+
1216
// Re-export semver utilities for backwards compatibility
1317
export {
1418
checkVersionCompatibility,
@@ -507,3 +511,71 @@ export function createLogger(verbose) {
507511
},
508512
}
509513
}
514+
515+
/**
516+
* Validates an agent file by reading and validating its content,
517+
* including version compatibility checking.
518+
*
519+
* Performs the following validations:
520+
* 1. Content structure validation (frontmatter, headers, keywords)
521+
* 2. Version compatibility checking against current OpenCode version
522+
*
523+
* @param {string} filePath - Path to the agent file to validate
524+
* @param {string} [currentVersion] - The current OpenCode version to check against (defaults to OPENCODE_VERSION)
525+
* @returns {{ valid: boolean, error?: string }} Validation result with optional error message
526+
* @throws {Error} If the file does not exist (ENOENT)
527+
* @throws {Error} If permission is denied reading the file (EACCES)
528+
* @throws {Error} If the file is a directory (EISDIR)
529+
* @throws {TypeError} If filePath is not a non-empty string
530+
*
531+
* @example
532+
* // Validate an agent file
533+
* const result = validateAgentFile('/path/to/agent.md')
534+
* if (!result.valid) {
535+
* console.error(`Validation failed: ${result.error}`)
536+
* }
537+
*
538+
* @example
539+
* // Use in a file copy loop
540+
* for (const file of agentFiles) {
541+
* const validation = validateAgentFile(join(sourceDir, file))
542+
* if (validation.valid) {
543+
* copyFileSync(join(sourceDir, file), join(targetDir, file))
544+
* }
545+
* }
546+
*
547+
* @example
548+
* // Validate against a specific version
549+
* const result = validateAgentFile('/path/to/agent.md', '1.0.0')
550+
*/
551+
export function validateAgentFile(filePath, currentVersion = OPENCODE_VERSION) {
552+
if (typeof filePath !== "string") {
553+
throw new TypeError(
554+
`validateAgentFile: filePath must be a string, got ${filePath === null ? "null" : typeof filePath}`,
555+
)
556+
}
557+
if (filePath.trim() === "") {
558+
throw new TypeError("validateAgentFile: filePath must not be empty")
559+
}
560+
561+
const content = readFileSync(filePath, "utf-8")
562+
const contentValidation = validateAgentContent(content)
563+
if (!contentValidation.valid) {
564+
return contentValidation
565+
}
566+
567+
// Check version compatibility from frontmatter
568+
const frontmatter = parseFrontmatter(content)
569+
if (frontmatter.found && frontmatter.fields.requires) {
570+
const requiresVersion = frontmatter.fields.requires
571+
const isCompatible = _checkVersionCompatibility(requiresVersion, currentVersion)
572+
if (!isCompatible) {
573+
return {
574+
valid: false,
575+
error: `Incompatible OpenCode version: requires ${requiresVersion}, but current version is ${currentVersion}`,
576+
}
577+
}
578+
}
579+
580+
return { valid: true }
581+
}

0 commit comments

Comments
 (0)