@@ -29,6 +29,49 @@ export interface DumpOptions {
29
29
skipGlobal ?: boolean // Skip global package processing for testing
30
30
}
31
31
32
+ function extractHookCommandsFromDepsYaml ( filePath : string , hookName : 'preSetup' | 'postSetup' | 'preActivation' | 'postActivation' ) : PostSetupCommand [ ] {
33
+ try {
34
+ const content = fs . readFileSync ( filePath , 'utf8' )
35
+ const lines = content . split ( / \r ? \n / )
36
+ const cmds : PostSetupCommand [ ] = [ ]
37
+ let inHook = false
38
+ let inCommands = false
39
+ let baseIndent = 0
40
+ for ( let i = 0 ; i < lines . length ; i ++ ) {
41
+ const raw = lines [ i ]
42
+ const indent = raw . length - raw . trimStart ( ) . length
43
+ const line = raw . trim ( )
44
+ if ( ! inHook ) {
45
+ if ( line . startsWith ( `${ hookName } :` ) ) {
46
+ inHook = true
47
+ baseIndent = indent
48
+ }
49
+ continue
50
+ }
51
+ if ( indent <= baseIndent && line . endsWith ( ':' ) ) {
52
+ break
53
+ }
54
+ if ( ! inCommands && line . startsWith ( 'commands:' ) ) {
55
+ inCommands = true
56
+ continue
57
+ }
58
+ if ( inCommands ) {
59
+ const m1 = line . match ( / c o m m a n d : \s * " ( [ ^ " ] + ) " / )
60
+ const m2 = line . match ( / c o m m a n d : \s * ' ( [ ^ ' ] + ) ' / )
61
+ if ( m1 || m2 ) {
62
+ const cmd = ( m1 ?. [ 1 ] || m2 ?. [ 1 ] ) as string
63
+ if ( cmd && cmd . length > 0 )
64
+ cmds . push ( { command : cmd } )
65
+ }
66
+ }
67
+ }
68
+ return cmds
69
+ }
70
+ catch {
71
+ return [ ]
72
+ }
73
+ }
74
+
32
75
/**
33
76
* Check if packages are installed in the given environment directory
34
77
*/
@@ -199,6 +242,13 @@ export async function detectLaravelProject(dir: string): Promise<{ isLaravel: bo
199
242
// Ignore errors checking migrations
200
243
}
201
244
245
+ // Execute project-level post-setup commands if enabled (skip in shell integration fast path)
246
+ const projectPreSetup = config . preSetup
247
+ if ( projectPreSetup ?. enabled && process . env . LAUNCHPAD_SHELL_INTEGRATION !== '1' ) {
248
+ const preSetupResults = await executepostSetup ( dir , projectPreSetup . commands || [ ] )
249
+ suggestions . push ( ...preSetupResults )
250
+ }
251
+
202
252
// Execute project-level post-setup commands if enabled (skip in shell integration fast path)
203
253
const projectPostSetup = config . postSetup
204
254
if ( projectPostSetup ?. enabled && process . env . LAUNCHPAD_SHELL_INTEGRATION !== '1' ) {
@@ -589,6 +639,20 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
589
639
try {
590
640
// Find dependency file using our comprehensive detection
591
641
const dependencyFile = findDependencyFile ( dir )
642
+ // Early pre-setup hook (before any installs/services) when config present in working dir
643
+ try {
644
+ if ( dependencyFile ) {
645
+ const fileCmds = extractHookCommandsFromDepsYaml ( dependencyFile , 'preSetup' )
646
+ if ( fileCmds . length > 0 ) {
647
+ await executepostSetup ( path . dirname ( dependencyFile ) , fileCmds )
648
+ }
649
+ }
650
+ const projectPreSetup = config . preSetup
651
+ if ( projectPreSetup ?. enabled && ! shellOutput && ! quiet && dependencyFile ) {
652
+ await executepostSetup ( path . dirname ( dependencyFile ) , projectPreSetup . commands || [ ] )
653
+ }
654
+ }
655
+ catch { }
592
656
593
657
if ( ! dependencyFile ) {
594
658
if ( ! quiet && ! shellOutput ) {
@@ -840,11 +904,30 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
840
904
if ( localPackages . length > 0 || globalPackages . length > 0 ) {
841
905
await installPackagesOptimized ( localPackages , globalPackages , envDir , globalEnvDir , dryrun , quiet )
842
906
// Visual separator after dependency install list
843
- try { console . log ( ) }
907
+ try {
908
+ console . log ( )
909
+ }
844
910
catch { }
845
911
}
846
912
847
913
// Auto-start services for any project that has services configuration
914
+ // Pre-activation hook (runs after install/services and before shell activation)
915
+ const preActivation = config . preActivation
916
+ if ( dependencyFile ) {
917
+ const filePostSetup = extractHookCommandsFromDepsYaml ( dependencyFile , 'postSetup' )
918
+ if ( filePostSetup . length > 0 ) {
919
+ await executepostSetup ( projectDir , filePostSetup )
920
+ }
921
+ }
922
+ if ( preActivation ?. enabled && ! isShellIntegration ) {
923
+ await executepostSetup ( projectDir , preActivation . commands || [ ] )
924
+ }
925
+ if ( dependencyFile ) {
926
+ const filePreActivation = extractHookCommandsFromDepsYaml ( dependencyFile , 'preActivation' )
927
+ if ( filePreActivation . length > 0 ) {
928
+ await executepostSetup ( projectDir , filePreActivation )
929
+ }
930
+ }
848
931
// Suppress interstitial processing messages during service startup phase
849
932
const prevProcessing = process . env . LAUNCHPAD_PROCESSING
850
933
process . env . LAUNCHPAD_PROCESSING = '0'
@@ -858,6 +941,13 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
858
941
await ensureProjectPhpIni ( projectDir , envDir )
859
942
await maybeRunLaravelPostSetup ( projectDir , envDir , isShellIntegration )
860
943
944
+ // Mark environment as ready for fast shell activation on subsequent prompts
945
+ try {
946
+ await fs . promises . mkdir ( path . join ( envDir ) , { recursive : true } )
947
+ await fs . promises . writeFile ( path . join ( envDir , '.launchpad_ready' ) , '1' )
948
+ }
949
+ catch { }
950
+
861
951
// Check for Laravel project and provide helpful suggestions
862
952
const laravelInfo = await detectLaravelProject ( projectDir )
863
953
if ( laravelInfo . isLaravel ) {
@@ -885,6 +975,18 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
885
975
const activation = ( config . shellActivationMessage || '✅ Environment activated for {path}' )
886
976
. replace ( '{path}' , process . cwd ( ) )
887
977
console . log ( activation )
978
+
979
+ // Post-activation hook (file-level then config)
980
+ if ( dependencyFile ) {
981
+ const filePostActivation = extractHookCommandsFromDepsYaml ( dependencyFile , 'postActivation' )
982
+ if ( filePostActivation . length > 0 ) {
983
+ await executepostSetup ( projectDir , filePostActivation )
984
+ }
985
+ }
986
+ const postActivation = config . postActivation
987
+ if ( postActivation ?. enabled ) {
988
+ await executepostSetup ( projectDir , postActivation . commands || [ ] )
989
+ }
888
990
}
889
991
}
890
992
catch ( error ) {
0 commit comments