@@ -23,6 +23,134 @@ export interface DumpOptions {
2323 skipGlobal ?: boolean // Skip global package processing for testing
2424}
2525
26+ /**
27+ * Check if packages are installed in the given environment directory
28+ */
29+ function checkMissingPackages ( packages : string [ ] , envDir : string ) : string [ ] {
30+ if ( packages . length === 0 )
31+ return [ ]
32+
33+ const pkgsDir = path . join ( envDir , 'pkgs' )
34+ if ( ! fs . existsSync ( pkgsDir ) ) {
35+ return packages // All packages are missing if pkgs dir doesn't exist
36+ }
37+
38+ const missingPackages : string [ ] = [ ]
39+
40+ for ( const packageSpec of packages ) {
41+ // Parse package spec (e.g., "php@^8.4.0" -> "php")
42+ const [ packageName ] = packageSpec . split ( '@' )
43+
44+ const packageDir = path . join ( pkgsDir , packageName )
45+ if ( ! fs . existsSync ( packageDir ) ) {
46+ missingPackages . push ( packageSpec )
47+ continue
48+ }
49+
50+ // Check if any version of this package is installed
51+ try {
52+ const versionDirs = fs . readdirSync ( packageDir , { withFileTypes : true } )
53+ . filter ( entry => entry . isDirectory ( ) && entry . name . startsWith ( 'v' ) )
54+
55+ if ( versionDirs . length === 0 ) {
56+ missingPackages . push ( packageSpec )
57+ }
58+ }
59+ catch {
60+ missingPackages . push ( packageSpec )
61+ }
62+ }
63+
64+ return missingPackages
65+ }
66+
67+ /**
68+ * Check if environment needs packages installed based on what's actually missing
69+ */
70+ function needsPackageInstallation ( localPackages : string [ ] , globalPackages : string [ ] , envDir : string , globalEnvDir : string ) : { needsLocal : boolean , needsGlobal : boolean , missingLocal : string [ ] , missingGlobal : string [ ] } {
71+ const missingLocal = checkMissingPackages ( localPackages , envDir )
72+ const missingGlobal = checkMissingPackages ( globalPackages , globalEnvDir )
73+
74+ return {
75+ needsLocal : missingLocal . length > 0 ,
76+ needsGlobal : missingGlobal . length > 0 ,
77+ missingLocal,
78+ missingGlobal,
79+ }
80+ }
81+
82+ /**
83+ * Detect if this is a Laravel project and provide setup assistance
84+ */
85+ function detectLaravelProject ( dir : string ) : { isLaravel : boolean , suggestions : string [ ] } {
86+ const artisanFile = path . join ( dir , 'artisan' )
87+ const composerFile = path . join ( dir , 'composer.json' )
88+ const appDir = path . join ( dir , 'app' )
89+
90+ if ( ! fs . existsSync ( artisanFile ) || ! fs . existsSync ( composerFile ) || ! fs . existsSync ( appDir ) ) {
91+ return { isLaravel : false , suggestions : [ ] }
92+ }
93+
94+ const suggestions : string [ ] = [ ]
95+
96+ // Check for .env file
97+ const envFile = path . join ( dir , '.env' )
98+ if ( ! fs . existsSync ( envFile ) ) {
99+ const envExample = path . join ( dir , '.env.example' )
100+ if ( fs . existsSync ( envExample ) ) {
101+ suggestions . push ( 'Copy .env.example to .env and configure database settings' )
102+ }
103+ }
104+
105+ // Check for database configuration
106+ if ( fs . existsSync ( envFile ) ) {
107+ try {
108+ const envContent = fs . readFileSync ( envFile , 'utf8' )
109+ if ( envContent . includes ( 'DB_CONNECTION=mysql' ) && ! envContent . includes ( 'DB_PASSWORD=' ) ) {
110+ suggestions . push ( 'Configure MySQL database credentials in .env file' )
111+ }
112+ if ( envContent . includes ( 'DB_CONNECTION=pgsql' ) && ! envContent . includes ( 'DB_PASSWORD=' ) ) {
113+ suggestions . push ( 'Configure PostgreSQL database credentials in .env file' )
114+ }
115+ if ( envContent . includes ( 'DB_CONNECTION=sqlite' ) ) {
116+ const dbFile = envContent . match ( / D B _ D A T A B A S E = ( .+ ) / ) ?. [ 1 ] ?. trim ( )
117+ if ( dbFile && ! fs . existsSync ( dbFile ) ) {
118+ suggestions . push ( `Create SQLite database file: touch ${ dbFile } ` )
119+ }
120+ }
121+ }
122+ catch {
123+ // Ignore errors reading .env file
124+ }
125+ }
126+
127+ // Check if migrations have been run
128+ try {
129+ const databaseDir = path . join ( dir , 'database' )
130+ const migrationsDir = path . join ( databaseDir , 'migrations' )
131+ if ( fs . existsSync ( migrationsDir ) ) {
132+ const migrations = fs . readdirSync ( migrationsDir ) . filter ( f => f . endsWith ( '.php' ) )
133+ if ( migrations . length > 0 ) {
134+ suggestions . push ( 'Run database migrations: php artisan migrate' )
135+
136+ // Check for seeders
137+ const seedersDir = path . join ( databaseDir , 'seeders' )
138+ if ( fs . existsSync ( seedersDir ) ) {
139+ const seeders = fs . readdirSync ( seedersDir ) . filter ( f => f . endsWith ( '.php' ) && f !== 'DatabaseSeeder.php' )
140+ if ( seeders . length > 0 ) {
141+ suggestions . push ( 'Seed database with test data: php artisan db:seed' )
142+ }
143+ }
144+ }
145+ }
146+ }
147+ catch {
148+ // Ignore errors checking migrations
149+ }
150+
151+ return { isLaravel : true , suggestions }
152+ }
153+
26154export async function dump ( dir : string , options : DumpOptions = { } ) : Promise < void > {
27155 const { dryrun = false , quiet = false , shellOutput = false , skipGlobal = process . env . NODE_ENV === 'test' || process . env . LAUNCHPAD_SKIP_GLOBAL_AUTO_SCAN === 'true' || process . env . LAUNCHPAD_ENABLE_GLOBAL_AUTO_SCAN !== 'true' } = options
28156
@@ -261,10 +389,19 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
261389 const globalBinPath = path . join ( globalEnvDir , 'bin' )
262390 const globalSbinPath = path . join ( globalEnvDir , 'sbin' )
263391
264- // If environments don't exist, trigger minimal installation
265- if ( ! fs . existsSync ( envBinPath ) && ( localPackages . length > 0 || globalPackages . length > 0 ) ) {
266- // Only install if we have packages and no environment
267- await installPackagesOptimized ( localPackages , globalPackages , envDir , globalEnvDir , dryrun , effectiveQuiet )
392+ // Check what packages are actually missing and need installation
393+ const packageStatus = needsPackageInstallation ( localPackages , globalPackages , envDir , globalEnvDir )
394+
395+ // Install missing packages if any are found
396+ if ( packageStatus . needsLocal || packageStatus . needsGlobal ) {
397+ await installPackagesOptimized (
398+ packageStatus . missingLocal ,
399+ packageStatus . missingGlobal ,
400+ envDir ,
401+ globalEnvDir ,
402+ dryrun ,
403+ effectiveQuiet ,
404+ )
268405 }
269406
270407 outputShellCode ( dir , envBinPath , envSbinPath , projectHash , sniffResult , globalBinPath , globalSbinPath )
@@ -277,6 +414,16 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
277414 await installPackagesOptimized ( localPackages , globalPackages , envDir , globalEnvDir , dryrun , effectiveQuiet )
278415 }
279416
417+ // Check for Laravel project and provide helpful suggestions
418+ const laravelInfo = detectLaravelProject ( projectDir )
419+ if ( laravelInfo . isLaravel && laravelInfo . suggestions . length > 0 && ! effectiveQuiet ) {
420+ console . log ( '\n🎯 Laravel project detected! Helpful commands:' )
421+ laravelInfo . suggestions . forEach ( ( suggestion ) => {
422+ console . log ( ` • ${ suggestion } ` )
423+ } )
424+ console . log ( )
425+ }
426+
280427 // Output shell code if requested
281428 if ( shellOutput ) {
282429 const envBinPath = path . join ( envDir , 'bin' )
0 commit comments