Skip to content

Commit ff1f4fa

Browse files
MrFlounderclaude
andcommitted
fix(pf): auto-detect deps and create package.json for providers
- writeProviderFile now detects imports and creates package.json - verify step runs npm install before promptfoo eval - Handles ws, node-fetch, axios automatically Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f099285 commit ff1f4fa

2 files changed

Lines changed: 56 additions & 1 deletion

File tree

plugins/promptfoo/src/agent/loop.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,19 @@ async function executeTool(
275275

276276
const configPath = configFile || state.configFile || 'promptfooconfig.yaml';
277277

278+
// Install dependencies if package.json exists
279+
const packageJsonPath = `${outputDir}/package.json`;
280+
if (fs.existsSync(packageJsonPath)) {
281+
try {
282+
execSync(`cd "${outputDir}" && npm install --silent 2>&1`, {
283+
timeout: 60000,
284+
encoding: 'utf-8',
285+
});
286+
} catch {
287+
// Ignore install errors, will fail in eval if deps missing
288+
}
289+
}
290+
278291
// Try to run promptfoo eval
279292
try {
280293
const output = execSync(

plugins/promptfoo/src/generator/config.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ ${Object.entries(envVars).map(([k, v]) => `# ${k}: ${v}`).join('\n') || '# (
119119
}
120120

121121
/**
122-
* Write a custom provider file
122+
* Write a custom provider file and package.json with dependencies
123123
*/
124124
export function writeProviderFile(options: {
125125
code: string;
@@ -153,9 +153,51 @@ export function writeProviderFile(options: {
153153

154154
fs.writeFileSync(filePath, fullCode, 'utf-8');
155155

156+
// For JS providers, detect and create package.json with dependencies
157+
if (filename.endsWith('.js')) {
158+
const deps = detectDependencies(code);
159+
if (Object.keys(deps).length > 0) {
160+
const packageJson = {
161+
name: 'promptfoo-provider',
162+
version: '1.0.0',
163+
type: 'module',
164+
dependencies: deps,
165+
};
166+
const packagePath = path.join(outputDir, 'package.json');
167+
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2), 'utf-8');
168+
}
169+
}
170+
156171
return filePath;
157172
}
158173

174+
/**
175+
* Detect npm dependencies from import statements in code
176+
*/
177+
function detectDependencies(code: string): Record<string, string> {
178+
const deps: Record<string, string> = {};
179+
180+
// Match: import X from 'package' or import { X } from 'package'
181+
const importRegex = /import\s+(?:[\w{}\s,*]+)\s+from\s+['"]([^'"./][^'"]*)['"]/g;
182+
let match;
183+
184+
while ((match = importRegex.exec(code)) !== null) {
185+
const pkg = match[1];
186+
// Skip node built-ins
187+
if (!pkg.startsWith('node:')) {
188+
// Common package versions
189+
const versions: Record<string, string> = {
190+
ws: '^8.18.0',
191+
'node-fetch': '^3.3.0',
192+
axios: '^1.6.0',
193+
};
194+
deps[pkg] = versions[pkg] || '*';
195+
}
196+
}
197+
198+
return deps;
199+
}
200+
159201
/**
160202
* Generate a simple HTTP provider config
161203
*/

0 commit comments

Comments
 (0)