Skip to content

fix: import skills cli edge cases - #108

Merged
ymkiux merged 4 commits into
mainfrom
fix/import-skills-cli
Apr 27, 2026
Merged

fix: import skills cli edge cases#108
ymkiux merged 4 commits into
mainfrom
fix/import-skills-cli

Conversation

@ymkiux

@ymkiux ymkiux commented Apr 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fix import-skills help handling and cleanup on download failures
  • Add GitHub archive fallback candidates (main/master, heads/tags)
  • Ensure doctor --output creates parent directories

Tests

  • npm test

Summary by CodeRabbit

  • New Features

    • Import command: added help flag handling and improved GitHub archive resolution with multiple candidate attempts and guaranteed temp-file cleanup on failure
  • Bug Fixes

    • Doctor command now creates missing output directories before writing files to prevent write failures
  • Tests

    • Added and expanded unit tests to cover CLI help behavior, GitHub URL parsing/candidates, and import command error cases

@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@awsl233777 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 53 minutes and 54 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 53 minutes and 54 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7b2a0058-cd69-4af2-8f45-b8acf3155f42

📥 Commits

Reviewing files that changed from the base of the PR and between 8442df9 and c4bbaf6.

📒 Files selected for processing (3)
  • cli/import-skills-url.js
  • tests/unit/cli-help.test.mjs
  • tests/unit/import-skills-url.test.mjs
📝 Walkthrough

Walkthrough

Creates missing output parent directories before writing; refactors CLI help printing; adds GitHub archive ZIP candidate generation, deduplication, HTTP(S) validation, sequential download attempts (skip 404s, abort on other errors), and guaranteed temp-dir cleanup; expands exports and tests for these behaviors.

Changes

Cohort / File(s) Summary
CLI entry / output write
cli.js
Create parent directory for --output prior to fs.writeFileSync; move long startup help into printMainHelp() and route no-args/help flags to it.
Import-skills URL command
cli/import-skills-url.js
Add --help/-h support, expose parseGithubRepoFromUrl and buildGithubArchiveZipCandidates. Build/dedupe/validate GitHub archive ZIP candidates, try downloads sequentially (continue on 404, abort on other errors), and ensure temp-dir cleanup in finally.
Unit tests & runner
tests/unit/import-skills-url.test.mjs, tests/unit/cli-help.test.mjs, tests/unit/run.mjs
Add tests for parsing, candidate generation, percent-encoding, help output capture, and CLI help invocations; import new test into test runner.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as "import-skills-url CLI"
    participant Parser as "parseGithubRepoFromUrl"
    participant Builder as "buildGithubArchiveZipCandidates"
    participant Validator as "HTTP(s) Validator"
    participant Downloader as "HTTP Downloader"
    participant Importer as "Skill Importer"

    User->>CLI: invoke with GitHub repo URL
    CLI->>Parser: parse URL -> {owner,repo,ref}
    Parser-->>CLI: repo descriptor
    CLI->>Builder: build candidate ZIP URLs
    Builder-->>CLI: candidates[]
    CLI->>Validator: filter HTTP(s) & dedupe
    Validator-->>CLI: validCandidates[]

    loop for each candidate
        CLI->>Downloader: download(candidate)
        alt 404
            Downloader-->>CLI: 404 -> continue
        else non-404 error
            Downloader-->>CLI: error -> abort flow
        else success
            Downloader-->>Importer: provide archive
            Importer-->>CLI: import complete
        end
    end

    CLI->>CLI: cleanup temp directory (finally)
    CLI-->>User: exit with result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested reviewers

  • awsl233777

Poem

🐰 I hop through folders, make the paths,

I parse the repo and try the tags,
Skip the 404s, fetch what I may,
Clean the temp burrow at end of day,
Imports snug — a carrot-coded play.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: import skills cli edge cases' directly relates to the main changes: hardening import-skills CLI, improving help handling, and fixing edge cases in CLI behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/import-skills-cli

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
cli/import-skills-url.js (1)

36-68: Optional: extract shared GitHub URL parsing.

buildGithubArchiveZipCandidates duplicates the parsing/validation block (protocol, hostname, owner/repo, .git stripping) already present in resolveGithubArchiveZipUrl. Consider factoring out a small helper like parseGithubRepoFromUrl(inputUrl) returning { owner, repo, ref? } (or null) and have both functions consume it. Defers any future fix (e.g., handling refs containing slashes such as feature/x) to a single place.

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

In `@cli/import-skills-url.js` around lines 36 - 68, The parsing/validation logic
in buildGithubArchiveZipCandidates duplicates code in
resolveGithubArchiveZipUrl; extract a single helper (e.g.,
parseGithubRepoFromUrl) that accepts inputUrl and returns {owner, repo, ref?} or
null, move protocol/hostname checks, .git stripping and pathname splitting into
it, then update buildGithubArchiveZipCandidates and resolveGithubArchiveZipUrl
to call parseGithubRepoFromUrl and build their candidate URLs from the returned
owner/repo/ref; ensure ref (when present) is preserved (including possible
slashes) and URL-encoded where used.
tests/unit/import-skills-url.test.mjs (1)

26-34: Tests cover the main branches; consider one small addition.

Coverage of the bare repo, /tree/<ref>, and non-github paths is sufficient. As a nice-to-have, consider asserting parity behaviors that the helper inherits from resolveGithubArchiveZipUrl (trailing slash, .git suffix, malformed input → []) so a future refactor that consolidates the two parsers can't silently regress one.

assert.deepEqual(buildGithubArchiveZipCandidates('https://github.com/foo/bar.git'), [
    'https://github.com/foo/bar/archive/refs/heads/main.zip',
    'https://github.com/foo/bar/archive/refs/heads/master.zip'
]);
assert.deepEqual(buildGithubArchiveZipCandidates('not a url'), []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/import-skills-url.test.mjs` around lines 26 - 34, Add assertions
to tests for buildGithubArchiveZipCandidates to cover parity with
resolveGithubArchiveZipUrl: verify inputs with a trailing .git suffix (e.g.,
'https://github.com/foo/bar.git') return the same two candidate URLs, confirm a
trailing slash variations are handled similarly, and assert malformed input
(e.g., 'not a url') returns an empty array; update tests in
tests/unit/import-skills-url.test.mjs near the existing
buildGithubArchiveZipCandidates cases so future refactors keep behavior
consistent with resolveGithubArchiveZipUrl.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/import-skills-url.js`:
- Around line 209-221: The parsing loop is capturing short flags like "-h" as
the URL because the URL-capture branch only excludes tokens starting with "--";
update the loop in the section that handles argv/token so that flag-style tokens
are detected before assigning options.url — either move the help/version checks
(token === '--help' || token === '-h' and similar) above the options.url
assignment or change the exclusion to reject any token that starts with '-'
(e.g., token.startsWith('-')) before setting options.url; ensure options.url is
only set when token is a non-flag value and keep subsequent isValidHttpUrl
checks unchanged.

---

Nitpick comments:
In `@cli/import-skills-url.js`:
- Around line 36-68: The parsing/validation logic in
buildGithubArchiveZipCandidates duplicates code in resolveGithubArchiveZipUrl;
extract a single helper (e.g., parseGithubRepoFromUrl) that accepts inputUrl and
returns {owner, repo, ref?} or null, move protocol/hostname checks, .git
stripping and pathname splitting into it, then update
buildGithubArchiveZipCandidates and resolveGithubArchiveZipUrl to call
parseGithubRepoFromUrl and build their candidate URLs from the returned
owner/repo/ref; ensure ref (when present) is preserved (including possible
slashes) and URL-encoded where used.

In `@tests/unit/import-skills-url.test.mjs`:
- Around line 26-34: Add assertions to tests for buildGithubArchiveZipCandidates
to cover parity with resolveGithubArchiveZipUrl: verify inputs with a trailing
.git suffix (e.g., 'https://github.com/foo/bar.git') return the same two
candidate URLs, confirm a trailing slash variations are handled similarly, and
assert malformed input (e.g., 'not a url') returns an empty array; update tests
in tests/unit/import-skills-url.test.mjs near the existing
buildGithubArchiveZipCandidates cases so future refactors keep behavior
consistent with resolveGithubArchiveZipUrl.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2b41d152-2a81-4b31-b356-b1db2d29cb2a

📥 Commits

Reviewing files that changed from the base of the PR and between 816ef6c and 81071f4.

📒 Files selected for processing (3)
  • cli.js
  • cli/import-skills-url.js
  • tests/unit/import-skills-url.test.mjs
📜 Review details
🔇 Additional comments (2)
cli.js (1)

6516-6517: Good guard before writing doctor output.

Line 6516 correctly ensures the parent directory exists before file write, preventing ENOENT on nested output paths.

cli/import-skills-url.js (1)

265-317: Candidate iteration and cleanup look correct.

Good handling: 404-only continues to next candidate, non-404 aborts immediately, lastError is rethrown if the loop exhausts without success, and finally guarantees tempDir removal regardless of the path taken (including the help/early-error paths above, which exit before tempDir is created). This matches the PR objective of cleanup-on-failure.

Comment thread cli/import-skills-url.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
cli/import-skills-url.js (1)

259-262: Redundant startsWith('--') guard.

After the parser tightening on Line 213 (!token.startsWith('-')), options.url can never be a ---prefixed token, so options.url.trim().startsWith('--') is unreachable. The !options.url check alone is sufficient; consider dropping the second clause to avoid implying the parser still allows it.

♻️ Suggested simplification
-    if (!options.url || options.url.trim().startsWith('--')) {
+    if (!options.url) {
         printImportSkillsUsage();
         throw new Error('错误: 缺少 URL(例如: https://github.com/<owner>/<repo>/archive/refs/heads/main.zip)');
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/import-skills-url.js` around lines 259 - 262, The guard checking
options.url.trim().startsWith('--') is redundant because the parser already
prevents tokens starting with '-' (see the token check around Line 213), so
simplify the validation in the import flow by removing the startsWith('--')
clause and only validate with if (!options.url) then call
printImportSkillsUsage() and throw the existing error; update the condition that
references options.url and ensure printImportSkillsUsage() and the Error message
remain unchanged.
tests/unit/cli-help.test.mjs (1)

23-23: Strict empty-stderr assertion may be flaky.

assert.equal(result.stderr, '') will fail if Node emits any warning to stderr (e.g., ExperimentalWarning, deprecation notices) during CLI startup, even when the help path is correct. Consider asserting stderr does not contain error-shaped output instead, e.g. assert.doesNotMatch(result.stderr, /error/i), or run with --no-warnings.

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

In `@tests/unit/cli-help.test.mjs` at line 23, The test's strict assertion
assert.equal(result.stderr, '') is flaky because Node warnings can appear on
stderr; update the test (in tests/unit/cli-help.test.mjs) to avoid exact-empty
check by asserting stderr does not contain error-like output instead (e.g.,
replace assert.equal(result.stderr, '') with assert.doesNotMatch(result.stderr,
/error/i) or a similar regex check against /error|exception/i), or alternatively
launch the CLI with NODE_OPTIONS='--no-warnings' (or include '--no-warnings' in
the spawn args) so result.stderr is not polluted by runtime warnings; target the
assertion using the existing result.stderr variable to implement the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/import-skills-url.js`:
- Around line 41-64: The bug is that resolveGithubArchiveZipUrl and
buildGithubArchiveZipCandidates call encodeURIComponent on the whole ref (e.g.,
"release/v1.0"), which turns "/" into "%2F" and breaks GitHub archive URLs; fix
by splitting the ref into path segments, encodeURIComponent each segment, then
rejoin with "/" before interpolating into the `${base}/heads/...` or
`${base}/tags/...` URLs (use the ref value produced by parseGithubRepoFromUrl
but encode per-segment); also update tests that assert tree/<ref> handling to
expect per-segment encoding for multi-part refs.

---

Nitpick comments:
In `@cli/import-skills-url.js`:
- Around line 259-262: The guard checking options.url.trim().startsWith('--') is
redundant because the parser already prevents tokens starting with '-' (see the
token check around Line 213), so simplify the validation in the import flow by
removing the startsWith('--') clause and only validate with if (!options.url)
then call printImportSkillsUsage() and throw the existing error; update the
condition that references options.url and ensure printImportSkillsUsage() and
the Error message remain unchanged.

In `@tests/unit/cli-help.test.mjs`:
- Line 23: The test's strict assertion assert.equal(result.stderr, '') is flaky
because Node warnings can appear on stderr; update the test (in
tests/unit/cli-help.test.mjs) to avoid exact-empty check by asserting stderr
does not contain error-like output instead (e.g., replace
assert.equal(result.stderr, '') with assert.doesNotMatch(result.stderr,
/error/i) or a similar regex check against /error|exception/i), or alternatively
launch the CLI with NODE_OPTIONS='--no-warnings' (or include '--no-warnings' in
the spawn args) so result.stderr is not polluted by runtime warnings; target the
assertion using the existing result.stderr variable to implement the change.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4e43056b-9752-481f-a80e-153d5f544ac1

📥 Commits

Reviewing files that changed from the base of the PR and between b7ab591 and 8442df9.

📒 Files selected for processing (5)
  • cli.js
  • cli/import-skills-url.js
  • tests/unit/cli-help.test.mjs
  • tests/unit/import-skills-url.test.mjs
  • tests/unit/run.mjs
✅ Files skipped from review due to trivial changes (1)
  • tests/unit/run.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/import-skills-url.test.mjs
📜 Review details
🔇 Additional comments (6)
cli/import-skills-url.js (2)

205-217: Help/-h handling — past finding addressed.

The help check now runs before URL capture, and the URL branch rejects any --prefixed token, so codexmate import-skills -h no longer gets swallowed as the URL. Matches the suggestion from the prior review.


273-311: Cleanup + sequential candidate flow look solid.

finally guarantees tempDir removal on every path, the 404-only continuation matches the documented intent, and finalUrl is captured per-iteration so the eventual importSkillsFromZipFile uses the candidate that actually downloaded. No partial-zip leakage between attempts because downloadUrlToFile rejects before opening the write stream on non-2xx responses.

cli.js (3)

6516-6517: Nice fix for doctor --output path handling.

Creating the parent directory before writing makes nested output paths reliable and prevents avoidable write failures.


13166-13195: Help output extraction is clean and maintainable.

Moving main help text into printMainHelp() improves reuse and keeps command help behavior centralized.


13212-13214: Help dispatch behavior is now consistent.

Routing both empty args and help flags through one path is straightforward and gives predictable CLI UX with successful exit.

tests/unit/cli-help.test.mjs (1)

17-17: The test global is available in this file—no import is needed. The project uses a custom test runner (tests/unit/run.mjs) that sets up globalThis.test before dynamically importing all test files, including this one. This is a valid pattern and works correctly as-is.

			> Likely an incorrect or invalid review comment.

Comment thread cli/import-skills-url.js
@ymkiux
ymkiux merged commit 204642e into main Apr 27, 2026
7 checks passed
@ymkiux
ymkiux deleted the fix/import-skills-cli branch April 27, 2026 02:30
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.

2 participants