66 "net/http"
77 "os"
88 "path/filepath"
9+ "regexp"
910 "slices"
1011 "strings"
1112
@@ -493,21 +494,18 @@ func configurePublishing(ctx context.Context, flags ConfigureGithubFlags) error
493494 }
494495
495496 secrets := make (map [string ]string )
496- var publishPaths , generationWorkflowFilePaths [] string
497+ workflowPaths := make ( map [ string ] targetWorkflowPaths )
497498
498499 for _ , name := range chosenTargets {
499- generationWorkflow , generationWorkflowFilePath , newPaths , err := writePublishingFile (workflowFile , workflowFile .Targets [name ], name , rootDir , actionWorkingDir )
500+ generationWorkflow , targetWorkflowPaths , err := writePublishingFile (workflowFile , workflowFile .Targets [name ], name , rootDir , actionWorkingDir )
500501 if err != nil {
501502 return err
502503 }
503504 for key , val := range generationWorkflow .Jobs .Generate .Secrets {
504505 secrets [key ] = val
505506 }
506507
507- if len (newPaths ) > 0 {
508- publishPaths = append (publishPaths , newPaths ... )
509- }
510- generationWorkflowFilePaths = append (generationWorkflowFilePaths , generationWorkflowFilePath )
508+ workflowPaths [name ] = targetWorkflowPaths
511509 }
512510
513511 if err := workflow .Save (filepath .Join (rootDir , actionWorkingDir ), workflowFile ); err != nil {
@@ -532,6 +530,12 @@ func configurePublishing(ctx context.Context, flags ConfigureGithubFlags) error
532530 status := []string {
533531 fmt .Sprintf ("Speakeasy workflow written to - %s" , workflowFilePath ),
534532 }
533+
534+ var publishPaths , generationWorkflowFilePaths []string
535+ for _ , wfp := range workflowPaths {
536+ publishPaths = append (publishPaths , wfp .publishWorkflowPaths ... )
537+ generationWorkflowFilePaths = append (generationWorkflowFilePaths , wfp .generationWorkflowPath )
538+ }
535539 if len (publishPaths ) > 0 {
536540 status = append (status , "GitHub action (generate) written to:" )
537541 for _ , path := range generationWorkflowFilePaths {
@@ -548,31 +552,138 @@ func configurePublishing(ctx context.Context, flags ConfigureGithubFlags) error
548552 }
549553 }
550554
551- var agenda []string
555+ agenda := []string {
556+ fmt .Sprintf ("• On GitHub navigate to %s and set up the following repository secrets:" , secretPath ),
557+ }
558+
552559 for key := range secrets {
553560 if key != config .GithubAccessToken {
554561 agenda = append (agenda , fmt .Sprintf ("\t ◦ Provide a secret with name %s" , styles .MakeBold (strings .ToUpper (key ))))
555562 }
556563 }
557- agenda = append (agenda , fmt .Sprintf ("• Push your repository to github! Navigate to %s to kick of your first publish." , actionPath ))
564+
565+ agenda = append (agenda , fmt .Sprintf ("• Push your repository to GitHub and navigate to %s to kick off your first publish!" , actionPath ))
566+
567+ // Add instructions for NPM Trusted Publishing (typescript/mcp-typescript)
568+ npmTrustedPublishingConfigs := make (map [string ]NPMTrustedPublishingConfig )
569+ for name , wfp := range workflowPaths {
570+ target := workflowFile .Targets [name ]
571+ if target .Publishing .NPM != nil {
572+ var publishPath string
573+ switch len (wfp .publishWorkflowPaths ) {
574+ case 0 :
575+ // No publish path means generation and publishing are combined into a single workflow
576+ publishPath = wfp .generationWorkflowPath
577+ case 1 :
578+ publishPath = wfp .publishWorkflowPaths [0 ]
579+ default :
580+ // For typescript/mcp-typescript, if the publish and generation workflow files
581+ // are distinct (pr mode), then we only expect a single publish path.
582+ return errors .Wrapf (err , "multiple publish workflow paths found for target %s" , name )
583+ }
584+
585+ // Get the packageName from the config file
586+ packageName := "<packageName>"
587+ outDir := ""
588+ if target .Output != nil {
589+ outDir = * target .Output
590+ }
591+ workflowDir := filepath .Join (rootDir , actionWorkingDir )
592+ configPath := filepath .Join (workflowDir , outDir )
593+ cfg , err := config .Load (configPath )
594+ if err == nil {
595+ if langCfg , ok := cfg .Config .Languages [target .Target ]; ok {
596+ if pkgName , ok := langCfg .Cfg ["packageName" ].(string ); ok {
597+ packageName = pkgName
598+ }
599+ }
600+ }
601+
602+ npmTrustedPublishingConfigs [name ] = NPMTrustedPublishingConfig {
603+ target : target ,
604+ workflowDir : filepath .Join (rootDir , actionWorkingDir ),
605+ actionPath : actionPath ,
606+ publishFileName : filepath .Base (publishPath ),
607+ packageName : packageName ,
608+ remoteURL : remoteURL ,
609+ }
610+ }
611+ }
612+ agenda = append (agenda , getNPMTrustedPublishingInstructions (ctx , npmTrustedPublishingConfigs )... )
558613
559614 logger .Println (styles .Info .Render ("Files successfully generated!\n " ))
560615 for _ , statusMsg := range status {
561616 logger .Println (styles .Info .Render (fmt .Sprintf ("• %s" , statusMsg )))
562617 }
563618 logger .Println (styles .Info .Render ("\n " ))
564619
565- if len (agenda ) != 0 {
566- agenda = append ([]string {
567- fmt .Sprintf ("• In your repo navigate to %s and setup the following repository secrets:" , secretPath ),
568- }, agenda ... )
620+ msg := styles .RenderInstructionalMessage ("For your publishing setup to be complete perform the following steps." ,
621+ agenda ... )
622+ logger .Println (msg )
623+
624+ return nil
625+ }
626+
627+ type NPMTrustedPublishingConfig = struct {
628+ target workflow.Target
629+ workflowDir string
630+ actionPath string
631+ publishFileName string
632+ packageName string
633+ remoteURL string
634+ }
635+
636+ func getNPMTrustedPublishingInstructions (ctx context.Context , npmConfigs map [string ]NPMTrustedPublishingConfig ) []string {
637+ var agenda []string
638+
639+ // Collect unique action paths
640+ actionPaths := make (map [string ][]string )
641+ for _ , npmConfig := range npmConfigs {
642+ if _ , exists := actionPaths [npmConfig .actionPath ]; ! exists {
643+ actionPaths [npmConfig .actionPath ] = []string {}
644+ }
645+ actionPaths [npmConfig .actionPath ] = append (actionPaths [npmConfig .actionPath ], npmConfig .packageName )
646+ }
647+
648+ for targetName , npmConfig := range npmConfigs {
649+ repoOwner := "<user>"
650+ repoName := "<repository>"
651+ if npmConfig .remoteURL != "" {
652+ // Expected format: "https://github.com/<user>/<repository>"
653+ re := regexp .MustCompile (`^https://github\.com/([^/]+)/([^/]+)/?$` )
654+ matches := re .FindStringSubmatch (npmConfig .remoteURL )
655+ if len (matches ) == 3 {
656+ repoOwner = matches [1 ]
657+ repoName = matches [2 ]
658+ }
659+ }
569660
570- msg := styles .RenderInstructionalMessage ("For your publishing setup to be complete perform the following steps." ,
571- agenda ... )
572- logger .Println (msg )
661+ if len (npmConfigs ) == 1 {
662+ agenda = append (agenda , fmt .Sprintf ("• Access your newly published package's settings at https://www.npmjs.com/package/%s/access" , npmConfig .packageName ))
663+ } else {
664+ agenda = append (agenda , fmt .Sprintf ("• [%s] Access the '%s' package's settings at https://www.npmjs.com/package/%s/access" , strings .ToUpper (npmConfig .target .Target ), targetName , npmConfig .packageName ))
665+ }
666+
667+ configLines := []string {
668+ fmt .Sprintf ("\t \t - Organization or user: %s" , repoOwner ),
669+ fmt .Sprintf ("\t \t - Repository: %s" , repoName ),
670+ fmt .Sprintf ("\t \t - Workflow filename: %s" , npmConfig .publishFileName ),
671+ "\t \t - Environment name: <Leave Blank>" ,
672+ }
673+ agenda = append (agenda , fmt .Sprintf ("\t ◦ Add 'GitHub Actions' as a 'Trusted Publisher' with the following configuration:\n %s" , strings .Join (configLines , "\n " )))
573674 }
574675
575- return nil
676+ for actionPath , packageNames := range actionPaths {
677+ if len (packageNames ) == 1 {
678+ agenda = append (agenda , fmt .Sprintf ("• Navigate to %s to regenerate and publish a new version of the %s package." , actionPath , packageNames [0 ]))
679+ agenda = append (agenda , fmt .Sprintf ("• Your package's latest version should now include a 'Provenance' at https://www.npmjs.com/package/%s#provenance" , packageNames [0 ]))
680+ } else {
681+ agenda = append (agenda , fmt .Sprintf ("• Navigate to %s to regenerate and publish new versions of your packages." , actionPath ))
682+ agenda = append (agenda , "• Your packages' latest versions should now be labelled with a green check mark and include a 'Provenance'." )
683+ }
684+ }
685+
686+ return agenda
576687}
577688
578689func configureTesting (ctx context.Context , flags ConfigureTestsFlags ) error {
@@ -898,7 +1009,7 @@ func configureGithub(ctx context.Context, flags ConfigureGithubFlags) error {
8981009 }
8991010
9001011 if len (secrets ) > 2 || ! autoConfigureRepoSuccess {
901- agenda = append (agenda , fmt .Sprintf ("• In your repo navigate to %s and setup the following repository secrets:" , secretPath ))
1012+ agenda = append (agenda , fmt .Sprintf ("• On GitHub navigate to %s and set up the following repository secrets:" , secretPath ))
9021013 }
9031014
9041015 for key := range secrets {
@@ -955,35 +1066,42 @@ func writeGenerationFile(workflowFile *workflow.Workflow, workingDir, workflowFi
9551066 return generationWorkflow , generationWorkflowFilePath , nil
9561067}
9571068
958- func writePublishingFile (wf * workflow.Workflow , target workflow.Target , targetName , currentWorkingDir , workflowFileDir string ) (* config.GenerateWorkflow , string , []string , error ) {
959- generationWorkflowFilePath := filepath .Join (currentWorkingDir , ".github/workflows/sdk_generation.yaml" )
1069+ type targetWorkflowPaths struct {
1070+ generationWorkflowPath string
1071+ publishWorkflowPaths []string
1072+ }
1073+
1074+ func writePublishingFile (wf * workflow.Workflow , target workflow.Target , targetName , currentWorkingDir , workflowFileDir string ) (* config.GenerateWorkflow , targetWorkflowPaths , error ) {
1075+ paths := targetWorkflowPaths {}
1076+ paths .generationWorkflowPath = filepath .Join (currentWorkingDir , ".github/workflows/sdk_generation.yaml" )
9601077 if len (wf .Targets ) > 1 {
9611078 sanitizedName := strings .ReplaceAll (strings .ToLower (targetName ), "-" , "_" )
962- generationWorkflowFilePath = filepath .Join (currentWorkingDir , fmt .Sprintf (".github/workflows/sdk_generation_%s.yaml" , sanitizedName ))
1079+ paths . generationWorkflowPath = filepath .Join (currentWorkingDir , fmt .Sprintf (".github/workflows/sdk_generation_%s.yaml" , sanitizedName ))
9631080 }
9641081
9651082 if _ , err := os .Stat (filepath .Join (currentWorkingDir , ".github/workflows" )); os .IsNotExist (err ) {
9661083 err = os .MkdirAll (filepath .Join (currentWorkingDir , ".github/workflows" ), 0o755 )
9671084 if err != nil {
968- return nil , "" , nil , err
1085+ return nil , paths , err
9691086 }
9701087 }
9711088
9721089 generationWorkflow := & config.GenerateWorkflow {}
973- if err := prompts .ReadGenerationFile (generationWorkflow , generationWorkflowFilePath ); err != nil {
974- return nil , "" , nil , fmt .Errorf ("you cannot run configure publishing when a github workflow file %s does not exist, try speakeasy configure github" , generationWorkflowFilePath )
1090+ if err := prompts .ReadGenerationFile (generationWorkflow , paths . generationWorkflowPath ); err != nil {
1091+ return nil , paths , fmt .Errorf ("you cannot run configure publishing when a github workflow file %s does not exist, try speakeasy configure github" , paths . generationWorkflowPath )
9751092 }
9761093
9771094 publishPaths , err := prompts .WritePublishing (wf , generationWorkflow , targetName , currentWorkingDir , workflowFileDir , target )
9781095 if err != nil {
979- return nil , "" , nil , errors .Wrapf (err , "failed to write publishing configs" )
1096+ return nil , paths , errors .Wrapf (err , "failed to write publishing configs" )
9801097 }
9811098
982- if err = prompts .WriteGenerationFile (generationWorkflow , generationWorkflowFilePath ); err != nil {
983- return nil , "" , nil , errors .Wrapf (err , "failed to write github workflow file" )
1099+ paths .publishWorkflowPaths = publishPaths
1100+ if err = prompts .WriteGenerationFile (generationWorkflow , paths .generationWorkflowPath ); err != nil {
1101+ return nil , paths , errors .Wrapf (err , "failed to write github workflow file" )
9841102 }
9851103
986- return generationWorkflow , generationWorkflowFilePath , publishPaths , nil
1104+ return generationWorkflow , paths , nil
9871105}
9881106
9891107func handleLegacySDKTarget (workingDir string , workflowFile * workflow.Workflow ) ([]string , []huh.Option [string ]) {
0 commit comments