( filePath: string, )
| 127 | * Validate a plugin manifest file (plugin.json) |
| 128 | */ |
| 129 | export async function validatePluginManifest( |
| 130 | filePath: string, |
| 131 | ): Promise<ValidationResult> { |
| 132 | const errors: ValidationError[] = [] |
| 133 | const warnings: ValidationWarning[] = [] |
| 134 | const absolutePath = path.resolve(filePath) |
| 135 | |
| 136 | // Read file content — handle ENOENT / EISDIR / permission errors directly |
| 137 | let content: string |
| 138 | try { |
| 139 | content = await readFile(absolutePath, { encoding: 'utf-8' }) |
| 140 | } catch (error: unknown) { |
| 141 | const code = getErrnoCode(error) |
| 142 | let message: string |
| 143 | if (code === 'ENOENT') { |
| 144 | message = `File not found: ${absolutePath}` |
| 145 | } else if (code === 'EISDIR') { |
| 146 | message = `Path is not a file: ${absolutePath}` |
| 147 | } else { |
| 148 | message = `Failed to read file: ${errorMessage(error)}` |
| 149 | } |
| 150 | return { |
| 151 | success: false, |
| 152 | errors: [{ path: 'file', message, code }], |
| 153 | warnings: [], |
| 154 | filePath: absolutePath, |
| 155 | fileType: 'plugin', |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | let parsed: unknown |
| 160 | try { |
| 161 | parsed = jsonParse(content) |
| 162 | } catch (error) { |
| 163 | return { |
| 164 | success: false, |
| 165 | errors: [ |
| 166 | { |
| 167 | path: 'json', |
| 168 | message: `Invalid JSON syntax: ${errorMessage(error)}`, |
| 169 | }, |
| 170 | ], |
| 171 | warnings: [], |
| 172 | filePath: absolutePath, |
| 173 | fileType: 'plugin', |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | // Check for path traversal in the parsed JSON before schema validation |
| 178 | // This ensures we catch security issues even if schema validation fails |
| 179 | if (parsed && typeof parsed === 'object') { |
| 180 | const obj = parsed as Record<string, unknown> |
| 181 | |
| 182 | // Check commands |
| 183 | if (obj.commands) { |
| 184 | const commands = Array.isArray(obj.commands) |
| 185 | ? obj.commands |
| 186 | : [obj.commands] |
no test coverage detected