@eldrex/plugin-sdk
Advanced tools
+315
-0
@@ -6,2 +6,6 @@ "use strict"; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
@@ -19,3 +23,314 @@ if (from && typeof from === "object" || typeof from === "function") { | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| DevDiffDevTools: () => DevDiffDevTools | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| // src/devtools.ts | ||
| var DevDiffDevTools = class _DevDiffDevTools { | ||
| /** | ||
| * Generates a realistic mock `ParsedDiff` for unit tests and local simulation. | ||
| */ | ||
| static mockDiff(options) { | ||
| const count = options?.filesCount || 2; | ||
| const additionsCount = options?.additionsPerFile || 5; | ||
| const deletionsCount = options?.deletionsPerFile || 2; | ||
| const customPaths = options?.filePaths || []; | ||
| const files = []; | ||
| const changes = []; | ||
| for (let i = 0; i < count; i++) { | ||
| const filePath = customPaths[i] || `src/module_${i + 1}.ts`; | ||
| const isNew = i === 0 && count > 1; | ||
| const isRename = Boolean(options?.includeRenames && i === 1); | ||
| const hunks = [ | ||
| { | ||
| header: `@@ -1,${deletionsCount} +1,${additionsCount} @@`, | ||
| oldStart: 1, | ||
| oldLines: deletionsCount, | ||
| newStart: 1, | ||
| newLines: additionsCount, | ||
| lines: [ | ||
| ...Array.from({ length: deletionsCount }, (_, idx) => ({ | ||
| type: "deletion", | ||
| content: `- const oldVar${idx} = ${idx};`, | ||
| ln1: idx + 1 | ||
| })), | ||
| ...Array.from({ length: additionsCount }, (_, idx) => ({ | ||
| type: "addition", | ||
| content: `+ const newVar${idx} = ${idx * 2}; // enhanced`, | ||
| ln2: idx + 1 | ||
| })) | ||
| ] | ||
| } | ||
| ]; | ||
| for (let d = 0; d < deletionsCount; d++) { | ||
| changes.push({ | ||
| type: "deletion", | ||
| line: d + 1, | ||
| content: `const oldVar${d} = ${d};` | ||
| }); | ||
| } | ||
| for (let a = 0; a < additionsCount; a++) { | ||
| changes.push({ | ||
| type: "addition", | ||
| line: a + 1, | ||
| content: `const newVar${a} = ${a * 2}; // enhanced` | ||
| }); | ||
| } | ||
| files.push({ | ||
| path: filePath, | ||
| oldPath: isRename ? `src/old_module_${i + 1}.ts` : isNew ? null : filePath, | ||
| newPath: filePath, | ||
| isNew, | ||
| isDeleted: false, | ||
| isRename, | ||
| additions: additionsCount, | ||
| deletions: deletionsCount, | ||
| hunks | ||
| }); | ||
| } | ||
| return { | ||
| files, | ||
| changes, | ||
| totalAdditions: count * additionsCount, | ||
| totalDeletions: count * deletionsCount, | ||
| isEmpty: files.length === 0, | ||
| hasConflicts: false | ||
| }; | ||
| } | ||
| /** | ||
| * Generates a mock `ProjectContext` for testing context-aware hooks. | ||
| */ | ||
| static mockContext(options) { | ||
| const files = options?.files || [ | ||
| "src/index.ts", | ||
| "src/engine.ts", | ||
| "package.json", | ||
| "README.md" | ||
| ]; | ||
| const languages = options?.languages || ["TypeScript", "JSON", "Markdown"]; | ||
| const dependencies = options?.dependencies || { | ||
| "@eldrex/core": "1.7.0", | ||
| typescript: "^5.5.0" | ||
| }; | ||
| return { | ||
| files, | ||
| languages, | ||
| dependencies, | ||
| structure: { | ||
| src: ["index.ts", "engine.ts"], | ||
| root: ["package.json", "README.md"] | ||
| }, | ||
| raw: `# ${options?.projectName || "mock-project"} | ||
| - Primary: TypeScript` | ||
| }; | ||
| } | ||
| /** | ||
| * Generates a mock `ChangelogResult` for testing post-analysis hooks. | ||
| */ | ||
| static mockChangelog(summary) { | ||
| return { | ||
| summary: summary || "## Added\n- Added modular DevTools suite for DevDiff Foundations.", | ||
| impact: "minor", | ||
| breaking: false, | ||
| files: [ | ||
| { | ||
| path: "src/devtools.ts", | ||
| explanation: "Introduced mock generators and testing harness." | ||
| } | ||
| ], | ||
| relatedIssues: ["#42"], | ||
| formattedOutput: summary || "# Changelog\n\n## Added\n- Added modular DevTools suite." | ||
| }; | ||
| } | ||
| /** | ||
| * Validates a plugin against DevDiff specification and version constraints. | ||
| */ | ||
| static validatePlugin(plugin) { | ||
| const errors = []; | ||
| const warnings = []; | ||
| if (!plugin || typeof plugin !== "object") { | ||
| return { valid: false, errors: ["Plugin must be an object."], warnings: [] }; | ||
| } | ||
| if (!plugin.id || typeof plugin.id !== "string") { | ||
| errors.push("Plugin 'id' is required and must be a non-empty string."); | ||
| } else if (!/^[a-z0-9-_@/]+$/.test(plugin.id)) { | ||
| warnings.push("Plugin 'id' should be alphanumeric with hyphens or underscores (e.g. '@org/my-plugin')."); | ||
| } | ||
| if (!plugin.name || typeof plugin.name !== "string") { | ||
| errors.push("Plugin 'name' is required."); | ||
| } | ||
| if (!plugin.version || typeof plugin.version !== "string") { | ||
| errors.push("Plugin 'version' is required (SemVer format)."); | ||
| } else if (!/^\d+\.\d+\.\d+/.test(plugin.version)) { | ||
| warnings.push("Plugin 'version' should follow standard SemVer (e.g. '1.0.0')."); | ||
| } | ||
| if (plugin.activate && typeof plugin.activate !== "function") { | ||
| errors.push("Plugin 'activate' must be a function if provided."); | ||
| } | ||
| if (plugin.deactivate && typeof plugin.deactivate !== "function") { | ||
| errors.push("Plugin 'deactivate' must be a function if provided."); | ||
| } | ||
| if (plugin.hooks) { | ||
| if (typeof plugin.hooks !== "object") { | ||
| errors.push("Plugin 'hooks' must be an object."); | ||
| } else { | ||
| const allowedHooks = [ | ||
| "beforeAnalysis", | ||
| "afterAnalysis", | ||
| "onError", | ||
| "onChangelogGenerated", | ||
| "onFileParsed", | ||
| "onSessionStart", | ||
| "onSessionEnd" | ||
| ]; | ||
| for (const hookName of Object.keys(plugin.hooks)) { | ||
| if (!allowedHooks.includes(hookName)) { | ||
| warnings.push(`Unknown hook '${hookName}' may not be called by the engine.`); | ||
| } else if (typeof plugin.hooks[hookName] !== "function") { | ||
| errors.push(`Hook '${hookName}' must be a function.`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return { | ||
| valid: errors.length === 0, | ||
| errors, | ||
| warnings | ||
| }; | ||
| } | ||
| /** | ||
| * Creates an in-memory test harness to test plugin execution end-to-end. | ||
| */ | ||
| static createTestHarness(plugin) { | ||
| const logs = []; | ||
| const errorsCaught = []; | ||
| const store = {}; | ||
| const mockContext = { | ||
| devdiffVersion: "1.7.0", | ||
| workspacePath: process.cwd(), | ||
| config: { | ||
| get: (key) => store[key], | ||
| set: async (key, value) => { | ||
| store[key] = value; | ||
| } | ||
| }, | ||
| storage: { | ||
| get: async (key) => store[key], | ||
| set: async (key, value) => { | ||
| store[key] = value; | ||
| }, | ||
| delete: async (key) => { | ||
| delete store[key]; | ||
| }, | ||
| clear: async () => { | ||
| for (const k of Object.keys(store)) delete store[k]; | ||
| } | ||
| }, | ||
| notifications: { | ||
| send: async (msg) => { | ||
| logs.push(`[NOTIFY] ${msg}`); | ||
| }, | ||
| sendToChannel: async (chan, msg) => { | ||
| logs.push(`[NOTIFY:${chan}] ${msg}`); | ||
| } | ||
| }, | ||
| logger: { | ||
| info: (msg) => logs.push(`[INFO] ${msg}`), | ||
| warn: (msg) => logs.push(`[WARN] ${msg}`), | ||
| error: (msg) => logs.push(`[ERROR] ${msg}`), | ||
| debug: (msg) => logs.push(`[DEBUG] ${msg}`) | ||
| }, | ||
| engine: { | ||
| getStatus: async () => ({ | ||
| sessionActive: true, | ||
| providerInfo: "mock-ollama (llama3.2:3b)", | ||
| stagedCount: 1, | ||
| unstagedCount: 0, | ||
| workspacePath: process.cwd() | ||
| }), | ||
| getProjectContext: async () => _DevDiffDevTools.mockContext(), | ||
| getRecentChanges: async () => [{ path: "src/index.ts", status: "modified" }] | ||
| } | ||
| }; | ||
| return { | ||
| getLogs: () => [...logs], | ||
| getErrors: () => [...errorsCaught], | ||
| async activate() { | ||
| if (plugin.activate) { | ||
| await plugin.activate(mockContext); | ||
| } | ||
| }, | ||
| async deactivate() { | ||
| if (plugin.deactivate) { | ||
| await plugin.deactivate(); | ||
| } | ||
| }, | ||
| async runBeforeAnalysis(diff, context) { | ||
| const inputDiff = diff || _DevDiffDevTools.mockDiff(); | ||
| const inputContext = context || _DevDiffDevTools.mockContext(); | ||
| if (plugin.hooks?.beforeAnalysis) { | ||
| try { | ||
| const res = await plugin.hooks.beforeAnalysis(inputDiff, inputContext); | ||
| return res || inputDiff; | ||
| } catch (err) { | ||
| errorsCaught.push(err); | ||
| if (plugin.hooks?.onError) { | ||
| await plugin.hooks.onError({ name: err.name || "Error", message: err.message, stack: err.stack }); | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
| return inputDiff; | ||
| }, | ||
| async runAfterAnalysis(changelog) { | ||
| const inputChangelog = changelog || _DevDiffDevTools.mockChangelog(); | ||
| if (plugin.hooks?.afterAnalysis) { | ||
| try { | ||
| const res = await plugin.hooks.afterAnalysis(inputChangelog); | ||
| return res || inputChangelog; | ||
| } catch (err) { | ||
| errorsCaught.push(err); | ||
| if (plugin.hooks?.onError) { | ||
| await plugin.hooks.onError({ name: err.name || "Error", message: err.message, stack: err.stack }); | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
| return inputChangelog; | ||
| } | ||
| }; | ||
| } | ||
| /** | ||
| * Benchmarks the execution speed and overhead of a plugin's hooks. | ||
| */ | ||
| static async benchmarkPlugin(plugin, options) { | ||
| const iterations = options?.iterations || 50; | ||
| const diff = options?.sampleDiff || this.mockDiff({ filesCount: 5, additionsPerFile: 10 }); | ||
| const context = this.mockContext(); | ||
| const changelog = this.mockChangelog(); | ||
| const harness = this.createTestHarness(plugin); | ||
| await harness.activate(); | ||
| const memBefore = process.memoryUsage().heapUsed; | ||
| const start = performance.now(); | ||
| for (let i = 0; i < iterations; i++) { | ||
| await harness.runBeforeAnalysis(diff, context); | ||
| await harness.runAfterAnalysis(changelog); | ||
| } | ||
| const duration = performance.now() - start; | ||
| const memAfter = process.memoryUsage().heapUsed; | ||
| await harness.deactivate(); | ||
| return { | ||
| pluginId: plugin.id, | ||
| iterations, | ||
| totalDurationMs: parseFloat(duration.toFixed(2)), | ||
| averageDurationMs: parseFloat((duration / iterations).toFixed(3)), | ||
| memoryDeltaBytes: Math.max(0, memAfter - memBefore) | ||
| }; | ||
| } | ||
| }; | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| DevDiffDevTools | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @eldrex/plugin-sdk\n *\n * Build DevDiff plugins without touching core.\n * Stable API, semantic versioning, TypeScript-first.\n */\n\nexport interface DiffLine {\n type: \"addition\" | \"deletion\" | \"normal\";\n content: string;\n ln1?: number;\n ln2?: number;\n}\n\nexport interface DiffHunk {\n header: string;\n oldStart: number;\n oldLines: number;\n newStart: number;\n newLines: number;\n lines: DiffLine[];\n}\n\nexport interface ParsedFileDiff {\n oldPath: string | null;\n newPath: string | null;\n isNew: boolean;\n isDeleted: boolean;\n isRename: boolean;\n hunks: DiffHunk[];\n path?: string;\n additions?: number;\n deletions?: number;\n isBinary?: boolean;\n content?: string;\n renamed?: boolean;\n}\n\nexport interface ParsedDiff {\n files: ParsedFileDiff[];\n changes: {\n type: \"addition\" | \"deletion\";\n line: number;\n content: string;\n }[];\n totalAdditions?: number;\n totalDeletions?: number;\n isEmpty?: boolean;\n hasConflicts?: boolean;\n}\n\nexport interface ProjectContext {\n files: string[];\n languages: string[];\n dependencies: Record<string, string>;\n structure: any;\n raw?: string;\n}\n\nexport interface ChangelogResult {\n summary: string;\n impact: \"none\" | \"minor\" | \"major\" | \"breaking\";\n breaking: boolean;\n files: {\n path: string;\n explanation: string;\n }[];\n relatedIssues: string[];\n formattedOutput: string;\n}\n\nexport interface DevDiffError {\n name: string;\n message: string;\n stack?: string;\n}\n\nexport interface ChangedFile {\n path: string;\n status: \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n}\n\nexport interface GitCommit {\n sha: string;\n message: string;\n author: string;\n date: string;\n}\n\nexport interface AIResult {\n summary: string;\n provider: string;\n model: string;\n tokensUsed?: number;\n cost?: number;\n}\n\nexport interface DevDiffStatus {\n sessionActive: boolean;\n providerInfo: string;\n stagedCount: number;\n unstagedCount: number;\n workspacePath: string;\n}\n\nexport interface DevDiffPlugin {\n /** Unique plugin ID */\n id: string;\n\n /** Human-readable name */\n name: string;\n\n /** Semantic version */\n version: string;\n\n /** Brief description */\n description: string;\n\n /** Author info */\n author: {\n name: string;\n email?: string;\n url?: string;\n };\n\n /** Minimum DevDiff version required */\n devdiffVersion: string;\n\n /** Plugin initialization */\n activate?: (context: PluginContext) => Promise<void>;\n\n /** Plugin cleanup */\n deactivate?: () => Promise<void>;\n\n /** Hooks */\n hooks?: {\n /** Called before AI analysis */\n beforeAnalysis?: (\n diff: ParsedDiff,\n context: ProjectContext,\n ) => Promise<ParsedDiff | void>;\n\n /** Called after AI analysis */\n afterAnalysis?: (\n changelog: ChangelogResult,\n ) => Promise<ChangelogResult | void>;\n\n /** Called on any error */\n onError?: (error: DevDiffError) => Promise<void>;\n\n /** Called when files change */\n onFileChange?: (files: ChangedFile[]) => Promise<void>;\n\n /** Called when a commit is detected */\n onCommit?: (commit: GitCommit) => Promise<void>;\n\n /** Called when AI call completes */\n onAIComplete?: (result: AIResult) => Promise<void>;\n };\n\n /** Custom commands to register */\n commands?: PluginCommand[];\n\n /** Custom configuration schema */\n configSchema?: Record<string, any>;\n}\n\nexport interface PluginContext {\n /** DevDiff version */\n devdiffVersion: string;\n\n /** Workspace path */\n workspacePath: string;\n\n /** Logger instance */\n logger: PluginLogger;\n\n /** Configuration access */\n config: PluginConfig;\n\n /** Storage for plugin data */\n storage: PluginStorage;\n\n /** Notification service */\n notifications: PluginNotifications;\n\n /** Access to DevDiff engine (read-only) */\n engine: {\n getStatus: () => Promise<DevDiffStatus>;\n getProjectContext: () => Promise<ProjectContext>;\n getRecentChanges: (since: string) => Promise<ChangedFile[]>;\n };\n}\n\nexport interface PluginLogger {\n debug(message: string, data?: any): void;\n info(message: string, data?: any): void;\n warn(message: string, data?: any): void;\n error(message: string, error?: Error): void;\n}\n\nexport interface PluginConfig {\n get(key: string): any;\n set(key: string, value: any): Promise<void>;\n}\n\nexport interface PluginStorage {\n get(key: string): Promise<any>;\n set(key: string, value: any): Promise<void>;\n delete(key: string): Promise<void>;\n clear(): Promise<void>;\n}\n\nexport interface PluginNotifications {\n send(message: string, options?: NotificationOptions): Promise<void>;\n sendToChannel(channel: string, message: string): Promise<void>;\n}\n\nexport interface PluginCommand {\n name: string;\n description: string;\n handler: (args: string[]) => Promise<void>;\n}\n\nexport interface NotificationOptions {\n level?: \"info\" | \"warning\" | \"error\";\n channels?: string[];\n title?: string;\n url?: string;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]} | ||
| {"version":3,"sources":["../src/index.ts","../src/devtools.ts"],"sourcesContent":["/**\n * @eldrex/plugin-sdk\n *\n * Build DevDiff plugins without touching core.\n * Stable API, semantic versioning, TypeScript-first.\n */\n\nexport interface DiffLine {\n type: \"addition\" | \"deletion\" | \"normal\";\n content: string;\n ln1?: number;\n ln2?: number;\n}\n\nexport interface DiffHunk {\n header: string;\n oldStart: number;\n oldLines: number;\n newStart: number;\n newLines: number;\n lines: DiffLine[];\n}\n\nexport interface ParsedFileDiff {\n oldPath: string | null;\n newPath: string | null;\n isNew: boolean;\n isDeleted: boolean;\n isRename: boolean;\n hunks: DiffHunk[];\n path?: string;\n additions?: number;\n deletions?: number;\n isBinary?: boolean;\n content?: string;\n renamed?: boolean;\n}\n\nexport interface ParsedDiff {\n files: ParsedFileDiff[];\n changes: {\n type: \"addition\" | \"deletion\";\n line: number;\n content: string;\n }[];\n totalAdditions?: number;\n totalDeletions?: number;\n isEmpty?: boolean;\n hasConflicts?: boolean;\n}\n\nexport interface ProjectContext {\n files: string[];\n languages: string[];\n dependencies: Record<string, string>;\n structure: any;\n raw?: string;\n}\n\nexport interface ChangelogResult {\n summary: string;\n impact: \"none\" | \"minor\" | \"major\" | \"breaking\";\n breaking: boolean;\n files: {\n path: string;\n explanation: string;\n }[];\n relatedIssues: string[];\n formattedOutput: string;\n}\n\nexport interface DevDiffError {\n name: string;\n message: string;\n stack?: string;\n}\n\nexport interface ChangedFile {\n path: string;\n status: \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n}\n\nexport interface GitCommit {\n sha: string;\n message: string;\n author: string;\n date: string;\n}\n\nexport interface AIResult {\n summary: string;\n provider: string;\n model: string;\n tokensUsed?: number;\n cost?: number;\n}\n\nexport interface DevDiffStatus {\n sessionActive: boolean;\n providerInfo: string;\n stagedCount: number;\n unstagedCount: number;\n workspacePath: string;\n}\n\nexport interface DevDiffPlugin {\n /** Unique plugin ID */\n id: string;\n\n /** Human-readable name */\n name: string;\n\n /** Semantic version */\n version: string;\n\n /** Brief description */\n description: string;\n\n /** Author info */\n author: {\n name: string;\n email?: string;\n url?: string;\n };\n\n /** Minimum DevDiff version required */\n devdiffVersion: string;\n\n /** Plugin initialization */\n activate?: (context: PluginContext) => Promise<void>;\n\n /** Plugin cleanup */\n deactivate?: () => Promise<void>;\n\n /** Hooks */\n hooks?: {\n /** Called before AI analysis */\n beforeAnalysis?: (\n diff: ParsedDiff,\n context: ProjectContext,\n ) => Promise<ParsedDiff | void>;\n\n /** Called after AI analysis */\n afterAnalysis?: (\n changelog: ChangelogResult,\n ) => Promise<ChangelogResult | void>;\n\n /** Called on any error */\n onError?: (error: DevDiffError) => Promise<void>;\n\n /** Called when files change */\n onFileChange?: (files: ChangedFile[]) => Promise<void>;\n\n /** Called when a commit is detected */\n onCommit?: (commit: GitCommit) => Promise<void>;\n\n /** Called when AI call completes */\n onAIComplete?: (result: AIResult) => Promise<void>;\n };\n\n /** Custom commands to register */\n commands?: PluginCommand[];\n\n /** Custom configuration schema */\n configSchema?: Record<string, any>;\n}\n\nexport interface PluginContext {\n /** DevDiff version */\n devdiffVersion: string;\n\n /** Workspace path */\n workspacePath: string;\n\n /** Logger instance */\n logger: PluginLogger;\n\n /** Configuration access */\n config: PluginConfig;\n\n /** Storage for plugin data */\n storage: PluginStorage;\n\n /** Notification service */\n notifications: PluginNotifications;\n\n /** Access to DevDiff engine (read-only) */\n engine: {\n getStatus: () => Promise<DevDiffStatus>;\n getProjectContext: () => Promise<ProjectContext>;\n getRecentChanges: (since: string) => Promise<ChangedFile[]>;\n };\n}\n\nexport interface PluginLogger {\n debug(message: string, data?: any): void;\n info(message: string, data?: any): void;\n warn(message: string, data?: any): void;\n error(message: string, error?: Error): void;\n}\n\nexport interface PluginConfig {\n get(key: string): any;\n set(key: string, value: any): Promise<void>;\n}\n\nexport interface PluginStorage {\n get(key: string): Promise<any>;\n set(key: string, value: any): Promise<void>;\n delete(key: string): Promise<void>;\n clear(): Promise<void>;\n}\n\nexport interface PluginNotifications {\n send(message: string, options?: NotificationOptions): Promise<void>;\n sendToChannel(channel: string, message: string): Promise<void>;\n}\n\nexport interface PluginCommand {\n name: string;\n description: string;\n handler: (args: string[]) => Promise<void>;\n}\n\nexport interface NotificationOptions {\n level?: \"info\" | \"warning\" | \"error\";\n channels?: string[];\n title?: string;\n url?: string;\n}\n\n// ── DEVTOOLS SUITE FOR DEVS & EXTENSION AUTHORS ──\nexport {\n DevDiffDevTools,\n MockDiffOptions,\n MockContextOptions,\n PluginValidationResult,\n BenchmarkResult,\n} from \"./devtools\";\n\n","import {\n ParsedDiff,\n ParsedFileDiff,\n ProjectContext,\n ChangelogResult,\n DevDiffPlugin,\n PluginContext,\n} from \"./index\";\n\nexport interface MockDiffOptions {\n filesCount?: number;\n additionsPerFile?: number;\n deletionsPerFile?: number;\n filePaths?: string[];\n includeRenames?: boolean;\n}\n\nexport interface MockContextOptions {\n files?: string[];\n languages?: string[];\n dependencies?: Record<string, string>;\n projectName?: string;\n}\n\nexport interface PluginValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\nexport interface BenchmarkResult {\n pluginId: string;\n iterations: number;\n totalDurationMs: number;\n averageDurationMs: number;\n memoryDeltaBytes: number;\n}\n\n/**\n * DevDiff Foundations DevTools\n * Utilities for developing, unit-testing, validating, and benchmarking DevDiff plugins and extensions.\n */\nexport class DevDiffDevTools {\n /**\n * Generates a realistic mock `ParsedDiff` for unit tests and local simulation.\n */\n static mockDiff(options?: MockDiffOptions): ParsedDiff {\n const count = options?.filesCount || 2;\n const additionsCount = options?.additionsPerFile || 5;\n const deletionsCount = options?.deletionsPerFile || 2;\n const customPaths = options?.filePaths || [];\n\n const files: ParsedFileDiff[] = [];\n const changes: Array<{ type: \"addition\" | \"deletion\"; line: number; content: string }> = [];\n\n for (let i = 0; i < count; i++) {\n const filePath = customPaths[i] || `src/module_${i + 1}.ts`;\n const isNew = i === 0 && count > 1;\n const isRename = Boolean(options?.includeRenames && i === 1);\n\n const hunks = [\n {\n header: `@@ -1,${deletionsCount} +1,${additionsCount} @@`,\n oldStart: 1,\n oldLines: deletionsCount,\n newStart: 1,\n newLines: additionsCount,\n lines: [\n ...Array.from({ length: deletionsCount }, (_, idx) => ({\n type: \"deletion\" as const,\n content: `- const oldVar${idx} = ${idx};`,\n ln1: idx + 1,\n })),\n ...Array.from({ length: additionsCount }, (_, idx) => ({\n type: \"addition\" as const,\n content: `+ const newVar${idx} = ${idx * 2}; // enhanced`,\n ln2: idx + 1,\n })),\n ],\n },\n ];\n\n for (let d = 0; d < deletionsCount; d++) {\n changes.push({\n type: \"deletion\",\n line: d + 1,\n content: `const oldVar${d} = ${d};`,\n });\n }\n for (let a = 0; a < additionsCount; a++) {\n changes.push({\n type: \"addition\",\n line: a + 1,\n content: `const newVar${a} = ${a * 2}; // enhanced`,\n });\n }\n\n files.push({\n path: filePath,\n oldPath: isRename ? `src/old_module_${i + 1}.ts` : isNew ? null : filePath,\n newPath: filePath,\n isNew,\n isDeleted: false,\n isRename,\n additions: additionsCount,\n deletions: deletionsCount,\n hunks,\n });\n }\n\n return {\n files,\n changes,\n totalAdditions: count * additionsCount,\n totalDeletions: count * deletionsCount,\n isEmpty: files.length === 0,\n hasConflicts: false,\n };\n }\n\n /**\n * Generates a mock `ProjectContext` for testing context-aware hooks.\n */\n static mockContext(options?: MockContextOptions): ProjectContext {\n const files = options?.files || [\n \"src/index.ts\",\n \"src/engine.ts\",\n \"package.json\",\n \"README.md\",\n ];\n const languages = options?.languages || [\"TypeScript\", \"JSON\", \"Markdown\"];\n const dependencies = options?.dependencies || {\n \"@eldrex/core\": \"1.7.0\",\n typescript: \"^5.5.0\",\n };\n\n return {\n files,\n languages,\n dependencies,\n structure: {\n src: [\"index.ts\", \"engine.ts\"],\n root: [\"package.json\", \"README.md\"],\n },\n raw: `# ${options?.projectName || \"mock-project\"}\\n- Primary: TypeScript`,\n };\n }\n\n /**\n * Generates a mock `ChangelogResult` for testing post-analysis hooks.\n */\n static mockChangelog(summary?: string): ChangelogResult {\n return {\n summary: summary || \"## Added\\n- Added modular DevTools suite for DevDiff Foundations.\",\n impact: \"minor\",\n breaking: false,\n files: [\n {\n path: \"src/devtools.ts\",\n explanation: \"Introduced mock generators and testing harness.\",\n },\n ],\n relatedIssues: [\"#42\"],\n formattedOutput: summary || \"# Changelog\\n\\n## Added\\n- Added modular DevTools suite.\",\n };\n }\n\n /**\n * Validates a plugin against DevDiff specification and version constraints.\n */\n static validatePlugin(plugin: any): PluginValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n if (!plugin || typeof plugin !== \"object\") {\n return { valid: false, errors: [\"Plugin must be an object.\"], warnings: [] };\n }\n\n if (!plugin.id || typeof plugin.id !== \"string\") {\n errors.push(\"Plugin 'id' is required and must be a non-empty string.\");\n } else if (!/^[a-z0-9-_@/]+$/.test(plugin.id)) {\n warnings.push(\"Plugin 'id' should be alphanumeric with hyphens or underscores (e.g. '@org/my-plugin').\");\n }\n\n if (!plugin.name || typeof plugin.name !== \"string\") {\n errors.push(\"Plugin 'name' is required.\");\n }\n\n if (!plugin.version || typeof plugin.version !== \"string\") {\n errors.push(\"Plugin 'version' is required (SemVer format).\");\n } else if (!/^\\d+\\.\\d+\\.\\d+/.test(plugin.version)) {\n warnings.push(\"Plugin 'version' should follow standard SemVer (e.g. '1.0.0').\");\n }\n\n if (plugin.activate && typeof plugin.activate !== \"function\") {\n errors.push(\"Plugin 'activate' must be a function if provided.\");\n }\n\n if (plugin.deactivate && typeof plugin.deactivate !== \"function\") {\n errors.push(\"Plugin 'deactivate' must be a function if provided.\");\n }\n\n if (plugin.hooks) {\n if (typeof plugin.hooks !== \"object\") {\n errors.push(\"Plugin 'hooks' must be an object.\");\n } else {\n const allowedHooks = [\n \"beforeAnalysis\",\n \"afterAnalysis\",\n \"onError\",\n \"onChangelogGenerated\",\n \"onFileParsed\",\n \"onSessionStart\",\n \"onSessionEnd\",\n ];\n for (const hookName of Object.keys(plugin.hooks)) {\n if (!allowedHooks.includes(hookName)) {\n warnings.push(`Unknown hook '${hookName}' may not be called by the engine.`);\n } else if (typeof plugin.hooks[hookName] !== \"function\") {\n errors.push(`Hook '${hookName}' must be a function.`);\n }\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n }\n\n /**\n * Creates an in-memory test harness to test plugin execution end-to-end.\n */\n static createTestHarness(plugin: DevDiffPlugin) {\n const logs: string[] = [];\n const errorsCaught: any[] = [];\n\n const store: Record<string, any> = {};\n const mockContext: PluginContext = {\n devdiffVersion: \"1.7.0\",\n workspacePath: process.cwd(),\n config: {\n get: (key: string) => store[key],\n set: async (key: string, value: any) => {\n store[key] = value;\n },\n },\n storage: {\n get: async (key: string) => store[key],\n set: async (key: string, value: any) => {\n store[key] = value;\n },\n delete: async (key: string) => {\n delete store[key];\n },\n clear: async () => {\n for (const k of Object.keys(store)) delete store[k];\n },\n },\n notifications: {\n send: async (msg) => {\n logs.push(`[NOTIFY] ${msg}`);\n },\n sendToChannel: async (chan, msg) => {\n logs.push(`[NOTIFY:${chan}] ${msg}`);\n },\n },\n logger: {\n info: (msg) => logs.push(`[INFO] ${msg}`),\n warn: (msg) => logs.push(`[WARN] ${msg}`),\n error: (msg) => logs.push(`[ERROR] ${msg}`),\n debug: (msg) => logs.push(`[DEBUG] ${msg}`),\n },\n engine: {\n getStatus: async () => ({\n sessionActive: true,\n providerInfo: \"mock-ollama (llama3.2:3b)\",\n stagedCount: 1,\n unstagedCount: 0,\n workspacePath: process.cwd(),\n }),\n getProjectContext: async () => DevDiffDevTools.mockContext(),\n getRecentChanges: async () => [{ path: \"src/index.ts\", status: \"modified\" }],\n },\n };\n\n return {\n getLogs: () => [...logs],\n getErrors: () => [...errorsCaught],\n\n async activate() {\n if (plugin.activate) {\n await plugin.activate(mockContext);\n }\n },\n\n async deactivate() {\n if (plugin.deactivate) {\n await plugin.deactivate();\n }\n },\n\n async runBeforeAnalysis(diff?: ParsedDiff, context?: ProjectContext): Promise<ParsedDiff> {\n const inputDiff = diff || DevDiffDevTools.mockDiff();\n const inputContext = context || DevDiffDevTools.mockContext();\n if (plugin.hooks?.beforeAnalysis) {\n try {\n const res = await plugin.hooks.beforeAnalysis(inputDiff, inputContext);\n return res || inputDiff;\n } catch (err: any) {\n errorsCaught.push(err);\n if (plugin.hooks?.onError) {\n await plugin.hooks.onError({ name: err.name || \"Error\", message: err.message, stack: err.stack });\n }\n throw err;\n }\n }\n return inputDiff;\n },\n\n async runAfterAnalysis(changelog?: ChangelogResult): Promise<ChangelogResult> {\n const inputChangelog = changelog || DevDiffDevTools.mockChangelog();\n if (plugin.hooks?.afterAnalysis) {\n try {\n const res = await plugin.hooks.afterAnalysis(inputChangelog);\n return res || inputChangelog;\n } catch (err: any) {\n errorsCaught.push(err);\n if (plugin.hooks?.onError) {\n await plugin.hooks.onError({ name: err.name || \"Error\", message: err.message, stack: err.stack });\n }\n throw err;\n }\n }\n return inputChangelog;\n },\n };\n }\n\n /**\n * Benchmarks the execution speed and overhead of a plugin's hooks.\n */\n static async benchmarkPlugin(\n plugin: DevDiffPlugin,\n options?: { iterations?: number; sampleDiff?: ParsedDiff },\n ): Promise<BenchmarkResult> {\n const iterations = options?.iterations || 50;\n const diff = options?.sampleDiff || this.mockDiff({ filesCount: 5, additionsPerFile: 10 });\n const context = this.mockContext();\n const changelog = this.mockChangelog();\n\n const harness = this.createTestHarness(plugin);\n await harness.activate();\n\n const memBefore = process.memoryUsage().heapUsed;\n const start = performance.now();\n\n for (let i = 0; i < iterations; i++) {\n await harness.runBeforeAnalysis(diff, context);\n await harness.runAfterAnalysis(changelog);\n }\n\n const duration = performance.now() - start;\n const memAfter = process.memoryUsage().heapUsed;\n\n await harness.deactivate();\n\n return {\n pluginId: plugin.id,\n iterations,\n totalDurationMs: parseFloat(duration.toFixed(2)),\n averageDurationMs: parseFloat((duration / iterations).toFixed(3)),\n memoryDeltaBytes: Math.max(0, memAfter - memBefore),\n };\n }\n}\n"],"mappings":"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","names":[]} |
+67
-1
@@ -0,2 +1,68 @@ | ||
| interface MockDiffOptions { | ||
| filesCount?: number; | ||
| additionsPerFile?: number; | ||
| deletionsPerFile?: number; | ||
| filePaths?: string[]; | ||
| includeRenames?: boolean; | ||
| } | ||
| interface MockContextOptions { | ||
| files?: string[]; | ||
| languages?: string[]; | ||
| dependencies?: Record<string, string>; | ||
| projectName?: string; | ||
| } | ||
| interface PluginValidationResult { | ||
| valid: boolean; | ||
| errors: string[]; | ||
| warnings: string[]; | ||
| } | ||
| interface BenchmarkResult { | ||
| pluginId: string; | ||
| iterations: number; | ||
| totalDurationMs: number; | ||
| averageDurationMs: number; | ||
| memoryDeltaBytes: number; | ||
| } | ||
| /** | ||
| * DevDiff Foundations DevTools | ||
| * Utilities for developing, unit-testing, validating, and benchmarking DevDiff plugins and extensions. | ||
| */ | ||
| declare class DevDiffDevTools { | ||
| /** | ||
| * Generates a realistic mock `ParsedDiff` for unit tests and local simulation. | ||
| */ | ||
| static mockDiff(options?: MockDiffOptions): ParsedDiff; | ||
| /** | ||
| * Generates a mock `ProjectContext` for testing context-aware hooks. | ||
| */ | ||
| static mockContext(options?: MockContextOptions): ProjectContext; | ||
| /** | ||
| * Generates a mock `ChangelogResult` for testing post-analysis hooks. | ||
| */ | ||
| static mockChangelog(summary?: string): ChangelogResult; | ||
| /** | ||
| * Validates a plugin against DevDiff specification and version constraints. | ||
| */ | ||
| static validatePlugin(plugin: any): PluginValidationResult; | ||
| /** | ||
| * Creates an in-memory test harness to test plugin execution end-to-end. | ||
| */ | ||
| static createTestHarness(plugin: DevDiffPlugin): { | ||
| getLogs: () => string[]; | ||
| getErrors: () => any[]; | ||
| activate(): Promise<void>; | ||
| deactivate(): Promise<void>; | ||
| runBeforeAnalysis(diff?: ParsedDiff, context?: ProjectContext): Promise<ParsedDiff>; | ||
| runAfterAnalysis(changelog?: ChangelogResult): Promise<ChangelogResult>; | ||
| }; | ||
| /** | ||
| * Benchmarks the execution speed and overhead of a plugin's hooks. | ||
| */ | ||
| static benchmarkPlugin(plugin: DevDiffPlugin, options?: { | ||
| iterations?: number; | ||
| sampleDiff?: ParsedDiff; | ||
| }): Promise<BenchmarkResult>; | ||
| } | ||
| /** | ||
| * @eldrex/plugin-sdk | ||
@@ -187,2 +253,2 @@ * | ||
| export type { AIResult, ChangedFile, ChangelogResult, DevDiffError, DevDiffPlugin, DevDiffStatus, DiffHunk, DiffLine, GitCommit, NotificationOptions, ParsedDiff, ParsedFileDiff, PluginCommand, PluginConfig, PluginContext, PluginLogger, PluginNotifications, PluginStorage, ProjectContext }; | ||
| export { type AIResult, type BenchmarkResult, type ChangedFile, type ChangelogResult, DevDiffDevTools, type DevDiffError, type DevDiffPlugin, type DevDiffStatus, type DiffHunk, type DiffLine, type GitCommit, type MockContextOptions, type MockDiffOptions, type NotificationOptions, type ParsedDiff, type ParsedFileDiff, type PluginCommand, type PluginConfig, type PluginContext, type PluginLogger, type PluginNotifications, type PluginStorage, type PluginValidationResult, type ProjectContext }; |
+67
-1
@@ -0,2 +1,68 @@ | ||
| interface MockDiffOptions { | ||
| filesCount?: number; | ||
| additionsPerFile?: number; | ||
| deletionsPerFile?: number; | ||
| filePaths?: string[]; | ||
| includeRenames?: boolean; | ||
| } | ||
| interface MockContextOptions { | ||
| files?: string[]; | ||
| languages?: string[]; | ||
| dependencies?: Record<string, string>; | ||
| projectName?: string; | ||
| } | ||
| interface PluginValidationResult { | ||
| valid: boolean; | ||
| errors: string[]; | ||
| warnings: string[]; | ||
| } | ||
| interface BenchmarkResult { | ||
| pluginId: string; | ||
| iterations: number; | ||
| totalDurationMs: number; | ||
| averageDurationMs: number; | ||
| memoryDeltaBytes: number; | ||
| } | ||
| /** | ||
| * DevDiff Foundations DevTools | ||
| * Utilities for developing, unit-testing, validating, and benchmarking DevDiff plugins and extensions. | ||
| */ | ||
| declare class DevDiffDevTools { | ||
| /** | ||
| * Generates a realistic mock `ParsedDiff` for unit tests and local simulation. | ||
| */ | ||
| static mockDiff(options?: MockDiffOptions): ParsedDiff; | ||
| /** | ||
| * Generates a mock `ProjectContext` for testing context-aware hooks. | ||
| */ | ||
| static mockContext(options?: MockContextOptions): ProjectContext; | ||
| /** | ||
| * Generates a mock `ChangelogResult` for testing post-analysis hooks. | ||
| */ | ||
| static mockChangelog(summary?: string): ChangelogResult; | ||
| /** | ||
| * Validates a plugin against DevDiff specification and version constraints. | ||
| */ | ||
| static validatePlugin(plugin: any): PluginValidationResult; | ||
| /** | ||
| * Creates an in-memory test harness to test plugin execution end-to-end. | ||
| */ | ||
| static createTestHarness(plugin: DevDiffPlugin): { | ||
| getLogs: () => string[]; | ||
| getErrors: () => any[]; | ||
| activate(): Promise<void>; | ||
| deactivate(): Promise<void>; | ||
| runBeforeAnalysis(diff?: ParsedDiff, context?: ProjectContext): Promise<ParsedDiff>; | ||
| runAfterAnalysis(changelog?: ChangelogResult): Promise<ChangelogResult>; | ||
| }; | ||
| /** | ||
| * Benchmarks the execution speed and overhead of a plugin's hooks. | ||
| */ | ||
| static benchmarkPlugin(plugin: DevDiffPlugin, options?: { | ||
| iterations?: number; | ||
| sampleDiff?: ParsedDiff; | ||
| }): Promise<BenchmarkResult>; | ||
| } | ||
| /** | ||
| * @eldrex/plugin-sdk | ||
@@ -187,2 +253,2 @@ * | ||
| export type { AIResult, ChangedFile, ChangelogResult, DevDiffError, DevDiffPlugin, DevDiffStatus, DiffHunk, DiffLine, GitCommit, NotificationOptions, ParsedDiff, ParsedFileDiff, PluginCommand, PluginConfig, PluginContext, PluginLogger, PluginNotifications, PluginStorage, ProjectContext }; | ||
| export { type AIResult, type BenchmarkResult, type ChangedFile, type ChangelogResult, DevDiffDevTools, type DevDiffError, type DevDiffPlugin, type DevDiffStatus, type DiffHunk, type DiffLine, type GitCommit, type MockContextOptions, type MockDiffOptions, type NotificationOptions, type ParsedDiff, type ParsedFileDiff, type PluginCommand, type PluginConfig, type PluginContext, type PluginLogger, type PluginNotifications, type PluginStorage, type PluginValidationResult, type ProjectContext }; |
+306
-0
@@ -0,1 +1,307 @@ | ||
| // src/devtools.ts | ||
| var DevDiffDevTools = class _DevDiffDevTools { | ||
| /** | ||
| * Generates a realistic mock `ParsedDiff` for unit tests and local simulation. | ||
| */ | ||
| static mockDiff(options) { | ||
| const count = options?.filesCount || 2; | ||
| const additionsCount = options?.additionsPerFile || 5; | ||
| const deletionsCount = options?.deletionsPerFile || 2; | ||
| const customPaths = options?.filePaths || []; | ||
| const files = []; | ||
| const changes = []; | ||
| for (let i = 0; i < count; i++) { | ||
| const filePath = customPaths[i] || `src/module_${i + 1}.ts`; | ||
| const isNew = i === 0 && count > 1; | ||
| const isRename = Boolean(options?.includeRenames && i === 1); | ||
| const hunks = [ | ||
| { | ||
| header: `@@ -1,${deletionsCount} +1,${additionsCount} @@`, | ||
| oldStart: 1, | ||
| oldLines: deletionsCount, | ||
| newStart: 1, | ||
| newLines: additionsCount, | ||
| lines: [ | ||
| ...Array.from({ length: deletionsCount }, (_, idx) => ({ | ||
| type: "deletion", | ||
| content: `- const oldVar${idx} = ${idx};`, | ||
| ln1: idx + 1 | ||
| })), | ||
| ...Array.from({ length: additionsCount }, (_, idx) => ({ | ||
| type: "addition", | ||
| content: `+ const newVar${idx} = ${idx * 2}; // enhanced`, | ||
| ln2: idx + 1 | ||
| })) | ||
| ] | ||
| } | ||
| ]; | ||
| for (let d = 0; d < deletionsCount; d++) { | ||
| changes.push({ | ||
| type: "deletion", | ||
| line: d + 1, | ||
| content: `const oldVar${d} = ${d};` | ||
| }); | ||
| } | ||
| for (let a = 0; a < additionsCount; a++) { | ||
| changes.push({ | ||
| type: "addition", | ||
| line: a + 1, | ||
| content: `const newVar${a} = ${a * 2}; // enhanced` | ||
| }); | ||
| } | ||
| files.push({ | ||
| path: filePath, | ||
| oldPath: isRename ? `src/old_module_${i + 1}.ts` : isNew ? null : filePath, | ||
| newPath: filePath, | ||
| isNew, | ||
| isDeleted: false, | ||
| isRename, | ||
| additions: additionsCount, | ||
| deletions: deletionsCount, | ||
| hunks | ||
| }); | ||
| } | ||
| return { | ||
| files, | ||
| changes, | ||
| totalAdditions: count * additionsCount, | ||
| totalDeletions: count * deletionsCount, | ||
| isEmpty: files.length === 0, | ||
| hasConflicts: false | ||
| }; | ||
| } | ||
| /** | ||
| * Generates a mock `ProjectContext` for testing context-aware hooks. | ||
| */ | ||
| static mockContext(options) { | ||
| const files = options?.files || [ | ||
| "src/index.ts", | ||
| "src/engine.ts", | ||
| "package.json", | ||
| "README.md" | ||
| ]; | ||
| const languages = options?.languages || ["TypeScript", "JSON", "Markdown"]; | ||
| const dependencies = options?.dependencies || { | ||
| "@eldrex/core": "1.7.0", | ||
| typescript: "^5.5.0" | ||
| }; | ||
| return { | ||
| files, | ||
| languages, | ||
| dependencies, | ||
| structure: { | ||
| src: ["index.ts", "engine.ts"], | ||
| root: ["package.json", "README.md"] | ||
| }, | ||
| raw: `# ${options?.projectName || "mock-project"} | ||
| - Primary: TypeScript` | ||
| }; | ||
| } | ||
| /** | ||
| * Generates a mock `ChangelogResult` for testing post-analysis hooks. | ||
| */ | ||
| static mockChangelog(summary) { | ||
| return { | ||
| summary: summary || "## Added\n- Added modular DevTools suite for DevDiff Foundations.", | ||
| impact: "minor", | ||
| breaking: false, | ||
| files: [ | ||
| { | ||
| path: "src/devtools.ts", | ||
| explanation: "Introduced mock generators and testing harness." | ||
| } | ||
| ], | ||
| relatedIssues: ["#42"], | ||
| formattedOutput: summary || "# Changelog\n\n## Added\n- Added modular DevTools suite." | ||
| }; | ||
| } | ||
| /** | ||
| * Validates a plugin against DevDiff specification and version constraints. | ||
| */ | ||
| static validatePlugin(plugin) { | ||
| const errors = []; | ||
| const warnings = []; | ||
| if (!plugin || typeof plugin !== "object") { | ||
| return { valid: false, errors: ["Plugin must be an object."], warnings: [] }; | ||
| } | ||
| if (!plugin.id || typeof plugin.id !== "string") { | ||
| errors.push("Plugin 'id' is required and must be a non-empty string."); | ||
| } else if (!/^[a-z0-9-_@/]+$/.test(plugin.id)) { | ||
| warnings.push("Plugin 'id' should be alphanumeric with hyphens or underscores (e.g. '@org/my-plugin')."); | ||
| } | ||
| if (!plugin.name || typeof plugin.name !== "string") { | ||
| errors.push("Plugin 'name' is required."); | ||
| } | ||
| if (!plugin.version || typeof plugin.version !== "string") { | ||
| errors.push("Plugin 'version' is required (SemVer format)."); | ||
| } else if (!/^\d+\.\d+\.\d+/.test(plugin.version)) { | ||
| warnings.push("Plugin 'version' should follow standard SemVer (e.g. '1.0.0')."); | ||
| } | ||
| if (plugin.activate && typeof plugin.activate !== "function") { | ||
| errors.push("Plugin 'activate' must be a function if provided."); | ||
| } | ||
| if (plugin.deactivate && typeof plugin.deactivate !== "function") { | ||
| errors.push("Plugin 'deactivate' must be a function if provided."); | ||
| } | ||
| if (plugin.hooks) { | ||
| if (typeof plugin.hooks !== "object") { | ||
| errors.push("Plugin 'hooks' must be an object."); | ||
| } else { | ||
| const allowedHooks = [ | ||
| "beforeAnalysis", | ||
| "afterAnalysis", | ||
| "onError", | ||
| "onChangelogGenerated", | ||
| "onFileParsed", | ||
| "onSessionStart", | ||
| "onSessionEnd" | ||
| ]; | ||
| for (const hookName of Object.keys(plugin.hooks)) { | ||
| if (!allowedHooks.includes(hookName)) { | ||
| warnings.push(`Unknown hook '${hookName}' may not be called by the engine.`); | ||
| } else if (typeof plugin.hooks[hookName] !== "function") { | ||
| errors.push(`Hook '${hookName}' must be a function.`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return { | ||
| valid: errors.length === 0, | ||
| errors, | ||
| warnings | ||
| }; | ||
| } | ||
| /** | ||
| * Creates an in-memory test harness to test plugin execution end-to-end. | ||
| */ | ||
| static createTestHarness(plugin) { | ||
| const logs = []; | ||
| const errorsCaught = []; | ||
| const store = {}; | ||
| const mockContext = { | ||
| devdiffVersion: "1.7.0", | ||
| workspacePath: process.cwd(), | ||
| config: { | ||
| get: (key) => store[key], | ||
| set: async (key, value) => { | ||
| store[key] = value; | ||
| } | ||
| }, | ||
| storage: { | ||
| get: async (key) => store[key], | ||
| set: async (key, value) => { | ||
| store[key] = value; | ||
| }, | ||
| delete: async (key) => { | ||
| delete store[key]; | ||
| }, | ||
| clear: async () => { | ||
| for (const k of Object.keys(store)) delete store[k]; | ||
| } | ||
| }, | ||
| notifications: { | ||
| send: async (msg) => { | ||
| logs.push(`[NOTIFY] ${msg}`); | ||
| }, | ||
| sendToChannel: async (chan, msg) => { | ||
| logs.push(`[NOTIFY:${chan}] ${msg}`); | ||
| } | ||
| }, | ||
| logger: { | ||
| info: (msg) => logs.push(`[INFO] ${msg}`), | ||
| warn: (msg) => logs.push(`[WARN] ${msg}`), | ||
| error: (msg) => logs.push(`[ERROR] ${msg}`), | ||
| debug: (msg) => logs.push(`[DEBUG] ${msg}`) | ||
| }, | ||
| engine: { | ||
| getStatus: async () => ({ | ||
| sessionActive: true, | ||
| providerInfo: "mock-ollama (llama3.2:3b)", | ||
| stagedCount: 1, | ||
| unstagedCount: 0, | ||
| workspacePath: process.cwd() | ||
| }), | ||
| getProjectContext: async () => _DevDiffDevTools.mockContext(), | ||
| getRecentChanges: async () => [{ path: "src/index.ts", status: "modified" }] | ||
| } | ||
| }; | ||
| return { | ||
| getLogs: () => [...logs], | ||
| getErrors: () => [...errorsCaught], | ||
| async activate() { | ||
| if (plugin.activate) { | ||
| await plugin.activate(mockContext); | ||
| } | ||
| }, | ||
| async deactivate() { | ||
| if (plugin.deactivate) { | ||
| await plugin.deactivate(); | ||
| } | ||
| }, | ||
| async runBeforeAnalysis(diff, context) { | ||
| const inputDiff = diff || _DevDiffDevTools.mockDiff(); | ||
| const inputContext = context || _DevDiffDevTools.mockContext(); | ||
| if (plugin.hooks?.beforeAnalysis) { | ||
| try { | ||
| const res = await plugin.hooks.beforeAnalysis(inputDiff, inputContext); | ||
| return res || inputDiff; | ||
| } catch (err) { | ||
| errorsCaught.push(err); | ||
| if (plugin.hooks?.onError) { | ||
| await plugin.hooks.onError({ name: err.name || "Error", message: err.message, stack: err.stack }); | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
| return inputDiff; | ||
| }, | ||
| async runAfterAnalysis(changelog) { | ||
| const inputChangelog = changelog || _DevDiffDevTools.mockChangelog(); | ||
| if (plugin.hooks?.afterAnalysis) { | ||
| try { | ||
| const res = await plugin.hooks.afterAnalysis(inputChangelog); | ||
| return res || inputChangelog; | ||
| } catch (err) { | ||
| errorsCaught.push(err); | ||
| if (plugin.hooks?.onError) { | ||
| await plugin.hooks.onError({ name: err.name || "Error", message: err.message, stack: err.stack }); | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
| return inputChangelog; | ||
| } | ||
| }; | ||
| } | ||
| /** | ||
| * Benchmarks the execution speed and overhead of a plugin's hooks. | ||
| */ | ||
| static async benchmarkPlugin(plugin, options) { | ||
| const iterations = options?.iterations || 50; | ||
| const diff = options?.sampleDiff || this.mockDiff({ filesCount: 5, additionsPerFile: 10 }); | ||
| const context = this.mockContext(); | ||
| const changelog = this.mockChangelog(); | ||
| const harness = this.createTestHarness(plugin); | ||
| await harness.activate(); | ||
| const memBefore = process.memoryUsage().heapUsed; | ||
| const start = performance.now(); | ||
| for (let i = 0; i < iterations; i++) { | ||
| await harness.runBeforeAnalysis(diff, context); | ||
| await harness.runAfterAnalysis(changelog); | ||
| } | ||
| const duration = performance.now() - start; | ||
| const memAfter = process.memoryUsage().heapUsed; | ||
| await harness.deactivate(); | ||
| return { | ||
| pluginId: plugin.id, | ||
| iterations, | ||
| totalDurationMs: parseFloat(duration.toFixed(2)), | ||
| averageDurationMs: parseFloat((duration / iterations).toFixed(3)), | ||
| memoryDeltaBytes: Math.max(0, memAfter - memBefore) | ||
| }; | ||
| } | ||
| }; | ||
| export { | ||
| DevDiffDevTools | ||
| }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]} | ||
| {"version":3,"sources":["../src/devtools.ts"],"sourcesContent":["import {\n ParsedDiff,\n ParsedFileDiff,\n ProjectContext,\n ChangelogResult,\n DevDiffPlugin,\n PluginContext,\n} from \"./index\";\n\nexport interface MockDiffOptions {\n filesCount?: number;\n additionsPerFile?: number;\n deletionsPerFile?: number;\n filePaths?: string[];\n includeRenames?: boolean;\n}\n\nexport interface MockContextOptions {\n files?: string[];\n languages?: string[];\n dependencies?: Record<string, string>;\n projectName?: string;\n}\n\nexport interface PluginValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\nexport interface BenchmarkResult {\n pluginId: string;\n iterations: number;\n totalDurationMs: number;\n averageDurationMs: number;\n memoryDeltaBytes: number;\n}\n\n/**\n * DevDiff Foundations DevTools\n * Utilities for developing, unit-testing, validating, and benchmarking DevDiff plugins and extensions.\n */\nexport class DevDiffDevTools {\n /**\n * Generates a realistic mock `ParsedDiff` for unit tests and local simulation.\n */\n static mockDiff(options?: MockDiffOptions): ParsedDiff {\n const count = options?.filesCount || 2;\n const additionsCount = options?.additionsPerFile || 5;\n const deletionsCount = options?.deletionsPerFile || 2;\n const customPaths = options?.filePaths || [];\n\n const files: ParsedFileDiff[] = [];\n const changes: Array<{ type: \"addition\" | \"deletion\"; line: number; content: string }> = [];\n\n for (let i = 0; i < count; i++) {\n const filePath = customPaths[i] || `src/module_${i + 1}.ts`;\n const isNew = i === 0 && count > 1;\n const isRename = Boolean(options?.includeRenames && i === 1);\n\n const hunks = [\n {\n header: `@@ -1,${deletionsCount} +1,${additionsCount} @@`,\n oldStart: 1,\n oldLines: deletionsCount,\n newStart: 1,\n newLines: additionsCount,\n lines: [\n ...Array.from({ length: deletionsCount }, (_, idx) => ({\n type: \"deletion\" as const,\n content: `- const oldVar${idx} = ${idx};`,\n ln1: idx + 1,\n })),\n ...Array.from({ length: additionsCount }, (_, idx) => ({\n type: \"addition\" as const,\n content: `+ const newVar${idx} = ${idx * 2}; // enhanced`,\n ln2: idx + 1,\n })),\n ],\n },\n ];\n\n for (let d = 0; d < deletionsCount; d++) {\n changes.push({\n type: \"deletion\",\n line: d + 1,\n content: `const oldVar${d} = ${d};`,\n });\n }\n for (let a = 0; a < additionsCount; a++) {\n changes.push({\n type: \"addition\",\n line: a + 1,\n content: `const newVar${a} = ${a * 2}; // enhanced`,\n });\n }\n\n files.push({\n path: filePath,\n oldPath: isRename ? `src/old_module_${i + 1}.ts` : isNew ? null : filePath,\n newPath: filePath,\n isNew,\n isDeleted: false,\n isRename,\n additions: additionsCount,\n deletions: deletionsCount,\n hunks,\n });\n }\n\n return {\n files,\n changes,\n totalAdditions: count * additionsCount,\n totalDeletions: count * deletionsCount,\n isEmpty: files.length === 0,\n hasConflicts: false,\n };\n }\n\n /**\n * Generates a mock `ProjectContext` for testing context-aware hooks.\n */\n static mockContext(options?: MockContextOptions): ProjectContext {\n const files = options?.files || [\n \"src/index.ts\",\n \"src/engine.ts\",\n \"package.json\",\n \"README.md\",\n ];\n const languages = options?.languages || [\"TypeScript\", \"JSON\", \"Markdown\"];\n const dependencies = options?.dependencies || {\n \"@eldrex/core\": \"1.7.0\",\n typescript: \"^5.5.0\",\n };\n\n return {\n files,\n languages,\n dependencies,\n structure: {\n src: [\"index.ts\", \"engine.ts\"],\n root: [\"package.json\", \"README.md\"],\n },\n raw: `# ${options?.projectName || \"mock-project\"}\\n- Primary: TypeScript`,\n };\n }\n\n /**\n * Generates a mock `ChangelogResult` for testing post-analysis hooks.\n */\n static mockChangelog(summary?: string): ChangelogResult {\n return {\n summary: summary || \"## Added\\n- Added modular DevTools suite for DevDiff Foundations.\",\n impact: \"minor\",\n breaking: false,\n files: [\n {\n path: \"src/devtools.ts\",\n explanation: \"Introduced mock generators and testing harness.\",\n },\n ],\n relatedIssues: [\"#42\"],\n formattedOutput: summary || \"# Changelog\\n\\n## Added\\n- Added modular DevTools suite.\",\n };\n }\n\n /**\n * Validates a plugin against DevDiff specification and version constraints.\n */\n static validatePlugin(plugin: any): PluginValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n if (!plugin || typeof plugin !== \"object\") {\n return { valid: false, errors: [\"Plugin must be an object.\"], warnings: [] };\n }\n\n if (!plugin.id || typeof plugin.id !== \"string\") {\n errors.push(\"Plugin 'id' is required and must be a non-empty string.\");\n } else if (!/^[a-z0-9-_@/]+$/.test(plugin.id)) {\n warnings.push(\"Plugin 'id' should be alphanumeric with hyphens or underscores (e.g. '@org/my-plugin').\");\n }\n\n if (!plugin.name || typeof plugin.name !== \"string\") {\n errors.push(\"Plugin 'name' is required.\");\n }\n\n if (!plugin.version || typeof plugin.version !== \"string\") {\n errors.push(\"Plugin 'version' is required (SemVer format).\");\n } else if (!/^\\d+\\.\\d+\\.\\d+/.test(plugin.version)) {\n warnings.push(\"Plugin 'version' should follow standard SemVer (e.g. '1.0.0').\");\n }\n\n if (plugin.activate && typeof plugin.activate !== \"function\") {\n errors.push(\"Plugin 'activate' must be a function if provided.\");\n }\n\n if (plugin.deactivate && typeof plugin.deactivate !== \"function\") {\n errors.push(\"Plugin 'deactivate' must be a function if provided.\");\n }\n\n if (plugin.hooks) {\n if (typeof plugin.hooks !== \"object\") {\n errors.push(\"Plugin 'hooks' must be an object.\");\n } else {\n const allowedHooks = [\n \"beforeAnalysis\",\n \"afterAnalysis\",\n \"onError\",\n \"onChangelogGenerated\",\n \"onFileParsed\",\n \"onSessionStart\",\n \"onSessionEnd\",\n ];\n for (const hookName of Object.keys(plugin.hooks)) {\n if (!allowedHooks.includes(hookName)) {\n warnings.push(`Unknown hook '${hookName}' may not be called by the engine.`);\n } else if (typeof plugin.hooks[hookName] !== \"function\") {\n errors.push(`Hook '${hookName}' must be a function.`);\n }\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n }\n\n /**\n * Creates an in-memory test harness to test plugin execution end-to-end.\n */\n static createTestHarness(plugin: DevDiffPlugin) {\n const logs: string[] = [];\n const errorsCaught: any[] = [];\n\n const store: Record<string, any> = {};\n const mockContext: PluginContext = {\n devdiffVersion: \"1.7.0\",\n workspacePath: process.cwd(),\n config: {\n get: (key: string) => store[key],\n set: async (key: string, value: any) => {\n store[key] = value;\n },\n },\n storage: {\n get: async (key: string) => store[key],\n set: async (key: string, value: any) => {\n store[key] = value;\n },\n delete: async (key: string) => {\n delete store[key];\n },\n clear: async () => {\n for (const k of Object.keys(store)) delete store[k];\n },\n },\n notifications: {\n send: async (msg) => {\n logs.push(`[NOTIFY] ${msg}`);\n },\n sendToChannel: async (chan, msg) => {\n logs.push(`[NOTIFY:${chan}] ${msg}`);\n },\n },\n logger: {\n info: (msg) => logs.push(`[INFO] ${msg}`),\n warn: (msg) => logs.push(`[WARN] ${msg}`),\n error: (msg) => logs.push(`[ERROR] ${msg}`),\n debug: (msg) => logs.push(`[DEBUG] ${msg}`),\n },\n engine: {\n getStatus: async () => ({\n sessionActive: true,\n providerInfo: \"mock-ollama (llama3.2:3b)\",\n stagedCount: 1,\n unstagedCount: 0,\n workspacePath: process.cwd(),\n }),\n getProjectContext: async () => DevDiffDevTools.mockContext(),\n getRecentChanges: async () => [{ path: \"src/index.ts\", status: \"modified\" }],\n },\n };\n\n return {\n getLogs: () => [...logs],\n getErrors: () => [...errorsCaught],\n\n async activate() {\n if (plugin.activate) {\n await plugin.activate(mockContext);\n }\n },\n\n async deactivate() {\n if (plugin.deactivate) {\n await plugin.deactivate();\n }\n },\n\n async runBeforeAnalysis(diff?: ParsedDiff, context?: ProjectContext): Promise<ParsedDiff> {\n const inputDiff = diff || DevDiffDevTools.mockDiff();\n const inputContext = context || DevDiffDevTools.mockContext();\n if (plugin.hooks?.beforeAnalysis) {\n try {\n const res = await plugin.hooks.beforeAnalysis(inputDiff, inputContext);\n return res || inputDiff;\n } catch (err: any) {\n errorsCaught.push(err);\n if (plugin.hooks?.onError) {\n await plugin.hooks.onError({ name: err.name || \"Error\", message: err.message, stack: err.stack });\n }\n throw err;\n }\n }\n return inputDiff;\n },\n\n async runAfterAnalysis(changelog?: ChangelogResult): Promise<ChangelogResult> {\n const inputChangelog = changelog || DevDiffDevTools.mockChangelog();\n if (plugin.hooks?.afterAnalysis) {\n try {\n const res = await plugin.hooks.afterAnalysis(inputChangelog);\n return res || inputChangelog;\n } catch (err: any) {\n errorsCaught.push(err);\n if (plugin.hooks?.onError) {\n await plugin.hooks.onError({ name: err.name || \"Error\", message: err.message, stack: err.stack });\n }\n throw err;\n }\n }\n return inputChangelog;\n },\n };\n }\n\n /**\n * Benchmarks the execution speed and overhead of a plugin's hooks.\n */\n static async benchmarkPlugin(\n plugin: DevDiffPlugin,\n options?: { iterations?: number; sampleDiff?: ParsedDiff },\n ): Promise<BenchmarkResult> {\n const iterations = options?.iterations || 50;\n const diff = options?.sampleDiff || this.mockDiff({ filesCount: 5, additionsPerFile: 10 });\n const context = this.mockContext();\n const changelog = this.mockChangelog();\n\n const harness = this.createTestHarness(plugin);\n await harness.activate();\n\n const memBefore = process.memoryUsage().heapUsed;\n const start = performance.now();\n\n for (let i = 0; i < iterations; i++) {\n await harness.runBeforeAnalysis(diff, context);\n await harness.runAfterAnalysis(changelog);\n }\n\n const duration = performance.now() - start;\n const memAfter = process.memoryUsage().heapUsed;\n\n await harness.deactivate();\n\n return {\n pluginId: plugin.id,\n iterations,\n totalDurationMs: parseFloat(duration.toFixed(2)),\n averageDurationMs: parseFloat((duration / iterations).toFixed(3)),\n memoryDeltaBytes: Math.max(0, memAfter - memBefore),\n };\n }\n}\n"],"mappings":"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","names":[]} |
+4
-2
| { | ||
| "name": "@eldrex/plugin-sdk", | ||
| "version": "1.8.1", | ||
| "version": "1.9.0", | ||
| "description": "SDK for building DevDiff plugins", | ||
@@ -21,3 +21,4 @@ "type": "module", | ||
| "tsup": "^8.0.0", | ||
| "typescript": "^5.5.0" | ||
| "typescript": "^5.5.0", | ||
| "vitest": "^3.2.6" | ||
| }, | ||
@@ -91,4 +92,5 @@ "publishConfig": { | ||
| "dev": "tsup --watch", | ||
| "test": "vitest run", | ||
| "typecheck": "tsc --noEmit" | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
83704
283.49%889
335.78%1
-50%3
50%2
100%1
Infinity%