Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
name: release
description: Create a release PR with version bump and changelog
disable-model-invocation: true
argument-hint: version
---

Create a PR with branch name `release/v$ARGUMENTS` containing only these changes:

1. Update version in `pyproject.toml`
2. Ensure `TONGUES_VERSION` matches between `.github/workflows/ci.yml` and `.github/workflows/release.yml`
3. Add a new version column to the Releases table in `README.md` with download links (keep the previous version column)

No other changes—no refactors, no fixes, no documentation updates.

## Changelog

Generate release notes from commits since the last tag (or all commits if no tags):
```
git log $(git describe --tags --abbrev=0 2>/dev/null || git rev-list --max-parents=0 HEAD)..HEAD --oneline
```

Focus on what matters to users:
- New features and capabilities
- Breaking changes or behavior changes
- Group all bug fixes as "Various bug fixes" (don't itemize)
- Omit internal refactors, test changes, and CI updates

Put the changelog in the PR body. The workflow extracts it for the GitHub release.

Run `just check` before pushing. PR title: `Release v$ARGUMENTS`

## After merge

Tag, push, and clean up:
```
git checkout main && git pull && git tag v$ARGUMENTS && git push --tags && git push origin --delete release/v$ARGUMENTS
```

The tag triggers a workflow that creates the GitHub release with transpiled binaries (parable.py, parable.js).
83 changes: 83 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

- name: Verify version matches pyproject.toml
run: |
toml_version=$(grep -oP 'version = "\K[^"]+' pyproject.toml)
if [ "$toml_version" != "${{ steps.version.outputs.version }}" ]; then
echo "Tag version (${{ steps.version.outputs.version }}) does not match pyproject.toml ($toml_version)"
exit 1
fi

- name: Get release notes from PR
id: notes
env:
GH_TOKEN: ${{ github.token }}
run: |
body=$(gh pr list --state merged --head "release/v${{ steps.version.outputs.version }}" --json body --jq '.[0].body // empty')
if [ -z "$body" ]; then
echo "No release PR found, using auto-generated notes"
echo "auto=true" >> $GITHUB_OUTPUT
else
echo "body<<EOF" >> $GITHUB_OUTPUT
echo "$body" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi

- uses: astral-sh/setup-uv@v4

- uses: extractions/setup-just@v2

- uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install Tongues
env:
TONGUES_VERSION: v0.2.4
run: |
mkdir -p "$HOME/.local/lib/tongues/lib" "$HOME/.local/lib/tongues/bin" "$HOME/.local/bin"
curl -sSfL "https://github.com/ldayton/Tongues/releases/download/${TONGUES_VERSION}/tongues.js" \
-o "$HOME/.local/lib/tongues/lib/tongues.js"
curl -sSfL "https://raw.githubusercontent.com/ldayton/Tongues/${TONGUES_VERSION}/tongues/bin/tongues.js" \
-o "$HOME/.local/lib/tongues/bin/tongues.js"
printf '#!/bin/sh\nexec node %s/.local/lib/tongues/bin/tongues.js "$@"\n' "$HOME" > "$HOME/.local/bin/tongues"
chmod +x "$HOME/.local/bin/tongues"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

- name: Run checks
run: just check

- name: Transpile release binaries
run: |
mkdir -p .out
tongues --target python -o .out/parable.py src/parable.py
tongues --target javascript -o .out/parable.js src/parable.py
echo 'module.exports = { parse, ParseError, MatchedPairError };' >> .out/parable.js

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
body: ${{ steps.notes.outputs.body }}
generate_release_notes: ${{ steps.notes.outputs.auto == 'true' }}
files: |
.out/parable.py
.out/parable.js
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ Parse bash exactly as bash does. One file, zero dependencies, in your language.

All releases, even Python, run through the transpiler. Current languages:

| Language | Min Version |
| ---------- | ----------------- |
| Javascript | Node.js 21 |
| Python | CPython/Pypy 3.12 |
| Language | Min Version | v0.1.0 |
| ---------- | ----------------- | ------------------------------------------------------------------------------------ |
| Javascript | Node.js 21 | [parable.js](https://github.com/ldayton/Parable/releases/download/v0.1.0/parable.js) |
| Python | CPython/Pypy 3.12 | [parable.py](https://github.com/ldayton/Parable/releases/download/v0.1.0/parable.py) |

Output code quality is a work in progress. Currently the transpiler prioritizes correctness over readability; generated code may not yet match hand-written idioms, yet.
Caveats: output code quality is a work in progress. Currently the transpiler prioritizes correctness over readability; generated code may not yet match hand-written idioms, yet. Additionally, the project's fuzzer still finds obscure edge cases where Parable differs from GNU Bash. While Parable matches bash for essentially 100% of real world examples, we're still shaking out remaining theoretical differences.

## Why Parable?

Expand Down
Loading