@@ -21,9 +21,9 @@ package super
2121import (
2222 "bytes"
2323 "fmt"
24- "io"
2524 "os"
2625 "path/filepath"
26+ "strings"
2727
2828 "github.com/onflow/flowkit/v2"
2929
@@ -42,22 +42,32 @@ import (
4242 "github.com/onflow/flow-cli/internal/util"
4343)
4444
45- type flagsSetup struct {
45+ type flagsInit struct {
4646 ConfigOnly bool `default:"false" flag:"config-only" info:"Only create a flow.json default config"`
4747}
4848
49- var setupFlags = flagsSetup {}
49+ var initFlags = flagsInit {}
50+
51+ const (
52+ // File permissions for created directories
53+ defaultDirPerm = 0755
54+ // Core Flow project files that indicate an existing Flow project
55+ flowConfigFile = "flow.json"
56+ // README files
57+ defaultReadmeFile = "README.md"
58+ flowReadmeFile = "README_flow.md"
59+ )
5060
5161// TODO: Add --config-only flag
52- var SetupCommand = & command.Command {
62+ var InitCommand = & command.Command {
5363 Cmd : & cobra.Command {
5464 Use : "init <project name>" ,
5565 Short : "Start a new Flow project" ,
5666 Example : "flow init my-project" ,
5767 Args : cobra .MaximumNArgs (1 ),
5868 GroupID : "super" ,
5969 },
60- Flags : & setupFlags ,
70+ Flags : & initFlags ,
6171 Run : create ,
6272}
6373
@@ -71,7 +81,7 @@ func create(
7181 var targetDir string
7282 var err error
7383
74- if setupFlags .ConfigOnly {
84+ if initFlags .ConfigOnly {
7585 if len (args ) > 0 {
7686 return nil , fmt .Errorf ("project name not required when using --config-only flag" )
7787 }
@@ -85,13 +95,61 @@ func create(
8595
8696 return nil , nil
8797 } else {
88- targetDir , err = startInteractiveSetup (args , logger )
98+ targetDir , err = startInteractiveInit (args , logger )
8999 if err != nil {
90100 return nil , err
91101 }
92102 }
93103
94- return & setupResult {targetDir : targetDir }, nil
104+ return & initResult {targetDir : targetDir }, nil
105+ }
106+
107+ func validateCurrentDirectoryForInit () error {
108+ pwd , err := os .Getwd ()
109+ if err != nil {
110+ return err
111+ }
112+
113+ // Only check for core Flow project files that would cause real conflicts
114+ coreFlowPaths := []string {
115+ flowConfigFile ,
116+ cadenceDir ,
117+ }
118+
119+ var conflicts []string
120+ for _ , path := range coreFlowPaths {
121+ fullPath := filepath .Join (pwd , path )
122+ if _ , err := os .Stat (fullPath ); err == nil {
123+ conflicts = append (conflicts , path )
124+ }
125+ }
126+
127+ if len (conflicts ) > 0 {
128+ return fmt .Errorf ("Flow project files already exist: %s. Cannot initialize Flow project in directory with existing Flow files" , strings .Join (conflicts , ", " ))
129+ }
130+
131+ return nil
132+ }
133+
134+ // resolveTargetDirectory determines the target directory for the Flow project
135+ // based on user input. Empty input means current directory.
136+ func resolveTargetDirectory (userInput string ) (string , error ) {
137+ if strings .TrimSpace (userInput ) == "" {
138+ // Validate current directory for Flow project conflicts
139+ if err := validateCurrentDirectoryForInit (); err != nil {
140+ return "" , err
141+ }
142+
143+ // Use current directory
144+ pwd , err := os .Getwd ()
145+ if err != nil {
146+ return "" , fmt .Errorf ("failed to get current working directory: %w" , err )
147+ }
148+ return pwd , nil
149+ }
150+
151+ // Use provided name to create new directory
152+ return getTargetDirectory (userInput )
95153}
96154
97155func updateGitignore (targetDir string , readerWriter flowkit.ReaderWriter ) error {
@@ -133,7 +191,7 @@ func createConfigOnly(targetDir string, readerWriter flowkit.ReaderWriter) error
133191 return nil
134192}
135193
136- func startInteractiveSetup (
194+ func startInteractiveInit (
137195 args []string ,
138196 logger output.Logger ,
139197) (string , error ) {
@@ -155,22 +213,20 @@ func startInteractiveSetup(
155213 Fs : afero .NewOsFs (),
156214 }
157215
158- // Ask for project name if not given
216+ // Resolve target directory from arguments or user input
217+ var userInput string
159218 if len (args ) < 1 {
160- userInput , err : = prompt .RunTextInput ("Enter the name of your project" , "Type your project name here..." )
219+ userInput , err = prompt .RunTextInput ("Enter the name of your project (leave blank to use current directory) " , "Type your project name here or press Enter for current directory ..." )
161220 if err != nil {
162221 return "" , fmt .Errorf ("error running project name: %v" , err )
163222 }
164-
165- targetDir , err = getTargetDirectory (userInput )
166- if err != nil {
167- return "" , err
168- }
169223 } else {
170- targetDir , err = getTargetDirectory (args [0 ])
171- if err != nil {
172- return "" , err
173- }
224+ userInput = args [0 ]
225+ }
226+
227+ targetDir , err = resolveTargetDirectory (userInput )
228+ if err != nil {
229+ return "" , err
174230 }
175231
176232 // Create a temp directory which will later be moved to the target directory if successful
@@ -212,6 +268,9 @@ func startInteractiveSetup(
212268 // cadence/tests/DefaultContract_test.cdc
213269 // README.md
214270
271+ // Determine README filename - avoid conflicts with existing README.md
272+ readmeFileName := getReadmeFileName (targetDir )
273+
215274 templates := []generator.TemplateItem {
216275 generator.ContractTemplate {
217276 Name : "Counter" ,
@@ -229,7 +288,7 @@ func startInteractiveSetup(
229288 },
230289 generator.FileTemplate {
231290 TemplatePath : "README.md.tmpl" ,
232- TargetPath : "README.md" ,
291+ TargetPath : readmeFileName ,
233292 Data : map [string ]interface {}{
234293 "Dependencies" : (func () []map [string ]interface {} {
235294 contracts := []map [string ]interface {}{}
@@ -277,69 +336,64 @@ func startInteractiveSetup(
277336 return "" , err
278337 }
279338
280- // Move the temp directory to the target directory
281- err = os .Rename (tempDir , targetDir )
282- if err != nil {
283- return "" , fmt .Errorf ("failed to move temp directory to target directory: %w" , err )
284- }
285-
286- return targetDir , nil
287- }
288-
289- // getTargetDirectory checks if the specified directory path is suitable for use.
290- // It verifies that the path points to an existing, empty directory.
291- // If the directory does not exist, the function returns the path without error,
292- // indicating that the path is available for use (assuming creation is handled elsewhere).
293- func getTargetDirectory (directory string ) (string , error ) {
294- pwd , err := os .Getwd ()
295- if err != nil {
296- return "" , err
297- }
298-
299- target := filepath .Join (pwd , directory )
300- info , err := os .Stat (target )
301- if ! os .IsNotExist (err ) {
302- if ! info .IsDir () {
303- return "" , fmt .Errorf ("%s is a file" , target )
304- }
305-
306- file , err := os .Open (target )
339+ // Move or copy the temp directory contents to the target directory
340+ pwd , _ := os .Getwd ()
341+ if targetDir == pwd {
342+ // For current directory, copy contents instead of moving the directory
343+ err = copyDirContents (tempDir , targetDir )
307344 if err != nil {
308- return "" , err
345+ return "" , fmt . Errorf ( "failed to copy temp directory contents to current directory: %w" , err )
309346 }
310- defer file . Close ()
311-
312- _ , err = file . Readdirnames ( 1 )
313- if err != io . EOF {
314- return "" , fmt .Errorf ("directory is not empty : %s " , target )
347+ } else {
348+ // For new directory, move the entire temp directory
349+ err = os . Rename ( tempDir , targetDir )
350+ if err != nil {
351+ return "" , fmt .Errorf ("failed to move temp directory to target directory : %w " , err )
315352 }
316353 }
317- return target , nil
354+
355+ return targetDir , nil
318356}
319357
320- type setupResult struct {
358+ type initResult struct {
321359 targetDir string
322360}
323361
324- func (s * setupResult ) String () string {
362+ func (s * initResult ) String () string {
325363 wd , _ := os .Getwd ()
326364 relDir , _ := filepath .Rel (wd , s .targetDir )
327365 out := bytes.Buffer {}
328366
329367 out .WriteString (fmt .Sprintf ("%s Congrats! your project was created.\n \n " , output .SuccessEmoji ()))
368+
369+ // Check if we created README_flow.md instead of README.md
370+ readmeFile := defaultReadmeFile
371+ if _ , err := os .Stat (filepath .Join (s .targetDir , flowReadmeFile )); err == nil {
372+ readmeFile = flowReadmeFile
373+ out .WriteString ("📝 Note: Created README_flow.md since README.md already exists.\n \n " )
374+ }
375+
330376 out .WriteString ("Start development by following these steps:\n " )
331- out .WriteString (fmt .Sprintf ("1. '%s' to change to your new project,\n " , output .Bold (fmt .Sprintf ("cd %s" , relDir ))))
332- out .WriteString (fmt .Sprintf ("2. '%s' or run Flowser to start the emulator,\n " , output .Bold ("flow emulator" )))
333- out .WriteString (fmt .Sprintf ("3. '%s' to test your project.\n \n " , output .Bold ("flow test" )))
334- out .WriteString (fmt .Sprintf ("You should also read README.md to learn more about the development process!\n " ))
377+
378+ // Only show cd command if not current directory
379+ if s .targetDir != wd {
380+ out .WriteString (fmt .Sprintf ("1. '%s' to change to your new project,\n " , output .Bold (fmt .Sprintf ("cd %s" , relDir ))))
381+ out .WriteString (fmt .Sprintf ("2. '%s' to start the emulator,\n " , output .Bold ("flow emulator" )))
382+ out .WriteString (fmt .Sprintf ("3. '%s' to test your project.\n \n " , output .Bold ("flow test" )))
383+ } else {
384+ out .WriteString (fmt .Sprintf ("1. '%s' to start the emulator,\n " , output .Bold ("flow emulator" )))
385+ out .WriteString (fmt .Sprintf ("2. '%s' to test your project.\n \n " , output .Bold ("flow test" )))
386+ }
387+
388+ out .WriteString (fmt .Sprintf ("You should also read %s to learn more about the development process!\n " , readmeFile ))
335389
336390 return out .String ()
337391}
338392
339- func (s * setupResult ) Oneliner () string {
393+ func (s * initResult ) Oneliner () string {
340394 return fmt .Sprintf ("Project created inside %s" , s .targetDir )
341395}
342396
343- func (s * setupResult ) JSON () any {
397+ func (s * initResult ) JSON () any {
344398 return nil
345399}
0 commit comments