Skip to content

Commit c6fb0c7

Browse files
marcelsafinCopilot
andcommitted
fix(scripts): stabilize PowerShell fallbacks
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 21e252c commit c6fb0c7

3 files changed

Lines changed: 70 additions & 8 deletions

File tree

scripts/powershell/common.ps1

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,18 @@ function Resolve-Template {
368368
}
369369
return 10
370370
}
371-
$priorities = @($presetEntries | ForEach-Object { & $priorityFor $_ })
372-
if ($priorities.Count -gt 1) {
373-
$allNumeric = @($priorities | Where-Object { $_ -isnot [ValueType] }).Count -eq 0
374-
$allStrings = @($priorities | Where-Object { $_ -isnot [string] }).Count -eq 0
371+
if ($presetEntries.Count -gt 1) {
372+
$allNumeric = $true
373+
$allStrings = $true
374+
foreach ($entry in $presetEntries) {
375+
$priority = & $priorityFor $entry
376+
if ($null -eq $priority -or $priority -isnot [ValueType]) {
377+
$allNumeric = $false
378+
}
379+
if ($null -eq $priority -or $priority -isnot [string]) {
380+
$allStrings = $false
381+
}
382+
}
375383
if (-not $allNumeric -and -not $allStrings) {
376384
throw 'Registry priorities are not mutually orderable'
377385
}
@@ -395,7 +403,7 @@ function Resolve-Template {
395403
}
396404
} else {
397405
# Fallback: alphabetical directory order
398-
foreach ($preset in Get-ChildItem -Path $presetsDir -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike '.*' }) {
406+
foreach ($preset in Get-ChildItem -Path $presetsDir -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike '.*' } | Sort-Object Name) {
399407
$candidate = Join-Path $preset.FullName "templates/$TemplateName.md"
400408
if (Test-Path $candidate) { return $candidate }
401409
}

tests/test_create_new_feature_python_parity.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,12 @@ def test_all_variants_persist_symlinked_specs_path_lexically(
700700
for current in repos:
701701
specs_target = tmp_path / f"{current.name}-specs"
702702
specs_target.mkdir()
703-
(current / "specs").symlink_to(specs_target, target_is_directory=True)
703+
try:
704+
(current / "specs").symlink_to(
705+
specs_target, target_is_directory=True
706+
)
707+
except (OSError, NotImplementedError):
708+
pytest.skip("Symlinks are not available in this environment")
704709

705710
bash = run(
706711
bash_cmd(repos[0], SCRIPT, "--json", "--number", "7", "x"),

tests/test_setup_plan_python_parity.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from tests.conftest import requires_bash
1010
from tests.parity_helpers import (
1111
HAS_POWERSHELL,
12+
POWERSHELL_EXE,
1213
bash_cmd,
1314
clean_env,
1415
install_scripts,
@@ -99,11 +100,18 @@ def test_python_missing_template_matches_bash(tmp_path: Path) -> None:
99100
"registry",
100101
[
101102
'{"presets": {"alpha": {"priority": "high"}, "beta": {"priority": 1}}}',
103+
'{"presets": {"alpha": {"priority": 2}, "beta": {"priority": 1}, "gamma": {"priority": null}}}',
102104
"[]",
103105
'{"presets":[]}',
104106
'{"presets":null}',
105107
],
106-
ids=["mixed_priorities", "list_root", "list_presets", "null_presets"],
108+
ids=[
109+
"mixed_priorities",
110+
"null_priority",
111+
"list_root",
112+
"list_presets",
113+
"null_presets",
114+
],
107115
)
108116
def test_all_variants_broken_registry_falls_back_to_dir_scan(
109117
tmp_path: Path, registry: str
@@ -118,8 +126,8 @@ def test_all_variants_broken_registry_falls_back_to_dir_scan(
118126
presets = repo / ".specify" / "presets"
119127
for name, body in (
120128
(".hidden", "# hidden\n"),
121-
("alpha", "# alpha plan\n"),
122129
("beta", "# beta plan\n"),
130+
("alpha", "# alpha plan\n"),
123131
):
124132
(presets / name / "templates").mkdir(parents=True)
125133
(presets / name / "templates" / "plan-template.md").write_text(
@@ -156,6 +164,47 @@ def test_all_variants_broken_registry_falls_back_to_dir_scan(
156164
assert plan.read_text(encoding="utf-8") == "# alpha plan\n"
157165

158166

167+
@pytest.mark.skipif(not HAS_POWERSHELL, reason="no PowerShell available")
168+
def test_powershell_broken_registry_fallback_sorts_directories(
169+
tmp_path: Path,
170+
) -> None:
171+
repo = _setup_repo(tmp_path, "powershell", template=False)
172+
presets = repo / ".specify" / "presets"
173+
for name in ("alpha", "beta"):
174+
templates = presets / name / "templates"
175+
templates.mkdir(parents=True)
176+
(templates / "plan-template.md").write_text(
177+
f"# {name} plan\n", encoding="utf-8"
178+
)
179+
(presets / ".registry").write_text("{broken", encoding="utf-8")
180+
181+
common = repo / ".specify" / "scripts" / "powershell" / "common.ps1"
182+
common_ps = str(common).replace("'", "''")
183+
alpha_ps = str(presets / "alpha").replace("'", "''")
184+
beta_ps = str(presets / "beta").replace("'", "''")
185+
repo_ps = str(repo).replace("'", "''")
186+
command = f"""
187+
. '{common_ps}'
188+
function Get-ChildItem {{
189+
@(
190+
[PSCustomObject]@{{ Name = 'beta'; FullName = '{beta_ps}' }}
191+
[PSCustomObject]@{{ Name = 'alpha'; FullName = '{alpha_ps}' }}
192+
)
193+
}}
194+
Resolve-Template -TemplateName 'plan-template' -RepoRoot '{repo_ps}'
195+
"""
196+
result = run(
197+
[POWERSHELL_EXE, "-NoProfile", "-Command", command],
198+
repo,
199+
)
200+
201+
assert result.returncode == 0
202+
assert result.stderr == ""
203+
assert Path(result.stdout.strip()).read_text(encoding="utf-8") == (
204+
"# alpha plan\n"
205+
)
206+
207+
159208
@requires_bash
160209
@pytest.mark.skipif(not HAS_POWERSHELL, reason="no PowerShell available")
161210
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)