@@ -3,7 +3,7 @@ import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'fs'
33import { join } from 'path'
44import { tmpdir } from 'os'
55import { existsSync , readFileSync } from 'fs'
6- import { detectTestRunner , detectPackageManager , detectMonorepo , runTests , stripLocalUvSources } from '../test-runner.js'
6+ import { detectTestRunner , detectPackageManager , detectMonorepo , runTests , stripLocalUvSources , stripHeavyPyDeps } from '../test-runner.js'
77
88// Helper: create a temp directory with specific files
99function createTempDir ( ) : string {
@@ -351,6 +351,190 @@ pkg-c = { path = "/absolute/path/to/pkg-c" }
351351 } )
352352} )
353353
354+ describe ( 'stripHeavyPyDeps' , ( ) => {
355+ let tempDir : string
356+
357+ beforeEach ( ( ) => {
358+ tempDir = createTempDir ( )
359+ } )
360+
361+ afterEach ( ( ) => {
362+ rmSync ( tempDir , { recursive : true , force : true } )
363+ } )
364+
365+ it ( 'strips heavy packages from base dependencies' , ( ) => {
366+ const pyprojectContent = `[project]
367+ name = "test-project"
368+ dependencies = [
369+ "requests>=2.28.0",
370+ "torch>=2.8.0",
371+ "torchvision>=0.24.1",
372+ "pillow>=10.0.0",
373+ "open-clip-torch>=2.20.0",
374+ "transformers>=4.57.3",
375+ "bitsandbytes>=0.41.0",
376+ "peft>=0.18.0",
377+ ]
378+
379+ [tool.hatch.build.targets.wheel]
380+ packages = ["my_package"]
381+ `
382+ touchFile ( tempDir , 'pyproject.toml' , pyprojectContent )
383+ touchFile ( tempDir , 'uv.lock' , 'some lock content' )
384+
385+ stripHeavyPyDeps ( tempDir )
386+
387+ const result = readFileSync ( join ( tempDir , 'pyproject.toml' ) , 'utf-8' )
388+ // Heavy packages should be removed
389+ expect ( result ) . not . toContain ( 'torch>=2.8.0' )
390+ expect ( result ) . not . toContain ( 'torchvision' )
391+ expect ( result ) . not . toContain ( 'open-clip-torch' )
392+ expect ( result ) . not . toContain ( 'transformers' )
393+ expect ( result ) . not . toContain ( 'bitsandbytes' )
394+ expect ( result ) . not . toContain ( 'peft' )
395+ // Lightweight packages should be preserved
396+ expect ( result ) . toContain ( 'requests>=2.28.0' )
397+ expect ( result ) . toContain ( 'pillow>=10.0.0' )
398+ // Other sections should be preserved
399+ expect ( result ) . toContain ( '[project]' )
400+ expect ( result ) . toContain ( '[tool.hatch.build.targets.wheel]' )
401+ // uv.lock should be deleted
402+ expect ( existsSync ( join ( tempDir , 'uv.lock' ) ) ) . toBe ( false )
403+ } )
404+
405+ it ( 'strips entire [project.optional-dependencies] section' , ( ) => {
406+ const pyprojectContent = `[project]
407+ name = "test-project"
408+ dependencies = [
409+ "requests>=2.28.0",
410+ ]
411+
412+ [project.optional-dependencies]
413+ dev = [
414+ "pytest>=8.0.0",
415+ ]
416+ training = [
417+ "torch>=2.8.0",
418+ "trl>=0.12.0",
419+ ]
420+ azure = [
421+ "azure-ai-ml>=1.12.0",
422+ ]
423+
424+ [tool.hatch.build.targets.wheel]
425+ packages = ["my_package"]
426+ `
427+ touchFile ( tempDir , 'pyproject.toml' , pyprojectContent )
428+
429+ stripHeavyPyDeps ( tempDir )
430+
431+ const result = readFileSync ( join ( tempDir , 'pyproject.toml' ) , 'utf-8' )
432+ // Entire optional-dependencies section should be removed
433+ expect ( result ) . not . toContain ( '[project.optional-dependencies]' )
434+ expect ( result ) . not . toContain ( 'pytest>=8.0.0' )
435+ expect ( result ) . not . toContain ( 'trl>=0.12.0' )
436+ expect ( result ) . not . toContain ( 'azure-ai-ml' )
437+ // Base deps and other sections should be preserved
438+ expect ( result ) . toContain ( 'requests>=2.28.0' )
439+ expect ( result ) . toContain ( '[tool.hatch.build.targets.wheel]' )
440+ } )
441+
442+ it ( 'handles nvidia-* prefix packages' , ( ) => {
443+ const pyprojectContent = `[project]
444+ name = "test-project"
445+ dependencies = [
446+ "requests>=2.28.0",
447+ "nvidia-cublas-cu12>=12.1.0",
448+ "nvidia-cuda-runtime-cu12>=12.0",
449+ ]
450+ `
451+ touchFile ( tempDir , 'pyproject.toml' , pyprojectContent )
452+
453+ stripHeavyPyDeps ( tempDir )
454+
455+ const result = readFileSync ( join ( tempDir , 'pyproject.toml' ) , 'utf-8' )
456+ expect ( result ) . not . toContain ( 'nvidia-cublas' )
457+ expect ( result ) . not . toContain ( 'nvidia-cuda-runtime' )
458+ expect ( result ) . toContain ( 'requests>=2.28.0' )
459+ } )
460+
461+ it ( 'preserves pyproject.toml with no heavy deps' , ( ) => {
462+ const pyprojectContent = `[project]
463+ name = "test-project"
464+ dependencies = [
465+ "requests>=2.28.0",
466+ "pillow>=10.0.0",
467+ ]
468+ `
469+ touchFile ( tempDir , 'pyproject.toml' , pyprojectContent )
470+ touchFile ( tempDir , 'uv.lock' , 'some lock content' )
471+
472+ stripHeavyPyDeps ( tempDir )
473+
474+ const result = readFileSync ( join ( tempDir , 'pyproject.toml' ) , 'utf-8' )
475+ expect ( result ) . toBe ( pyprojectContent )
476+ // uv.lock should NOT be deleted when no changes were made
477+ expect ( existsSync ( join ( tempDir , 'uv.lock' ) ) ) . toBe ( true )
478+ } )
479+
480+ it ( 'handles openadapt-evals-like pyproject.toml' , ( ) => {
481+ const pyprojectContent = `[project]
482+ name = "openadapt-evals"
483+ version = "0.46.0"
484+ dependencies = [
485+ "open-clip-torch>=2.20.0",
486+ "pillow>=10.0.0",
487+ "pydantic-settings>=2.0.0",
488+ "requests>=2.28.0",
489+ "openai>=1.0.0",
490+ "anthropic>=0.76.0",
491+ "openadapt-ml>=0.11.0",
492+ ]
493+
494+ [project.optional-dependencies]
495+ dev = [
496+ "pytest>=8.0.0",
497+ "ruff>=0.1.0",
498+ ]
499+ training = [
500+ "imagehash>=4.3.0",
501+ ]
502+ verl = [
503+ "verl>=0.3.0",
504+ ]
505+
506+ [tool.uv.sources]
507+ openadapt-ml = { path = "../openadapt-ml", editable = true }
508+
509+ [tool.hatch.build.targets.wheel]
510+ packages = ["openadapt_evals"]
511+ `
512+ touchFile ( tempDir , 'pyproject.toml' , pyprojectContent )
513+
514+ stripHeavyPyDeps ( tempDir )
515+
516+ const result = readFileSync ( join ( tempDir , 'pyproject.toml' ) , 'utf-8' )
517+ // Heavy base dep should be stripped
518+ expect ( result ) . not . toContain ( 'open-clip-torch' )
519+ // Optional deps section entirely removed
520+ expect ( result ) . not . toContain ( '[project.optional-dependencies]' )
521+ expect ( result ) . not . toContain ( 'verl' )
522+ // Lightweight base deps preserved
523+ expect ( result ) . toContain ( 'pillow>=10.0.0' )
524+ expect ( result ) . toContain ( 'requests>=2.28.0' )
525+ expect ( result ) . toContain ( 'openai>=1.0.0' )
526+ expect ( result ) . toContain ( 'anthropic>=0.76.0' )
527+ expect ( result ) . toContain ( 'openadapt-ml>=0.11.0' )
528+ // Other sections preserved
529+ expect ( result ) . toContain ( '[tool.uv.sources]' )
530+ expect ( result ) . toContain ( '[tool.hatch.build.targets.wheel]' )
531+ } )
532+
533+ it ( 'does nothing when pyproject.toml does not exist' , ( ) => {
534+ expect ( ( ) => stripHeavyPyDeps ( tempDir ) ) . not . toThrow ( )
535+ } )
536+ } )
537+
354538describe ( 'runTests with real commands' , ( ) => {
355539 let tempDir : string
356540
0 commit comments