diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b021ea1..5fe5c41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,8 +32,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32-wasip1 + - run: rustup target add wasm32-wasip1 - run: cargo check --target wasm32-wasip1 working-directory: editors/zed diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..358d632 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,100 @@ +name: Release Please + +on: + push: + branches: [main] + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + version: ${{ steps.release.outputs.version }} + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + build: + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + strategy: + matrix: + include: + - goos: linux + goarch: amd64 + vscode_target: linux-x64 + - goos: linux + goarch: arm64 + vscode_target: linux-arm64 + - goos: darwin + goarch: amd64 + vscode_target: darwin-x64 + - goos: darwin + goarch: arm64 + vscode_target: darwin-arm64 + - goos: windows + goarch: amd64 + vscode_target: win32-x64 + - goos: windows + goarch: arm64 + vscode_target: win32-arm64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: jdx/mise-action@v4 + + - name: Build release binaries + run: mise run build:release + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + GASTRO_VERSION: ${{ needs.release-please.outputs.version }} + + - name: Build VS Code extension + run: | + npm install --prefix editors/vscode + EXT="${{ matrix.goos == 'windows' && '.exe' || '' }}" + cp "tmp/release/gastro-lsp-${{ matrix.goos }}-${{ matrix.goarch }}${EXT}" "editors/vscode/bin/gastro-lsp${EXT}" + npx --prefix editors/vscode @vscode/vsce package --target ${{ matrix.vscode_target }} -o "tmp/release/gastro-vscode-${{ matrix.vscode_target }}.vsix" + + - uses: actions/upload-artifact@v4 + with: + name: release-${{ matrix.goos }}-${{ matrix.goarch }} + path: tmp/release/* + + publish: + needs: [release-please, build] + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + path: tmp/release + pattern: release-* + merge-multiple: true + + - name: Upload binaries to GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: gh release upload "${{ needs.release-please.outputs.tag_name }}" tmp/release/* + + - name: Publish to VS Code Marketplace + if: ${{ vars.VSCE_PAT != '' }} + run: npx @vscode/vsce publish --packagePath tmp/release/*.vsix + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + + - name: Publish to Open VSX + if: ${{ vars.OVSX_PAT != '' }} + run: npx ovsx publish tmp/release/*.vsix + env: + OVSX_PAT: ${{ secrets.OVSX_PAT }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 24a2f7d..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Release - -on: - push: - tags: ["v*"] - -permissions: - contents: write - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: jdx/mise-action@v4 - - run: mise run test - - build: - needs: test - strategy: - matrix: - include: - - goos: linux - goarch: amd64 - - goos: linux - goarch: arm64 - - goos: darwin - goarch: amd64 - - goos: darwin - goarch: arm64 - - goos: windows - goarch: amd64 - - goos: windows - goarch: arm64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: jdx/mise-action@v4 - - run: mise run build:release - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - - uses: actions/upload-artifact@v4 - with: - name: binaries-${{ matrix.goos }}-${{ matrix.goarch }} - path: tmp/release/* - - release: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/download-artifact@v4 - with: - path: tmp/release - pattern: binaries-* - merge-multiple: true - - uses: softprops/action-gh-release@v2 - with: - files: tmp/release/* - generate_release_notes: true diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..e18ee07 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.0" +} diff --git a/AGENTS.md b/AGENTS.md index 11dc56d..61d9e5d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,7 @@ - If you encounter a changed file, that you didn't edit. Ask if you should revert it or commit it. - Before presenting a plan, identify any ambigious parts of it. Ask questions until none remain. - Always run tests with `-race` to test for race-conditions. +- All new 3rd party dependencies need to be approved. That applies to code, packages and workflows. ### References diff --git a/DECISIONS.md b/DECISIONS.md index c55ff28..81da753 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -18,3 +18,6 @@ - **2026-04-02** (m+git@andri.dk) Codegen template unification: evaluated and rejected. Keeping `handlerTmpl` (pages) and `componentTmpl` (components) as separate Go templates in `generate.go`. Pages stream directly to `http.ResponseWriter`; components buffer into `template.HTML`. Merging would force pages to buffer unnecessarily and add conditional complexity for ~10 lines of shared code. Fixed a real bug instead: both templates now handle `template.Execute` errors that were previously silently discarded. - **2026-04-02** (m+git@andri.dk) Bare function calls for components: replaced `{{ render Component (dict ...) }}` with `{{ Component (dict ...) }}`. Leaf components are now standard Go template function calls -- no transformation needed. `wrap` keyword stays for components with children. FuncMap registration uses PascalCase names directly (`fm["Card"]`) instead of `__gastro_` prefix. Added enhanced error messages for undefined PascalCase functions ("did you forget to import it?") and collision warnings when user `WithFuncs()` names shadow component names. LSP updated with AST-based component detection. - **2026-04-02** (m+git@andri.dk) LSP: AST-based component prop validation. Refactored `DiagnoseComponentProps`, `DetectComponentTagContext`, and `DetectPropValueContext` from regex-only to AST-primary with regex fallback. Fixes: bare component calls (`{{ Card }}`) now emit missing-prop warnings; nested parentheses in dict args (`(dict "Date" (.X | timeFormat "Jan"))`) no longer break prop extraction. Regex fallbacks also improved to handle bare calls (for completions during editing when the template is syntactically incomplete). Design guideline added: prefer AST over regex for template analysis in the LSP. +- **2026-04-03** (m+git@andri.dk) Release automation: adopted conventional commits + release-please for trunk-based development with automatic versioning. SemVer with `bump-minor-pre-major` (breaking changes only bump minor while on v0.x). release-please creates a Release PR on each push to main; merging it tags and creates a GitHub Release. Old `release.yml` (manual `v*` tag trigger + `softprops/action-gh-release`) replaced by `release-please.yml`. Removed `dtolnay/rust-toolchain` (Ubuntu runners include rustup) and `softprops/action-gh-release` (replaced by `gh release upload`). Only approved third-party actions: `jdx/mise-action` and `googleapis/release-please-action`. +- **2026-04-03** (m+git@andri.dk) VS Code extension distribution: platform-specific `.vsix` packages bundling the correct `gastro-lsp` binary per OS/arch. Published to both VS Code Marketplace (`VSCE_PAT`) and Open VSX (`OVSX_PAT`). Version kept in sync with the Go module via release-please `extra-files`. +- **2026-04-03** (m+git@andri.dk) Version embedding: both `gastro` and `gastro-lsp` binaries accept `--version` / `version` / `-v` flags. Version injected at build time via `-ldflags "-X main.version=..."` using `GASTRO_VERSION` env var (defaults to `dev`). diff --git a/TODO.md b/TODO.md index 5f4e4ea..097aa32 100644 --- a/TODO.md +++ b/TODO.md @@ -2,6 +2,7 @@ ## Backlog +- [ ] Gastro files without frontmatter are also valid. It's awkward that you have to declare empty props, and not use them for plain components. - [x] Live reload in dev mode: inject JS snippet into HTML responses, SSE endpoint `GET /__gastro/reload`, file-based signal (`.gastro/.reload`) written by CLI after regeneration, browser reconnects with backoff on disconnect - [x] `gastro dev` smart restart: wire `ClassifyChange`/`DetectChangedSection` (already implemented in `internal/watcher/`) into the CLI so template-only changes skip the rebuild+restart cycle (templates already load from disk in dev mode) - [x] `gastro new` scaffold command: generate a minimal project skeleton (pages/, components/, static/, main.go, go.mod) diff --git a/cmd/gastro-lsp/main.go b/cmd/gastro-lsp/main.go index 2873a19..ec8d0e0 100644 --- a/cmd/gastro-lsp/main.go +++ b/cmd/gastro-lsp/main.go @@ -22,10 +22,18 @@ import ( "github.com/andrioid/gastro/internal/parser" ) +// Set at build time via -ldflags "-X main.version=..." +var version = "dev" + // gastro-lsp: a Language Server Protocol server for .gastro files. // Communicates via JSON-RPC 2.0 over stdin/stdout. func main() { + if len(os.Args) > 1 && (os.Args[1] == "version" || os.Args[1] == "--version" || os.Args[1] == "-v") { + fmt.Println(version) + return + } + log.SetOutput(os.Stderr) log.Println("gastro-lsp: starting") @@ -266,7 +274,7 @@ func (s *server) handleInitialize(msg *jsonRPCMessage) *jsonRPCMessage { }, "serverInfo": map[string]any{ "name": "gastro-lsp", - "version": "0.1.0", + "version": version, }, }, } diff --git a/cmd/gastro/main.go b/cmd/gastro/main.go index e494b6f..8fae3b5 100644 --- a/cmd/gastro/main.go +++ b/cmd/gastro/main.go @@ -16,6 +16,9 @@ import ( "github.com/andrioid/gastro/internal/watcher" ) +// Set at build time via -ldflags "-X main.version=..." +var version = "dev" + const ( fileWatchInterval = 500 * time.Millisecond debounceDelay = 200 * time.Millisecond @@ -28,6 +31,9 @@ func main() { } switch os.Args[1] { + case "version", "--version", "-v": + fmt.Println(version) + return case "generate": if err := runGenerate(); err != nil { fmt.Fprintf(os.Stderr, "gastro generate: %v\n", err) @@ -56,6 +62,8 @@ func main() { } func printUsage() { + fmt.Fprintf(os.Stderr, "gastro %s\n", version) + fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Usage: gastro ") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Commands:") @@ -63,6 +71,7 @@ func printUsage() { fmt.Fprintln(os.Stderr, " generate Compile .gastro files to .gastro/ directory") fmt.Fprintln(os.Stderr, " build Generate + go build -> single binary") fmt.Fprintln(os.Stderr, " dev Watch mode with hot reload (port 4242 or PORT env)") + fmt.Fprintln(os.Stderr, " version Print version") } func runGenerate() error { diff --git a/docs/contributing.md b/docs/contributing.md index 88ade3d..9829dab 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -190,6 +190,51 @@ stdin/stdout. ## Commit Messages -- Summarise the change in 1-2 sentences. -- Focus on the "why" rather than the "what". -- Use present tense ("add", "fix", "update", not "added", "fixed"). +This project uses [Conventional Commits](https://www.conventionalcommits.org/) +to automate versioning and changelog generation via +[release-please](https://github.com/googleapis/release-please). + +### Format + +``` +type(scope): short description + +Optional body explaining *why*, not *what*. +``` + +### Types + +| Type | When to use | +|------|-------------| +| `feat` | New user-facing functionality | +| `fix` | Bug fix | +| `docs` | Documentation only | +| `refactor` | Code change that neither fixes a bug nor adds a feature | +| `test` | Adding or updating tests | +| `chore` | Build, CI, dependency updates | +| `ci` | CI/CD workflow changes | + +### Scope (optional but encouraged) + +Use the package or area being changed: `parser`, `codegen`, `lsp`, `router`, +`cli`, `vscode`, `neovim`, `zed`, `ci`. + +### Examples + +``` +feat(lsp): add component auto-import on completion +fix(codegen): handle duplicate child template names +docs: add SSE guide +chore(ci): replace softprops action with gh CLI +refactor(parser): extract backtick tracking into helper +``` + +### Breaking changes + +Add `!` after the type/scope, or include `BREAKING CHANGE:` in the commit body. +release-please will bump the minor version (while on v0.x) or the major version +(v1+). + +``` +feat(codegen)!: replace syntax with template actions +``` diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore new file mode 100644 index 0000000..2c35b2c --- /dev/null +++ b/editors/vscode/.vscodeignore @@ -0,0 +1,3 @@ +.gitignore +node_modules/** +package-lock.json diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 07b00be..3562316 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -1,9 +1,19 @@ { "name": "gastro-vscode", "displayName": "Gastro", - "description": "Language support for Gastro (.gastro) files", + "description": "Language support for Gastro (.gastro) files - syntax highlighting, LSP, and completions", "version": "0.1.0", "publisher": "andrioid", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/andrioid/gastro", + "directory": "editors/vscode" + }, + "homepage": "https://github.com/andrioid/gastro", + "bugs": { + "url": "https://github.com/andrioid/gastro/issues" + }, "engines": { "vscode": "^1.80.0" }, @@ -11,6 +21,7 @@ "onLanguage:gastro" ], "categories": ["Programming Languages"], + "keywords": ["gastro", "go", "template", "component", "language-server"], "dependencies": { "vscode-languageclient": "^9.0.1" }, diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..03be2a9 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,26 @@ +{ + "release-type": "go", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "include-component-in-tag": false, + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "refactor", "section": "Refactoring" }, + { "type": "docs", "section": "Documentation" }, + { "type": "chore", "section": "Miscellaneous" }, + { "type": "ci", "section": "Miscellaneous", "hidden": true }, + { "type": "test", "section": "Miscellaneous", "hidden": true } + ], + "packages": { + ".": { + "extra-files": [ + { + "type": "json", + "path": "editors/vscode/package.json", + "jsonpath": "$.version" + } + ] + } + } +} diff --git a/scripts/build/gastro b/scripts/build/gastro index 2f99a97..e146566 100755 --- a/scripts/build/gastro +++ b/scripts/build/gastro @@ -5,6 +5,8 @@ set -euo pipefail +VERSION="${GASTRO_VERSION:-dev}" + mkdir -p bin -go build -o bin/gastro ./cmd/gastro/ -echo "Built bin/gastro" +go build -ldflags "-X main.version=${VERSION}" -o bin/gastro ./cmd/gastro/ +echo "Built bin/gastro (${VERSION})" diff --git a/scripts/build/gastro-lsp b/scripts/build/gastro-lsp index 537ec48..3cc8565 100755 --- a/scripts/build/gastro-lsp +++ b/scripts/build/gastro-lsp @@ -5,6 +5,8 @@ set -euo pipefail +VERSION="${GASTRO_VERSION:-dev}" + mkdir -p bin -go build -o bin/gastro-lsp ./cmd/gastro-lsp/ -echo "Built bin/gastro-lsp" +go build -ldflags "-X main.version=${VERSION}" -o bin/gastro-lsp ./cmd/gastro-lsp/ +echo "Built bin/gastro-lsp (${VERSION})" diff --git a/scripts/build/release b/scripts/build/release index 6ff9c4c..2391822 100755 --- a/scripts/build/release +++ b/scripts/build/release @@ -4,6 +4,8 @@ set -euo pipefail OUTDIR="tmp/release" +VERSION="${GASTRO_VERSION:-dev}" +LDFLAGS="-X main.version=${VERSION} -s -w" BINARIES=("gastro" "gastro-lsp") PLATFORMS=( "linux/amd64" @@ -31,8 +33,8 @@ for platform in "${PLATFORMS[@]}"; do output="${output}.exe" fi - echo "Building ${bin} for ${os}/${arch}..." - GOOS="$os" GOARCH="$arch" CGO_ENABLED=0 go build -o "$output" "./cmd/${bin}/" + echo "Building ${bin} for ${os}/${arch} (${VERSION})..." + GOOS="$os" GOARCH="$arch" CGO_ENABLED=0 go build -ldflags "${LDFLAGS}" -o "$output" "./cmd/${bin}/" done done diff --git a/scripts/build/vscode b/scripts/build/vscode index 8cecf4e..838d5ba 100755 --- a/scripts/build/vscode +++ b/scripts/build/vscode @@ -4,5 +4,7 @@ set -euo pipefail -go build -o editors/vscode/bin/gastro-lsp ./cmd/gastro-lsp/ -echo "Built editors/vscode/bin/gastro-lsp" +VERSION="${GASTRO_VERSION:-dev}" + +go build -ldflags "-X main.version=${VERSION}" -o editors/vscode/bin/gastro-lsp ./cmd/gastro-lsp/ +echo "Built editors/vscode/bin/gastro-lsp (${VERSION})"