Skip to content

fix(importer-curl): read combined short flags as separate options - #577

Open
NgoQuocViet2001 wants to merge 1 commit into
mountain-loop:mainfrom
NgoQuocViet2001:fix-curl-combined-short-flags
Open

fix(importer-curl): read combined short flags as separate options#577
NgoQuocViet2001 wants to merge 1 commit into
mountain-loop:mainfrom
NgoQuocViet2001:fix-curl-combined-short-flags

Conversation

@NgoQuocViet2001

Copy link
Copy Markdown

curl -fsSL https://example.com imports with a URL of sSL. The real URL is dropped.

                                    before                         after
curl -fsSL https://yaak.app/x.sh -> "sSL"                       -> "https://yaak.app/x.sh"
curl -sS   https://yaak.app      -> "S"                         -> "https://yaak.app"
curl -L    https://yaak.app      -> "https://yaak.app"          -> unchanged
curl -XPOST https://yaak.app     -> "https://yaak.app"          -> unchanged

Why

The tokenizer split any single-dash token longer than two characters after its first character, assuming a squished value:

if (token.startsWith("-") && !token.startsWith("--") && token.length > 2) {
  return [token.slice(0, 2), token.slice(2)];
}

That is right for -XPOST but wrong for a cluster of boolean flags. curl reads a short cluster left to right, one option per character, until an option that takes a value — that one swallows the remainder. -fsSL is four boolean flags, so splitting after -f leaves sSL as a positional argument. -f, -s, -S and -L are not in SUPPORTED_FLAGS, so they are skipped, and sSL lands in singletons — where singletons[0] is read as the URL:

const urlArg = getPairValue(flagsByName, (singletons[0] as string) || "", ["url"]);

-fsSL is how most install instructions invoke curl and -sS is common in scripts, so a pasted command silently imports a request pointing at a fragment of the flag cluster. Single flags were never affected, which is why the existing cases pass.

The change

expandShortFlags walks the cluster and stops at the first flag that takes a value, which then consumes the rest:

  • -fsSL-f -s -S -L
  • -XPOST-X POST (unchanged)
  • -sSXPOST-s -S -X POST
  • -X alone → -X, so the existing "next token is the value" path still applies

Which short flags take a value is derived rather than restated, so it cannot drift from the tables above it:

const VALUE_SHORT_FLAGS = SUPPORTED_FLAGS.flat().filter(
  (name) => name.length === 1 && !BOOLEAN_FLAGS.includes(name),
);

That resolves to b d F H X u today. importCommand's own squished-argument branch is left alone — it no longer sees a squished token from this path, but it is harmless as a second reading.

Tests

Two cases in tests/index.test.ts: a pure boolean cluster, and a cluster ending in a value flag. Negative control — restoring the old splitter fails exactly those two and leaves the other 52 green:

 × Imports combined short flags
 × Imports a combined short cluster ending in a value flag
 Tests  2 failed | 52 passed (54)

With the fix, vp test --run tests in plugins/importer-curl: 54 passed (54), 2 files. oxlint reports nothing for the two changed files.

Adjacent, not fixed here

An unsupported flag that takes a value still leaves its value as a positional, so curl -o out.txt https://example.com imports with the URL out.txt. That is the same "first positional becomes the URL" path, but closing it needs a table of curl's value-taking options rather than a parsing rule, so I left it out of this change. Happy to follow up if you want it.

I did not run vp fmt — both files already fail --check on a clean main in my environment (it reports "No config found, using defaults"), so reformatting would have buried the fix in unrelated churn. The pre-commit hook's vp check --fix did run over the two staged files.

`curl -fsSL https://example.com` imported with a URL of `sSL`.

The tokenizer split any single-dash token longer than two characters after the
first character, on the assumption it was a flag with a squished value like
`-XPOST`. curl instead reads a short cluster left to right, one option per
character, until an option that takes a value — that one swallows the rest.
`-fsSL` is four boolean flags, so splitting after `-f` left `sSL` as a
positional argument, and the first positional is what the importer reads as the
URL. The real URL was dropped.

`-fsSL` is how most install instructions invoke curl, and `-sS` in scripts, so
the imported request pointed at a fragment of the flag cluster rather than
anywhere real. Single flags were unaffected, which is why the existing cases
pass.

Expand a cluster by walking its characters, ending at the first flag that takes
a value. Which short flags those are is derived from SUPPORTED_FLAGS minus
BOOLEAN_FLAGS, so it does not drift from the tables above it. `-XPOST` still
resolves to `-X POST`, and `-sSXPOST` now resolves to `-s -S -X POST`.
@greptile-apps

greptile-apps Bot commented Aug 17, 2026

Copy link
Copy Markdown

Greptile Summary

The PR replaces the unconditional first-character split for compact cURL options with short-cluster expansion that stops when a supported value-taking option is encountered.

  • Correctly expands boolean clusters such as -fsSL.
  • Preserves attached values such as -XPOST, including after preceding boolean options.
  • Adds focused regression coverage for both forms.

Confidence Score: 5/5

The PR appears safe to merge, with the changed parser addressing the reported short-flag cluster failures without an identified changed-code regression.

The expansion now emits each boolean short option separately and stops at supported value-taking options, preserving the intended URL and method in the covered command forms.

Important Files Changed

Filename Overview
plugins/importer-curl/src/index.ts Replaces unconditional compact-option splitting with cURL-style short-cluster expansion derived from the existing supported-option tables.
plugins/importer-curl/tests/index.test.ts Adds regression tests for an all-boolean cluster and a cluster ending in an attached method value.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Compact short-option token] --> B{Current option takes a value?}
  B -->|No| C[Emit option]
  C --> D{Characters remain?}
  D -->|Yes| B
  D -->|No| E[Continue with next token]
  B -->|Yes| F[Emit option]
  F --> G{Attached remainder exists?}
  G -->|Yes| H[Emit remainder as value]
  G -->|No| E
Loading

Reviews (1): Last reviewed commit: "fix(importer-curl): read combined short ..." | Re-trigger Greptile

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