Skip to content

fix(setup): preserve interactive init and cancellation#42

Merged
ChangeHow merged 1 commit into
mainfrom
fix/setup-cancellation
Jul 12, 2026
Merged

fix(setup): preserve interactive init and cancellation#42
ChangeHow merged 1 commit into
mainfrom
fix/setup-cancellation

Conversation

@ChangeHow

Copy link
Copy Markdown
Owner

问题

  • init 错误地把整个初始化流程设为非交互,替用户选择步骤和软件
  • .zshrc 覆盖确认处按 Ctrl-C 会被当作拒绝覆盖,后续安装仍继续
  • Homebrew 同步安装期间的 SIGINT 会被 brewInstall 吞掉

修复

  • init 恢复步骤、工具和应用的交互式问答,只有底层包安装保持非交互
  • prompt cancel 立即终止 setup
  • Homebrew 安装向上传播 SIGINT
  • 隔离性能测试与用户环境变量
  • 同步中英文文档及 installer/CLI 文案

验证

  • npm test(231 tests passed)
  • git diff --check

@github-actions

Copy link
Copy Markdown

Zsh startup benchmark

Ubuntu 24.04 with Linuxbrew, atuin, fnm, fzf, zoxide, zinit, and the shipped plugins. Lower is better.

Warm startup (daily use)

Command Mean [ms] Min [ms] Max [ms] Relative
current (f2e4f1b) 67.9 ± 1.9 64.5 71.4 1.00 ± 0.04
main (8165d07) 67.6 ± 1.6 64.1 70.3 1.00
previous commit (8165d07) 67.8 ± 1.7 64.7 71.4 1.00 ± 0.03

Cache-miss startup

Completion and tool-init caches are removed before every run; installed tools and plugins remain.

Command Mean [ms] Min [ms] Max [ms] Relative
current (f2e4f1b) 433.6 ± 2.5 430.2 437.4 1.00 ± 0.01
main (8165d07) 436.0 ± 2.7 432.6 440.4 1.01 ± 0.01
previous commit (8165d07) 431.7 ± 3.6 425.8 436.7 1.00

@ChangeHow
ChangeHow merged commit 4b0e035 into main Jul 12, 2026
2 checks passed
@ChangeHow
ChangeHow deleted the fix/setup-cancellation branch July 12, 2026 07:21

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request updates the init command to run an interactive guided initialization instead of a non-interactive setup with defaults, updates documentation and help text accordingly, and improves error handling by propagating Ctrl-C from Homebrew installations and handling .zshrc overwrite cancellations. The review feedback highlights that mapping init to runSetup() without arguments makes it identical to setup, creating redundant options in the CLI and installer. Additionally, the test assertion in tests/shell.test.js using expect.objectContaining inside toThrow is not supported in Vitest/Jest and should be refactored to catch and assert the error properties directly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/cli.js
Comment on lines 31 to 33
case "init":
await runSetup({ defaults: true });
await runSetup();
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

With this change, the init command now calls runSetup() with no arguments, making it identical in behavior to the setup command. Both commands will run the full interactive setup flow.

This redundancy makes the distinction between init and setup in install.sh, README.md, and the help text confusing for users, as they are presented as two different options (e.g., 'interactive, choose each step yourself' vs 'interactive guided initialization') but result in the exact same experience.

If init and setup are intended to be identical, consider simplifying the CLI and installer by removing the redundant option or clearly documenting init as an alias. If they are intended to be different, init should pass specific options to runSetup to differentiate its behavior.

Comment thread install.sh
echo 'How would you like to run suitup?' >&2
echo ' 1) setup — interactive, choose each step yourself' >&2
echo ' 2) init — non-interactive, install everything with recommended defaults' >&2
echo ' 2) init — interactive guided initialization' >&2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since the init command has been updated in src/cli.js to call runSetup() without any arguments, it now behaves identically to the setup command.

Presenting these as two distinct options here (with different descriptions) is misleading to the user, as both choices will launch the exact same interactive setup flow. Consider removing this prompt and defaulting to a single setup flow, or updating the descriptions to reflect that they are now identical.

Comment thread tests/shell.test.js
throw Object.assign(new Error("interrupted"), { signal: "SIGINT", status: null });
});

expect(() => brewInstall("jq")).toThrow(expect.objectContaining({ interrupted: true }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In Vitest (and Jest), passing an asymmetric matcher like expect.objectContaining directly to toThrow (or toThrowError) is not natively supported and may not assert the properties of the thrown error as expected. It often defaults to checking if any error is thrown, which can lead to silent test passes even if the error properties are incorrect.

To reliably assert custom properties on a thrown error, you should catch the error and assert its properties directly.

    let thrownError;
    try {
      brewInstall("jq");
    } catch (error) {
      thrownError = error;
    }
    expect(thrownError).toBeDefined();
    expect(thrownError.interrupted).toBe(true);

ChangeHow added a commit that referenced this pull request Jul 13, 2026
## Background

PR #42 changed the `init` route from `runSetup({ defaults: true })` to
`runSetup()`. That made `init` behave exactly like interactive `setup`,
even though the two commands are intended to serve different use cases.
The same path could also launch `p10k configure`, introducing a
secondary interactive wizard into an otherwise unattended initialization
flow.

`setup` should collect user choices. `init` should pass an explicit
option to the shared setup engine so recommended steps, tools, apps, and
presets are selected without prompts.

## Root cause

The CLI stopped passing the recommended-defaults option for `init`.
Without that parameter, the shared setup engine entered its normal
prompt-driven path. In addition, the defaults branch explicitly launched
the Powerlevel10k configuration wizard when no existing configuration
was found.

## Changes

- restore `init` routing through `runSetup({ defaults: true })`
- keep Powerlevel10k personalization out of the non-interactive flow
- document the permanent `setup`/`init` design boundary in `AGENTS.md`
- add `CLAUDE.md` as a symlink to `AGENTS.md` so coding agents share the
same project guidance
- synchronize CLI help, installer messaging, and both README
translations
- update regression tests to assert the recommended-defaults parameter
and non-interactive messaging

## Design contract

- `setup` remains interactive and asks for steps and content
- `init` remains non-interactive and selects recommended content through
setup-engine parameters and recommended-value lists
- package installation stays non-interactive after selections are
resolved
- secondary personalization tools such as `p10k configure` require a
later explicit user action

## Verification

- `npm test`
  - 234 Vitest tests passed
  - 12 Zsh performance tests passed
- `bash -n install.sh`
- `git diff --check`
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