-
Notifications
You must be signed in to change notification settings - Fork 6.2k
Implement polyglot scripts to avoid os specific commands in commands #1610
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,11 @@ | ||
| * text=auto eol=lf | ||
|
|
||
| # Polyglot wrappers - MUST use Unix line endings (LF) to work on Windows | ||
| scripts/check-prerequisites text eol=lf | ||
| scripts/setup-plan text eol=lf | ||
| scripts/create-new-feature text eol=lf | ||
| scripts/update-agent-context text eol=lf | ||
|
|
||
| # Platform-specific scripts (explicit for clarity) | ||
| scripts/bash/*.sh text eol=lf | ||
| scripts/powershell/*.ps1 text eol=crlf |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,35 +49,37 @@ generate_commands() { | |
| # Normalize line endings | ||
| file_content=$(tr -d '\r' < "$template") | ||
|
|
||
| # Extract description and script command from YAML frontmatter | ||
| # Extract description from YAML frontmatter | ||
| description=$(printf '%s\n' "$file_content" | awk '/^description:/ {sub(/^description:[[:space:]]*/, ""); print; exit}') | ||
| script_command=$(printf '%s\n' "$file_content" | awk -v sv="$script_variant" '/^[[:space:]]*'"$script_variant"':[[:space:]]*/ {sub(/^[[:space:]]*'"$script_variant"':[[:space:]]*/, ""); print; exit}') | ||
|
|
||
| # Extract script command from scripts: section (both sh and ps now have same path) | ||
| script_command=$(printf '%s\n' "$file_content" | awk '/^[[:space:]]*sh:[[:space:]]*/ {sub(/^[[:space:]]*sh:[[:space:]]*/, ""); print; exit}') | ||
|
|
||
| if [[ -z $script_command ]]; then | ||
| echo "Warning: no script command found for $script_variant in $template" >&2 | ||
| script_command="(Missing script command for $script_variant)" | ||
| echo "Warning: no script command found in $template" >&2 | ||
| script_command="(Missing script command)" | ||
| fi | ||
|
Comment on lines
+55
to
61
|
||
|
|
||
| # Extract agent_script command from YAML frontmatter if present | ||
| # Extract agent_script command from agent_scripts: section if present | ||
| agent_script_command=$(printf '%s\n' "$file_content" | awk ' | ||
| /^agent_scripts:$/ { in_agent_scripts=1; next } | ||
| in_agent_scripts && /^[[:space:]]*'"$script_variant"':[[:space:]]*/ { | ||
| sub(/^[[:space:]]*'"$script_variant"':[[:space:]]*/, "") | ||
| in_agent_scripts && /^[[:space:]]*sh:[[:space:]]*/ { | ||
| sub(/^[[:space:]]*sh:[[:space:]]*/, "") | ||
| exit | ||
| } | ||
| in_agent_scripts && /^[a-zA-Z]/ { in_agent_scripts=0 } | ||
| ') | ||
|
|
||
| # Replace {SCRIPT} placeholder with the script command | ||
| # Replace {SCRIPT} placeholder with the actual polyglot wrapper path | ||
| body=$(printf '%s\n' "$file_content" | sed "s|{SCRIPT}|${script_command}|g") | ||
|
|
||
| # Replace {AGENT_SCRIPT} placeholder with the agent script command if found | ||
| # Replace {AGENT_SCRIPT} placeholder if found | ||
| if [[ -n $agent_script_command ]]; then | ||
| body=$(printf '%s\n' "$body" | sed "s|{AGENT_SCRIPT}|${agent_script_command}|g") | ||
| fi | ||
|
|
||
| # Remove the scripts: and agent_scripts: sections from frontmatter while preserving YAML structure | ||
| # Remove the scripts: and agent_scripts: sections from frontmatter | ||
| body=$(printf '%s\n' "$body" | awk ' | ||
| /^---$/ { print; if (++dash_count == 1) in_frontmatter=1; else in_frontmatter=0; next } | ||
| in_frontmatter && /^scripts:$/ { skip_scripts=1; next } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,157 @@ | ||||||||||||||||||||||||||||||||||
| name: Test Polyglot Wrappers | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||
| push: | ||||||||||||||||||||||||||||||||||
| branches: ["main"] | ||||||||||||||||||||||||||||||||||
| paths: | ||||||||||||||||||||||||||||||||||
| - 'scripts/**' | ||||||||||||||||||||||||||||||||||
| - 'templates/commands/**' | ||||||||||||||||||||||||||||||||||
| - '.gitattributes' | ||||||||||||||||||||||||||||||||||
| - 'tests/test-polyglot-wrappers.sh' | ||||||||||||||||||||||||||||||||||
| - '.github/workflows/test-polyglot-wrappers.yml' | ||||||||||||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||||||||||||
| paths: | ||||||||||||||||||||||||||||||||||
| - 'scripts/**' | ||||||||||||||||||||||||||||||||||
| - 'templates/commands/**' | ||||||||||||||||||||||||||||||||||
| - '.gitattributes' | ||||||||||||||||||||||||||||||||||
| - 'tests/test-polyglot-wrappers.sh' | ||||||||||||||||||||||||||||||||||
| - '.github/workflows/test-polyglot-wrappers.yml' | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||
| test-unix: | ||||||||||||||||||||||||||||||||||
| name: Test on ${{ matrix.os }} | ||||||||||||||||||||||||||||||||||
| runs-on: ${{ matrix.os }} | ||||||||||||||||||||||||||||||||||
| strategy: | ||||||||||||||||||||||||||||||||||
| fail-fast: false | ||||||||||||||||||||||||||||||||||
| matrix: | ||||||||||||||||||||||||||||||||||
| os: [ubuntu-latest, macos-latest] | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||
| - name: Checkout | ||||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - name: Set execute permissions | ||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||
| chmod +x scripts/check-prerequisites | ||||||||||||||||||||||||||||||||||
| chmod +x scripts/setup-plan | ||||||||||||||||||||||||||||||||||
| chmod +x scripts/create-new-feature | ||||||||||||||||||||||||||||||||||
| chmod +x scripts/update-agent-context | ||||||||||||||||||||||||||||||||||
| chmod +x scripts/bash/*.sh | ||||||||||||||||||||||||||||||||||
| chmod +x tests/test-polyglot-wrappers.sh | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - name: Run polyglot wrapper tests | ||||||||||||||||||||||||||||||||||
| run: tests/test-polyglot-wrappers.sh | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - name: Test wrapper execution (check-prerequisites) | ||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||
| echo "Testing check-prerequisites wrapper..." | ||||||||||||||||||||||||||||||||||
| scripts/check-prerequisites --help | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - name: Test wrapper execution (setup-plan) | ||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||
| echo "Testing setup-plan wrapper..." | ||||||||||||||||||||||||||||||||||
| scripts/setup-plan --help | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - name: Test wrapper execution (create-new-feature) | ||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||
| echo "Testing create-new-feature wrapper..." | ||||||||||||||||||||||||||||||||||
| scripts/create-new-feature --help | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - name: Verify line endings (LF) | ||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||
| echo "Verifying polyglot wrappers have LF line endings..." | ||||||||||||||||||||||||||||||||||
| for wrapper in check-prerequisites setup-plan create-new-feature update-agent-context; do | ||||||||||||||||||||||||||||||||||
| if file "scripts/$wrapper" | grep -q "CRLF"; then | ||||||||||||||||||||||||||||||||||
| echo "ERROR: scripts/$wrapper has CRLF line endings (should be LF)" | ||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
| echo "✓ scripts/$wrapper has correct LF line endings" | ||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| test-windows: | ||||||||||||||||||||||||||||||||||
| name: Test on Windows | ||||||||||||||||||||||||||||||||||
| runs-on: windows-latest | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||
| - name: Checkout | ||||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - name: Test wrapper execution via Git Bash (check-prerequisites) | ||||||||||||||||||||||||||||||||||
| shell: bash | ||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||
| echo "Testing check-prerequisites wrapper via Git Bash..." | ||||||||||||||||||||||||||||||||||
| chmod +x scripts/check-prerequisites | ||||||||||||||||||||||||||||||||||
| chmod +x scripts/bash/*.sh | ||||||||||||||||||||||||||||||||||
| scripts/check-prerequisites --help | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - name: Test wrapper execution via Git Bash (setup-plan) | ||||||||||||||||||||||||||||||||||
| shell: bash | ||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||
| echo "Testing setup-plan wrapper via Git Bash..." | ||||||||||||||||||||||||||||||||||
| chmod +x scripts/setup-plan | ||||||||||||||||||||||||||||||||||
| scripts/setup-plan --help | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - name: Test wrapper execution via cmd.exe (check-prerequisites) | ||||||||||||||||||||||||||||||||||
| shell: cmd | ||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||
| echo Testing check-prerequisites wrapper via cmd.exe... | ||||||||||||||||||||||||||||||||||
| scripts\check-prerequisites --help | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - name: Test wrapper execution via PowerShell (check-prerequisites) | ||||||||||||||||||||||||||||||||||
| shell: pwsh | ||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||
| Write-Host "Testing check-prerequisites wrapper via PowerShell..." | ||||||||||||||||||||||||||||||||||
| & "scripts/check-prerequisites" --help | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+100
to
+107
|
||||||||||||||||||||||||||||||||||
| echo Testing check-prerequisites wrapper via cmd.exe... | |
| scripts\check-prerequisites --help | |
| - name: Test wrapper execution via PowerShell (check-prerequisites) | |
| shell: pwsh | |
| run: | | |
| Write-Host "Testing check-prerequisites wrapper via PowerShell..." | |
| & "scripts/check-prerequisites" --help | |
| echo Testing check-prerequisites script via cmd.exe using PowerShell... | |
| pwsh -File scripts/powershell/check-prerequisites.ps1 --help | |
| - name: Test wrapper execution via PowerShell (check-prerequisites) | |
| shell: pwsh | |
| run: | | |
| Write-Host "Testing check-prerequisites script via PowerShell..." | |
| & "scripts/powershell/check-prerequisites.ps1" --help |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,117 @@ The toolkit supports multiple AI coding assistants, allowing teams to use their | |||||
|
|
||||||
| - Any changes to `__init__.py` for the Specify CLI require a version rev in `pyproject.toml` and addition of entries to `CHANGELOG.md`. | ||||||
|
|
||||||
| ## Polyglot Wrapper Scripts | ||||||
|
|
||||||
| To eliminate platform-specific script references and merge conflicts in mixed-OS teams, Spec Kit uses polyglot wrapper scripts that work on both Unix and Windows. | ||||||
|
|
||||||
| ### How Polyglot Wrappers Work | ||||||
|
|
||||||
| Wrapper scripts (e.g., `scripts/check-prerequisites`) have no file extension and use a special pattern: | ||||||
|
|
||||||
| ```bash | ||||||
| #!/usr/bin/env bash | ||||||
| # 2>nul & @echo off & goto :batch | ||||||
|
|
||||||
| # ===== UNIX SECTION ===== | ||||||
| "$(dirname "$0")/bash/script-name.sh" "$@" | ||||||
|
||||||
| "$(dirname "$0")/bash/script-name.sh" "$@" | |
| bash "$(dirname "$0")/bash/script-name.sh" "$@" |
Copilot
AI
Feb 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Windows example uses @powershell ..., but the repository’s PowerShell scripts are pwsh-targeted (e.g., scripts/powershell/*.ps1 start with #!/usr/bin/env pwsh). Using powershell here is likely to mislead users into running scripts with Windows PowerShell 5.1, which may fail on the shebang line; consider updating the example to use pwsh instead.
| @powershell -ExecutionPolicy Bypass -File "%~dp0powershell\script-name.ps1" %* | |
| @pwsh -ExecutionPolicy Bypass -File "%~dp0powershell\script-name.ps1" %* |
Copilot
AI
Feb 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section label says “PowerShell script (scripts/update-agent-context)”, but the platform-specific implementation lives under scripts/powershell/update-agent-context.ps1 (the wrapper is scripts/update-agent-context). The heading is likely to confuse contributors; update the path in the heading to the actual PowerShell script location.
| ##### PowerShell script (`scripts/update-agent-context`) | |
| ##### PowerShell script (`scripts/powershell/update-agent-context.ps1`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generate-Commandsnow always extracts{SCRIPT}from thesh:frontmatter line, butBuild-Variantstill copies only one ofscripts/bashorscripts/powershelldepending on$Script. This combination can produce apspackage whose command files call the polyglot wrapper even though the wrapper’s Unix delegation target isn’t present in that package. Consider either bundling both script directories in both variants when using wrappers, or restoring per-variant extraction.