openpets 1.0.4 → 1.0.6

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.
Files changed (96) hide show
  1. package/dist/data/api.json +3172 -0
  2. package/dist/src/core/ai-client-base/index.d.ts +47 -0
  3. package/dist/src/core/ai-client-base/index.d.ts.map +1 -0
  4. package/dist/src/core/ai-client-base/index.js +168 -0
  5. package/dist/src/core/ai-client-base/index.js.map +1 -0
  6. package/dist/src/core/browser.d.ts +10 -0
  7. package/dist/src/core/browser.d.ts.map +1 -0
  8. package/{browser.ts → dist/src/core/browser.js} +4 -4
  9. package/dist/src/core/browser.js.map +1 -0
  10. package/dist/src/core/build-pet.d.ts +2 -0
  11. package/dist/src/core/build-pet.d.ts.map +1 -0
  12. package/dist/src/core/build-pet.js +364 -0
  13. package/dist/src/core/build-pet.js.map +1 -0
  14. package/dist/src/core/cli.d.ts +3 -0
  15. package/dist/src/core/cli.d.ts.map +1 -0
  16. package/dist/src/core/cli.js +244 -0
  17. package/dist/src/core/cli.js.map +1 -0
  18. package/dist/src/core/config-manager.d.ts +13 -0
  19. package/dist/src/core/config-manager.d.ts.map +1 -0
  20. package/dist/src/core/config-manager.js +59 -0
  21. package/dist/src/core/config-manager.js.map +1 -0
  22. package/dist/src/core/deploy-pet.d.ts +2 -0
  23. package/dist/src/core/deploy-pet.d.ts.map +1 -0
  24. package/dist/src/core/deploy-pet.js +66 -0
  25. package/dist/src/core/deploy-pet.js.map +1 -0
  26. package/dist/src/core/index.d.ts +11 -0
  27. package/dist/src/core/index.d.ts.map +1 -0
  28. package/dist/src/core/index.js +11 -0
  29. package/dist/src/core/index.js.map +1 -0
  30. package/dist/src/core/local-cache.d.ts +69 -0
  31. package/dist/src/core/local-cache.d.ts.map +1 -0
  32. package/dist/src/core/local-cache.js +212 -0
  33. package/dist/src/core/local-cache.js.map +1 -0
  34. package/dist/src/core/logger.d.ts.map +1 -0
  35. package/{logger.js → dist/src/core/logger.js} +8 -9
  36. package/dist/src/core/logger.js.map +1 -0
  37. package/dist/src/core/mcp-factory.d.ts +12 -0
  38. package/dist/src/core/mcp-factory.d.ts.map +1 -0
  39. package/dist/src/core/mcp-factory.js +143 -0
  40. package/dist/src/core/mcp-factory.js.map +1 -0
  41. package/dist/src/core/mcp-server.d.ts +3 -0
  42. package/dist/src/core/mcp-server.d.ts.map +1 -0
  43. package/dist/src/core/mcp-server.js +55 -0
  44. package/dist/src/core/mcp-server.js.map +1 -0
  45. package/dist/src/core/migrate-plugin.d.ts +15 -0
  46. package/dist/src/core/migrate-plugin.d.ts.map +1 -0
  47. package/dist/src/core/migrate-plugin.js +181 -0
  48. package/dist/src/core/migrate-plugin.js.map +1 -0
  49. package/dist/src/core/pets-registry.d.ts +47 -0
  50. package/dist/src/core/pets-registry.d.ts.map +1 -0
  51. package/dist/src/core/pets-registry.js +109 -0
  52. package/dist/src/core/pets-registry.js.map +1 -0
  53. package/dist/src/core/plugin-factory.d.ts +58 -0
  54. package/dist/src/core/plugin-factory.d.ts.map +1 -0
  55. package/dist/src/core/plugin-factory.js +212 -0
  56. package/dist/src/core/plugin-factory.js.map +1 -0
  57. package/dist/src/core/prompt-utils.d.ts +14 -0
  58. package/dist/src/core/prompt-utils.d.ts.map +1 -0
  59. package/dist/src/core/prompt-utils.js +106 -0
  60. package/dist/src/core/prompt-utils.js.map +1 -0
  61. package/dist/src/core/schema-helpers.d.ts +33 -0
  62. package/dist/src/core/schema-helpers.d.ts.map +1 -0
  63. package/dist/src/core/schema-helpers.js +46 -0
  64. package/dist/src/core/schema-helpers.js.map +1 -0
  65. package/dist/src/core/search-pets.d.ts +29 -0
  66. package/dist/src/core/search-pets.d.ts.map +1 -0
  67. package/dist/src/core/search-pets.js +196 -0
  68. package/dist/src/core/search-pets.js.map +1 -0
  69. package/dist/src/core/types.d.ts +63 -0
  70. package/dist/src/core/types.d.ts.map +1 -0
  71. package/dist/src/core/types.js +2 -0
  72. package/dist/src/core/types.js.map +1 -0
  73. package/dist/src/core/validate-pet.d.ts +40 -0
  74. package/dist/src/core/validate-pet.d.ts.map +1 -0
  75. package/dist/src/core/validate-pet.js +650 -0
  76. package/dist/src/core/validate-pet.js.map +1 -0
  77. package/package.json +7 -11
  78. package/ai-client-base/index.ts +0 -117
  79. package/build-pet.ts +0 -429
  80. package/cli.ts +0 -179
  81. package/config-manager.ts +0 -82
  82. package/deploy-pet.ts +0 -91
  83. package/index.ts +0 -10
  84. package/local-cache.ts +0 -280
  85. package/logger.ts +0 -143
  86. package/mcp-factory.ts +0 -180
  87. package/mcp-server.ts +0 -69
  88. package/migrate-plugin.ts +0 -220
  89. package/pets-registry.ts +0 -160
  90. package/plugin-factory.ts +0 -309
  91. package/prompt-utils.ts +0 -130
  92. package/schema-helpers.ts +0 -59
  93. package/search-pets.ts +0 -267
  94. package/types.ts +0 -68
  95. package/validate-pet.ts +0 -594
  96. /package/{logger.d.ts → dist/src/core/logger.d.ts} +0 -0
package/validate-pet.ts DELETED
@@ -1,594 +0,0 @@
1
- import { readFileSync, existsSync } from 'fs'
2
- import { join } from 'path'
3
- import { execSync } from 'child_process'
4
-
5
- export interface ValidationResult {
6
- valid: boolean
7
- errors: string[]
8
- warnings: string[]
9
- codePatternWarnings?: string[]
10
- }
11
-
12
- export interface PackageValidation {
13
- name: string
14
- path: string
15
- result: ValidationResult
16
- usesModernPattern?: boolean
17
- usesPluginFactory?: boolean
18
- }
19
-
20
- export interface ValidationReport {
21
- total: number
22
- passed: number
23
- failed: number
24
- details: PackageValidation[]
25
- }
26
-
27
- export class PluginValidator {
28
- private requiredFields = [
29
- 'name',
30
- 'version',
31
- 'title',
32
- 'subtitle',
33
- 'description',
34
- 'main',
35
- 'types',
36
- 'keywords',
37
- 'dependencies',
38
- 'queries',
39
- 'scenarios'
40
- ]
41
-
42
- private requiredScripts = [
43
- 'test:all'
44
- ]
45
-
46
- validatePlugin(pluginPath: string): PackageValidation {
47
- const packageJsonPath = join(pluginPath, 'package.json')
48
-
49
- if (!existsSync(packageJsonPath)) {
50
- return {
51
- name: this.getFolderName(pluginPath),
52
- path: pluginPath,
53
- result: {
54
- valid: false,
55
- errors: ['package.json not found'],
56
- warnings: []
57
- }
58
- }
59
- }
60
-
61
- try {
62
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
63
- const result = this.validatePackageJson(packageJson, pluginPath)
64
-
65
- const codePatternAnalysis = this.analyzeCodePatterns(pluginPath)
66
-
67
- return {
68
- name: packageJson.name || this.getFolderName(pluginPath),
69
- path: pluginPath,
70
- result: {
71
- ...result,
72
- codePatternWarnings: codePatternAnalysis.warnings
73
- },
74
- usesModernPattern: codePatternAnalysis.usesModernPattern,
75
- usesPluginFactory: codePatternAnalysis.usesPluginFactory
76
- }
77
- } catch (error) {
78
- return {
79
- name: this.getFolderName(pluginPath),
80
- path: pluginPath,
81
- result: {
82
- valid: false,
83
- errors: [`Invalid JSON in package.json: ${error instanceof Error ? error.message : String(error)}`],
84
- warnings: []
85
- }
86
- }
87
- }
88
- }
89
-
90
- private validatePackageJson(pkg: any, pluginPath: string): ValidationResult {
91
- const errors: string[] = []
92
- const warnings: string[] = []
93
-
94
- // Check schema reference
95
- const validSchemas = [
96
- '../../src/shared-utils/opencode-plugin.schema.json',
97
- 'https://pets.studio/config.json'
98
- ]
99
- if (!pkg.$schema || !validSchemas.includes(pkg.$schema)) {
100
- errors.push('Missing or incorrect schema reference. Expected: "../../src/shared-utils/opencode-plugin.schema.json" or "https://pets.studio/config.json"')
101
- }
102
-
103
- // Check required fields
104
- for (const field of this.requiredFields) {
105
- if (!pkg[field]) {
106
- errors.push(`Missing required field: ${field}`)
107
- }
108
- }
109
-
110
- // Validate name format
111
- if (pkg.name && !this.isValidPluginName(pkg.name)) {
112
- errors.push(`Invalid plugin name format. Should be 'openpets/plugin-name'`)
113
- }
114
-
115
- // Validate keywords
116
- if (pkg.keywords) {
117
- if (!Array.isArray(pkg.keywords)) {
118
- errors.push('keywords must be an array')
119
- } else {
120
- const requiredKeywords = ['opencode', 'plugin']
121
- for (const keyword of requiredKeywords) {
122
- if (!pkg.keywords.includes(keyword)) {
123
- warnings.push(`Missing recommended keyword: ${keyword}`)
124
- }
125
- }
126
- }
127
- }
128
-
129
- // Validate repository
130
- if (pkg.repository) {
131
- if (!pkg.repository.type || !pkg.repository.url) {
132
- errors.push('repository must have type and url fields')
133
- }
134
- }
135
-
136
- // Validate dependencies - @opencode-ai/plugin should only be in shared-utils
137
- if (pkg.dependencies && pkg.dependencies['@opencode-ai/plugin']) {
138
- errors.push('@opencode-ai/plugin should only be in shared-utils, not in individual pets')
139
- }
140
-
141
- if (pkg.peerDependencies && pkg.peerDependencies['@opencode-ai/plugin']) {
142
- errors.push('@opencode-ai/plugin should only be in shared-utils, not in individual pets')
143
- }
144
-
145
- // Check for required @opencode/shared-utils dependency
146
- if (!pkg.dependencies || !pkg.dependencies['@opencode/shared-utils']) {
147
- errors.push('Missing required dependency "@opencode/shared-utils"')
148
- }
149
-
150
- // Validate scripts
151
- if (pkg.scripts) {
152
- for (const script of this.requiredScripts) {
153
- if (!pkg.scripts[script]) {
154
- warnings.push(`Missing recommended script: ${script}`)
155
- }
156
- }
157
- } else {
158
- warnings.push('No scripts defined - consider adding test scripts')
159
- }
160
-
161
- // Validate envVariables structure if present
162
- if (pkg.envVariables) {
163
- const envValidation = this.validateEnvVariables(pkg.envVariables)
164
- errors.push(...envValidation.errors)
165
- warnings.push(...envValidation.warnings)
166
- }
167
-
168
- // Validate queries (required)
169
- if (!pkg.queries) {
170
- errors.push('Missing required field: queries')
171
- } else {
172
- if (!Array.isArray(pkg.queries)) {
173
- errors.push('queries must be an array')
174
- } else if (pkg.queries.length === 0) {
175
- errors.push('queries array cannot be empty - add at least one example query')
176
- } else {
177
- // Validate each query is a non-empty string
178
- pkg.queries.forEach((query: any, index: number) => {
179
- if (typeof query !== 'string' || query.trim().length === 0) {
180
- errors.push(`queries[${index}] must be a non-empty string`)
181
- }
182
- })
183
- }
184
- }
185
-
186
- // Validate scenarios (required)
187
- if (!pkg.scenarios) {
188
- errors.push('Missing required field: scenarios')
189
- } else {
190
- const scenarioValidation = this.validateScenarios(pkg.scenarios)
191
- if (Object.keys(pkg.scenarios).length === 0) {
192
- errors.push('scenarios object cannot be empty - add at least one test scenario')
193
- }
194
- errors.push(...scenarioValidation.errors)
195
- warnings.push(...scenarioValidation.warnings)
196
- }
197
-
198
- // Check for essential files
199
- const essentialFiles = ['index.ts', 'opencode.json']
200
- for (const file of essentialFiles) {
201
- if (!existsSync(join(pluginPath, file))) {
202
- errors.push(`Missing essential file: ${file}`)
203
- }
204
- }
205
-
206
- // Check for .env.example
207
- if (pkg.envVariables && !existsSync(join(pluginPath, '.env.example'))) {
208
- errors.push('envVariables defined but .env.example file missing - create this file to document required environment variables')
209
- }
210
-
211
- // Check for README
212
- if (!existsSync(join(pluginPath, 'README.md'))) {
213
- warnings.push('README.md file missing')
214
- }
215
-
216
- // Validate optional but recommended metadata fields
217
- this.validateOptionalMetadata(pkg, pluginPath, warnings, errors)
218
-
219
- return {
220
- valid: errors.length === 0,
221
- errors,
222
- warnings
223
- }
224
- }
225
-
226
- private validateEnvVariables(envVars: any): ValidationResult {
227
- const errors: string[] = []
228
- const warnings: string[] = []
229
-
230
- if (!envVars.required && !envVars.optional) {
231
- warnings.push('envVariables defined but no required or optional sections found')
232
- return { valid: true, errors, warnings }
233
- }
234
-
235
- const validateVarArray = (vars: any[], type: string) => {
236
- if (!Array.isArray(vars)) {
237
- errors.push(`envVariables.${type} must be an array`)
238
- return
239
- }
240
-
241
- vars.forEach((envVar, index) => {
242
- if (!envVar.name) {
243
- errors.push(`envVariables.${type}[${index}] missing name field`)
244
- }
245
- if (!envVar.description) {
246
- warnings.push(`envVariables.${type}[${index}] missing description field`)
247
- }
248
- if (!envVar.provider) {
249
- warnings.push(`envVariables.${type}[${index}] missing provider field`)
250
- }
251
- if (typeof envVar.priority !== 'number') {
252
- warnings.push(`envVariables.${type}[${index}] missing or invalid priority field`)
253
- }
254
- })
255
- }
256
-
257
- if (envVars.required) {
258
- validateVarArray(envVars.required, 'required')
259
- }
260
- if (envVars.optional) {
261
- validateVarArray(envVars.optional, 'optional')
262
- }
263
-
264
- return { valid: errors.length === 0, errors, warnings }
265
- }
266
-
267
- private validateScenarios(scenarios: any): ValidationResult {
268
- const errors: string[] = []
269
- const warnings: string[] = []
270
-
271
- if (typeof scenarios !== 'object') {
272
- errors.push('scenarios must be an object')
273
- return { valid: false, errors, warnings }
274
- }
275
-
276
- Object.entries(scenarios).forEach(([key, value]) => {
277
- if (Array.isArray(value)) {
278
- // Array of commands - validate each is a non-empty string
279
- value.forEach((cmd, index) => {
280
- if (typeof cmd !== 'string' || cmd.trim().length === 0) {
281
- errors.push(`scenarios.${key}[${index}] must be a non-empty string`)
282
- }
283
- })
284
- } else if (typeof value === 'string') {
285
- // Single command string
286
- if (value.trim().length === 0) {
287
- errors.push(`scenarios.${key} must be a non-empty string`)
288
- }
289
- } else {
290
- errors.push(`scenarios.${key} must be a string or array of strings`)
291
- }
292
- })
293
-
294
- if (Object.keys(scenarios).length === 0) {
295
- warnings.push('scenarios object is empty - consider adding test scenarios')
296
- }
297
-
298
- return { valid: errors.length === 0, errors, warnings }
299
- }
300
-
301
- private validateOptionalMetadata(pkg: any, pluginPath: string, warnings: string[], errors: string[]): void {
302
- const hasIcon = pkg.icon && typeof pkg.icon === 'string'
303
- const hasHomeWebsite = pkg.homepage && typeof pkg.homepage === 'string'
304
-
305
- if (!hasIcon && !hasHomeWebsite) {
306
- warnings.push('Missing both "icon" and "homepage" fields - provide at least one for visual representation (icon: "assets/icon.png" or homepage for favicon fallback)')
307
- } else if (hasIcon && pkg.icon.includes('assets/')) {
308
- const iconPath = join(pluginPath, pkg.icon)
309
- if (!existsSync(iconPath)) {
310
- warnings.push(`icon file not found at path: ${pkg.icon}`)
311
- }
312
- }
313
-
314
- if (!pkg.categories) {
315
- warnings.push('Missing optional field "categories" - should be an array of category strings (e.g., ["productivity", "developer-tools"])')
316
- } else if (!Array.isArray(pkg.categories)) {
317
- errors.push('categories must be an array')
318
- } else if (pkg.categories.length === 0) {
319
- warnings.push('categories array is empty - add at least one category')
320
- }
321
-
322
- const hasProviders = pkg.providers && Array.isArray(pkg.providers) && pkg.providers.length > 0
323
-
324
- if (!pkg.homepage) {
325
- if (!hasProviders) {
326
- warnings.push('Missing optional field "homepage" - should be a URL to the original website/service this plugin integrates with')
327
- }
328
- } else if (typeof pkg.homepage === 'string') {
329
- try {
330
- new URL(pkg.homepage)
331
- } catch {
332
- errors.push('homepage must be a valid URL')
333
- }
334
- }
335
-
336
- if (!pkg.faq) {
337
- warnings.push('Missing optional field "faq" - should be an array of FAQ items with "question" and "answer" attributes')
338
- } else if (!Array.isArray(pkg.faq)) {
339
- errors.push('faq must be an array')
340
- } else {
341
- pkg.faq.forEach((item: any, index: number) => {
342
- if (typeof item !== 'object' || item === null) {
343
- errors.push(`faq[${index}] must be an object with "question" and "answer" fields`)
344
- } else {
345
- if (!item.question || typeof item.question !== 'string') {
346
- errors.push(`faq[${index}] missing or invalid "question" field`)
347
- }
348
- if (!item.answer || typeof item.answer !== 'string') {
349
- errors.push(`faq[${index}] missing or invalid "answer" field`)
350
- }
351
- }
352
- })
353
- }
354
-
355
- if (pkg.commands) {
356
- if (!Array.isArray(pkg.commands)) {
357
- errors.push('commands must be an array')
358
- } else {
359
- pkg.commands.forEach((command: any, index: number) => {
360
- if (typeof command !== 'object' || command === null) {
361
- errors.push(`commands[${index}] must be an object`)
362
- return
363
- }
364
-
365
- if (!command.name || typeof command.name !== 'string') {
366
- errors.push(`commands[${index}] missing or invalid "name" field`)
367
- }
368
- if (!command.title || typeof command.title !== 'string') {
369
- errors.push(`commands[${index}] missing or invalid "title" field`)
370
- }
371
- if (!command.description || typeof command.description !== 'string') {
372
- errors.push(`commands[${index}] missing or invalid "description" field`)
373
- }
374
-
375
- if (command.arguments !== undefined) {
376
- if (!Array.isArray(command.arguments)) {
377
- errors.push(`commands[${index}].arguments must be an array`)
378
- } else {
379
- command.arguments.forEach((arg: any, argIndex: number) => {
380
- if (typeof arg !== 'object' || arg === null) {
381
- errors.push(`commands[${index}].arguments[${argIndex}] must be an object`)
382
- return
383
- }
384
- if (!arg.name || typeof arg.name !== 'string') {
385
- errors.push(`commands[${index}].arguments[${argIndex}] missing or invalid "name" field`)
386
- }
387
- if (!arg.placeholder || typeof arg.placeholder !== 'string') {
388
- errors.push(`commands[${index}].arguments[${argIndex}] missing or invalid "placeholder" field`)
389
- }
390
- if (!arg.type || typeof arg.type !== 'string') {
391
- errors.push(`commands[${index}].arguments[${argIndex}] missing or invalid "type" field`)
392
- }
393
- })
394
- }
395
- }
396
- })
397
- }
398
- }
399
-
400
- if (!pkg.envVariables) {
401
- warnings.push('Missing optional field "envVariables" - consider documenting required environment variables with "required" and "optional" arrays')
402
- }
403
- }
404
-
405
- private isValidPluginName(name: string): boolean {
406
- return /^openpets\/[a-z0-9-]+$/.test(name)
407
- }
408
-
409
- private getFolderName(path: string): string {
410
- return path.split('/').pop() || 'unknown'
411
- }
412
-
413
- validatePluginByName(pluginName: string, petsDirectory: string): PackageValidation {
414
- const pluginPath = join(petsDirectory, pluginName)
415
- return this.validatePlugin(pluginPath)
416
- }
417
-
418
- validateAllPlugins(petsDirectory: string): ValidationReport {
419
- const plugins = this.getPluginDirectories(petsDirectory)
420
- const details: PackageValidation[] = []
421
-
422
- for (const pluginPath of plugins) {
423
- const validation = this.validatePlugin(pluginPath)
424
- details.push(validation)
425
- }
426
-
427
- const passed = details.filter(d => d.result.valid).length
428
- const failed = details.filter(d => !d.result.valid).length
429
-
430
- return {
431
- total: details.length,
432
- passed,
433
- failed,
434
- details
435
- }
436
- }
437
-
438
- private getPluginDirectories(petsDirectory: string): string[] {
439
- try {
440
- const result = execSync(`find "${petsDirectory}" -maxdepth 1 -type d -not -name ".*" | sort`, {
441
- encoding: 'utf-8'
442
- })
443
- .split('\n')
444
- .filter(dir => dir.trim() && dir !== petsDirectory)
445
-
446
- return result
447
- } catch (error) {
448
- console.error(`Error reading pets directory: ${error}`)
449
- return []
450
- }
451
- }
452
-
453
- private analyzeCodePatterns(pluginPath: string): {
454
- usesModernPattern: boolean
455
- usesPluginFactory: boolean
456
- warnings: string[]
457
- } {
458
- const indexPath = join(pluginPath, 'index.ts')
459
- const warnings: string[] = []
460
-
461
- if (!existsSync(indexPath)) {
462
- return { usesModernPattern: false, usesPluginFactory: false, warnings }
463
- }
464
-
465
- try {
466
- const code = readFileSync(indexPath, 'utf-8')
467
-
468
- const hasToolImport = /import\s+(?:type\s+)?{\s*[^}]*tool[^}]*}\s+from\s+['"]@opencode-ai\/plugin['"]/.test(code)
469
- const hasZodImport = /import.*zod/.test(code)
470
- const usesToolBuilder = /tool\s*\(\s*{/.test(code)
471
- const usesInputSchema = /inputSchema\s*:\s*{/.test(code)
472
- const usesPluginFactory = /createPlugin|createSingleTool/.test(code) ||
473
- /from\s+['"].*plugin-factory['"]/.test(code)
474
-
475
- const usesModernPattern = hasZodImport && usesPluginFactory && !usesInputSchema
476
-
477
- if (usesInputSchema && !usesToolBuilder) {
478
- warnings.push('⚠️ LEGACY PATTERN: Uses inputSchema instead of Zod schema')
479
- }
480
-
481
- if (!hasZodImport && usesInputSchema) {
482
- warnings.push('⚠️ LEGACY PATTERN: Missing Zod import for type-safe schemas')
483
- }
484
-
485
- if (!usesPluginFactory) {
486
- warnings.push('❌ Must use plugin-factory (createPlugin/createSingleTool) - direct tool() usage is not supported')
487
- }
488
-
489
- // Check for problematic nested schema patterns
490
- const nestedSchemaPatterns = this.detectNestedSchemas(code)
491
- if (nestedSchemaPatterns.length > 0) {
492
- warnings.push('❌ SCHEMA ERROR: Detected unsupported nested schema patterns:')
493
- nestedSchemaPatterns.forEach(pattern => {
494
- warnings.push(` ${pattern}`)
495
- })
496
- warnings.push(' Use JSON strings instead. See src/core/plugin-factory.ts for details.')
497
- }
498
-
499
- return {
500
- usesModernPattern,
501
- usesPluginFactory,
502
- warnings
503
- }
504
- } catch (error) {
505
- warnings.push(`Error analyzing code patterns: ${error instanceof Error ? error.message : String(error)}`)
506
- return { usesModernPattern: false, usesPluginFactory: false, warnings }
507
- }
508
- }
509
-
510
- private detectNestedSchemas(code: string): string[] {
511
- const problems: string[] = []
512
-
513
- // Check for z.array(z.object({...}))
514
- if (/z\.array\s*\(\s*z\.object\s*\(/.test(code)) {
515
- problems.push('z.array(z.object({...})) - nested object in array')
516
- }
517
-
518
- // Check for z.object({ field: z.object({...}) })
519
- if (/z\.object\s*\(\s*\{[^}]*:\s*z\.object\s*\(/.test(code)) {
520
- problems.push('z.object({ nested: z.object({...}) }) - nested object in object')
521
- }
522
-
523
- // Check for z.record(...)
524
- if (/z\.record\s*\(/.test(code)) {
525
- problems.push('z.record(...) - record types are not supported')
526
- }
527
-
528
- // Check for z.union with objects
529
- if (/z\.union\s*\(\s*\[[^\]]*z\.object/.test(code)) {
530
- problems.push('z.union([z.object({...}), ...]) - union with complex types')
531
- }
532
-
533
- // Check for z.discriminatedUnion
534
- if (/z\.discriminatedUnion\s*\(/.test(code)) {
535
- problems.push('z.discriminatedUnion(...) - discriminated unions not supported')
536
- }
537
-
538
- return problems
539
- }
540
-
541
- printReport(report: ValidationReport): void {
542
- console.log('\n🔍 Pets Plugin Validation Report')
543
- console.log('==================================')
544
- console.log(`Total plugins: ${report.total}`)
545
- console.log(`✅ Passed: ${report.passed}`)
546
- console.log(`❌ Failed: ${report.failed}`)
547
- console.log('')
548
-
549
- const modernCount = report.details.filter(d => d.usesModernPattern).length
550
- const legacyCount = report.details.filter(d => !d.usesModernPattern).length
551
-
552
- console.log('📊 Code Pattern Analysis:')
553
- console.log(` ✅ Modern pattern (plugin-factory + Zod): ${modernCount}`)
554
- console.log(` ⚠️ Legacy pattern (inputSchema): ${legacyCount}`)
555
- console.log('')
556
-
557
- for (const detail of report.details) {
558
- const status = detail.result.valid ? '✅' : '❌'
559
- const patternBadge = detail.usesModernPattern ? '🆕' : '⏳'
560
- console.log(`${status} ${patternBadge} ${detail.name}`)
561
-
562
- if (detail.result.errors.length > 0) {
563
- detail.result.errors.forEach(error => {
564
- console.log(` ❌ ${error}`)
565
- })
566
- }
567
-
568
- if (detail.result.warnings.length > 0) {
569
- detail.result.warnings.forEach(warning => {
570
- console.log(` ⚠️ ${warning}`)
571
- })
572
- }
573
-
574
- if (detail.result.codePatternWarnings && detail.result.codePatternWarnings.length > 0) {
575
- detail.result.codePatternWarnings.forEach(warning => {
576
- console.log(` ${warning}`)
577
- })
578
- }
579
-
580
- console.log('')
581
- }
582
-
583
- console.log('==================================')
584
- if (report.failed === 0) {
585
- console.log('🎉 All plugins passed validation!')
586
- } else {
587
- console.log(`💥 ${report.failed} plugin(s) failed validation. Please fix the errors above.`)
588
- }
589
-
590
- if (legacyCount > 0) {
591
- console.log(`\n💡 Tip: ${legacyCount} plugin(s) use legacy patterns. Consider migrating to plugin-factory + Zod.`)
592
- }
593
- }
594
- }
File without changes