@@ -43,11 +43,18 @@ import (
4343 "github.com/spf13/afero"
4444)
4545
46+ // copyIgnoreDirectories are directories to skip when copying a template.
47+ var copyIgnoreDirectories = []string {".git" , ".venv" , "node_modules" }
48+
49+ // copyIgnoreFiles are files to skip when copying a template.
50+ var copyIgnoreFiles = []string {".DS_Store" }
51+
4652// CreateArgs are the arguments passed into the Create function
4753type CreateArgs struct {
4854 AppName string
4955 Template Template
5056 GitBranch string
57+ Subdir string
5158}
5259
5360// Create will create a new Slack app on the file system and app manifest on the Slack API.
@@ -119,8 +126,19 @@ func Create(ctx context.Context, clients *shared.ClientFactory, log *logger.Logg
119126 }))
120127
121128 // Create the project from a templateURL
122- if err := createApp (ctx , projectDirPath , createArgs .Template , createArgs .GitBranch , log , clients .Fs ); err != nil {
123- return "" , slackerror .Wrap (err , slackerror .ErrAppCreate )
129+ subdir , err := normalizeSubdir (createArgs .Subdir )
130+ if err != nil {
131+ return "" , err
132+ }
133+
134+ if subdir != "" {
135+ if err := createAppFromSubdir (ctx , projectDirPath , createArgs .Template , createArgs .GitBranch , subdir , log , clients .Fs ); err != nil {
136+ return "" , slackerror .Wrap (err , slackerror .ErrAppCreate )
137+ }
138+ } else {
139+ if err := createApp (ctx , projectDirPath , createArgs .Template , createArgs .GitBranch , log , clients .Fs ); err != nil {
140+ return "" , slackerror .Wrap (err , slackerror .ErrAppCreate )
141+ }
124142 }
125143
126144 // Change into the project directory to configure defaults and dependencies
@@ -315,8 +333,8 @@ func createApp(ctx context.Context, dirPath string, template Template, gitBranch
315333 copyDirectoryOpts := goutils.CopyDirectoryOpts {
316334 Src : template .path ,
317335 Dst : dirPath ,
318- IgnoreDirectories : [] string { ".git" , ".venv" , "node_modules" } ,
319- IgnoreFiles : [] string { ".DS_Store" } ,
336+ IgnoreDirectories : copyIgnoreDirectories ,
337+ IgnoreFiles : copyIgnoreFiles ,
320338 }
321339 if err := goutils .CopyDirectory (copyDirectoryOpts ); err != nil {
322340 return slackerror .Wrap (err , "error copying local template" )
@@ -333,6 +351,60 @@ func createApp(ctx context.Context, dirPath string, template Template, gitBranch
333351 return nil
334352}
335353
354+ // normalizeSubdir cleans the subdir path and returns "" if it resolves to root.
355+ func normalizeSubdir (subdir string ) (string , error ) {
356+ if subdir == "" {
357+ return "" , nil
358+ }
359+ cleaned := filepath .Clean (subdir )
360+ if cleaned == "." || cleaned == "/" {
361+ return "" , nil
362+ }
363+ if ! filepath .IsLocal (cleaned ) {
364+ return "" , slackerror .New (slackerror .ErrSubdirNotFound ).
365+ WithMessage ("Subdirectory path %q must be relative and within the template" , subdir )
366+ }
367+ return cleaned , nil
368+ }
369+
370+ // createAppFromSubdir clones the full template into a temp directory, then copies
371+ // only the specified subdirectory to the final project path.
372+ func createAppFromSubdir (ctx context.Context , dirPath string , template Template , gitBranch string , subdir string , log * logger.Logger , fs afero.Fs ) error {
373+ tmpDirRoot := afero .GetTempDir (fs , "" )
374+ tmpDir , err := afero .TempDir (fs , tmpDirRoot , "slack-create-" )
375+ if err != nil {
376+ return slackerror .Wrap (err , "failed to create temporary directory" )
377+ }
378+ defer func () { _ = fs .RemoveAll (tmpDir ) }()
379+
380+ cloneDir := filepath .Join (tmpDir , "repo" )
381+ if err := createApp (ctx , cloneDir , template , gitBranch , log , fs ); err != nil {
382+ return err
383+ }
384+
385+ subdirPath := filepath .Join (cloneDir , subdir )
386+ info , err := fs .Stat (subdirPath )
387+ if err != nil {
388+ if os .IsNotExist (err ) {
389+ return slackerror .New (slackerror .ErrSubdirNotFound ).
390+ WithMessage ("Subdirectory %q was not found in the template" , subdir ).
391+ WithRemediation ("Check that the path exists in the template at %q" , template .GetTemplatePath ())
392+ }
393+ return slackerror .Wrap (err , "failed to access subdirectory" )
394+ }
395+ if ! info .IsDir () {
396+ return slackerror .New (slackerror .ErrSubdirNotFound ).
397+ WithMessage ("Path %q in the template is not a directory" , subdir )
398+ }
399+
400+ return goutils .CopyDirectory (goutils.CopyDirectoryOpts {
401+ Src : subdirPath ,
402+ Dst : dirPath ,
403+ IgnoreDirectories : copyIgnoreDirectories ,
404+ IgnoreFiles : copyIgnoreFiles ,
405+ })
406+ }
407+
336408// InstallProjectDependencies installs the project runtime dependencies or
337409// continues with next steps if that fails. You can specify the manifestSource
338410// for the project configuration file (default: ManifestSourceLocal)
0 commit comments