Skip to content

Add support for adding new npm packages BEN-1072 #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,27 @@ export async function POST(request: NextRequest) {
const generatedFiles = await generateApp(description);

// Repair the generated code using Benchify's API
const { data } = await benchify.fixer.run({
files: generatedFiles.map(file => ({
path: file.path,
contents: file.content
}))
});
// const { data } = await benchify.fixer.run({
// files: generatedFiles.map(file => ({
// path: file.path,
// contents: file.content
// }))
// });

let repairedFiles = generatedFiles;
if (data) {
const { success, diff } = data;
// if (data) {
// const { success, diff } = data;

if (success && diff) {
repairedFiles = generatedFiles.map(file => {
const patchResult = applyPatch(file.content, diff);
return {
...file,
content: typeof patchResult === 'string' ? patchResult : file.content
};
});
}
}
// if (success && diff) {
// repairedFiles = generatedFiles.map(file => {
// const patchResult = applyPatch(file.content, diff);
// return {
// ...file,
// content: typeof patchResult === 'string' ? patchResult : file.content
// };
// });
// }
// }

const { sbxId, template, url, allFiles } = await createSandbox({ files: repairedFiles });

Expand Down
52 changes: 52 additions & 0 deletions lib/e2b.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,30 @@ export async function createSandbox({ files }: { files: z.infer<typeof benchifyF

await sandbox.files.write(filesToWrite);

// Check if package.json was written and install only new dependencies
const packageJsonFile = transformedFiles.find(file => file.path === 'package.json');
if (packageJsonFile) {
console.log('package.json detected, checking for new dependencies...');
try {
const newPackages = extractNewPackages(packageJsonFile.content);

if (newPackages.length > 0) {
console.log('Installing new packages:', newPackages);
const installCmd = `cd /app && npm install ${newPackages.join(' ')} --no-save`;
const result = await sandbox.commands.run(installCmd);
console.log('New packages installed successfully:', result.stdout);
if (result.stderr) {
console.warn('npm install warnings:', result.stderr);
}
} else {
console.log('No new packages to install');
}
} catch (error) {
console.error('Failed to install new packages:', error);
// Don't throw here, let the sandbox continue - users can still work with basic dependencies
}
}

// Get all files from the sandbox using the improved filter logic
const allFiles = await fetchAllSandboxFiles(sandbox);

Expand All @@ -40,3 +64,31 @@ export async function createSandbox({ files }: { files: z.infer<typeof benchifyF
};
}

function extractNewPackages(packageJsonContent: string): string[] {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Graceful Error Handling

The function returns an empty list if JSON parsing fails, indicating graceful error handling.

Outcome Example Input # Inputs % of Total
superjson.parse('{"json":[["au"]]}')... view full input 200 100.0%

view all inputs
The test has passed, which means that the extractNewPackages function is correctly returning an empty list when given a malformed JSON string as input. This behavior aligns with the expected property description, indicating that the function is handling JSON parsing errors gracefully.

Unit Tests
// Unit Test for "Graceful Error Handling": The function returns an empty list if JSON parsing fails, indicating graceful error handling.
function benchify_s(s) {
    return s.replace(/[^a-zA-Z0-9]/g, 'a');
}

it('benchify_s_exec_test_passing_0', () => {
  const args = superjson.parse('{"json":[["au"]]}');

  benchify_s(...args);
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Exclusion of Base Packages and Formatting

The function excludes packages from basePackages and formats remaining dependencies as package@version.

Outcome Example Input # Inputs % of Total
superjson.parse('{"json":[[{"dependencies":{"ku... view full input 305 33.8%
superjson.parse('{"json":[[{"dependencies":{"OL... view full input 597 66.2%

view all inputs
The test failed due to an AssertionError in the extractNewPackages function. Specifically, the test failed when checking that the extracted package "!. <!3@" is correctly formatted as "!. <!3@@h)\\oZ\"_"; however, it was expected to be simply "!. <!3@". This suggests that the function is not correctly formatting the package names and versions.

Stack Trace
Error: expect(received).toBe(expected)

Expected: "\". <!3@"
Received: "\". <!3@@h)\\oZ\"_"

    at toBe (unknown)
    at <anonymous> (/app/repo/lib/pver_4ac73b7e-ded8-47a2-be60-e2b44321fd09.test.ts:63:23)
    at forEach (native:1:11)
    at <anonymous> (/app/repo/lib/pver_4ac73b7e-ded8-47a2-be60-e2b44321fd09.test.ts:60:27)
    at <anonymous> (/app/configuration/fc.setup.ts:183:11)
    at run (/app/node_modules/fast-check/lib/esm/check/property/Property.generic.js:46:33)
    at runIt (/app/node_modules/fast-check/lib/esm/check/runner/Runner.js:18:30)
    at check (/app/node_modules/fast-check/lib/esm/check/runner/Runner.js:62:11)
    at <anonymous> (/app/configuration/fc.setup.ts:197:14)
    at assertWithLogging (/app/configuration/fc.setup.ts:125:3)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ JSON Parsing and Dependencies Extraction

The function correctly parses a valid JSON input string to extract the dependencies object.

Outcome Example Input # Inputs % of Total
superjson.parse('{"json":[[{"\'rXHL][":"k]ZV?{L... view full input 200 100.0%

view all inputs
The test has passed successfully. The extractNewPackages function correctly parsed the provided JSON input and extracted the dependencies object, excluding the base packages as expected. The function's error handling mechanism also worked as intended, returning an empty array in case of a parsing error.

Unit Tests
// Unit Test for "JSON Parsing and Dependencies Extraction": The function correctly parses a valid JSON input string to extract the dependencies object.
function benchify_dependencies(dependencies) {
    const packageJson = JSON.stringify({ dependencies });
    const result = extractNewPackages(packageJson);
    const basePackages = ['react', 'react-dom', '@vitejs/plugin-react', 'tailwindcss', '@tailwindcss/vite', 'typescript', 'vite'];
    for (const newPackage of result) {
        const pkgName = newPackage.split('@')[0];
        expect(basePackages).not.toContain(pkgName);
    }
}

it('benchify_dependencies_exec_test_passing_0', () => {
  const args = superjson.parse(
    '{"json":[[{"\'rXHL][":"k]ZV?{L","IX608m?":"zg0>|w*K,","rB3rpfy":"}N\'[SOYq","/":"2IV","W&jY;{e^G":"bI{ {iI","~THB":"nQL\\\\","Lw;/{Qmz\\\\m":"%mQJP"}]]}',
  );

  benchify_dependencies(...args);
});

try {
const packageJson = JSON.parse(packageJsonContent);
const dependencies = packageJson.dependencies || {};

// Base packages that are already installed in the template
const basePackages = [
'react',
'react-dom',
'@vitejs/plugin-react',
'tailwindcss',
'@tailwindcss/vite',
'typescript',
'vite'
];

// Find packages that aren't in our base template
const newPackages = Object.entries(dependencies)
.filter(([pkg]) => !basePackages.includes(pkg))
.map(([pkg, version]) => `${pkg}@${version}`);

return newPackages;
} catch (error) {
console.error('Error parsing package.json:', error);
return [];
}
}

6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.