mirror of
https://github.com/github/spec-kit.git
synced 2026-07-03 12:28:06 +08:00
fix: replace xargs trim with sed to handle quotes in descriptions (#2351)
xargs re-parses stdin as shell tokens, causing 'unterminated quote' errors when feature descriptions contain apostrophes, double quotes, or backslashes. Replace with sed-based whitespace trim that preserves input verbatim. Add regression tests for special characters in descriptions (core and extension scripts), plus a negative test for whitespace-only input. Fixes #2339
This commit is contained in:
@@ -95,7 +95,7 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Trim whitespace and validate description is not empty
|
# Trim whitespace and validate description is not empty
|
||||||
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs)
|
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
|
||||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||||
echo "Error: Feature description cannot be empty or contain only whitespace" >&2
|
echo "Error: Feature description cannot be empty or contain only whitespace" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Trim whitespace and validate description is not empty (e.g., user passed only whitespace)
|
# Trim whitespace and validate description is not empty (e.g., user passed only whitespace)
|
||||||
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs)
|
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
|
||||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||||
echo "Error: Feature description cannot be empty or contain only whitespace" >&2
|
echo "Error: Feature description cannot be empty or contain only whitespace" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -1257,3 +1257,67 @@ class TestFeatureDirectoryResolution:
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
pytest.fail("FEATURE_DIR not found in PowerShell output")
|
pytest.fail("FEATURE_DIR not found in PowerShell output")
|
||||||
|
|
||||||
|
|
||||||
|
# ── Description Quoting Tests (issue #2339) ──────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
@requires_bash
|
||||||
|
class TestDescriptionQuoting:
|
||||||
|
"""Descriptions with quotes, apostrophes, and backslashes must not break the script.
|
||||||
|
|
||||||
|
Regression tests for https://github.com/github/spec-kit/issues/2339
|
||||||
|
"""
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"description",
|
||||||
|
[
|
||||||
|
"Add user's profile page",
|
||||||
|
"Fix the \"login\" bug",
|
||||||
|
"Handle path\\with\\backslashes",
|
||||||
|
"It's a \"complex\" feature\\here",
|
||||||
|
],
|
||||||
|
ids=["apostrophe", "double-quotes", "backslashes", "mixed"],
|
||||||
|
)
|
||||||
|
def test_core_script_handles_special_chars(self, git_repo: Path, description: str):
|
||||||
|
"""Core create-new-feature.sh succeeds with special characters in description."""
|
||||||
|
result = run_script(git_repo, "--dry-run", "--short-name", "feat", description)
|
||||||
|
assert result.returncode == 0, (
|
||||||
|
f"Script failed for description {description!r}: {result.stderr}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"description",
|
||||||
|
[
|
||||||
|
"Add user's profile page",
|
||||||
|
"Fix the \"login\" bug",
|
||||||
|
"Handle path\\with\\backslashes",
|
||||||
|
"It's a \"complex\" feature\\here",
|
||||||
|
],
|
||||||
|
ids=["apostrophe", "double-quotes", "backslashes", "mixed"],
|
||||||
|
)
|
||||||
|
def test_ext_script_handles_special_chars(self, ext_git_repo: Path, description: str):
|
||||||
|
"""Extension create-new-feature.sh succeeds with special characters in description."""
|
||||||
|
script = (
|
||||||
|
ext_git_repo / ".specify" / "extensions" / "git" / "scripts" / "bash" / "create-new-feature.sh"
|
||||||
|
)
|
||||||
|
result = subprocess.run(
|
||||||
|
["bash", str(script), "--dry-run", "--short-name", "feat", description],
|
||||||
|
cwd=ext_git_repo,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
assert result.returncode == 0, (
|
||||||
|
f"Script failed for description {description!r}: {result.stderr}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_whitespace_only_still_rejected(self, git_repo: Path):
|
||||||
|
"""Whitespace-only descriptions must still be rejected after trimming."""
|
||||||
|
result = run_script(git_repo, "--dry-run", "--short-name", "feat", " ")
|
||||||
|
assert result.returncode != 0
|
||||||
|
assert "empty" in result.stderr.lower() or "whitespace" in result.stderr.lower()
|
||||||
|
|
||||||
|
def test_plain_description_still_works(self, git_repo: Path):
|
||||||
|
"""Plain description without special characters continues to work."""
|
||||||
|
result = run_script(git_repo, "--dry-run", "--short-name", "feat", "Add login feature")
|
||||||
|
assert result.returncode == 0, result.stderr
|
||||||
|
|||||||
Reference in New Issue
Block a user