Skip to content

feat(self-update): auto-configure PATH for install and update#103

Merged
BlackHole1 merged 4 commits into
mainfrom
add-auto-add-path
Apr 24, 2026
Merged

feat(self-update): auto-configure PATH for install and update#103
BlackHole1 merged 4 commits into
mainfrom
add-auto-add-path

Conversation

@BlackHole1
Copy link
Copy Markdown
Member

Self-managed installs could succeed while leaving future shells unable to find oo until users edited PATH manually. Persist the managed bin directory across supported shells, print a restart note when that succeeds, and keep an explicit opt-out with --no-modify-path and OO_NO_MODIFY_PATH.

fixed: #99

Self-managed installs could succeed while leaving future shells
unable to find `oo` until users edited PATH manually. Persist the
managed bin directory across supported shells, print a restart note
when that succeeds, and keep an explicit opt-out with
`--no-modify-path` and `OO_NO_MODIFY_PATH`.

Signed-off-by: Kevin Cui <bh@bugs.cc>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Summary by CodeRabbit

  • New Features

    • Added --no-modify-path flag and OO_NO_MODIFY_PATH env var to skip automatic PATH updates (flag OR env).
    • Added clearer user messages for partial/failed/skipped PATH updates.
  • User-facing Behavior

    • CLI now attempts to persist PATH for future shells, prompts restart on success, or prints which directory to add on failure.
    • Lists which profiles were updated vs not and provides next-step instructions.
  • Documentation

    • Command docs (including Chinese) updated with new option and PATH semantics.
  • Tests

    • Expanded tests for PATH handling, env parsing, and output messages.

Walkthrough

Adds an opt-out for automatic PATH modification via --no-modify-path and OO_NO_MODIFY_PATH (OR semantics). Commands install and update resolve the effective preference, pass modifyPath into the self-update flow, and centralize PATH persistence via ensureSelfUpdateExecutableDirectoryOnPathensureExecutableDirectoryOnPath. PATH outcomes are reported with a new SelfUpdatePathConfigurationResult shape and rendered by writeSelfUpdatePathNoteIfNeeded. Also adds env-boolean parsing, i18n strings, and tests for the new behaviors.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as "oo install / oo update"
    participant Pref as "resolveSelfUpdateModifyPath"
    participant SelfUpdate as "performSelfUpdateOperation / ensureSelfUpdateExecutableDirectoryOnPath"
    participant PathWriter as "ensureExecutableDirectoryOnPath"
    participant Output as "writeSelfUpdatePathNoteIfNeeded"

    User->>CLI: run command (with optional --no-modify-path or OO_NO_MODIFY_PATH)
    CLI->>Pref: resolve modifyPath from flag + env
    Pref-->>CLI: effectiveModifyPath
    CLI->>SelfUpdate: performSelfUpdateOperation(modifyPath: effectiveModifyPath)
    SelfUpdate->>SelfUpdate: determine executableDirectory
    alt modifyPath == false
        SelfUpdate-->>CLI: pathConfiguration = skipped | already-configured
    else
        SelfUpdate->>PathWriter: ensureExecutableDirectoryOnPath(executableDirectory, modifyPath)
        PathWriter-->>SelfUpdate: pathConfiguration (already-configured | configured | partial-configured | failed)
    end
    CLI->>Output: writeSelfUpdatePathNoteIfNeeded(executableDirectory, pathConfiguration)
    Output-->>User: prints appropriate message (none / added+restart / partial lists+restart / manual setup)
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required format and accurately describes the main objective of auto-configuring PATH for install and update commands.
Description check ✅ Passed The description is directly related to the changeset, explaining the problem, solution, and opt-out mechanism, with a reference to the fixed issue.
Linked Issues check ✅ Passed The PR fully addresses issue #99 by implementing PATH persistence across multiple shell profiles, providing installation guidance, and allowing users to opt-out via flag and environment variable.
Out of Scope Changes check ✅ Passed All changes directly support the linked objectives: PATH configuration, shell profile handling, opt-out mechanisms, and related documentation updates. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch add-auto-add-path

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (1)
src/application/commands/self-update.cli.test.ts (1)

142-147: Make the PATH assertions platform-neutral.

These expectations hardcode <HOME>/.local/bin, so they become brittle on Windows and whenever the managed bin path changes. Build the expected path with node:path helpers (or a shared snapshot helper) and interpolate it into the expected stdout instead of embedding POSIX separators.

As per coding guidelines, "Never assume POSIX path separators in code, tests, snapshots, or assertions; use node:path helpers (join(), resolve(), relative()) for path construction."

Also applies to: 181-186, 222-226, 274-278, 421-425, 470-475, 511-516, 554-558, 610-614, 666-670

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/application/commands/self-update.cli.test.ts` around lines 142 - 147, The
test hardcodes POSIX separators in the expected stdout (e.g.
"<HOME>/.local/bin"), which breaks on Windows; update the assertions in
createSelfUpdateInstallSnapshot expectations (and the other similar snapshots
referenced) to build the managed bin path using node:path helpers and
interpolate that constructed path into the expected stdout string instead of
embedding "<HOME>/.local/bin". Concretely, import or require node:path (e.g.
path.join(home, ".local", "bin") or use the shared snapshot helper if
available), compute expectedManagedBinPath once, then assert stdout contains
`Add ${expectedManagedBinPath} to PATH...` while leaving the rest of the
snapshot unchanged; keep the call to selfUpdateRuntime.configurePathCallCount()
as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/application/self-update/path-configuration.ts`:
- Around line 128-173: The loop may produce mixed outcomes (some profiles
written, some failed) but current logic returns "configured" whenever
configuredTargets.length > 0; change this so partial successes are not reported
as fully configured: after the loop, compute failedTargets =
configurations.map(c=>c.profilePath) minus configuredTargets and
alreadyConfiguredTargets, and if failedTargets.length > 0 return status "failed"
(or a distinct "partial-failure" if you prefer) with failedTargets; only return
"configured" when configuredTargets.length > 0 and failedTargets.length === 0,
and keep the existing "already-configured" branch for when nothing was changed
but no failures occurred; update references to configurations,
writeShellProfileConfiguration, configuredTargets, alreadyConfiguredTargets, and
options.runtime.logger.* accordingly.
- Around line 493-504: Replace the fragile reference-equality guard that
compares options.env to process.env with an explicit boolean flag on the options
object (e.g., canModifyRealProcess or isProduction) and use that flag in
ensureExecutableDirectoryOnPath/ensureSelfUpdateExecutableDirectoryOnPath to
decide whether to modify the real process PATH; update the option type/interface
and all call sites that pass options (including runCli,
createCliExecutionContext, update.ts) to set the new flag appropriately so the
check becomes if (!options.canModifyRealProcess) { log debug and return {
status: "failed" } } instead of comparing env objects.

---

Nitpick comments:
In `@src/application/commands/self-update.cli.test.ts`:
- Around line 142-147: The test hardcodes POSIX separators in the expected
stdout (e.g. "<HOME>/.local/bin"), which breaks on Windows; update the
assertions in createSelfUpdateInstallSnapshot expectations (and the other
similar snapshots referenced) to build the managed bin path using node:path
helpers and interpolate that constructed path into the expected stdout string
instead of embedding "<HOME>/.local/bin". Concretely, import or require
node:path (e.g. path.join(home, ".local", "bin") or use the shared snapshot
helper if available), compute expectedManagedBinPath once, then assert stdout
contains `Add ${expectedManagedBinPath} to PATH...` while leaving the rest of
the snapshot unchanged; keep the call to
selfUpdateRuntime.configurePathCallCount() as-is.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44a22f7c-5190-44d5-85aa-983ee6117013

📥 Commits

Reviewing files that changed from the base of the PR and between e48ff1f and ba522f8.

⛔ Files ignored due to path filters (4)
  • src/application/bootstrap/__snapshots__/run-cli.test.ts.snap is excluded by !**/*.snap
  • src/application/commands/__snapshots__/self-update.cli.test.ts.snap is excluded by !**/*.snap
  • src/application/commands/config/__snapshots__/index.cli.test.ts.snap is excluded by !**/*.snap
  • src/application/commands/skills/__snapshots__/index.cli.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (16)
  • docs/commands.md
  • docs/commands.zh-CN.md
  • src/application/commands/install.ts
  • src/application/commands/self-update-output.test.ts
  • src/application/commands/self-update-output.ts
  • src/application/commands/self-update.cli.test.ts
  • src/application/commands/update.ts
  • src/application/contracts/self-update.ts
  • src/application/self-update/core.ts
  • src/application/self-update/modify-path-preference.test.ts
  • src/application/self-update/modify-path-preference.ts
  • src/application/self-update/path-configuration.test.ts
  • src/application/self-update/path-configuration.ts
  • src/application/shared/env-boolean.test.ts
  • src/application/shared/env-boolean.ts
  • src/i18n/catalog.ts

Comment thread src/application/self-update/path-configuration.ts
Comment thread src/application/self-update/path-configuration.ts Outdated
PATH setup can touch multiple shell profiles, so a single failed write
should not be reported as a full success. Report partial setup results
with separate updated and failed profile lists so users know what still
needs manual attention.

Gate Windows user PATH registry writes behind an explicit runtime flag
that is enabled only by the real CLI entry point. This keeps executeCli
and test sandboxes safe by default while still allowing production
installs and updates to persist PATH changes.

Signed-off-by: Kevin Cui <bh@bugs.cc>
@BlackHole1
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
src/application/self-update/modify-path-preference.test.ts (1)

8-45: Extract repeated resolver setup into a local test helper.

The test intent is clear, but the repeated { env, modifyPathFlag } setup is duplicated across cases and can be centralized.

♻️ Suggested refactor
 describe("resolveSelfUpdateModifyPath", () => {
+    function resolve(modifyPathFlag: boolean, envValue?: string): boolean {
+        return resolveSelfUpdateModifyPath({
+            env: envValue === undefined
+                ? {}
+                : { [selfUpdateNoModifyPathEnvName]: envValue },
+            modifyPathFlag,
+        });
+    }
+
     test("modifies PATH by default when neither flag nor env disables it", () => {
-        expect(resolveSelfUpdateModifyPath({
-            env: {},
-            modifyPathFlag: true,
-        })).toBeTrue();
+        expect(resolve(true)).toBeTrue();
     });
@@
     test("skips PATH modification when --no-modify-path is passed", () => {
-        expect(resolveSelfUpdateModifyPath({
-            env: {},
-            modifyPathFlag: false,
-        })).toBeFalse();
+        expect(resolve(false)).toBeFalse();
     });
@@
             expect(resolveSelfUpdateModifyPath({
-                env: { [selfUpdateNoModifyPathEnvName]: value },
-                modifyPathFlag: true,
-            })).toBeFalse();
+            expect(resolve(true, value)).toBeFalse();
         }
     });

As per coding guidelines: "Extract repeated setup (mocks, stubs, setup objects) into local factory functions in test files; avoid copy-pasting test setup".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/application/self-update/modify-path-preference.test.ts` around lines 8 -
45, The tests repeatedly construct the same input object for
resolveSelfUpdateModifyPath with env and modifyPathFlag; extract a small local
factory/helper (e.g., makeInput or buildResolveInput) inside the test file to
produce { env, modifyPathFlag } and replace each inline object with calls to
that helper, referencing resolveSelfUpdateModifyPath and
selfUpdateNoModifyPathEnvName to ensure env keys are set consistently and the
tests remain identical in behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/application/self-update/path-configuration.ts`:
- Around line 291-295: The selection pipeline (selectedConfigurations) can
include the same ".profile" fallback twice when both the candidate filter and
the subsequent fallback-append logic add a ".profile" entry; update the logic
that builds selectedConfigurations and the fallback append routine (referencing
selectedConfigurations, candidates, shouldConfigureShellProfile, and shellName)
to deduplicate by the profile target (e.g., use a Set of target paths or dedupe
by a unique key) so that a ".profile" configuration is only added once; ensure
deduplication runs after combining initial selections and any fallback entries
so the final array contains unique profile targets only.

---

Nitpick comments:
In `@src/application/self-update/modify-path-preference.test.ts`:
- Around line 8-45: The tests repeatedly construct the same input object for
resolveSelfUpdateModifyPath with env and modifyPathFlag; extract a small local
factory/helper (e.g., makeInput or buildResolveInput) inside the test file to
produce { env, modifyPathFlag } and replace each inline object with calls to
that helper, referencing resolveSelfUpdateModifyPath and
selfUpdateNoModifyPathEnvName to ensure env keys are set consistently and the
tests remain identical in behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e8e61954-f559-4f4e-b208-791d3c9e6b61

📥 Commits

Reviewing files that changed from the base of the PR and between e48ff1f and 772201b.

⛔ Files ignored due to path filters (4)
  • src/application/bootstrap/__snapshots__/run-cli.test.ts.snap is excluded by !**/*.snap
  • src/application/commands/__snapshots__/self-update.cli.test.ts.snap is excluded by !**/*.snap
  • src/application/commands/config/__snapshots__/index.cli.test.ts.snap is excluded by !**/*.snap
  • src/application/commands/skills/__snapshots__/index.cli.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (17)
  • docs/commands.md
  • docs/commands.zh-CN.md
  • src/application/bootstrap/run-cli.ts
  • src/application/commands/install.ts
  • src/application/commands/self-update-output.test.ts
  • src/application/commands/self-update-output.ts
  • src/application/commands/self-update.cli.test.ts
  • src/application/commands/update.ts
  • src/application/contracts/self-update.ts
  • src/application/self-update/core.ts
  • src/application/self-update/modify-path-preference.test.ts
  • src/application/self-update/modify-path-preference.ts
  • src/application/self-update/path-configuration.test.ts
  • src/application/self-update/path-configuration.ts
  • src/application/shared/env-boolean.test.ts
  • src/application/shared/env-boolean.ts
  • src/i18n/catalog.ts

Comment thread src/application/self-update/path-configuration.ts
Unknown shells can trigger an extra .profile fallback after bash selection has already included the same profile path. Avoiding that duplicate keeps reported targets accurate and prevents redundant profile writes during PATH setup.

Signed-off-by: Kevin Cui <bh@bugs.cc>
Copy link
Copy Markdown

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/application/self-update/path-configuration.ts`:
- Around line 485-492: The PowerShell snippet built in
createPowerShellProfilePathSnippet uses $env:Path which is lowercase-sensitive
and can be $null on PowerShell 7; update the strings that reference the
environment variable (inside the array returned by
createPowerShellProfilePathSnippet and related to pathConfigurationSentinel) to
use $env:PATH and guard against null before calling .Split() (e.g., treat
$env:PATH as empty string when null or check -and $env:PATH). Ensure the two
lines that currently call $env:Path.Split(...) and reference $env:Path are
changed to use $env:PATH with a null-safe expression.
- Around line 322-389: createBashProfileCandidates currently returns only
existing bash profile files and only synthesizes fallbacks when none exist,
which leaves the alternate bash startup path unconfigured; update
createBashProfileCandidates to (1) include ".bash_login" in the profileNames
check on non-darwin platforms so login-chain detection is complete, and (2) when
at least one profile exists, still synthesize the complementary profile(s) so
both login and non-login startup paths are covered (i.e., for each of the
intended fallbackNames ensure an entry is returned with profileExists true/false
based on the readTextFileIfExists results rather than returning early when
existing.length > 0). Use the existing symbols profileNames, fallbackNames,
readTextFileIfExists, and createBashProfileCandidates to implement this
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5b385788-e303-4cf5-9be3-c95a5b83088c

📥 Commits

Reviewing files that changed from the base of the PR and between 772201b and 9199752.

📒 Files selected for processing (2)
  • src/application/self-update/path-configuration.test.ts
  • src/application/self-update/path-configuration.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/application/self-update/path-configuration.test.ts

Comment thread src/application/self-update/path-configuration.ts
Comment thread src/application/self-update/path-configuration.ts
Users with only a bash login profile or only .bashrc could still miss oo in the other startup mode. Fill in the missing fallback profile and make the PowerShell snippet handle an empty PATH without relying on case-specific environment variable spelling.

Signed-off-by: Kevin Cui <bh@bugs.cc>
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
src/application/self-update/path-configuration.ts (1)

331-335: Consider constructing profileNames from the declared constants.

The interactiveProfileName and loginProfileNames constants are defined but not used to construct profileNames. This creates a slight maintenance risk if the constants are updated but the array is forgotten.

♻️ Optional: derive profileNames from constants
 const interactiveProfileName = ".bashrc";
 const loginProfileNames = [".bash_profile", ".bash_login", ".profile"] as const;
-const profileNames = options.platform === "darwin"
-    ? [".bash_profile", ".bash_login", ".bashrc", ".profile"]
-    : [".bashrc", ".bash_profile", ".bash_login", ".profile"];
+const profileNames = options.platform === "darwin"
+    ? [...loginProfileNames.slice(0, 2), interactiveProfileName, loginProfileNames[2]]
+    : [interactiveProfileName, ...loginProfileNames];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/application/self-update/path-configuration.ts` around lines 331 - 335,
Construct profileNames by reusing the existing constants interactiveProfileName
and loginProfileNames instead of hardcoding arrays; update the logic that sets
profileNames (currently using options.platform === "darwin") to produce the
macOS order [ ...loginProfileNames, interactiveProfileName, ".profile" ] and the
non-mac order [ interactiveProfileName, ...loginProfileNames, ".profile" ] so
changes to interactiveProfileName or loginProfileNames automatically propagate;
ensure you reference the symbols interactiveProfileName, loginProfileNames,
profileNames and options.platform in the modified code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/application/self-update/path-configuration.ts`:
- Around line 331-335: Construct profileNames by reusing the existing constants
interactiveProfileName and loginProfileNames instead of hardcoding arrays;
update the logic that sets profileNames (currently using options.platform ===
"darwin") to produce the macOS order [ ...loginProfileNames,
interactiveProfileName, ".profile" ] and the non-mac order [
interactiveProfileName, ...loginProfileNames, ".profile" ] so changes to
interactiveProfileName or loginProfileNames automatically propagate; ensure you
reference the symbols interactiveProfileName, loginProfileNames, profileNames
and options.platform in the modified code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c078436-d78b-4f8a-a500-4f93e5e459d4

📥 Commits

Reviewing files that changed from the base of the PR and between 9199752 and 555600c.

📒 Files selected for processing (2)
  • src/application/self-update/path-configuration.test.ts
  • src/application/self-update/path-configuration.ts

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.

某些情况下 codex 的查找路径可能不对。

1 participant