diff --git a/.claude/rules/workflow.md b/.claude/rules/workflow.md index 36b522eb..8f243bff 100644 --- a/.claude/rules/workflow.md +++ b/.claude/rules/workflow.md @@ -127,7 +127,7 @@ If either is wrong, stop and fix it before proceeding. glb update --claim ``` -Commit semi-frequently - don't save everything for one giant commit. +Commit semi-frequently - don't save everything for one giant commit. Use **conventional commits** (`feat:`, `fix:`, `chore:`, `docs:`, `ci:`, `refactor:`, `test:`). Append `!` for breaking changes (e.g. `feat!:`). These prefixes drive automatic version bumps and changelog generation via release-please. **Before every commit**, run `cargo fmt` (and `floe fmt` if you touched `.fl` files). Never commit unformatted code. diff --git a/.github/release-please-config.json b/.github/release-please-config.json new file mode 100644 index 00000000..7831619b --- /dev/null +++ b/.github/release-please-config.json @@ -0,0 +1,12 @@ +{ + "packages": { + ".": { + "release-type": "rust", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "extra-files": [ + "crates/floe-wasm/Cargo.toml" + ] + } + } +} diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json new file mode 100644 index 00000000..466df71c --- /dev/null +++ b/.github/release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.1.0" +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43ee47bc..3d0f1ae3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: - name: Tests run: cargo test - - name: Build release for LSP tests + - name: Build for integration tests run: cargo build - name: Setup Node.js diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..2c02d768 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,22 @@ +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 }} + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + config-file: .github/release-please-config.json + manifest-file: .github/release-please-manifest.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..7beda16b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,167 @@ +name: Release + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + name: Build ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + archive: tar.gz + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + archive: tar.gz + cross: true + - target: x86_64-apple-darwin + os: macos-latest + archive: tar.gz + - target: aarch64-apple-darwin + os: macos-latest + archive: tar.gz + - target: x86_64-pc-windows-msvc + os: windows-latest + archive: zip + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: "1.94.0" + targets: ${{ matrix.target }} + + - name: Install cross + if: matrix.cross + run: cargo install cross --locked + + - name: Build + run: | + if [ "${{ matrix.cross }}" = "true" ]; then + cross build --release --target ${{ matrix.target }} + else + cargo build --release --target ${{ matrix.target }} + fi + shell: bash + + - name: Package (unix) + if: matrix.archive == 'tar.gz' + run: | + cd target/${{ matrix.target }}/release + tar czf ../../../floe-${{ matrix.target }}.tar.gz floe + shell: bash + + - name: Package (windows) + if: matrix.archive == 'zip' + run: | + cd target/${{ matrix.target }}/release + 7z a ../../../floe-${{ matrix.target }}.zip floe.exe + shell: bash + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: floe-${{ matrix.target }} + path: floe-${{ matrix.target }}.${{ matrix.archive }} + + release: + name: Create Release + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: artifacts/* + + publish-crate: + name: Publish to crates.io + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: "1.94.0" + + - name: Publish + run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + + publish-npm: + name: Publish to npm + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Install dependencies + run: pnpm install + + - name: Build and publish Vite plugin + working-directory: integrations/vite-plugin-floe + run: | + pnpm build + npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + publish-extension: + name: Publish to Open VSX + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install dependencies + working-directory: editors/vscode + run: npm install + + - name: Build extension + working-directory: editors/vscode + run: npm run compile + + - name: Install ovsx + run: npm install -g ovsx + + - name: Publish to Open VSX + working-directory: editors/vscode + run: ovsx publish -p ${{ secrets.OVSX_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..978f5cf2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +All notable changes to Floe will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/), +and this project adheres to [Semantic Versioning](https://semver.org/). + +## [Unreleased] + +### Added +- Pipe operator (`|>`) with first-arg default and `_` placeholder +- Exhaustive pattern matching with `match` expressions +- Result (`Ok`/`Err`) and Option (`Some`/`None`) types +- `?` operator for Result/Option unwrapping +- Tagged unions with multi-depth matching +- Branded and opaque types +- Type constructors with named arguments and defaults +- Pipe lambdas (`|x| expr`) and dot shorthand (`.field`) +- JSX support with inline match and pipe expressions +- Language server with diagnostics, completions, and go-to-definition +- Code formatter (`floe fmt`) +- Vite plugin for dev/build integration +- VS Code extension with syntax highlighting and LSP +- Browser playground (WASM) +- `floe init` project scaffolding +- `floe watch` for auto-recompilation diff --git a/CLAUDE.md b/CLAUDE.md index f807807d..75682880 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,9 +20,62 @@ Do **not** use parser generators (pest, nom, lalrpop). The parser is handwritten recursive descent for better error recovery and LSP integration. -## Commits +## Releases & Versioning -Use **conventional commits** for all commit messages and PR titles. Prefixes: `feat:`, `fix:`, `chore:`, `test:`. This is required for automated versioning with release-please. See `.claude/rules/workflow.md` for details. +This project uses **conventional commits** + **release-please** for automated versioning and releases. + +### How it works + +1. **You write commits with prefixes:** + + | Prefix | Version bump | Example | + |---|---|---| + | `fix:` | patch (0.1.0 -> 0.1.1) | `fix: crash on nested match` | + | `feat:` | minor (0.1.0 -> 0.2.0) | `feat: add pipe lambdas` | + | `feat!:` | major (0.1.0 -> 1.0.0) | `feat!: remove arrow functions` | + | `chore:`, `docs:`, `ci:`, `refactor:`, `test:` | no bump | `docs: update README` | + +2. **release-please watches main** and auto-opens a "Release PR" that: + - Bumps the version in `Cargo.toml` based on commit prefixes + - Updates `CHANGELOG.md` with entries generated from commit messages + - Title looks like `chore(main): release 0.2.0` + +3. **Merging the Release PR** creates a git tag (`v0.2.0`) and a GitHub Release. + +4. **The tag triggers the release workflow** which: + - Cross-compiles binaries for macOS (arm64 + x86), Linux (x86 + arm64), Windows + - Uploads them as assets on the GitHub Release + - Publishes `floe` to crates.io + - Publishes `@floelang/vite-plugin` to npm + - Publishes the VS Code extension to Open VSX + +### Package names + +| Package | Registry | Name | +|---|---|---| +| Compiler CLI | crates.io | `floe` | +| Vite plugin | npm | `@floelang/vite-plugin` | +| VS Code extension | Open VSX | `floelang.floe` | + +### What you need to do + +- Write meaningful conventional commit messages (the CHANGELOG is generated from them) +- Periodically merge the Release PR that release-please opens +- That's it - everything else is automated + +### Config files + +| File | Purpose | +|---|---| +| `.github/release-please-config.json` | release-please settings (release type, extra files to bump) | +| `.github/release-please-manifest.json` | current version tracking | +| `.github/workflows/release-please.yml` | workflow that opens Release PRs | +| `.github/workflows/release.yml` | workflow that builds binaries on tag push | +| `CHANGELOG.md` | auto-maintained changelog | + +### Pre-1.0 strategy + +While pre-1.0, breaking changes bump minor (0.1 -> 0.2) not major. Go to 1.0.0 when the language syntax is stable and people are using it in real projects. ## Task Tracking with glb diff --git a/Cargo.toml b/Cargo.toml index a22aba09..9ad290ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,13 @@ resolver = "3" name = "floe" version = "0.1.0" edition = "2024" +description = "A programming language that compiles to TypeScript + React" +license = "MIT" +repository = "https://github.com/milkyskies/floe" +homepage = "https://github.com/milkyskies/floe" +keywords = ["compiler", "typescript", "react", "programming-language"] +categories = ["compilers", "development-tools"] +readme = "README.md" [lib] name = "floe" diff --git a/crates/floe-wasm/Cargo.toml b/crates/floe-wasm/Cargo.toml index 5e76b74e..fa55cbdb 100644 --- a/crates/floe-wasm/Cargo.toml +++ b/crates/floe-wasm/Cargo.toml @@ -2,6 +2,10 @@ name = "floe-wasm" version = "0.1.0" edition = "2024" +description = "WASM build of the Floe compiler for browser usage" +license = "MIT" +repository = "https://github.com/milkyskies/floe" +publish = false [lib] crate-type = ["cdylib", "rlib"] diff --git a/docs/site/src/content/docs/tooling/vite.md b/docs/site/src/content/docs/tooling/vite.md index 6a5e962f..9b653a17 100644 --- a/docs/site/src/content/docs/tooling/vite.md +++ b/docs/site/src/content/docs/tooling/vite.md @@ -2,12 +2,12 @@ title: Vite Plugin --- -The `vite-plugin-floe` package lets Vite transform `.fl` files during development and production builds. +The `@floelang/vite-plugin` package lets Vite transform `.fl` files during development and production builds. ## Installation ```bash -npm install -D vite-plugin-floe +npm install -D @floelang/vite-plugin ``` Make sure `floe` is installed and available in your PATH. @@ -17,7 +17,7 @@ Make sure `floe` is installed and available in your PATH. ```typescript // vite.config.ts import { defineConfig } from "vite" -import floe from "vite-plugin-floe" +import floe from "@floelang/vite-plugin" export default defineConfig({ plugins: [floe()], @@ -46,7 +46,7 @@ floe({ // vite.config.ts import { defineConfig } from "vite" import react from "@vitejs/plugin-react" -import floe from "vite-plugin-floe" +import floe from "@floelang/vite-plugin" export default defineConfig({ plugins: [ diff --git a/editors/vscode/icons/README.md b/editors/vscode/icons/README.md new file mode 100644 index 00000000..f7ffecb2 --- /dev/null +++ b/editors/vscode/icons/README.md @@ -0,0 +1,5 @@ +Extension icons needed: + +- `floe-icon.png` - 128x128, used as the VS Code Marketplace icon +- `floe-light.png` - file icon for `.fl` files in light themes +- `floe-dark.png` - file icon for `.fl` files in dark themes diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 214d0265..65839887 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -3,7 +3,9 @@ "displayName": "Floe", "description": "Language support for Floe (.fl) - syntax highlighting, diagnostics, and LSP integration", "version": "0.1.0", - "publisher": "floe", + "license": "MIT", + "publisher": "floelang", + "icon": "icons/floe-icon.png", "repository": { "type": "git", "url": "https://github.com/milkyskies/floe" diff --git a/examples/store/package.json b/examples/store/package.json index d6495144..0e6237f4 100644 --- a/examples/store/package.json +++ b/examples/store/package.json @@ -23,6 +23,6 @@ "tailwindcss": "^4.1.0", "typescript": "^5.8.0", "vite": "^6.0.0", - "vite-plugin-floe": "workspace:*" + "@floelang/vite-plugin": "workspace:*" } } diff --git a/examples/store/vite.config.ts b/examples/store/vite.config.ts index 6d401fe7..bef11362 100644 --- a/examples/store/vite.config.ts +++ b/examples/store/vite.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from "vite"; import tailwindcss from "@tailwindcss/vite"; -import floe from "vite-plugin-floe"; +import floe from "@floelang/vite-plugin"; import path from "node:path"; export default defineConfig({ diff --git a/examples/todo-app/package.json b/examples/todo-app/package.json index d9ffe6d8..63bb9d5f 100644 --- a/examples/todo-app/package.json +++ b/examples/todo-app/package.json @@ -25,6 +25,6 @@ "tailwindcss": "^4.1.0", "typescript": "^5.8.0", "vite": "^6.0.0", - "vite-plugin-floe": "workspace:*" + "@floelang/vite-plugin": "workspace:*" } } diff --git a/examples/todo-app/vite.config.ts b/examples/todo-app/vite.config.ts index 6d401fe7..bef11362 100644 --- a/examples/todo-app/vite.config.ts +++ b/examples/todo-app/vite.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from "vite"; import tailwindcss from "@tailwindcss/vite"; -import floe from "vite-plugin-floe"; +import floe from "@floelang/vite-plugin"; import path from "node:path"; export default defineConfig({ diff --git a/integrations/vite-plugin-floe/package.json b/integrations/vite-plugin-floe/package.json index cdbf8201..af80cb19 100644 --- a/integrations/vite-plugin-floe/package.json +++ b/integrations/vite-plugin-floe/package.json @@ -1,5 +1,5 @@ { - "name": "vite-plugin-floe", + "name": "@floelang/vite-plugin", "version": "0.1.0", "description": "Vite plugin for Floe - compile .fl files to TypeScript", "type": "module", @@ -18,6 +18,15 @@ "floe" ], "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/milkyskies/floe", + "directory": "integrations/vite-plugin-floe" + }, + "homepage": "https://github.com/milkyskies/floe", "peerDependencies": { "vite": "^5.0.0 || ^6.0.0" }, diff --git a/integrations/vite-plugin-floe/src/index.ts b/integrations/vite-plugin-floe/src/index.ts index 56bdde2c..4ebd435d 100644 --- a/integrations/vite-plugin-floe/src/index.ts +++ b/integrations/vite-plugin-floe/src/index.ts @@ -15,7 +15,7 @@ export interface FloeOptions { * @example * ```ts * import { defineConfig } from "vite" - * import floe from "vite-plugin-floe" + * import floe from "@floelang/vite-plugin" * * export default defineConfig({ * plugins: [floe()], diff --git a/package.json b/package.json index 45ece37c..36bbb021 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "scripts": { - "build:plugin": "pnpm --filter vite-plugin-floe build", + "build:plugin": "pnpm --filter @floelang/vite-plugin build", "dev:todo": "pnpm build:plugin && pnpm --filter floe-todo-app dev", "dev:store": "pnpm build:plugin && pnpm --filter floe-store dev", "dev:docs": "pnpm --filter floe-docs dev", diff --git a/playground/.gitignore b/playground/.gitignore index b6511d56..01d0a084 100644 --- a/playground/.gitignore +++ b/playground/.gitignore @@ -1,2 +1 @@ pkg/ -node_modules/ diff --git a/playground/build.sh b/playground/build.sh new file mode 100755 index 00000000..9c4ee9cc --- /dev/null +++ b/playground/build.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Build the WASM package for the playground +# Requires: wasm-pack (cargo install wasm-pack) + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CRATE_DIR="$SCRIPT_DIR/../crates/floe-wasm" +OUT_DIR="$SCRIPT_DIR/pkg" + +echo "Building floe-wasm..." +wasm-pack build "$CRATE_DIR" --target web --out-dir "$OUT_DIR" + +echo "Done! Open playground/index.html in a browser." diff --git a/playground/index.html b/playground/index.html index 324d3b96..dd7d4e0c 100644 --- a/playground/index.html +++ b/playground/index.html @@ -191,21 +191,21 @@

Floe Playground

diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b316231..3da4670c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,6 +60,9 @@ importers: specifier: ^4.3.6 version: 4.3.6 devDependencies: + '@floelang/vite-plugin': + specifier: workspace:* + version: link:../../integrations/vite-plugin-floe '@tailwindcss/vite': specifier: ^4.1.0 version: 4.2.1(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)) @@ -78,9 +81,6 @@ importers: vite: specifier: ^6.0.0 version: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1) - vite-plugin-floe: - specifier: workspace:* - version: link:../../integrations/vite-plugin-floe examples/store-ts: dependencies: @@ -149,6 +149,9 @@ importers: specifier: ^4.3.6 version: 4.3.6 devDependencies: + '@floelang/vite-plugin': + specifier: workspace:* + version: link:../../integrations/vite-plugin-floe '@tailwindcss/vite': specifier: ^4.1.0 version: 4.2.1(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)) @@ -170,9 +173,6 @@ importers: vite: specifier: ^6.0.0 version: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1) - vite-plugin-floe: - specifier: workspace:* - version: link:../../integrations/vite-plugin-floe integrations/vite-plugin-floe: devDependencies: