-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Problem
The scope dispatch logic is hard-coded with switch statements on plugin names in two locations:
1. cmd/helpers.go — scopeAllConnections() (used by configureAllPhases → init and configure full):
switch r.Plugin {
case "github":
_, err := scopeGitHub(client, r.ConnectionID, r.Organization, scopeOpts)
case "gh-copilot":
_, err := scopeCopilot(client, r.ConnectionID, r.Organization, r.Enterprise)
default:
fmt.Printf(" ⚠️ Scope configuration for %q is not yet supported\n", r.Plugin)
}2. cmd/configure_scopes.go — runConfigureScopes() (standalone scope add command):
switch selectedPlugin {
case "github":
_, err = scopeGitHub(client, connID, org, opts)
case "gh-copilot":
_, err = scopeCopilot(client, connID, org, enterprise)
default:
return fmt.Errorf("scope configuration for %q is not yet supported", selectedPlugin)
}This violates the tool-agnostic design principle. Connection creation is already declarative (driven by ConnectionDef fields) — adding a new plugin only requires a registry entry. But scope creation requires touching switch statements in two places. Adding GitLab (#13) or Azure DevOps (#14) would require editing both files.
Dependencies
Blocked by:
configure scope add/list/delete: Add CRUD subcommands for scope management #55 (scope CRUD) — restructures the scope files this issue modifies; landconfigure scope add/list/delete: Add CRUD subcommands for scope management #55 first to avoid merge conflicts
Blocks:
- Dynamic plugin-specific flag validation and contextual help #59 (dynamic flag validation) — extends
ConnectionDeffurther;ScopeFuncestablishes the pattern
Parallel with: #58 (flag docs) — independent changes, no file overlap
Proposed solution
Add a ScopeFunc field to ConnectionDef in cmd/connection_types.go. Each plugin registers its scope handler as a function pointer. The dispatch code becomes a single generic call with no switch statement.
1. Add ScopeFunc to ConnectionDef
// ScopeHandler is a function that configures scopes for a connection.
// It receives the client, connection ID, org, enterprise, and an options struct.
// It returns the BlueprintConnection entry (for project creation) and an error.
type ScopeHandler func(client *devlake.Client, connID int, org, enterprise string, opts *ScopeOpts) (*devlake.BlueprintConnection, error)
type ConnectionDef struct {
// ... existing fields ...
ScopeFunc ScopeHandler // nil = "scope configuration not yet supported"
}2. Register scope handlers in connectionRegistry
var connectionRegistry = []*ConnectionDef{
{
Plugin: "github",
DisplayName: "GitHub",
ScopeFunc: scopeGitHubHandler,
// ... existing fields ...
},
{
Plugin: "gh-copilot",
DisplayName: "GitHub Copilot",
ScopeFunc: scopeCopilotHandler,
// ... existing fields ...
},
{
Plugin: "gitlab",
DisplayName: "GitLab",
ScopeFunc: nil, // coming soon
// ...
},
}The handler functions wrap the existing scopeGitHub() and scopeCopilot() to match the unified signature.
3. Replace switch statements with generic dispatch
In scopeAllConnections():
for _, r := range results {
def := FindConnectionDef(r.Plugin)
if def == nil || def.ScopeFunc == nil {
fmt.Printf(" ⚠️ Scope configuration for %q is not yet supported\n", r.Plugin)
continue
}
_, err := def.ScopeFunc(client, r.ConnectionID, r.Organization, r.Enterprise, scopeOpts)
if err != nil {
fmt.Printf(" ⚠️ %s scope setup failed: %v\n", def.DisplayName, err)
}
}In runConfigureScopes() / runScopeAdd():
def := FindConnectionDef(selectedPlugin)
if def == nil || def.ScopeFunc == nil {
return fmt.Errorf("scope configuration for %q is not yet supported", selectedPlugin)
}
_, err := def.ScopeFunc(client, connID, org, enterprise, opts)4. Handle plugin-specific interactive prompts
The GitHub scope handler includes interactive DORA pattern prompts (deployment regex, production regex, incident label). These stay inside scopeGitHubHandler — the handler function owns its interactive flow. The unified ScopeHandler signature passes *ScopeOpts which carries the defaults and flag values.
For Copilot, the handler is simpler — it just calls putCopilotScope().
5. Future: adding GitLab
With this pattern, adding GitLab scopes means:
- Write
scopeGitLabHandler()that knows about GitLab groups/projects - Set
ScopeFunc: scopeGitLabHandleron the GitLabConnectionDefentry - Done — no switch statements to touch
Files to change
| File | Change |
|---|---|
cmd/connection_types.go |
Add ScopeHandler type and ScopeFunc field to ConnectionDef |
cmd/connection_types.go |
Set ScopeFunc on github and gh-copilot registry entries |
cmd/configure_scopes.go |
Replace switch with def.ScopeFunc() call |
cmd/helpers.go |
Replace switch in scopeAllConnections() with def.ScopeFunc() call |
cmd/configure_scopes.go |
Create scopeGitHubHandler and scopeCopilotHandler wrapper functions |
Acceptance criteria
- No
case "github":orcase "gh-copilot":in scope dispatch code -
ConnectionDefhas aScopeFuncfield -
githubandgh-copilotentries inconnectionRegistrysetScopeFunc -
scopeAllConnections()inhelpers.gousesdef.ScopeFunc()— no switch -
runScopeAdd()(orrunConfigureScopes()) usesdef.ScopeFunc()— no switch - A
ConnectionDefwithScopeFunc: nilprints "not yet supported" (graceful fallback) - Interactive DORA prompts for GitHub still work (handled inside the handler)
-
initandconfigure fullorchestrators continue to work -
go build ./...andgo test ./...pass
References
cmd/helpers.go:198-250—scopeAllConnections()with hard-coded switchcmd/configure_scopes.go:224-228—runConfigureScopes()with hard-coded switchcmd/connection_types.go—ConnectionDefandconnectionRegistry- Plugin registry skill:
.github/skills/plugin-registry/SKILL.md - GitLab plugin support #13 — GitLab plugin support (will benefit from this)
- Azure DevOps plugin support #14 — Azure DevOps plugin support (will benefit from this)
configure scope add/list/delete: Add CRUD subcommands for scope management #55 — scope CRUD restructuring (should land first)