@@ -2,6 +2,8 @@ import { existsSync, readFileSync, rmSync } from 'fs';
22import { tmpdir } from 'os' ;
33import { join } from 'path' ;
44import { afterEach , describe , expect , it } from 'vitest' ;
5+ import { execa } from 'execa' ;
6+ import { ConfigBuilder } from '../../config/builder.js' ;
57import { ProjectConfig } from '../../config/schema.js' ;
68import { ProjectGenerator } from '../../generator/index.js' ;
79
@@ -430,5 +432,105 @@ describe('ProjectGenerator Integration', () => {
430432 expect ( result . errors . length ) . toBeGreaterThan ( 0 ) ;
431433 expect ( result . errors [ 0 ] ) . toContain ( 'already exists' ) ;
432434 } ) ;
435+
436+ it ( 'should reject invalid project names before generation' , ( ) => {
437+ const validation = new ConfigBuilder ( )
438+ . setName ( 'Invalid Name With Spaces' )
439+ . setPath ( getTempProjectPath ( 'invalid-name' ) )
440+ . validate ( ) ;
441+
442+ expect ( validation . success ) . toBe ( false ) ;
443+ expect ( validation . errors ?. join ( ' ' ) ) . toMatch ( / n a m e | v a l i d a t i o n / i) ;
444+ } ) ;
445+
446+ it ( 'should continue project generation when git is unavailable' , async ( ) => {
447+ const config = createBaseConfig ( {
448+ name : 'no-git-available' ,
449+ git : { init : true , initialCommit : false } ,
450+ } ) ;
451+ projectPaths . push ( config . path ) ;
452+
453+ const originalPath = process . env . PATH ;
454+ process . env . PATH =
455+ process . platform === 'win32' ? 'C:\\nonexistent-git-bin' : '/nonexistent-git-bin' ;
456+
457+ try {
458+ const generator = new ProjectGenerator ( config ) ;
459+ const result = await generator . generate ( ) ;
460+
461+ expect ( result . success ) . toBe ( true ) ;
462+ expect ( result . warnings . join ( ' ' ) ) . toMatch ( / G i t i n i t i a l i z a t i o n f a i l e d / i) ;
463+ expect ( existsSync ( join ( config . path , 'package.json' ) ) ) . toBe ( true ) ;
464+ } finally {
465+ if ( originalPath === undefined ) {
466+ delete process . env . PATH ;
467+ } else {
468+ process . env . PATH = originalPath ;
469+ }
470+ }
471+ } ) ;
472+
473+ it ( 'should fail fast when npm registry is unreachable' , { timeout : 60000 } , async ( ) => {
474+ const config = createBaseConfig ( { name : 'network-timeout-test' } ) ;
475+ projectPaths . push ( config . path ) ;
476+
477+ const generator = new ProjectGenerator ( config ) ;
478+ const generation = await generator . generate ( ) ;
479+ expect ( generation . success ) . toBe ( true ) ;
480+
481+ const install = await execa (
482+ 'npm' ,
483+ [
484+ 'install' ,
485+ '--registry=http://127.0.0.1:9' ,
486+ `--cache=${ join ( config . path , '.npm-cache-network-failure' ) } ` ,
487+ '--prefer-offline=false' ,
488+ '--fetch-retries=0' ,
489+ '--fetch-timeout=1' ,
490+ '--fetch-retry-mintimeout=1' ,
491+ '--fetch-retry-maxtimeout=1' ,
492+ '--no-audit' ,
493+ '--no-fund' ,
494+ ] ,
495+ {
496+ cwd : config . path ,
497+ reject : false ,
498+ timeout : 30000 ,
499+ }
500+ ) ;
501+
502+ expect ( install . exitCode ) . not . toBe ( 0 ) ;
503+ expect ( `${ install . stdout } \n${ install . stderr } ` ) . toMatch (
504+ / E C O N N R E F U S E D | E T I M E D O U T | E A I _ A G A I N | E N O T F O U N D | n e t w o r k | f e t c h / i
505+ ) ;
506+ } ) ;
507+
508+ it ( 'should handle Windows path edge cases gracefully' , async ( ) => {
509+ if ( process . platform !== 'win32' ) {
510+ expect ( true ) . toBe ( true ) ;
511+ return ;
512+ }
513+
514+ const windowsStylePath = join (
515+ tmpdir ( ) ,
516+ `crf win edge ${ Date . now ( ) } ` ,
517+ 'nested path' ,
518+ 'app'
519+ ) . replace ( / \/ / g, '\\' ) ;
520+ const config = createBaseConfig ( {
521+ name : 'windows-path-edge' ,
522+ path : windowsStylePath ,
523+ git : { init : false , initialCommit : false } ,
524+ } ) ;
525+ projectPaths . push ( config . path ) ;
526+
527+ const generator = new ProjectGenerator ( config ) ;
528+ const result = await generator . generate ( ) ;
529+
530+ expect ( config . path ) . toContain ( '\\' ) ;
531+ expect ( result . success ) . toBe ( true ) ;
532+ expect ( result . errors ) . toHaveLength ( 0 ) ;
533+ expect ( existsSync ( join ( config . path , 'package.json' ) ) ) . toBe ( true ) ;
534+ } ) ;
433535 } ) ;
434536} ) ;
0 commit comments