@@ -60,6 +60,7 @@ type SealightsParameters struct {
6060 ProxyPassword string
6161 ProjectRoot string
6262 TestStage string
63+ NpmRunScript string
6364}
6465
6566type SealightsRunOptions struct {
@@ -155,8 +156,17 @@ func (sl *SealightsHook) SetApplicationStartInProcfile(stager *libbuildpack.Stag
155156 originalStartCommand := string (bytes )
156157 _ , usePackageJson := sl .usePackageJson (originalStartCommand , stager )
157158 if usePackageJson {
159+ // Extract script name from command or use configured default
160+ scriptName , err := sl .ExtractNpmRunScriptName (originalStartCommand )
161+ if err != nil {
162+ sl .Log .Warning ("Failed to extract script name from command '%s', using configured default: %s" , originalStartCommand , err )
163+ scriptName = sl .parameters .NpmRunScript
164+ if scriptName == "" {
165+ scriptName = "start"
166+ }
167+ }
158168 // move to package json scenario
159- return sl .SetApplicationStartInPackageJson (stager )
169+ return sl .SetApplicationStartInPackageJson (stager , scriptName )
160170 }
161171
162172 // we suppose that format is "web: node <application>"
@@ -182,6 +192,62 @@ func (sl *SealightsHook) SetApplicationStartInProcfile(stager *libbuildpack.Stag
182192 return nil
183193}
184194
195+ func (sl * SealightsHook ) ExtractNpmRunScriptName (command string ) (string , error ) {
196+ // Remove leading "web:" prefix if present
197+ cleanCommand := strings .TrimSpace (command )
198+ if strings .HasPrefix (cleanCommand , "web:" ) {
199+ cleanCommand = strings .TrimSpace (cleanCommand [4 :])
200+ }
201+
202+ // Handle commands with cd prefix (e.g., "cd app && npm run start")
203+ if strings .Contains (cleanCommand , "&&" ) {
204+ parts := strings .Split (cleanCommand , "&&" )
205+ if len (parts ) >= 2 {
206+ cleanCommand = strings .TrimSpace (parts [len (parts )- 1 ])
207+ }
208+ }
209+
210+ // Extract script name from npm commands
211+ // Patterns to match:
212+ // - "npm start" -> "start"
213+ // - "npm run start-dev" -> "start-dev"
214+ // - "npm run dev" -> "dev"
215+ patterns := []string {
216+ `^npm\s+run\s+([a-zA-Z0-9\-_]+)` , // npm run <script>
217+ `^npm\s+([a-zA-Z0-9\-_]+)` , // npm <script>
218+ }
219+
220+ for _ , pattern := range patterns {
221+ re , err := regexp .Compile (pattern )
222+ if err != nil {
223+ sl .Log .Warning ("Failed to compile regex pattern %s: %s" , pattern , err )
224+ continue
225+ }
226+
227+ matches := re .FindStringSubmatch (cleanCommand )
228+ if len (matches ) >= 2 {
229+ scriptName := matches [1 ]
230+ sl .Log .Debug ("Extracted npm script name: %s from command: %s" , scriptName , command )
231+ return scriptName , nil
232+ }
233+ }
234+
235+ return "" , fmt .Errorf ("failed to extract npm script name from command: %s" , command )
236+ }
237+
238+ func (sl * SealightsHook ) ValidateNpmRunScript (packageJson map [string ]interface {}, scriptName string ) error {
239+ scripts , ok := packageJson ["scripts" ].(map [string ]interface {})
240+ if ! ok || scripts == nil {
241+ return fmt .Errorf ("no scripts section found in package.json" )
242+ }
243+
244+ if _ , exists := scripts [scriptName ]; ! exists {
245+ return fmt .Errorf ("script '%s' not found in package.json" , scriptName )
246+ }
247+
248+ return nil
249+ }
250+
185251func (sl * SealightsHook ) usePackageJson (originalStartCommand string , stager * libbuildpack.Stager ) (error , bool ) {
186252
187253 isNpmCommand , err := regexp .MatchString (`(^(web:\s)?cd[^&]*\s&&\snpm)|(^(web:\s)?npm)` , originalStartCommand )
@@ -251,26 +317,43 @@ func (sl *SealightsHook) getSealightsOptions(app string) *SealightsRunOptions {
251317 return o
252318}
253319
254- func (sl * SealightsHook ) SetApplicationStartInPackageJson (stager * libbuildpack.Stager ) error {
320+ func (sl * SealightsHook ) SetApplicationStartInPackageJson (stager * libbuildpack.Stager , targetScript string ) error {
255321 packageJson , err := sl .ReadPackageJson (stager )
256322 if err != nil {
257323 return err
258324 }
259- scripts , _ := packageJson ["scripts" ].(map [string ]interface {})
260- if scripts == nil {
261- return fmt .Errorf ("failed to read scripts from %s" , PackageJsonFile )
325+
326+ // Validate that the target script exists
327+ err = sl .ValidateNpmRunScript (packageJson , targetScript )
328+ if err != nil {
329+ // Try fallback to "start" if configured script doesn't exist
330+ if targetScript != "start" {
331+ sl .Log .Warning ("Script '%s' not found, falling back to 'start': %s" , targetScript , err )
332+ fallbackErr := sl .ValidateNpmRunScript (packageJson , "start" )
333+ if fallbackErr != nil {
334+ return fmt .Errorf ("target script '%s' not found and fallback to 'start' failed: %s" , targetScript , fallbackErr )
335+ }
336+ targetScript = "start"
337+ } else {
338+ return err
339+ }
262340 }
263- originalStartScript , _ := scripts ["start" ].(string )
341+
342+ scripts := packageJson ["scripts" ].(map [string ]interface {})
343+ originalStartScript , _ := scripts [targetScript ].(string )
264344 if originalStartScript == "" {
265- return fmt .Errorf ("failed to read start from scripts in %s" , PackageJsonFile )
345+ return fmt .Errorf ("failed to read %s script from %s" , targetScript , PackageJsonFile )
266346 }
267- // we suppose that format is "start: node <application>"
347+
348+ // Update the command with Sealights injection
268349 var newCmd string
269350 newCmd , err = sl .updateStartCommand (originalStartScript )
270351 if err != nil {
271352 return err
272353 }
273- packageJson ["scripts" ].(map [string ]interface {})["start" ] = newCmd
354+
355+ sl .Log .Debug ("Injecting Sealights into '%s' script: %s -> %s" , targetScript , originalStartScript , newCmd )
356+ scripts [targetScript ] = newCmd
274357
275358 err = libbuildpack .NewJSON ().Write (filepath .Join (stager .BuildDir (), PackageJsonFile ), packageJson )
276359 if err != nil {
@@ -284,9 +367,9 @@ func (sl *SealightsHook) SetApplicationStartInPackageJson(stager *libbuildpack.S
284367func (sl * SealightsHook ) ReadPackageJson (stager * libbuildpack.Stager ) (map [string ]interface {}, error ) {
285368 p := map [string ]interface {}{}
286369
287- if err := libbuildpack .NewJSON ().Load (filepath .Join (stager .BuildDir (), "package.json" ), & p ); err != nil {
370+ if err := libbuildpack .NewJSON ().Load (filepath .Join (stager .BuildDir (), PackageJsonFile ), & p ); err != nil {
288371 if err != nil {
289- sl .Log .Error ("failed to read %s error: %s" , Procfile , err .Error ())
372+ sl .Log .Error ("failed to read %s error: %s" , PackageJsonFile , err .Error ())
290373 return nil , err
291374 }
292375 }
@@ -303,11 +386,20 @@ func (sl *SealightsHook) SetApplicationStartInManifest(stager *libbuildpack.Stag
303386
304387 _ , usePackageJson := sl .usePackageJson (originalStartCommand , stager )
305388 if usePackageJson {
389+ // Extract script name from command or use configured default
390+ scriptName , err := sl .ExtractNpmRunScriptName (originalStartCommand )
391+ if err != nil {
392+ sl .Log .Warning ("Failed to extract script name from command '%s', using configured default: %s" , originalStartCommand , err )
393+ scriptName = sl .parameters .NpmRunScript
394+ if scriptName == "" {
395+ scriptName = "start"
396+ }
397+ }
306398 // move to package json scenario
307- return sl .SetApplicationStartInPackageJson (stager )
399+ return sl .SetApplicationStartInPackageJson (stager , scriptName )
308400 }
309401
310- // we suppose that format is "start: node <application>"
402+ // we suppose that format is "node <application>"
311403 var newCmd string
312404 newCmd , err = sl .updateStartCommand (originalStartCommand )
313405 if err != nil {
@@ -437,8 +529,13 @@ func (sl *SealightsHook) injectSealights(stager *libbuildpack.Stager) error {
437529 sl .Log .Info ("Integrating sealights into manifest.yml" )
438530 return sl .SetApplicationStartInManifest (stager )
439531 } else {
440- sl .Log .Info ("Integrating sealights into package.json" )
441- return sl .SetApplicationStartInPackageJson (stager )
532+ sl .Log .Info ("Integrating sealights into package.json" )
533+ // Use configured script name or default to "start"
534+ scriptName := sl .parameters .NpmRunScript
535+ if scriptName == "" {
536+ scriptName = "start"
537+ }
538+ return sl .SetApplicationStartInPackageJson (stager , scriptName )
442539 }
443540}
444541
@@ -487,6 +584,7 @@ func (sl *SealightsHook) parseVcapServices() {
487584 ProxyPassword : queryString ("proxyPassword" ),
488585 ProjectRoot : queryString ("projectRoot" ),
489586 TestStage : queryString ("testStage" ),
587+ NpmRunScript : queryString ("npmRunScript" ),
490588 }
491589
492590 // write warning in case token is not provided
0 commit comments