Skip to content

Add lets self doc command#311

Merged
kindermax merged 4 commits intomasterfrom
codex/add-lets-self-doc-command
Mar 19, 2026
Merged

Add lets self doc command#311
kindermax merged 4 commits intomasterfrom
codex/add-lets-self-doc-command

Conversation

@kindermax
Copy link
Collaborator

@kindermax kindermax commented Mar 19, 2026

Summary

  • introduce lets self doc and hook it into the root self command so doc browsing is a valid built-in action
  • add the browser utility, its unit tests, and integration coverage in bats to record opened URLs without a config file
  • document the feature in docs/docs/changelog.md and ensure config errors are suppressed during doc/help/lsp/self invocations

Testing

  • Not run (not requested)

Summary by Sourcery

Add a new self documentation command that opens the online docs and ensure it works without requiring a config file or failing on config errors for doc-related commands.

New Features:

  • Introduce a lets self doc (alias docs) command that opens the Lets online documentation in the default browser.

Enhancements:

  • Allow self and doc commands to bypass config errors similar to help, completion, and lsp commands.
  • Add a cross-platform browser-opening utility used by the documentation command.

Documentation:

  • Document the new lets self doc command in the changelog.

Tests:

  • Add unit tests for the browser utility behavior on supported and unsupported platforms.
  • Add tests for the self doc command behavior, including URL opening and error propagation.
  • Add an integration test to verify lets self doc works and opens the expected docs URL even when no config file is present.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 19, 2026

Reviewer's Guide

Adds a new lets self doc subcommand that opens the online documentation in a browser using a new cross-platform browser utility, wires it into the root self command and config-error handling, and covers it with unit, integration, and changelog updates.

Sequence diagram for lets self doc command execution

sequenceDiagram
    actor User
    participant LetsCLI as lets_binary
    participant CobraRoot as RootCommand
    participant SelfCmd as SelfCommand
    participant DocCmd as DocCommand
    participant Util as UtilOpenURL
    participant Browser as SystemBrowser

    User->>LetsCLI: run lets self doc
    LetsCLI->>CobraRoot: execute
    CobraRoot->>SelfCmd: dispatch self
    SelfCmd->>DocCmd: dispatch doc
    DocCmd->>Util: OpenURL(letsDocsURL)
    Util->>Browser: exec.Command(browserCommand, url)
    Browser-->>Util: process started
    Util-->>DocCmd: nil error
    DocCmd-->>SelfCmd: nil error
    SelfCmd-->>CobraRoot: nil error
    CobraRoot-->>LetsCLI: exit code 0
    LetsCLI-->>User: documentation opened in browser
Loading

Class diagram for new doc command and browser utility

classDiagram
    class CobraCommand {
        +string Use
        +string~slice~ Aliases
        +string Short
        +function Args(cmd CobraCommand, args string~slice~) error
        +function RunE(cmd CobraCommand, args string~slice~) error
    }

    class DocCommandFactory {
        +const letsDocsURL string
        +initDocCommand(openURL func(string) error) *CobraCommand
    }

    class SelfCommandInitializer {
        +openURL func(string) error
        +InitSelfCmd(rootCmd *CobraCommand, version string)
    }

    class UtilBrowser {
        +browserCommand(goos string, url string) (string, string~slice~, error)
        +OpenURL(url string) error
    }

    SelfCommandInitializer --> CobraCommand : configures
    SelfCommandInitializer --> DocCommandFactory : calls initDocCommand
    DocCommandFactory --> CobraCommand : constructs
    SelfCommandInitializer --> UtilBrowser : uses OpenURL via openURL var
    UtilBrowser --> CobraCommand : used indirectly in command execution
Loading

Flow diagram for config error handling including self and doc commands

flowchart TD
    A[Start lets CLI] --> B[Parse flags and commands]
    B --> C{Current command name}
    C --> D[completion/help/self/lsp/doc]
    C --> E[Other command]

    D --> F{Config error?}
    F --> G[Do not fail on config error]
    G --> H[Proceed with command]

    E --> I{Config error?}
    I --> J[Fail on config error]
    J --> K[Exit with error]
    I --> H
    F --> H
Loading

File-Level Changes

Change Details Files
Introduce lets self doc/lets self docs command that opens the documentation URL and is wired into the self command.
  • Create a new Cobra subcommand factory initDocCommand with aliases doc and docs, no args, and a RunE implementation that calls an injectable openURL function and wraps failures with a user-facing error message.
  • Expose a constant letsDocsURL pointing at the quick start documentation URL and use it as the target for the doc command.
  • Register the new doc command from InitSelfCmd on the existing self command, passing in the package-level openURL variable for dependency injection.
internal/cmd/doc.go
internal/cmd/self.go
Add a browser utility for opening URLs in a platform-specific way, plus unit tests.
  • Implement browserCommand to map GOOS values darwinopen, linuxxdg-open, and return an error for unsupported platforms.
  • Implement OpenURL to derive the command using runtime.GOOS, spawn the browser command asynchronously via exec.Command.Start, and release the process if available, returning wrapped start errors.
  • Add tests for browserCommand covering Darwin, Linux, and an unsupported platform case.
internal/util/browser.go
internal/util/browser_test.go
Ensure the self doc command is testable and properly integrated, including behavior under failure.
  • Introduce a package-level openURL variable in the self command package, initialized to util.OpenURL, to allow stubbing in tests.
  • Add tests for lets self doc to verify the browser opener is called with the correct documentation URL and that opener errors are surfaced with the expected wrapped error message.
internal/cmd/self.go
internal/cmd/root_test.go
Add integration coverage for lets self doc without a config file by faking the browser command.
  • Extend the no_lets_file.bats suite with a test that injects a fake xdg-open into PATH, captures the URL into a temp file using LETS_TEST_OPENED_URL_FILE, and runs lets self doc with a non-existent config.
  • Assert successful command execution, wait briefly for the URL file to appear, and verify it contains the expected documentation URL, cleaning up temporary resources afterward.
tests/no_lets_file.bats
Update configuration error handling and documentation.
  • Expand the root command set in failOnConfigError so that self and doc are treated like help/completion/lsp and do not fail on config errors when invoked without other flags.
  • Document the new lets self doc command addition in the changelog.
cmd/lets/main.go
docs/docs/changelog.md

Possibly linked issues

  • #: PR adds the lets self doc command, browser utility, tests, and docs exactly as requested in the issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 security issue, 2 other issues, and left some high level feedback:

Security issues:

  • Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code. (link)

General comments:

  • The openURL package-level variable in self.go is mutated in tests, which can be brittle and race-prone if tests are ever run in parallel; consider wiring the opener via function arguments/command construction (like initDocCommand(util.OpenURL)) and injecting a stub from tests instead of relying on a mutable global.
  • In failOnConfigError, adding both "self" and "doc" to rootCommands may not accurately model lets self doc (where current.Name() is just "doc"), and also pre-emptively disables config errors for a potential top-level doc command; consider basing the decision on the full command path or parent command chain instead of just current.Name().
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `openURL` package-level variable in `self.go` is mutated in tests, which can be brittle and race-prone if tests are ever run in parallel; consider wiring the opener via function arguments/command construction (like `initDocCommand(util.OpenURL)`) and injecting a stub from tests instead of relying on a mutable global.
- In `failOnConfigError`, adding both `"self"` and `"doc"` to `rootCommands` may not accurately model `lets self doc` (where `current.Name()` is just `"doc"`), and also pre-emptively disables config errors for a potential top-level `doc` command; consider basing the decision on the full command path or parent command chain instead of just `current.Name()`.

## Individual Comments

### Comment 1
<location path="internal/util/browser_test.go" line_range="41-23" />
<code_context>
+		}
+	})
+
+	t.Run("unsupported", func(t *testing.T) {
+		_, _, err := browserCommand("windows", "https://lets-cli.org")
+		if err == nil {
+			t.Fatal("expected unsupported platform error")
+		}
+	})
+}
</code_context>
<issue_to_address>
**suggestion (testing):** Add an assertion on the error message for unsupported platforms

This subtest only checks that an error is returned. Please also assert that `err.Error()` mentions the OS (e.g. contains "windows") so the test enforces a descriptive, platform-specific error rather than allowing a generic one.

Suggested implementation:

```golang
	t.Run("unsupported", func(t *testing.T) {
		_, _, err := browserCommand("windows", "https://lets-cli.org")
		if err == nil {
			t.Fatal("expected unsupported platform error")
		}
		if !strings.Contains(err.Error(), "windows") {
			t.Fatalf("expected error to mention platform \"windows\", got %q", err.Error())
		}
	})
}

```

Ensure that `strings` is imported at the top of `internal/util/browser_test.go`:

- Add `import "strings"` to the existing import block if it is not already present.
</issue_to_address>

### Comment 2
<location path="tests/no_lets_file.bats" line_range="55-57" />
<code_context>
+}
+
+@test "no_lets_file: lets self doc opens docs without config" {
+    fake_bin_dir="$(mktemp -d)"
+    opened_url_file="$(mktemp)"
+    rm -f "${opened_url_file}"
+
+    cat > "${fake_bin_dir}/xdg-open" <<'EOF'
</code_context>
<issue_to_address>
**nitpick (testing):** Use Bats teardown or traps to ensure temporary files/directories are cleaned up even when the test fails

`fake_bin_dir` and `opened_url_file` are only removed at the end of the test body, so a failing assertion will skip cleanup and leave temporary files/directories behind. Move this cleanup into a `teardown` function or a `trap` so it always runs, even on failure.
</issue_to_address>

### Comment 3
<location path="internal/util/browser.go" line_range="26" />
<code_context>
	cmd := exec.Command(name, args...)
</code_context>
<issue_to_address>
**security (go.lang.security.audit.dangerous-exec-command):** Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +55 to +57
fake_bin_dir="$(mktemp -d)"
opened_url_file="$(mktemp)"
rm -f "${opened_url_file}"
Copy link

Choose a reason for hiding this comment

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

nitpick (testing): Use Bats teardown or traps to ensure temporary files/directories are cleaned up even when the test fails

fake_bin_dir and opened_url_file are only removed at the end of the test body, so a failing assertion will skip cleanup and leave temporary files/directories behind. Move this cleanup into a teardown function or a trap so it always runs, even on failure.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 19, 2026

Greptile Summary

This PR introduces a lets self doc command (with a docs alias) that opens the online documentation at https://lets-cli.org/docs/quick_start in the user's default browser. The implementation adds a browser.go utility using a fire-and-forget exec.Command approach, wires it into the existing self subcommand, and correctly exempts self and doc from the config-error-check guard in main.go so the command works even when no lets.yaml is present.

Key changes and issues:

  • Windows support missingbrowserCommand only handles darwin and linux; all other platforms (including Windows) return an "unsupported platform" error, making lets self doc completely broken on Windows. The unit test even labels Windows as the "unsupported" case, confirming this is a known but undocumented gap.
  • Bats integration test is Linux-only — The test fakes only xdg-open. On macOS, OpenURL calls open instead, so the fake binary is never invoked, opened_url_file is never written, and the test will fail in any macOS CI environment.
  • No stdout feedback on success — The doc command opens the browser silently. Because the process is fire-and-forget, users have no confirmation that anything happened; a short "Opening …" message to cmd.OutOrStdout() would improve the UX.
  • The failOnConfigError exclusion-set change and the unit tests in root_test.go are well-structured and follow existing patterns correctly.

Confidence Score: 3/5

  • Safe to merge for Linux/macOS users, but the command is broken on Windows and the integration test will fail in a macOS CI environment.
  • The core feature works correctly on Linux and macOS. However, two P1 issues prevent a clean merge: Windows users will always receive an "unsupported platform" error from lets self doc, and the bats integration test is Linux-specific and will fail on any macOS CI runner. These should be addressed before merging to avoid shipping a broken experience on an entire platform and introducing a flaky test.
  • internal/util/browser.go (missing Windows case) and tests/no_lets_file.bats (Linux-only fake binary).

Important Files Changed

Filename Overview
internal/util/browser.go New file implementing cross-platform URL opener; missing Windows support causes lets self doc to fail on Windows with an "unsupported platform" error.
internal/util/browser_test.go Unit tests for browserCommand; covers darwin, linux, and "unsupported" (Windows) — the Windows test explicitly asserts an error, confirming the intentional gap.
internal/cmd/doc.go New doc subcommand under self; clean implementation but provides no stdout feedback to the user when the browser opens successfully.
internal/cmd/self.go Wires initDocCommand into the self subcommand and exposes openURL as a package-level variable for test injection; straightforward change.
cmd/lets/main.go Adds "self" and "doc" to the failOnConfigError exclusion set so config-load errors are suppressed when the user invokes lets self doc; correct approach following the existing pattern.
internal/cmd/root_test.go Adds two unit tests for lets self doc: one for success and one for opener error propagation; both use a package-level variable swap pattern (non-parallel, so no data race).
tests/no_lets_file.bats Integration test for lets self doc without a config file; fakes only xdg-open, making the test Linux-specific — it will fail on macOS CI where open is used instead.
docs/docs/changelog.md Adds a changelog entry for the new lets self doc command; no issues.

Sequence Diagram

sequenceDiagram
    participant User
    participant main as cmd/lets/main.go
    participant cobra as cobra (rootCmd)
    participant self as self command
    participant doc as doc command
    participant browser as internal/util/browser.go
    participant OS as OS process (xdg-open / open)

    User->>main: lets self doc
    main->>cobra: Traverse(["self","doc"])
    cobra-->>main: current=doc command
    main->>main: failOnConfigError()<br/>"doc" is in exclusion set → false
    Note over main: config error suppressed
    main->>cobra: ExecuteContext(ctx)
    cobra->>self: route to self
    self->>doc: route to doc
    doc->>browser: openURL("https://lets-cli.org/docs/quick_start")
    browser->>browser: browserCommand(runtime.GOOS, url)
    alt darwin
        browser->>OS: exec.Command("open", url).Start()
    else linux
        browser->>OS: exec.Command("xdg-open", url).Start()
    else windows / other
        browser-->>doc: error: unsupported platform
        doc-->>User: "can not open documentation: unsupported platform"
    end
    OS->>OS: Process.Release() (fire-and-forget)
    browser-->>doc: nil
    doc-->>User: (silent success)
Loading

Last reviewed commit: "Add lets self doc co..."

Comment on lines +54 to +85
@test "no_lets_file: lets self doc opens docs without config" {
fake_bin_dir="$(mktemp -d)"
opened_url_file="$(mktemp)"
rm -f "${opened_url_file}"

cat > "${fake_bin_dir}/xdg-open" <<'EOF'
#!/usr/bin/env bash
printf "%s" "$1" > "${LETS_TEST_OPENED_URL_FILE}"
EOF
chmod +x "${fake_bin_dir}/xdg-open"

PATH="${fake_bin_dir}:${PATH}" \
LETS_CONFIG=${NOT_EXISTED_LETS_FILE} \
LETS_TEST_OPENED_URL_FILE="${opened_url_file}" \
run lets self doc

assert_success

for _ in $(seq 1 20); do
if [[ -f "${opened_url_file}" ]]; then
break
fi
sleep 0.1
done

run cat "${opened_url_file}"
assert_success
assert_output "https://lets-cli.org/docs/quick_start"

rm -rf "${fake_bin_dir}"
rm -f "${opened_url_file}"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

P1 Integration test is Linux-only and will fail on macOS

The test fakes only xdg-open, which is the binary chosen by OpenURL on Linux. On macOS, OpenURL calls open instead (see internal/util/browser.go). If this test is ever run in a macOS CI environment, the fake binary is never invoked, opened_url_file is never written, and the final cat assertion fails.

To make the test portable, create a fake for both launchers:

cat > "${fake_bin_dir}/xdg-open" <<'EOF'
#!/usr/bin/env bash
printf "%s" "$1" > "${LETS_TEST_OPENED_URL_FILE}"
EOF
chmod +x "${fake_bin_dir}/xdg-open"

# macOS uses `open`
cat > "${fake_bin_dir}/open" <<'EOF'
#!/usr/bin/env bash
printf "%s" "$1" > "${LETS_TEST_OPENED_URL_FILE}"
EOF
chmod +x "${fake_bin_dir}/open"

Alternatively, add a platform guard and skip on unsupported platforms.

@kindermax kindermax force-pushed the codex/add-lets-self-doc-command branch from 1aad4c4 to 47eb6d5 Compare March 19, 2026 08:34
@kindermax kindermax force-pushed the codex/add-lets-self-doc-command branch from 743952f to efef308 Compare March 19, 2026 08:42
kindermax and others added 2 commits March 19, 2026 10:44
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@kindermax kindermax merged commit c73fc07 into master Mar 19, 2026
5 checks passed
@kindermax kindermax deleted the codex/add-lets-self-doc-command branch March 19, 2026 08:58
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.

1 participant