Skip to content

Commit 9f8bcdf

Browse files
authored
feat: monorepo (#167)
* feat: monorepo * chore: dont allow path outside current root * making messages mayus * feat: add push and pull (#169) * chore: adding apps of monorepo to hx config * fix: load hx for apps inside a monorepo * chore: check direcotry exists * feat:push pull * fix: global hx should not have isMonorepo and workspace * renaming vars * chore: using rel * deliting de world member * fix typo * chore: simplify default response for hx push (#170) * chore: adding apps of monorepo to hx config * fix: load hx for apps inside a monorepo * chore: check direcotry exists * feat:push pull * fix: global hx should not have isMonorepo and workspace * chore: adding emojis, making the output of push smaller * chore: making the error symbol red * chore: new message when no envs pushed * refactor: adding magic to the messages
1 parent 4f83def commit 9f8bcdf

File tree

10 files changed

+298
-122
lines changed

10 files changed

+298
-122
lines changed

cmd/env/pull/pull.go

+48
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,49 @@ func RunPull(args []string, forceFlag bool) error {
5858
return err
5959
}
6060

61+
// Check if this is a monorepo
62+
if manifest.IsMonorepoProject() && manifest.Project != nil {
63+
// Store current directory
64+
currentDir, err := os.Getwd()
65+
if err != nil {
66+
return fmt.Errorf("failed to get current directory: %w", err)
67+
}
68+
69+
// Pull for each workspace app
70+
for _, appDir := range manifest.Project.Apps {
71+
if !Silent {
72+
printer.Print(fmt.Sprintf("Pulling for workspace app: %s", appDir))
73+
}
74+
75+
// Change to app directory
76+
err = os.Chdir(appDir)
77+
if err != nil {
78+
printer.Warning(fmt.Sprintf("Failed to change to directory %s: %s", appDir, err))
79+
continue
80+
}
81+
82+
// Run pull for this app
83+
err = pullForApp(args, forceFlag)
84+
if err != nil {
85+
printer.Warning(fmt.Sprintf("Failed to pull for app %s: %s", appDir, err))
86+
}
87+
88+
// Change back to original directory
89+
err = os.Chdir(currentDir)
90+
if err != nil {
91+
return fmt.Errorf("failed to return to original directory: %w", err)
92+
}
93+
}
94+
95+
return nil
96+
}
97+
98+
// If not a monorepo, proceed with regular pull
99+
return pullForApp(args, forceFlag)
100+
}
101+
102+
// pullForApp contains the original pull logic
103+
func pullForApp(args []string, forceFlag bool) error {
61104
db, err := database.Restore()
62105
if err != nil {
63106
return err
@@ -80,6 +123,11 @@ func RunPull(args []string, forceFlag bool) error {
80123
return err
81124
}
82125

126+
manifest, err := manifest.Restore()
127+
if err != nil {
128+
return err
129+
}
130+
83131
var envName string
84132
if len(args) == 1 {
85133
envName = args[0]

cmd/env/push/push.go

+66-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package push
22

33
import (
44
"fmt"
5+
"os"
56
"strings"
67

78
"github.com/Hyphen/cli/internal/database"
@@ -54,6 +55,54 @@ func RunPush(args []string, secretKeyId int64) error {
5455
return err
5556
}
5657

58+
// Check if this is a monorepo
59+
if manifest.IsMonorepoProject() && manifest.Project != nil {
60+
// Store current directory
61+
currentDir, err := os.Getwd()
62+
if err != nil {
63+
return fmt.Errorf("failed to get current directory: %w", err)
64+
}
65+
66+
// Push for each workspace member
67+
for _, memberDir := range manifest.Project.Apps {
68+
if !Silent {
69+
printer.Print(fmt.Sprintf("Pushing for workspace member: %s", memberDir))
70+
}
71+
72+
// Change to member directory
73+
err = os.Chdir(memberDir)
74+
if err != nil {
75+
printer.Warning(fmt.Sprintf("Failed to change to directory %s: %s", memberDir, err))
76+
continue
77+
}
78+
79+
// Run push for this member
80+
err = pushForMember(args, secretKeyId)
81+
if err != nil {
82+
printer.Warning(fmt.Sprintf("Failed to push for member %s: %s", memberDir, err))
83+
}
84+
85+
// Change back to original directory
86+
err = os.Chdir(currentDir)
87+
if err != nil {
88+
return fmt.Errorf("failed to return to original directory: %w", err)
89+
}
90+
}
91+
92+
return nil
93+
}
94+
95+
// If not a monorepo, proceed with regular push
96+
return pushForMember(args, secretKeyId)
97+
}
98+
99+
// pushForMember contains the original push logic
100+
func pushForMember(args []string, secretKeyId int64) error {
101+
manifest, err := manifest.Restore()
102+
if err != nil {
103+
return err
104+
}
105+
57106
db, err := database.Restore()
58107
if err != nil {
59108
return err
@@ -78,6 +127,7 @@ func RunPush(args []string, secretKeyId int64) error {
78127

79128
var envsToPush []string
80129
var envsPushed []string
130+
var skippedEnvs []string
81131
if len(args) == 1 {
82132
envsToPush = append(envsToPush, args[0])
83133
} else {
@@ -90,13 +140,14 @@ func RunPush(args []string, secretKeyId int64) error {
90140
if err := service.checkIfLocalEnvsExistAsEnvironments(envsToPush, orgId, projectId); err != nil {
91141
return err
92142
}
143+
93144
for _, envName := range envsToPush {
94145
e, err := env.GetLocalEncryptedEnv(envName, nil, manifest)
95146
if err != nil {
96147
printer.Error(nil, err)
97148
continue
98149
}
99-
err, skippable := service.putEnv(orgId, envName, appId, e, manifest.GetSecretKey(), manifest, secretKeyId)
150+
err, skippable := service.putEnv(orgId, envName, appId, e, manifest.GetSecretKey(), manifest, secretKeyId, &skippedEnvs)
100151
if err != nil {
101152
printer.Error(nil, err)
102153
continue
@@ -106,7 +157,7 @@ func RunPush(args []string, secretKeyId int64) error {
106157
}
107158

108159
if !Silent {
109-
printPushSummary(envsToPush, envsPushed)
160+
printPushSummary(envsToPush, envsPushed, skippedEnvs)
110161
}
111162
return nil
112163
}
@@ -123,7 +174,7 @@ func newService(envService env.EnvServicer, db database.Database) *service {
123174
}
124175
}
125176

126-
func (s *service) putEnv(orgID, envName, appID string, e env.Env, secretKey secretkey.SecretKeyer, m manifest.Manifest, secretKeyId int64) (err error, skippable bool) {
177+
func (s *service) putEnv(orgID, envName, appID string, e env.Env, secretKey secretkey.SecretKeyer, m manifest.Manifest, secretKeyId int64, skippedEnvs *[]string) (err error, skippable bool) {
127178
// Check local environment
128179
currentLocalEnv, exists := s.db.GetSecret(database.SecretKey{
129180
ProjectId: *m.ProjectId,
@@ -136,9 +187,7 @@ func (s *service) putEnv(orgID, envName, appID string, e env.Env, secretKey secr
136187
return err, false
137188
}
138189
if skippable && m.SecretKeyId == secretKeyId {
139-
if !Silent {
140-
printer.Info(fmt.Sprintf("Local %s environment is already up to date - skipping", envName))
141-
}
190+
*skippedEnvs = append(*skippedEnvs, envName)
142191
return nil, true
143192
}
144193
}
@@ -219,17 +268,24 @@ func (s *service) getLocalEnvsNamesFromFiles() ([]string, error) {
219268
return envs, nil
220269
}
221270

222-
func printPushSummary(envsToPush []string, envsPushed []string) {
271+
func printPushSummary(envsToPush []string, envsPushed []string, skippedEnvs []string) {
223272
if len(envsToPush) > 1 {
224-
printer.PrintDetail("Local environments", strings.Join(envsToPush, ", "))
225273
if len(envsPushed) > 0 {
226-
printer.PrintDetail("Environments pushed", strings.Join(envsPushed, ", "))
274+
printer.Success(fmt.Sprintf("%s %s", "pushed: ", strings.Join(envsPushed, ", ")))
227275
} else {
228-
printer.PrintDetail("Environments pushed", "None")
276+
printer.Success("pushed: everything is up to date")
277+
}
278+
if flags.VerboseFlag {
279+
if len(skippedEnvs) > 0 {
280+
printer.PrintDetail("skipped", strings.Join(skippedEnvs, ", "))
281+
} else {
282+
printer.PrintDetail("skipped", "None")
283+
}
229284
}
230285
} else {
231286
if len(envsToPush) == 1 && len(envsPushed) == 1 {
232287
printer.Success(fmt.Sprintf("Successfully pushed environment '%s'", envsToPush[0]))
233288
}
234289
}
290+
235291
}

cmd/initialize/initlizeMonorepo.go

+60-21
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,66 @@ func runInitMonorepo(cmd *cobra.Command, args []string) {
4141
initproject.IsMonorepo = true
4242
initproject.RunInitProject(cmd, args)
4343

44-
// Get apps to initialize
45-
appsOfMonorepoDir, err := prompt.PromptForMonorepoApps(cmd)
46-
if err != nil {
47-
printer.Error(cmd, err)
48-
return
49-
}
50-
5144
m, err := manifest.Restore()
5245
if err != nil {
5346
printer.Error(cmd, err)
5447
return
5548
}
5649

57-
// Initialize each app
58-
for _, appDir := range appsOfMonorepoDir {
59-
printer.Info(fmt.Sprintf("Initializing app %s", appDir))
60-
err := initializeMonorepoApp(cmd, appDir, orgID, m.Config, appService, envService, m.Secret)
50+
for {
51+
appPath, err := prompt.PromptString(cmd, "Provide the path to an application:")
52+
if err != nil {
53+
printer.Error(cmd, err)
54+
return
55+
}
56+
57+
cleanPath := filepath.Clean(appPath)
58+
59+
if _, err := os.Stat(cleanPath); os.IsNotExist(err) {
60+
printer.Error(cmd, fmt.Errorf("Directory does not exist: %s", cleanPath))
61+
continue
62+
}
63+
64+
if _, err := os.Stat(cleanPath); os.IsNotExist(err) {
65+
printer.Error(cmd, fmt.Errorf("directory does not exist: %s", cleanPath))
66+
continue
67+
}
68+
69+
currentDir, err := os.Getwd()
6170
if err != nil {
62-
printer.Error(cmd, fmt.Errorf("failed to initialize app %s: %w", appDir, err))
71+
printer.Error(cmd, fmt.Errorf("Failed to get current directory: %w", err))
72+
return
73+
}
74+
75+
absPath, err := filepath.Abs(cleanPath)
76+
if err != nil {
77+
printer.Error(cmd, fmt.Errorf("Invalid path: %w", err))
78+
continue
79+
}
80+
81+
// Check if path is within current directory using filepath.Rel
82+
relPath, err := filepath.Rel(currentDir, absPath)
83+
if err != nil || strings.HasPrefix(relPath, "..") {
84+
printer.Error(cmd, fmt.Errorf("Invalid path: must be within current directory"))
6385
continue
6486
}
87+
88+
printer.Info(fmt.Sprintf("Initializing app %s", cleanPath))
89+
err = initializeMonorepoApp(cmd, cleanPath, orgID, m.Config, appService, envService, m.Secret)
90+
if err != nil {
91+
printer.Error(cmd, fmt.Errorf("Failed to initialize app %s: %w", cleanPath, err))
92+
continue
93+
}
94+
95+
if err := manifest.AddAppToLocalProject(cleanPath); err != nil {
96+
printer.Error(cmd, fmt.Errorf("Failed to add app to local project: %w", err))
97+
continue
98+
}
99+
100+
moreApps := prompt.PromptYesNo(cmd, "Do you have another app?", false)
101+
if !moreApps.Confirmed {
102+
break
103+
}
65104
}
66105
}
67106

@@ -73,7 +112,7 @@ func initializeMonorepoApp(cmd *cobra.Command, appDir string, orgID string, mc m
73112
var appName string
74113
if !response.Confirmed {
75114
if response.IsFlag {
76-
return fmt.Errorf("operation cancelled due to --no flag for app: %s", defaultAppName)
115+
return fmt.Errorf("Operation cancelled due to --no flag for app: %s", defaultAppName)
77116
}
78117

79118
// Prompt for a new app name
@@ -97,7 +136,7 @@ func initializeMonorepoApp(cmd *cobra.Command, appDir string, orgID string, mc m
97136

98137
if !response.Confirmed {
99138
if response.IsFlag {
100-
return fmt.Errorf("operation cancelled due to --no flag for app ID: %s", defaultAppAlternateId)
139+
return fmt.Errorf("Operation cancelled due to --no flag for app ID: %s", defaultAppAlternateId)
101140
}
102141

103142
// Prompt for a custom app ID
@@ -133,7 +172,7 @@ func initializeMonorepoApp(cmd *cobra.Command, appDir string, orgID string, mc m
133172
return handleErr
134173
}
135174
if existingApp == nil {
136-
return fmt.Errorf("operation cancelled for app: %s", appName)
175+
return fmt.Errorf("Operation cancelled for app: %s", appName)
137176
}
138177

139178
newApp = *existingApp
@@ -201,7 +240,7 @@ func CreateAndPushEmptyEnvFileMonorepo(cmd *cobra.Command, envService *env.EnvSe
201240

202241
// Ensure the directory exists
203242
if err := os.MkdirAll(appDir, 0755); err != nil {
204-
return fmt.Errorf("failed to create directory %s: %w", appDir, err)
243+
return fmt.Errorf("Failed to create directory %s: %w", appDir, err)
205244
}
206245

207246
err = CreateGitignoredFileMonorepo(cmd, fullEnvPath, envFileName)
@@ -246,7 +285,7 @@ func CreateAndPushEmptyEnvFileMonorepo(cmd *cobra.Command, envService *env.EnvSe
246285
}
247286

248287
if err := db.UpsertSecret(secretKey, newEnvDecrypted, version); err != nil {
249-
return fmt.Errorf("failed to save local environment: %w", err)
288+
return fmt.Errorf("Failed to save local environment: %w", err)
250289
}
251290

252291
return nil
@@ -256,7 +295,7 @@ func CreateGitignoredFileMonorepo(cmd *cobra.Command, fullPath, fileName string)
256295
// Ensure the directory exists
257296
dir := filepath.Dir(fullPath)
258297
if err := os.MkdirAll(dir, 0755); err != nil {
259-
return fmt.Errorf("failed to create directory %s: %w", dir, err)
298+
return fmt.Errorf("Failed to create directory %s: %w", dir, err)
260299
}
261300

262301
// check if the file already exists.
@@ -268,20 +307,20 @@ func CreateGitignoredFileMonorepo(cmd *cobra.Command, fullPath, fileName string)
268307
// Create the file
269308
file, err := os.Create(fullPath)
270309
if err != nil {
271-
printer.Error(cmd, fmt.Errorf("error creating %s: %w", fullPath, err))
310+
printer.Error(cmd, fmt.Errorf("Error creating %s: %w", fullPath, err))
272311
return err
273312
}
274313
defer file.Close()
275314

276315
// Write '# KEY=Value' to the file
277316
_, err = file.WriteString("# Example\n# KEY=Value\n")
278317
if err != nil {
279-
printer.Error(cmd, fmt.Errorf("error writing to %s: %w", fullPath, err))
318+
printer.Error(cmd, fmt.Errorf("Error writing to %s: %w", fullPath, err))
280319
return err
281320
}
282321

283322
if err := gitutil.EnsureGitignore(fileName); err != nil {
284-
printer.Error(cmd, fmt.Errorf("error adding %s to .gitignore: %w. Please do this manually if you wish", fileName, err))
323+
printer.Error(cmd, fmt.Errorf("Error adding %s to .gitignore: %w. Please do this manually if you wish", fileName, err))
285324
// don't error here. Keep going.
286325
}
287326

0 commit comments

Comments
 (0)