From a14a1e00ae52eba783a3c7c07ccdf1facbbca648 Mon Sep 17 00:00:00 2001 From: "Christian G. Warden" Date: Tue, 9 Dec 2025 15:36:57 -0600 Subject: [PATCH] Add --namespace Flag To Scratch Org Creation - Add --namespace flag to `force login scratch` command for specifying scratch org namespace - Fix GetSourceDir regression where parent directory search could find unrelated source directories (e.g., /tmp/src) when running in a temp directory. Now only walks up parent directories if the current path contains a source directory component. --- command/login.go | 9 ++++++--- command/login_test.go | 7 +++++++ command/root.go | 12 +++++++++++- config/config.go | 28 ++++++++++++++++++---------- lib/auth.go | 8 ++++++-- lib/scratch.go | 7 +++++++ 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/command/login.go b/command/login.go index a6694d4e..de53634e 100644 --- a/command/login.go +++ b/command/login.go @@ -133,6 +133,7 @@ logged in system. non-production server to login to (values are 'pre', "product", "product shortcut for features (can be specified multiple times); see command help for available products") scratchCmd.Flags().StringToString("quantity", map[string]string{}, "override default quantity for features (e.g., FinancialServicesUser=5); default quantity is 10") + scratchCmd.Flags().String("namespace", "", "namespace for the scratch org") scratchCmd.Flags().Var( enumflag.New(&selectedEdition, "edition", ScratchEditionIds, enumflag.EnumCaseInsensitive), "edition", @@ -188,6 +189,7 @@ Examples: force login scratch --product fsc force login scratch --feature PersonAccounts --feature StateAndCountryPicklist force login scratch --product fsc --quantity FinancialServicesUser=20 + force login scratch --namespace myns force login scratch --edition Enterprise --product fsc force login scratch --setting enableEnhancedNotes force login scratch --setting enableQuote @@ -195,11 +197,12 @@ Examples: force login scratch --product healthcloud`, Run: func(cmd *cobra.Command, args []string) { scratchUser, _ := cmd.Flags().GetString("username") + scratchNamespace, _ := cmd.Flags().GetString("namespace") quantities, _ := cmd.Flags().GetStringToString("quantity") allFeatures := expandProductsToFeatures(selectedProducts, selectedFeatures, quantities) edition := ScratchEditionIds[selectedEdition][0] allSettings := expandProductsToSettings(selectedProducts, selectedSettings) - scratchLogin(scratchUser, allFeatures, edition, allSettings) + scratchLogin(scratchUser, allFeatures, edition, allSettings, scratchNamespace) }, } @@ -326,8 +329,8 @@ func expandProductsToSettings(products []ScratchProduct, settings []ScratchSetti return uniqueSettings } -func scratchLogin(scratchUser string, features []string, edition string, settings []string) { - _, err := ForceScratchCreateLoginAndSave(scratchUser, features, edition, settings, os.Stderr) +func scratchLogin(scratchUser string, features []string, edition string, settings []string, namespace string) { + _, err := ForceScratchCreateLoginAndSaveWithNamespace(scratchUser, features, edition, settings, namespace, os.Stderr) if err != nil { ErrorAndExit(err.Error()) } diff --git a/command/login_test.go b/command/login_test.go index 1790a09c..b01c2ae7 100644 --- a/command/login_test.go +++ b/command/login_test.go @@ -362,3 +362,10 @@ func TestScratchSettingIds_AllSettingsDefined(t *testing.T) { } } } + +func TestScratchCommandHasNamespaceFlag(t *testing.T) { + flag := scratchCmd.Flags().Lookup("namespace") + if flag == nil { + t.Fatal("Flag namespace not found") + } +} diff --git a/command/root.go b/command/root.go index 342c9774..99a01f4b 100644 --- a/command/root.go +++ b/command/root.go @@ -40,8 +40,18 @@ func init() { for current.Parent() != nil && current.Parent() != RootCmd { current = current.Parent() } + isLoginScratch := current.Name() == "login" && cmd.Name() == "scratch" + if isLoginScratch && account != "" { + if err := SetActiveLogin(account); err != nil { + ErrorAndExit(err.Error()) + } + } switch current.Name() { - case "force", "login", "completion", "usedxauth", "logins": + case "force", "completion", "usedxauth", "logins": + case "login": + if isLoginScratch { + initializeSession() + } default: initializeSession() } diff --git a/config/config.go b/config/config.go index 8ee8481c..e7b5a94f 100644 --- a/config/config.go +++ b/config/config.go @@ -235,17 +235,25 @@ func GetSourceDir() (dir string, err error) { } } - // Check the current directory and then start looking up at our parents. - // When dir's parent is identical, it means we're at the root. If we blow - // past the actual root, we should drop to the next section of code - for dir != filepath.Dir(dir) { - dir = filepath.Dir(dir) - for _, src := range sourceDirs { - adir := filepath.Join(dir, src) - if IsSourceDir(adir) { - dir = adir - return + // Check if we're inside an existing source directory by looking at parents. + // Only do this if the current path contains a source directory component, + // otherwise we might find unrelated directories (e.g., /tmp/src). + for _, src := range sourceDirs { + if strings.Contains(base, string(filepath.Separator)+src+string(filepath.Separator)) || + strings.HasSuffix(base, string(filepath.Separator)+src) { + // We appear to be inside a source tree, walk up to find it + dir = base + for dir != filepath.Dir(dir) { + dir = filepath.Dir(dir) + for _, s := range sourceDirs { + adir := filepath.Join(dir, s) + if IsSourceDir(adir) { + dir = adir + return + } + } } + break } } diff --git a/lib/auth.go b/lib/auth.go index 8512043f..788bafbe 100644 --- a/lib/auth.go +++ b/lib/auth.go @@ -127,17 +127,21 @@ func ForceLoginAtEndpointAndSaveSoap(endpoint string, user_name string, password // Create a new scratch org, login, and make it active func ForceScratchLoginAndSave(output *os.File) (username string, err error) { - return ForceScratchCreateLoginAndSave("", []string{}, "", []string{}, output) + return ForceScratchCreateLoginAndSaveWithNamespace("", []string{}, "", []string{}, "", output) } func ForceScratchCreateLoginAndSave(scratchUser string, features []string, edition string, settings []string, output *os.File) (username string, err error) { + return ForceScratchCreateLoginAndSaveWithNamespace(scratchUser, features, edition, settings, "", output) +} + +func ForceScratchCreateLoginAndSaveWithNamespace(scratchUser string, features []string, edition string, settings []string, namespace string, output *os.File) (username string, err error) { force, err := ActiveForce() if err != nil { err = errors.New("You must be logged into a Dev Hub org to authenticate as a scratch org user.") return } fmt.Fprintln(os.Stderr, "Creating new Scratch Org...") - scratchOrgId, err := force.CreateScratchOrgWithUserFeaturesEditionAndSettings(scratchUser, features, edition, settings) + scratchOrgId, err := force.CreateScratchOrgWithUserFeaturesEditionSettingsAndNamespace(scratchUser, features, edition, settings, namespace) if err != nil { return } diff --git a/lib/scratch.go b/lib/scratch.go index 1093bf4f..7584fe86 100644 --- a/lib/scratch.go +++ b/lib/scratch.go @@ -216,6 +216,10 @@ func (f *Force) CreateScratchOrgWithUserFeaturesAndEdition(username string, feat } func (f *Force) CreateScratchOrgWithUserFeaturesEditionAndSettings(username string, features []string, edition string, settings []string) (id string, err error) { + return f.CreateScratchOrgWithUserFeaturesEditionSettingsAndNamespace(username, features, edition, settings, "") +} + +func (f *Force) CreateScratchOrgWithUserFeaturesEditionSettingsAndNamespace(username string, features []string, edition string, settings []string, namespace string) (id string, err error) { params := make(map[string]string) params["ConnectedAppCallbackUrl"] = "http://localhost:1717/OauthRedirect" params["ConnectedAppConsumerKey"] = "PlatformCLI" @@ -238,6 +242,9 @@ func (f *Force) CreateScratchOrgWithUserFeaturesEditionAndSettings(username stri // Note: settings parameter is kept for later deployment after org creation params["OrgName"] = "Force CLI Scratch" + if namespace != "" { + params["Namespace"] = namespace + } if username != "" { params["Username"] = username }