Skip to content

feat: add JSON config file support #3609

Merged
chronark merged 46 commits intomainfrom
add-config-generation-to-cli
Jul 23, 2025
Merged

feat: add JSON config file support #3609
chronark merged 46 commits intomainfrom
add-config-generation-to-cli

Conversation

@ogzhanolguncu
Copy link
Contributor

@ogzhanolguncu ogzhanolguncu commented Jul 16, 2025

What does this PR do?

No need for manual --workspace-id, --project-id and --context flags by introducing persistent configuration.

# One-time setup
unkey init

# Edit generated unkey.json, then deploy without flags
unkey deploy

Changes:

  • New CLI architecture moved to /pkg
  • unkey init creates config template
  • --config=./path for project-specific configs
  • Future: all deploy params will be configurable

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • Run go run main.go init
  • Update your unkey.json file with correct workspaceId and projectId and your desired context path
  • Run go run main.go deploy

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

  • New Features

    • Introduced a robust, interactive deployment command with configuration file support, Docker image building/pushing, and real-time deployment status tracking.
    • Added interactive configuration initialization for deployments, including prompts and validation.
    • Enhanced help descriptions and usage instructions for several commands.
    • Improved CLI flag handling with environment variable support, validation, and default values.
    • Added a new internal CLI framework with structured command and flag abstractions, detailed help output, and improved error reporting.
    • Implemented Docker image build and push utilities with progress feedback and error classification.
  • Bug Fixes

    • Improved required flag validation to ensure correct detection from environment variables and defaults.
  • Refactor

    • Replaced the CLI framework and refactored command and flag definitions for greater consistency and maintainability.
    • Simplified and streamlined command implementations, removing deprecated or redundant commands and logic.
    • Updated command-line flag declarations to use concise helper functions with chained options.
    • Removed legacy CLI command and flag parsing code, replacing with new internal implementations.
  • Chores

    • Removed unused dependencies and outdated files.
    • Added comprehensive unit tests for CLI flag parsing and validation.

@vercel
Copy link

vercel bot commented Jul 16, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
dashboard ⬜️ Ignored (Inspect) Visit Preview Jul 23, 2025 0:43am
engineering ⬜️ Ignored (Inspect) Visit Preview Jul 23, 2025 0:43am

@changeset-bot
Copy link

changeset-bot bot commented Jul 16, 2025

⚠️ No Changeset found

Latest commit: bb80723

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 16, 2025

📝 Walkthrough
## Walkthrough

The CLI flag declarations in several main command files (`go/cmd/api/main.go`, `go/cmd/ctrl/main.go`, etc.) were refactored to use a new internal CLI package with concise flag constructor functions, replacing verbose struct literals and removing the external `urfave/cli/v3` dependency. The semantics and runtime logic of the flags remain unchanged. Additionally, a comprehensive custom CLI framework was introduced replacing the previous `urfave/cli` usage, including new command and flag abstractions, help system, and command execution flow. Several CLI commands and related files were either removed or rewritten, notably the deploy and version commands, with new deployment logic and Docker integration added. New configuration management and interactive initialization commands were introduced. The CLI tool's main entry point and some command files were deleted or replaced. Extensive new tests for the CLI flag and command framework were added.

## Changes

| File(s)                                              | Change Summary |
|-----------------------------------------------------|----------------|
| go/cmd/api/main.go, go/cmd/ctrl/main.go             | Refactored CLI flag declarations to use new internal `cli` package with concise flag constructor functions and option chaining; removed `urfave/cli/v3` dependency. |
| go/pkg/cli/command.go, go/pkg/cli/flag.go           | Added new CLI framework with `Command` struct, typed flag interfaces and implementations, parsing, validation, and error handling; replaces previous CLI framework. |
| go/pkg/cli/help.go                                   | Added detailed CLI help display methods for commands and flags. |
| go/pkg/cli/flag_test.go                              | Added extensive unit tests covering CLI flag parsing, validation, environment variables, and command flag accessors. |
| go/pkg/cli/parser.go                                 | Refactored command flag map initialization and required flag validation to use new flag interface methods. |
| go/cmd/cli/cli/command.go, go/cmd/cli/cli/flag.go, go/cmd/cli/cli/help.go | Deleted previous CLI framework implementation files. |
| go/cmd/cli/main.go                                   | Deleted CLI application main entry point file. |
| go/cmd/cli/commands/deploy/build_docker.go          | Deleted previous Docker build and push implementation for deploy command. |
| go/cmd/cli/commands/deploy/deploy.go                 | Deleted previous deploy command implementation. |
| go/cmd/cli/commands/init/init.go                      | Deleted previous init command implementation. |
| go/cmd/cli/commands/versions/versions.go             | Deleted previous versions command implementation. |
| go/cmd/deploy/build_docker.go                         | Added new Docker image build and push logic with detailed progress, error classification, and UI integration. |
| go/cmd/deploy/control_plane.go                        | Modified `ControlPlaneClient` to hold deploy options by value and added delay in step processing for demo purposes. |
| go/cmd/deploy/config.go                               | Added configuration loading, creation, merging, and validation for deployment config files (`unkey.json`). |
| go/cmd/deploy/init.go                                 | Added interactive `init` command handler for creating deployment config files with user prompts and confirmation. |
| go/cmd/deploy/main.go                                 | Rewrote deploy command with full deployment workflow including config loading, Docker build/push, deployment status tracking, and UI updates. |
| go/cmd/deploy/ui.go                                   | Added symbol constants for UI output, replaced hardcoded symbols with constants. |
| go/cmd/healthcheck/main.go                            | Refactored healthcheck command to use new CLI package, improved argument handling and output. |
| go/cmd/quotacheck/main.go                             | Refactored quotacheck command flags to new CLI package style, added usage and description. |
| go/cmd/run/main.go                                    | Added action to run command to print usage info and available services; switched to new CLI package. |
| go/cmd/version/bootstrap.go                           | Deleted temporary bootstrap command for project creation in DB. |
| go/cmd/version/main.go                                | Removed complex version create command, simplified subcommands with stub implementations; switched to new CLI package flags. |
| go/pkg/cli/output.go                                  | Deleted streaming output type and related command output rendering code. |
| go/go.mod                                            | Removed dependency on `github.com/urfave/cli/v3`. |
| go/main.go                                           | Updated CLI app initialization to use new CLI package, added description field, simplified error printing. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User as User CLI Input
    participant CLI as cli.Command Framework
    participant Flags as cli.Flag Implementations
    participant Deploy as Deploy Command Logic
    participant Docker as Docker Build/Push
    participant ControlPlane as Control Plane Client
    participant UI as Deployment UI Spinner

    User->>CLI: Run command with args
    CLI->>Flags: Parse flags (from args/env/default)
    Flags-->>CLI: Return parsed flag values
    CLI-->>Deploy: Invoke command action with flags
    Deploy->>Docker: Validate and build Docker image (if needed)
    Docker-->>Deploy: Build result or errors
    Deploy->>Docker: Push image to registry (optional)
    Docker-->>Deploy: Push result or errors
    Deploy->>ControlPlane: Create deployment version
    ControlPlane-->>Deploy: Version creation response
    Deploy->>ControlPlane: Poll deployment status and steps
    ControlPlane-->>Deploy: Step events and status updates
    Deploy->>UI: Update spinner and print step info
    UI-->>User: Display deployment progress and results

Estimated code review effort

4 (~90 minutes)

Possibly related PRs

  • feat: Unkey Deploy CLI #3564: Introduces a new deploy CLI with its own command and flag framework and Docker deployment logic; related due to replacing external CLI package with internal framework and redefining commands.
  • feat: send otel data to otel endpoint #2970: Updates OpenTelemetry flags in go/cmd/api/main.go overlapping with flag refactoring in the same file.

Suggested reviewers

  • chronark
  • Flo4604
  • perkinsjr


</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=unkeyed/unkey&utm_content=3609):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

@ogzhanolguncu ogzhanolguncu mentioned this pull request Jul 16, 2025
18 tasks
@graphite-app
Copy link

graphite-app bot commented Jul 23, 2025

Graphite Automations

"Post a GIF when PR approved" took an action on this PR • (07/23/25)

1 gif was posted to this PR based on Andreas Thomas's automation.

Copy link
Member

Flo4604 commented Jul 23, 2025

aren't we missing the clickhouse token now?

@chronark chronark dismissed their stale review July 23, 2025 08:46

missing chproxy flags

@chronark
Copy link
Collaborator

good catch, I'll add it later

@ogzhanolguncu
Copy link
Contributor Author

@Flo4604 @chronark added those CH flags back 🫡 we are good to go

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d953360 and 0d55fb6.

📒 Files selected for processing (1)
  • go/cmd/api/main.go (2 hunks)
🧠 Learnings (2)
📓 Common learnings
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:71-73
Timestamp: 2025-07-15T15:11:30.840Z
Learning: In the go/cmd/cli/commands/deploy/ CLI codebase, ogzhanolguncu uses a UI-layer error handling pattern where errors are displayed through ui.PrintError() and ui.PrintErrorDetails(), followed by returning nil to indicate the error has been handled at the UI level, rather than propagating errors up the call stack.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:14-14
Timestamp: 2025-07-15T14:47:43.718Z
Learning: In go/cmd/cli/commands/deploy/deploy.go, the DEBUG_DELAY constant is temporary code that ogzhanolguncu plans to remove in a future PR, rather than making it configurable.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/*/cmd/* : Service binary code should be in `<service>/cmd/<service | command>`.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's `*.go` code changes significantly, increase the patch-level version number.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:153-158
Timestamp: 2025-07-16T09:18:45.379Z
Learning: In the go/cmd/cli/commands/deploy/ CLI codebase, ogzhanolguncu prefers to allow deployment to continue even when Docker push fails (around lines 153-158 in deploy.go) because the team is working locally and needs this behavior for local development workflows where registry access might not be available.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3297
File: apps/dashboard/lib/trpc/routers/authorization/roles/query.ts:210-323
Timestamp: 2025-06-04T20:13:12.060Z
Learning: The user ogzhanolguncu prefers explicit, duplicated code over abstracted helper functions when it improves readability, even if it means some duplication in filter building functions in the authorization roles query module.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3480
File: apps/dashboard/app/new-2/hooks/use-workspace-step.tsx:47-79
Timestamp: 2025-07-09T11:35:51.724Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep invariant checks that throw errors for cases that shouldn't happen in normal operation (like null workspace ID checks), rather than adding graceful error handling code for edge cases that would only occur if someone tampers with the actual flow.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3375
File: apps/dashboard/app/(app)/settings/root-keys/components/table/hooks/use-root-keys-list-query.ts:0-0
Timestamp: 2025-06-25T20:32:10.471Z
Learning: In the Unkey codebase, ogzhanolguncu prefers strict validation with fail-fast error handling. When validation errors occur that shouldn't happen in normal operation (like invalid operators), throwing errors to crash the page is preferred over graceful error handling or console logging.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3499
File: apps/dashboard/app/new/hooks/use-workspace-step.tsx:19-26
Timestamp: 2025-07-11T13:00:05.416Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep commented code for planned future features (like slug-based workspaces) rather than removing it, as it serves as a reference for upcoming implementation.
go/cmd/api/main.go (2)

Learnt from: ogzhanolguncu
PR: #3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.

Learnt from: ogzhanolguncu
PR: #3564
File: go/cmd/cli/commands/deploy/deploy.go:71-73
Timestamp: 2025-07-15T15:11:30.840Z
Learning: In the go/cmd/cli/commands/deploy/ CLI codebase, ogzhanolguncu uses a UI-layer error handling pattern where errors are displayed through ui.PrintError() and ui.PrintErrorDetails(), followed by returning nil to indicate the error has been handled at the UI level, rather than propagating errors up the call stack.

🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:71-73
Timestamp: 2025-07-15T15:11:30.840Z
Learning: In the go/cmd/cli/commands/deploy/ CLI codebase, ogzhanolguncu uses a UI-layer error handling pattern where errors are displayed through ui.PrintError() and ui.PrintErrorDetails(), followed by returning nil to indicate the error has been handled at the UI level, rather than propagating errors up the call stack.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:14-14
Timestamp: 2025-07-15T14:47:43.718Z
Learning: In go/cmd/cli/commands/deploy/deploy.go, the DEBUG_DELAY constant is temporary code that ogzhanolguncu plans to remove in a future PR, rather than making it configurable.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/*/cmd/* : Service binary code should be in `<service>/cmd/<service | command>`.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's `*.go` code changes significantly, increase the patch-level version number.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:153-158
Timestamp: 2025-07-16T09:18:45.379Z
Learning: In the go/cmd/cli/commands/deploy/ CLI codebase, ogzhanolguncu prefers to allow deployment to continue even when Docker push fails (around lines 153-158 in deploy.go) because the team is working locally and needs this behavior for local development workflows where registry access might not be available.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3297
File: apps/dashboard/lib/trpc/routers/authorization/roles/query.ts:210-323
Timestamp: 2025-06-04T20:13:12.060Z
Learning: The user ogzhanolguncu prefers explicit, duplicated code over abstracted helper functions when it improves readability, even if it means some duplication in filter building functions in the authorization roles query module.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3480
File: apps/dashboard/app/new-2/hooks/use-workspace-step.tsx:47-79
Timestamp: 2025-07-09T11:35:51.724Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep invariant checks that throw errors for cases that shouldn't happen in normal operation (like null workspace ID checks), rather than adding graceful error handling code for edge cases that would only occur if someone tampers with the actual flow.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3375
File: apps/dashboard/app/(app)/settings/root-keys/components/table/hooks/use-root-keys-list-query.ts:0-0
Timestamp: 2025-06-25T20:32:10.471Z
Learning: In the Unkey codebase, ogzhanolguncu prefers strict validation with fail-fast error handling. When validation errors occur that shouldn't happen in normal operation (like invalid operators), throwing errors to crash the page is preferred over graceful error handling or console logging.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3499
File: apps/dashboard/app/new/hooks/use-workspace-step.tsx:19-26
Timestamp: 2025-07-11T13:00:05.416Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep commented code for planned future features (like slug-based workspaces) rather than removing it, as it serves as a reference for upcoming implementation.
go/cmd/api/main.go (2)

Learnt from: ogzhanolguncu
PR: #3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.

Learnt from: ogzhanolguncu
PR: #3564
File: go/cmd/cli/commands/deploy/deploy.go:71-73
Timestamp: 2025-07-15T15:11:30.840Z
Learning: In the go/cmd/cli/commands/deploy/ CLI codebase, ogzhanolguncu uses a UI-layer error handling pattern where errors are displayed through ui.PrintError() and ui.PrintErrorDetails(), followed by returning nil to indicate the error has been handled at the UI level, rather than propagating errors up the call stack.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Build / Build
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Test Packages / Test
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
go/cmd/api/main.go (4)

7-7: LGTM! Clean dependency migration.

The switch from external urfave/cli/v3 to the internal CLI package reduces external dependencies and provides better control over the CLI framework.


19-24: Excellent refactoring with improved readability.

The new CLI flag syntax is clean, consistent, and maintains all the original semantics (names, defaults, environment variables). The multi-line formatting significantly improves readability.


27-34: Well-preserved functionality in the new syntax.

The instance identification flags properly maintain all original behavior including multiple environment variable support for the region flag and auto-generation logic for instance-id.


37-75: Comprehensive flag migration maintains all semantics.

All configuration flags (database, caching, observability, TLS, vault, S3) properly preserve their original behavior including required status, default values, and environment variable bindings. The use of cli.StringSlice() for vault-master-keys is particularly well-handled.

@ogzhanolguncu ogzhanolguncu requested a review from chronark July 23, 2025 10:37
Copy link
Contributor Author

@chronark can I get another approve for this one?

@ogzhanolguncu ogzhanolguncu enabled auto-merge July 23, 2025 10:57
Copy link
Contributor Author

I wouldn't mind a force merge 😄 test are a bit flaky atm

Copy link
Collaborator

it’s not flaky
a test related to your changes failed

Environment variable error: invalid boolean value in INVALID_BOOL=“maybe”: strconv.ParseBool: parsing “maybe”: invalid syntax

Copy link
Contributor Author

:oof: lemme take a look

This commit gets rid of one of the test due to its requiring us to mock
Exit function. Its creating more work than its worth
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
go/pkg/cli/flag_test.go (2)

139-158: Same validation bypass issue for float flags

This test has the same validation bypass issue as mentioned earlier for string flags. Environment variable values should be validated to maintain data integrity.


184-202: Consistent validation bypass pattern

The StringSlice flag tests follow the same pattern of bypassing validation for environment variables.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d55fb6 and 206ea81.

📒 Files selected for processing (1)
  • go/pkg/cli/flag_test.go (1 hunks)
🧠 Learnings (1)
📓 Common learnings
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:71-73
Timestamp: 2025-07-15T15:11:30.840Z
Learning: In the go/cmd/cli/commands/deploy/ CLI codebase, ogzhanolguncu uses a UI-layer error handling pattern where errors are displayed through ui.PrintError() and ui.PrintErrorDetails(), followed by returning nil to indicate the error has been handled at the UI level, rather than propagating errors up the call stack.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:14-14
Timestamp: 2025-07-15T14:47:43.718Z
Learning: In go/cmd/cli/commands/deploy/deploy.go, the DEBUG_DELAY constant is temporary code that ogzhanolguncu plans to remove in a future PR, rather than making it configurable.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/*/cmd/* : Service binary code should be in `<service>/cmd/<service | command>`.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's `*.go` code changes significantly, increase the patch-level version number.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:153-158
Timestamp: 2025-07-16T09:18:45.379Z
Learning: In the go/cmd/cli/commands/deploy/ CLI codebase, ogzhanolguncu prefers to allow deployment to continue even when Docker push fails (around lines 153-158 in deploy.go) because the team is working locally and needs this behavior for local development workflows where registry access might not be available.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3297
File: apps/dashboard/lib/trpc/routers/authorization/roles/query.ts:210-323
Timestamp: 2025-06-04T20:13:12.060Z
Learning: The user ogzhanolguncu prefers explicit, duplicated code over abstracted helper functions when it improves readability, even if it means some duplication in filter building functions in the authorization roles query module.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3480
File: apps/dashboard/app/new-2/hooks/use-workspace-step.tsx:47-79
Timestamp: 2025-07-09T11:35:51.724Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep invariant checks that throw errors for cases that shouldn't happen in normal operation (like null workspace ID checks), rather than adding graceful error handling code for edge cases that would only occur if someone tampers with the actual flow.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3375
File: apps/dashboard/app/(app)/settings/root-keys/components/table/hooks/use-root-keys-list-query.ts:0-0
Timestamp: 2025-06-25T20:32:10.471Z
Learning: In the Unkey codebase, ogzhanolguncu prefers strict validation with fail-fast error handling. When validation errors occur that shouldn't happen in normal operation (like invalid operators), throwing errors to crash the page is preferred over graceful error handling or console logging.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3499
File: apps/dashboard/app/new/hooks/use-workspace-step.tsx:19-26
Timestamp: 2025-07-11T13:00:05.416Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep commented code for planned future features (like slug-based workspaces) rather than removing it, as it serves as a reference for upcoming implementation.
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:71-73
Timestamp: 2025-07-15T15:11:30.840Z
Learning: In the go/cmd/cli/commands/deploy/ CLI codebase, ogzhanolguncu uses a UI-layer error handling pattern where errors are displayed through ui.PrintError() and ui.PrintErrorDetails(), followed by returning nil to indicate the error has been handled at the UI level, rather than propagating errors up the call stack.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:14-14
Timestamp: 2025-07-15T14:47:43.718Z
Learning: In go/cmd/cli/commands/deploy/deploy.go, the DEBUG_DELAY constant is temporary code that ogzhanolguncu plans to remove in a future PR, rather than making it configurable.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/*/cmd/* : Service binary code should be in `<service>/cmd/<service | command>`.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's `*.go` code changes significantly, increase the patch-level version number.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3564
File: go/cmd/cli/commands/deploy/deploy.go:153-158
Timestamp: 2025-07-16T09:18:45.379Z
Learning: In the go/cmd/cli/commands/deploy/ CLI codebase, ogzhanolguncu prefers to allow deployment to continue even when Docker push fails (around lines 153-158 in deploy.go) because the team is working locally and needs this behavior for local development workflows where registry access might not be available.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3297
File: apps/dashboard/lib/trpc/routers/authorization/roles/query.ts:210-323
Timestamp: 2025-06-04T20:13:12.060Z
Learning: The user ogzhanolguncu prefers explicit, duplicated code over abstracted helper functions when it improves readability, even if it means some duplication in filter building functions in the authorization roles query module.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3480
File: apps/dashboard/app/new-2/hooks/use-workspace-step.tsx:47-79
Timestamp: 2025-07-09T11:35:51.724Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep invariant checks that throw errors for cases that shouldn't happen in normal operation (like null workspace ID checks), rather than adding graceful error handling code for edge cases that would only occur if someone tampers with the actual flow.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3375
File: apps/dashboard/app/(app)/settings/root-keys/components/table/hooks/use-root-keys-list-query.ts:0-0
Timestamp: 2025-06-25T20:32:10.471Z
Learning: In the Unkey codebase, ogzhanolguncu prefers strict validation with fail-fast error handling. When validation errors occur that shouldn't happen in normal operation (like invalid operators), throwing errors to crash the page is preferred over graceful error handling or console logging.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3499
File: apps/dashboard/app/new/hooks/use-workspace-step.tsx:19-26
Timestamp: 2025-07-11T13:00:05.416Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep commented code for planned future features (like slug-based workspaces) rather than removing it, as it serves as a reference for upcoming implementation.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Build / Build
  • GitHub Check: Test Packages / Test
  • GitHub Check: autofix
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
go/pkg/cli/flag_test.go (5)

56-85: Well-structured bool flag tests

The tests correctly cover the expected behavior for boolean flags, including the common CLI pattern where an empty value defaults to true.


204-232: Good coverage of command-level flag behavior

Tests correctly verify that command-line arguments take precedence over environment variables and that required flags are enforced.


234-407: Comprehensive Require method testing*

Excellent test coverage for the Require* methods, properly verifying both success cases and panic behavior for missing or wrongly-typed flags. This aligns well with the fail-fast error handling approach.


429-445: Well-implemented validation functions

The URL and port validation functions follow standard patterns and correctly validate their respective inputs.


447-811: Exceptional error handling test coverage

The error handling tests are remarkably thorough, covering:

  • Specific error types and error wrapping
  • Error message quality and context
  • Helper methods for generating informative error messages
  • Both panic and non-panic error scenarios

The test for error message quality (lines 784-811) is particularly valuable for ensuring good developer experience.

@ogzhanolguncu ogzhanolguncu added this pull request to the merge queue Jul 23, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jul 23, 2025
@ogzhanolguncu ogzhanolguncu added this pull request to the merge queue Jul 23, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jul 23, 2025
Copy link
Contributor Author

@chronark can you merge it for me? merge queue is acting weird again

@chronark chronark merged commit 37916bc into main Jul 23, 2025
17 of 18 checks passed
@chronark chronark deleted the add-config-generation-to-cli branch July 23, 2025 13:53
@coderabbitai coderabbitai bot mentioned this pull request Jul 24, 2025
18 tasks
@coderabbitai coderabbitai bot mentioned this pull request Aug 8, 2025
18 tasks
@coderabbitai coderabbitai bot mentioned this pull request Sep 15, 2025
18 tasks
@coderabbitai coderabbitai bot mentioned this pull request Oct 3, 2025
17 tasks
@coderabbitai coderabbitai bot mentioned this pull request Oct 14, 2025
18 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants