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
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
pull_request:
branches: [dev, main]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Test suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Lint and format checks
run: bun run check

- name: Run unit tests
run: bun test --pass-with-no-tests

- name: Generator sync check
run: |
bun run build
if ! git diff --exit-code -- themes/; then
echo "::error::Generated theme artifacts are out of sync with palette/. Run 'bun run build' and commit the results."
git diff -- themes/
exit 1
fi
71 changes: 71 additions & 0 deletions .github/workflows/release-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Release PR

on:
workflow_dispatch:
inputs:
version:
description: "Next version (e.g. 1.2.0)"
required: true
type: string

jobs:
open-release-pr:
name: Bump version and open release PR
runs-on: ubuntu-latest
steps:
- name: Validate version format
run: |
if ! [[ "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Version must be X.Y.Z (no pre-release suffixes). Got: ${{ inputs.version }}"
exit 1
fi

- uses: actions/checkout@v4
with:
ref: dev
fetch-depth: 0
token: ${{ secrets.RELEASE_PR_TOKEN }}

- name: Confirm version is strictly greater than current
run: |
CURRENT=$(node -p "require('./package.json').version")
NEXT="${{ inputs.version }}"
HIGHEST=$(printf '%s\n%s\n' "$CURRENT" "$NEXT" | sort -V | tail -n1)
if [ "$CURRENT" = "$NEXT" ] || [ "$HIGHEST" != "$NEXT" ]; then
echo "::error::New version ($NEXT) must be strictly greater than current ($CURRENT)"
exit 1
fi
echo "Bumping $CURRENT -> $NEXT"

- uses: oven-sh/setup-bun@v2

- name: Bump version in all manifests
run: bun scripts/bump-version.ts "${{ inputs.version }}"

- name: Commit and push bump
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "chore: bump version to ${{ inputs.version }}"
git push origin dev

- name: Open release PR
env:
GH_TOKEN: ${{ secrets.RELEASE_PR_TOKEN }}
run: |
PR_BODY_FILE=$(mktemp)
cat > "$PR_BODY_FILE" <<'EOF'
<!-- release-notes -->
## Release notes

_Replace this paragraph with a short narrative for the release. If left unchanged, GitHub's auto-generated notes will be used instead._
EOF

PR_URL=$(gh pr create \
--base main \
--head dev \
--title "Release v${{ inputs.version }}" \
--body-file "$PR_BODY_FILE")

echo "Opened release PR: $PR_URL" >> "$GITHUB_STEP_SUMMARY"
217 changes: 217 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
name: Release

on:
push:
branches: [main]

jobs:
tag-and-release:
name: Tag and create GitHub release
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
is_release: ${{ steps.tag-check.outputs.is_release }}
tag: ${{ steps.version.outputs.tag }}
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Read version from package.json
id: version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
echo "Version on main: $VERSION"

- name: Check if tag already exists (idempotency)
id: tag-check
run: |
if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
echo "Tag ${{ steps.version.outputs.tag }} already exists — skipping release."
echo "is_release=false" >> "$GITHUB_OUTPUT"
else
echo "is_release=true" >> "$GITHUB_OUTPUT"
fi

- name: Build release notes
if: steps.tag-check.outputs.is_release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_BODY=$(gh pr list \
--state merged \
--base main \
--limit 1 \
--json body \
--jq '.[0].body // ""')

if [ -n "$PR_BODY" ] && ! echo "$PR_BODY" | grep -q "Replace this paragraph"; then
echo "Using release PR body for notes."
printf '%s\n' "$PR_BODY" > /tmp/release-notes.md
else
echo "Falling back to auto-generated notes."
PREVIOUS_TAG=$(git tag --sort=-creatordate \
| grep -v "^${{ steps.version.outputs.tag }}$" \
| head -n1)
if [ -n "$PREVIOUS_TAG" ]; then
gh api "repos/${{ github.repository }}/releases/generate-notes" \
-f tag_name="${{ steps.version.outputs.tag }}" \
-f previous_tag_name="$PREVIOUS_TAG" \
--jq '.body' > /tmp/release-notes.md
else
gh api "repos/${{ github.repository }}/releases/generate-notes" \
-f tag_name="${{ steps.version.outputs.tag }}" \
--jq '.body' > /tmp/release-notes.md
fi
fi

echo "--- Release notes preview ---"
cat /tmp/release-notes.md
echo "--- end preview ---"

- name: Create GitHub release
if: steps.tag-check.outputs.is_release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ steps.version.outputs.tag }}" \
--title "Synthpunk ${{ steps.version.outputs.tag }}" \
--notes-file /tmp/release-notes.md

- name: Create and push tag
if: steps.tag-check.outputs.is_release == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag "${{ steps.version.outputs.tag }}"
git push origin "${{ steps.version.outputs.tag }}"

build-artifacts:
name: Build and upload release artifacts
needs: tag-and-release
if: needs.tag-and-release.outputs.is_release == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.tag-and-release.outputs.tag }}

- uses: oven-sh/setup-bun@v2

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Build themes
run: bun run build

- name: Run tests
run: bun test --pass-with-no-tests

- name: Upload Group 2 files to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.tag-and-release.outputs.tag }}
run: |
gh release upload "$TAG" \
themes/starship/starship.toml \
themes/wezterm/synthpunk-pastel-dark.toml \
themes/wezterm/synthpunk-pastel-light.toml \
themes/wezterm/synthpunk-neon-dark.toml \
themes/wezterm/synthpunk-neon-light.toml

- name: Build VS Code VSIX
run: |
cd themes/vscode
npx --yes @vscode/vsce package -o "../../synthpunk-${{ needs.tag-and-release.outputs.tag }}.vsix"

- name: Upload VSIX as workflow artifact
uses: actions/upload-artifact@v4
with:
name: synthpunk-vsix
path: synthpunk-${{ needs.tag-and-release.outputs.tag }}.vsix

publish-vscode:
name: Publish to VS Code Marketplace
needs: build-artifacts
if: vars.PUBLISH_VSCODE == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: synthpunk-vsix

- name: Publish to VS Code Marketplace
run: npx --yes @vscode/vsce publish --package-path synthpunk-*.vsix -p ${{ secrets.VS_MARKETPLACE_TOKEN }}

publish-openvsx:
name: Publish to OpenVSX
needs: build-artifacts
if: vars.PUBLISH_OPENVSX == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: synthpunk-vsix

- name: Publish to OpenVSX
run: npx --yes ovsx publish synthpunk-*.vsix -p ${{ secrets.OVSX_TOKEN }}

publish-zed:
name: Publish Zed extension
needs: [build-artifacts, tag-and-release]
if: vars.PUBLISH_ZED == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.tag-and-release.outputs.tag }}

- name: Publish Zed extension
working-directory: themes/zed
run: |
# The zed CLI may not be available on CI runners. If this fails,
# see RELEASE.md for manual publishing instructions.
zed extension publish

publish-neovim:
name: Publish to synthpunk.nvim repo
needs: [build-artifacts, tag-and-release]
if: vars.PUBLISH_NVIM == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.tag-and-release.outputs.tag }}
fetch-depth: 0

- name: Clone synthpunk.nvim
env:
SSH_DEPLOY_KEY: ${{ secrets.SYNTHPUNK_NVIM_DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_DEPLOY_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan github.com >> ~/.ssh/known_hosts
GIT_SSH_COMMAND="ssh -i ~/.ssh/deploy_key" git clone git@github.com:slowdini/synthpunk.nvim.git /tmp/synthpunk.nvim

- name: Copy neovim files and push
env:
TAG: ${{ needs.tag-and-release.outputs.tag }}
GIT_SSH_COMMAND: ssh -i ~/.ssh/deploy_key
run: |
cd /tmp/synthpunk.nvim
find . -maxdepth 1 -not -name '.git' -not -name '.' -exec rm -rf {} +
cp -r "$GITHUB_WORKSPACE/themes/neovim/"* .
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "chore: release $TAG"
git tag "$TAG"
git push origin main
git push origin "$TAG"
46 changes: 46 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# AGENTS.md

## Project structure

Synthpunk is a synthwave-inspired color scheme that ships as themes for multiple tools. All theme artifacts are generated from a single palette source.

- `palette/` — source-of-truth color palette data (the only place to edit colors)
- `generator/` — TypeScript theme-generation engine (run via `bun run build`)
- `themes/` — generated artifacts, one subdirectory per target:
- `vscode/` — VS Code extension package
- `zed/` — Zed extension package
- `neovim/` — Neovim colorscheme (Lua)
- `wezterm/` — WezTerm color scheme files (TOML)
- `starship/` — Starship prompt config (TOML)
- `scripts/` — release tooling (version bumping)
- `tests/` — cross-target tests (version lockstep)
- `assets/` — preview images for the README
- `schemas/` — JSON schemas for palette files

## Commands

```sh
bun install # install dependencies
bun run build # regenerate all theme artifacts from palette/
bun test # run all tests
bun run check # biome lint/format + tsc typecheck
bun run format # auto-format with biome
```

## Key rules

- **Never edit `themes/**` directly.** These files are generated. Edit `palette/` and `generator/`, then run `bun run build`. CI enforces that committed artifacts match generator output.
- **Version lockstep.** The version in `package.json`, `themes/vscode/package.json`, and `themes/zed/extension.toml` must always match. Use `bun scripts/bump-version.ts <version>` to bump all at once. The lockstep is enforced by `tests/lockstep.test.ts`.

## Commit conventions

Use Conventional Commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`.

## Release process

See [`RELEASE.md`](RELEASE.md) for the full release operations guide. The short version:

1. Feature PRs merge into `dev` after CI passes.
2. Trigger the "Release PR" workflow with the next version — it bumps manifests and opens a `dev → main` PR.
3. Merge the release PR into `main` — this auto-tags, creates the GitHub release, and publishes to all configured channels.
4. Merge `main` back into `dev`.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Synthpunk contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading
Loading