Release 0.3.7: supported plugins + env cleanup#53
Conversation
Major refactor to remove GitHub/Copilot-specific assumptions across the CLI: Bug fixes: - Add Name field to ConnectionTestRequest (fixes Copilot test 500 error) - Write early state checkpoint in deploy_azure after RG creation - Allow cleanup --resource-group without state file ConnectionDef as single source of truth: - Add RateLimitPerHour, EnableGraphql, TokenPrompt, OrgPrompt, EnterprisePrompt fields to ConnectionDef - BuildCreateRequest/BuildTestRequest read fields from def (no plugin branches) - pluginDisplayName sources from ConnectionDef registry Token resolution data-driven: - Rewrite token.Resolve to accept ResolveOpts struct - Remove pluginEnvFileKeys/pluginEnvVarKeys/pluginDisplayName switch funcs - Callers pass def.EnvFileKeys, def.EnvVarNames, def.DisplayName directly Per-plugin token + org resolution: - runConnectionsInternal resolves token, org, enterprise per-plugin - Remove upfront GitHub org prompt from init wizard - Org/enterprise derived from connection results for downstream phases Tool-agnostic strings (~25 replacements): - Flag descriptions: Organization slug, Enterprise slug, PAT - Plugin lists generated dynamically from availablePluginSlugs() - Error messages use registry data, not hardcoded plugin names Generic project descriptions: - Replace HasGitHub/HasCopilot booleans with PluginNames []string - Project description built from active plugin display names Scope handling cleanup: - Remove SkipGitHub/SkipCopilot from ScopeOpts - runConfigureScopes uses single-plugin flow with generic validation - ensureScopeConfig accepts plugin parameter (no hardcoded github) - discoverConnections iterates AvailableConnections() dynamically
There was a problem hiding this comment.
Pull request overview
This PR prepares the 0.3.7 release by making token handling and plugin configuration more tool-agnostic, updating CLI behavior around .devlake.env cleanup, and expanding/reshaping the documentation set to match the new UX and supported plugins.
Changes:
- Refactors token resolution to be fully data-driven via
token.ResolveOpts(no hardcoded plugin switching inside the token package). - Updates connection/scope/project orchestration to use the plugin registry as the single source of truth (rate limits, GraphQL enablement, token/env keys, prompts), and adds early Azure state checkpointing.
- Adds/refreshes command docs (init/status/deploy/etc.) and reorganizes README with supported plugins + token/state file guidance.
Reviewed changes
Copilot reviewed 32 out of 34 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/token/resolve.go | Switches token resolution to Resolve(opts) with caller-provided lookup keys/names. |
| internal/token/resolve_test.go | Updates tests to use ResolveOpts helpers for multiple plugins. |
| internal/envfile/envfile.go | Formatting-only changes to .devlake.env parser utilities. |
| internal/devlake/client.go | Generalizes connection create comment; adds Name to test request payload. |
| cmd/connection_types.go | Expands plugin registry to include rate limits, GraphQL flag, prompts, env keys. |
| cmd/connection_types_test.go | Updates tests for rate limit defaults and BuildTestRequest name propagation. |
| cmd/configure_connections.go | Uses registry-driven plugin lists and token.ResolveOpts for token lookup. |
| cmd/configure_connection_update.go | Updates help text and plugin flag description to use registry-driven list. |
| cmd/configure_connection_test_cmd.go | Updates --plugin help to use registry-driven list. |
| cmd/configure_connection_list.go | Updates --plugin help to use registry-driven list. |
| cmd/configure_scopes.go | Simplifies scope flow to single-plugin selection with --connection-id and registry validation. |
| cmd/configure_scopes_test.go | Adjusts tests for new --connection-id and updated validation behavior. |
| cmd/configure_projects.go | Removes --org/--plugin flags; derives defaults from state and uses plugin-name list. |
| cmd/configure_full.go | Makes configure full interactive-only; resolves token/org/enterprise per plugin and cleans env file. |
| cmd/init.go | Adds --skip-cleanup; refactors init flow to be more registry-driven and per-plugin. |
| cmd/deploy_azure.go | Writes an early partial .devlake-azure.json checkpoint after RG creation. |
| cmd/cleanup.go | Allows Azure cleanup to proceed with --resource-group even if state file is missing. |
| cmd/root.go | Updates root help “Typical workflow” text (removes old --org reference). |
| README.md | Reorganizes README (prereqs/quickstart/day-2/supported plugins/command reference) and adds new doc links. |
| docs/token-handling.md | New: consolidated token resolution, env file, cleanup behavior, and security notes. |
| docs/state-files.md | New: explains state file purpose, discovery chain, and cleanup. |
| docs/status.md | New: documents status command output and interpretation. |
| docs/init.md | New: documents the 4-phase init wizard and how it differs from configure full. |
| docs/deploy.md | New: documents local + Azure deploy flows and options. |
| docs/day-2.md | New: day-2 operational guidance (status, rotate token, add repos, teardown). |
| docs/configure-connection.md | New: full connection CRUD reference + token resolution order + scope requirements. |
| docs/configure-scope.md | New: scope management reference + DORA patterns + repo resolution. |
| docs/configure-project.md | New: project/blueprint/sync reference including pipeline monitoring. |
| docs/configure-full.md | New: describes interactive “connections → scopes → project” flow and flags. |
| docs/concepts.md | New: conceptual model (connection/scope/project/blueprint). |
| docs/cleanup.md | New: cleanup behavior for local vs Azure, auto-detection, flags. |
| docs/images/.gitkeep | Adds docs images folder placeholder. |
| AGENTS.md | Updates engineering guidance around plugin registry and documentation expectations. |
| .github/skills/plugin-registry/SKILL.md | New: internal skill doc describing plugin registry patterns and flows. |
Comments suppressed due to low confidence (1)
cmd/configure_projects.go:248
finalizeProjectwritesStateProject.Organizationfromopts.Org, butrunConfigureProjectsno longer populatesOrgwhen callingfinalizeProject. This means the organization field in the state file will always be empty fromconfigure project. Either populateOrgfrom the state connections (similar todefaultName) or remove/stop writing that field if it’s no longer meaningful.
return finalizeProject(finalizeProjectOpts{
Client: client,
StatePath: statePath,
State: state,
ProjectName: projectName,
Connections: connections,
Repos: allRepos,
PluginNames: pluginNames,
Cron: opts.Cron,
TimeAfter: opts.TimeAfter,
SkipSync: opts.SkipSync,
Wait: opts.Wait,
Timeout: opts.Timeout,
})
| if vals, err := envfile.Load(envFilePath); err == nil { | ||
| for _, key := range pluginEnvFileKeys(plugin) { | ||
| for _, key := range opts.EnvFileKeys { | ||
| if v, ok := vals[key]; ok && v != "" { | ||
| return &ResolveResult{Token: v, Source: "envfile", EnvFilePath: envFilePath}, nil | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
envfile.Load errors (e.g., permission denied, malformed file read issues) are currently ignored and token resolution silently falls back to environment/prompt. This can make debugging hard and may prompt unexpectedly even though an --env-file was provided. Consider returning a clear error when envfile.Load returns a non-nil error (it already returns nil error for non-existent files).
| if !term.IsTerminal(int(syscall.Stdin)) { | ||
| envVarExample := pluginEnvVarKeys(plugin)[0] | ||
| return nil, fmt.Errorf("no %s PAT found and stdin is not a terminal.\n"+ | ||
| envVarExample := "" | ||
| if len(opts.EnvVarNames) > 0 { | ||
| envVarExample = opts.EnvVarNames[0] | ||
| } | ||
| return nil, fmt.Errorf("no %s token found and stdin is not a terminal.\n"+ | ||
| "Provide a token via --token, .devlake.env file, or $%s", displayName, envVarExample) | ||
| } |
There was a problem hiding this comment.
When opts.EnvVarNames is empty, the non-interactive error message will render or $ with a blank variable name (because envVarExample stays empty). Consider omitting the $... portion when there is no env var example, or requiring EnvVarNames to be non-empty and returning a config error.
| // helper to build ResolveOpts for GitHub-family plugins | ||
| func ghOpts(flagValue, envFile string) ResolveOpts { | ||
| return ResolveOpts{ | ||
| FlagValue: flagValue, | ||
| EnvFilePath: envFile, | ||
| EnvFileKeys: []string{"GITHUB_PAT", "GITHUB_TOKEN", "GH_TOKEN"}, | ||
| EnvVarNames: []string{"GITHUB_TOKEN", "GH_TOKEN"}, | ||
| DisplayName: "GitHub", | ||
| } |
There was a problem hiding this comment.
ghOpts doesn’t include GITHUB_PAT in EnvVarNames, but the CLI now documents and configures GITHUB_PAT as a supported environment variable for GitHub/Copilot. Updating this helper (and/or adding a test) would ensure GITHUB_PAT env-var support is covered and stays in sync with ConnectionDef.EnvVarNames.
| ``` | ||
| for each selected ConnectionDef: | ||
| 1. Resolve token using def.EnvVarNames, def.TokenPrompt | ||
| 2. Prompt for org if def.NeedsOrg (using def.OrgPrompt) | ||
| 3. Prompt for enterprise if def.NeedsEnterprise (using def.EnterprisePrompt) | ||
| 4. Test & create connection | ||
| ``` |
There was a problem hiding this comment.
This section says orchestrators resolve tokens using def.TokenPrompt, but current code passes def.DisplayName into token.ResolveOpts.DisplayName and TokenPrompt is unused. Please update this doc to match the implementation (or update the implementation to actually use TokenPrompt) so future plugin additions don’t follow incorrect guidance.
| if err := os.WriteFile(stateFile, data, 0644); err != nil { | ||
| fmt.Fprintf(os.Stderr, "⚠️ Could not save early state checkpoint: %v\n", err) | ||
| } |
There was a problem hiding this comment.
Terminal output: this warning prints an emoji without a leading blank line, which can jam against prior output. For consistency with the repo’s terminal output standards, consider prefixing with a newline (e.g., start the format string with \n).
|
|
||
| Typical workflow: | ||
| 1. gh devlake deploy local # spin up DevLake with Docker Compose | ||
| 2. gh devlake configure full # create connections + scopes + project |
There was a problem hiding this comment.
The root help text has inconsistent indentation on the numbered list (line 2 uses a tab while the others use spaces), which can render misaligned in terminals. Consider using consistent spaces for all list items in the raw string.
| 2. gh devlake configure full # create connections + scopes + project | |
| 2. gh devlake configure full # create connections + scopes + project |
| # Deploy DevLake locally (downloads official Docker Compose files) | ||
| gh devlake deploy local --dir ./my-devlake | ||
| cd my-devlake && docker compose up -d | ||
| > **Blog post:** [Beyond Copilot Dashboards: Measuring What AI Actually Changes](<!-- TODO: replace with actual blog URL -->) — why DORA + Copilot correlation matters and what this tool enables. |
There was a problem hiding this comment.
This README section includes a placeholder blog URL (<!-- TODO: replace with actual blog URL -->). For a release doc, this should either be replaced with a real link or removed to avoid shipping broken/invalid markdown.
| > **Blog post:** [Beyond Copilot Dashboards: Measuring What AI Actually Changes](<!-- TODO: replace with actual blog URL -->) — why DORA + Copilot correlation matters and what this tool enables. | |
| > **Blog post:** Beyond Copilot Dashboards: Measuring What AI Actually Changes — why DORA + Copilot correlation matters and what this tool enables. |
| RequiredScopes []string // PAT scopes needed for this plugin | ||
| ScopeHint string // short hint for error messages | ||
| TokenPrompt string // label for masked token prompt (e.g. "GitHub PAT") | ||
| OrgPrompt string // label for org prompt; empty = not prompted | ||
| EnterprisePrompt string // label for enterprise prompt; empty = not prompted | ||
| EnvVarNames []string // environment variable names for token resolution | ||
| EnvFileKeys []string // .devlake.env keys for token resolution |
There was a problem hiding this comment.
TokenPrompt is defined on ConnectionDef but isn’t used in token resolution or prompting (callers pass def.DisplayName into token.ResolveOpts.DisplayName). This is confusing because it suggests a prompt label is configurable but currently has no effect. Either wire TokenPrompt into the token prompt/error messaging, or remove the field to avoid drift.
Summary\n- README: supported plugins section (w/ required PAT scopes) moved above command reference; PAT moved out of prerequisites\n- Docs: tool-agnostic token handling + state files, multi-plugin .devlake.env example\n- CLI: init/configure full delete .devlake.env by default (with --skip-cleanup); token env var parity (GITHUB_PAT)\n\n## Testing\n- go test ./...\n- go vet ./...\n- go build -o gh-devlake.exe .\n- Smoke: help output shows new flags; root help no longer references --org for configure full\n