diff --git a/.agent/skills/effect-context-manager/SKILL.md b/.agent/skills/effect-context-manager/SKILL.md index d5c34a2e..d417e151 100644 --- a/.agent/skills/effect-context-manager/SKILL.md +++ b/.agent/skills/effect-context-manager/SKILL.md @@ -1,8 +1,8 @@ --- name: effect-context-manager description: > - Gestiona el entorno de referencia de Effect v4 alojado en el worktree ./effect-reference. - Trigger: Cuando se necesita consultar código de effect-smol, actualizar el contexto, o configurar en nueva máquina. + Manages the local Effect v4 and Alchemy reference clones hosted in ./.effect-reference. + Trigger: When Effect or Alchemy code needs to be consulted, the context needs to be updated, or a new machine needs to be set up. license: Apache-2.0 metadata: author: gentleman-programming @@ -11,90 +11,116 @@ metadata: ## When to Use -- Clonar el proyecto en una nueva máquina y montar el worktree de effect-reference -- Actualizar el contexto de Effect desde el origen remoto (effect-smol) -- Consultar patrones de Effect-TS en el código de referencia -- Verificar que el worktree está sincronizado correctamente +- Clone the local Effect v4 and Alchemy references on a new machine +- Update Effect from the `main` branch of `Effect-TS/effect` +- Update Alchemy from the `main` branch of `alchemy-run/alchemy` +- Consult current patterns directly in the reference clones +- Verify that both depth-1 clones are synchronized with their upstreams -## Critical Patterns - -### Protocolo 1: Setup en Nueva Máquina (Clone & Mount) +## Canonical Sources -Cuando el directorio `./effect-reference` no existe o el usuario menciona "nueva máquina": - -```bash -# 1. Obtener la rama huérfana del remoto -git fetch origin effect-context +- `.effect-reference/effect` is a depth-1 clone of the `main` branch of `https://github.com/Effect-TS/effect.git` and is the local Effect v4 reference. +- Effect v3 corresponds to the `v3` branch of the same `Effect-TS/effect` repository; it must not be used as the target for the v4 reference. +- `.effect-reference/alchemy` is a depth-1 clone of the `main` branch of `https://github.com/alchemy-run/alchemy.git` and is the canonical Effect-based Alchemy next/alpha reference. +- `alchemy-run/alchemy-async` is the former async implementation, not the current canonical reference. -# 2. Montar el worktree -git worktree add .effect-reference origin/effect-context +## Critical Patterns -# 3. Confirmar lectura del archivo de migración -cat .effect-reference/MIGRATION.md -``` +### Protocol 1: Setup on a New Machine -### Protocolo 2: Actualización desde el Origen (Sync) +The reference directories are ignored by Git and are independent clones. They are neither worktrees of the main repository nor orphan branches. -Cuando el usuario pida "actualizar el contexto de Effect" o "traer lo último de effect-smol": +When one of the directories does not exist or the user mentions a new machine: ```bash -# 1. Entrar al directorio del worktree -cd .effect-reference - -# 2. Descargar archivos más recientes (shallow) -git fetch https://github.com/Effect-TS/effect-smol.git main --depth 1 - -# 3. Sobrescribir archivos locales sin mezclar historiales -git checkout FETCH_HEAD -- . +# 1. Create the ignored container directory +mkdir -p .effect-reference + +# 2. Clone only the canonical branches with depth 1 +git clone --depth 1 --branch main https://github.com/Effect-TS/effect.git .effect-reference/effect +git clone --depth 1 --branch main https://github.com/alchemy-run/alchemy.git .effect-reference/alchemy + +# 3. Confirm the remote, branch, and depth of each clone +git -C .effect-reference/effect remote get-url origin +git -C .effect-reference/effect branch --show-current +git -C .effect-reference/effect rev-parse --is-shallow-repository +git -C .effect-reference/alchemy remote get-url origin +git -C .effect-reference/alchemy branch --show-current +git -C .effect-reference/alchemy rev-parse --is-shallow-repository +``` -# 4. Limpiar archivos eliminados en el origen -git add -A +If one of the clones already exists, do not run `git clone` again in that directory. Use the corresponding synchronization protocol. -# 5. Crear commit de sincronización -git commit -m "chore: sync latest effect-smol source" +### Protocol 2: Update from Upstream Sources -# 6. Sincronizar con el repositorio del usuario -git push origin effect-context +When the user asks to update the context, first confirm that each clone is clean and points to the expected remote and branch: -# 7. Volver a la raíz -cd .. +```bash +# Effect v4 +git -C .effect-reference/effect status --short --branch +git -C .effect-reference/effect remote get-url origin +git -C .effect-reference/effect branch --show-current +git -C .effect-reference/effect pull --ff-only --depth 1 origin main + +# Alchemy next/alpha +git -C .effect-reference/alchemy status --short --branch +git -C .effect-reference/alchemy remote get-url origin +git -C .effect-reference/alchemy branch --show-current +git -C .effect-reference/alchemy pull --ff-only --depth 1 origin main ``` -## Restricciones Críticas de Seguridad +Do not overwrite local changes in the clones. If `status --short` shows changes or `pull --ff-only` cannot fast-forward, stop and resolve the state explicitly. -| Restricción | Descripción | -| ----------------------- | --------------------------------------------------------------------------------------------------------------- | -| **Aislamiento Total** | Nunca hacer `git merge` entre la rama actual y `effect-context` | -| **Modo Solo-Lectura** | No sugerir cambios de código dentro de `./effect-reference` | -| **Limpieza de Commits** | Los archivos de `.effect-reference` NUNCA deben aparecer en `git status` de ramas de desarrollo (`dev`, `main`) | +## Critical Safety Constraints -## Verificación del Estado +| Constraint | Description | +| ------------------------ | ---------------------------------------------------------------------------------------------------------- | +| **Total Isolation** | Never run `git merge` between the reference clones and the development branches | +| **Read-Only Mode** | Do not suggest code changes inside `.effect-reference/effect` or `.effect-reference/alchemy` | +| **Independent Clones** | Do not mount these references as worktrees or maintain them through orphan branches of the main repository | +| **Ignored Content** | Files under `.effect-reference` must not be included in commits on development branches | +| **Safe Synchronization** | Update only clean clones by fast-forwarding from the `main` branch of their expected `origin` | -```bash -# Verificar que es un worktree válido -cd .effect-reference && git status - -# Ver ramas disponibles -git branch -a +## Status Verification -# Verificar que está en rama effect-context -git rev-parse --abbrev-ref HEAD +```bash +# Verify the remote, branch, and shallow clone status of Effect +git -C .effect-reference/effect remote get-url origin +git -C .effect-reference/effect branch --show-current +git -C .effect-reference/effect rev-parse --is-shallow-repository +git -C .effect-reference/effect rev-parse HEAD +git ls-remote https://github.com/Effect-TS/effect.git refs/heads/main + +# Verify the remote, branch, and shallow clone status of Alchemy +git -C .effect-reference/alchemy remote get-url origin +git -C .effect-reference/alchemy branch --show-current +git -C .effect-reference/alchemy rev-parse --is-shallow-repository +git -C .effect-reference/alchemy rev-parse HEAD +git ls-remote https://github.com/alchemy-run/alchemy.git refs/heads/main + +# Both clones must remain clean +git -C .effect-reference/effect status --short --branch +git -C .effect-reference/alchemy status --short --branch ``` ## Commands ```bash -# Setup inicial -git fetch origin effect-context && git worktree add .effect-reference origin/effect-context +# Initial setup +git clone --depth 1 --branch main https://github.com/Effect-TS/effect.git .effect-reference/effect +git clone --depth 1 --branch main https://github.com/alchemy-run/alchemy.git .effect-reference/alchemy -# Sincronizar con latest -cd .effect-reference && git fetch https://github.com/Effect-TS/effect-smol.git main --depth 1 && git checkout FETCH_HEAD -- . && git add -A && git commit -m "chore: sync latest effect-smol source" && git push origin effect-context && cd .. +# Synchronize with the latest upstream state +git -C .effect-reference/effect pull --ff-only --depth 1 origin main +git -C .effect-reference/alchemy pull --ff-only --depth 1 origin main -# Verificar estado -cd .effect-reference && git status && git branch +# Verify status +git -C .effect-reference/effect status --short --branch +git -C .effect-reference/alchemy status --short --branch ``` -## Recursos +## Resources -- **Referencia Effect**: [effect-reference/](./effect-reference/) -- **Guía de Migración**: [effect-reference/MIGRATION.md](./effect-reference/MIGRATION.md) +- **Effect v4 Reference**: [.effect-reference/effect/](../../../.effect-reference/effect/) +- **Effect Migration Guide**: [.effect-reference/effect/MIGRATION.md](../../../.effect-reference/effect/MIGRATION.md) +- **Alchemy next/alpha Reference**: [.effect-reference/alchemy/](../../../.effect-reference/alchemy/) diff --git a/.agent/skills/effect-pattern-discovery/SKILL.md b/.agent/skills/effect-pattern-discovery/SKILL.md index 3053f14b..ee699ae1 100644 --- a/.agent/skills/effect-pattern-discovery/SKILL.md +++ b/.agent/skills/effect-pattern-discovery/SKILL.md @@ -1,7 +1,7 @@ --- name: effect-pattern-discovery description: > - Effect-TS patterns sourced from effect-smol reference implementation. + Effect-TS patterns sourced from the Effect v4 reference on Effect-TS/effect main. Trigger: When implementing Effect, Layer, Schema, Pipe, Context, or error handling patterns. license: Apache-2.0 metadata: @@ -173,23 +173,31 @@ const AppLayer = UserServiceLive.pipe( const CombinedLayer = Layer.merge(UserServiceLive, DatabaseServiceLive) ``` +## Reference Selection + +- `.effect-reference/effect` is the ignored, standalone, depth-1 clone of `https://github.com/Effect-TS/effect.git` on `main` and is the canonical Effect v4 source. +- Effect v3 lives on the `v3` branch of the same `Effect-TS/effect` repository. +- `.effect-reference/alchemy` is the ignored, standalone, depth-1 clone of `https://github.com/alchemy-run/alchemy.git` on `main` and is the canonical Effect-based Alchemy next/alpha reference. +- `alchemy-run/alchemy-async` is the former async implementation; do not use it as the canonical current Alchemy reference. + ## Commands ```bash -# Scan for similar patterns in effect-smol -ls .effect-reference/packages/effect/src/ +# Scan for similar patterns in the Effect v4 reference +ls .effect-reference/effect/packages/effect/src/ # Check internal implementations -cat .effect-reference/packages/effect/src/internal/effect.ts +cat .effect-reference/effect/packages/effect/src/internal/effect.ts # Look at module exports -cat .effect-reference/packages/effect/src/index.ts +cat .effect-reference/effect/packages/effect/src/index.ts + +# Explore current Effect-based Alchemy patterns +ls .effect-reference/alchemy/ ``` ## Resources -- **effect-smol source**: `.effect-reference/packages/effect/src/` -- **Pattern docs**: `.effect-reference/.patterns/` -- **Error handling**: `.effect-reference/.patterns/error-handling.md` -- **Module organization**: `.effect-reference/.patterns/module-organization.md` -- **Library development**: `.effect-reference/.patterns/effect-library-development.md` +- **Effect v4 source**: `.effect-reference/effect/packages/effect/src/` +- **Effect migration guide**: `.effect-reference/effect/MIGRATION.md` +- **Alchemy next/alpha source**: `.effect-reference/alchemy/` diff --git a/.github/SETUP.md b/.github/SETUP.md index d8aacb43..3ab72ed0 100644 --- a/.github/SETUP.md +++ b/.github/SETUP.md @@ -1,274 +1,124 @@ -# 🚀 CI/CD Setup Guide +# CI and npm release setup -This guide explains how to set up the automated CI/CD pipeline for publishing packages to NPM and JSR (Deno). +Effectify has three intentionally separate release channels. Alpha and beta are branch-driven prereleases; stable publication is always a manual decision. -## 📋 Prerequisites +## Release channel map -1. **NPM Account**: You need an NPM account with publish permissions -2. **JSR Account**: You need a JSR account for Deno packages (optional) -3. **GitHub Repository**: With admin access to configure secrets +| Channel | Trigger | npm tag | Workflow | +| ------- | ---------------------------------------- | ------------------ | -------------------------------------- | +| Alpha | Push to `dev` | `alpha` | `.github/workflows/release-alpha.yml` | +| Beta | Push to `master` | `beta` | `.github/workflows/cd.yml` | +| Stable | Manual workflow against current `master` | default (`latest`) | `.github/workflows/release-stable.yml` | -## 🔐 Required Secrets +A `chore(release):` commit pushed by a release workflow does not start another beta publication. Stable has no push trigger and cannot be reached by a normal branch push. -Configure these secrets in your GitHub repository settings (`Settings > Secrets and variables > Actions`): +## Required repository setup -### NPM Token +Use Node.js 24.19.0 and pnpm 10.14.0 locally when reproducing workflow checks. -- **Name**: `NPM_TOKEN` -- **Description**: NPM authentication token for publishing packages -- **How to get**: - 1. Go to [npmjs.com](https://www.npmjs.com/) and log in - 2. Go to `Account Settings > Access Tokens` - 3. Click `Generate New Token` - 4. Select `Automation` type (for CI/CD) - 5. Copy the token and add it as `NPM_TOKEN` secret +Configure these GitHub Actions secrets under **Settings > Secrets and variables > Actions**: -### JSR OIDC Configuration (Recommended) +| Secret | Purpose | +| --------------- | ----------------------------------------------------------------------------------------- | +| `NPM_TOKEN` | npm authentication and provenance publication | +| `RELEASE_TOKEN` | Optional checkout token for stable release git operations; `GITHUB_TOKEN` is the fallback | -- **Method**: OIDC (OpenID Connect) - more secure than personal tokens -- **Setup**: Link your package to your GitHub repository in JSR -- **How to configure**: - 1. Go to your package `@effectify/solid-query` on [jsr.io](https://jsr.io/) - 2. Go to the **"Settings"** tab - 3. In **"GitHub repository"** field, enter your repository name (e.g., `your-username/effectify`) - 4. Click **"Link"** to connect the package to your repository - 5. No secrets needed in GitHub - OIDC handles authentication automatically -- **Reference**: [JSR Publishing from GitHub Actions](https://jsr.io/docs/publishing-packages#publishing-from-github-actions) +The release jobs request `contents: write` for Nx release commits, tags, and GitHub releases, and `id-token: write` for npm provenance. -## 🏗️ Workflow Overview +## Nx release projects -### CI Workflow (`.github/workflows/ci.yml`) +All release workflows derive their allowlist from `nx.json`. The seven current Nx project names are: -- **Triggers**: Push to `master`, `main`, `develop` branches and PRs -- **Purpose**: Development and PR validation -- **Jobs**: - - 🔍 **Lint & Format**: Checks code style and formatting - - 🔍 **Type Check**: Validates TypeScript types - - 🏗️ **Build**: Builds affected projects and uploads artifacts - - 🧪 **Test**: Runs tests for affected projects - - 📊 **Summary**: CI results dashboard +1. `@effectify/react-router` +2. `@effectify/react-query` +3. `@effectify/node-better-auth` +4. `@effectify/solid-query` +5. `@effectify/react-router-better-auth` +6. `@effectify/prisma` +7. `@effectify/hatchet` -### Release Workflow (`.github/workflows/release.yml`) +Use these project names—not filesystem paths—in manual workflow inputs. -- **Triggers**: Push to `master` branch only -- **Purpose**: Production release and publishing -- **Optimized**: Tries to reuse build artifacts from CI -- **Jobs**: - - 🔍 **Detect Changes**: Determines if release is needed - - 🚀 **Release & Publish**: Handles versioning, changelog, and publishing - - 📢 **Notify**: Provides status notifications +## Exact workflow behavior -## 🎯 How It Works +### CI: `.github/workflows/ci.yml` -### 1. Change Detection +**Triggers:** pull requests that are opened, synchronized, reopened, or marked ready for review, plus pushes to `dev`. -The workflow automatically detects if any of the configured release projects have changes: +For non-draft pull requests, CI runs the static release-policy contract, affected lint and format checks, affected type checks, affected builds, and affected tests. The release-policy contract is dependency-free and runs with Node.js 24.19.0: -- `packages/react/remix` -- `packages/react/router` -- `packages/node/better-auth` -- `packages/solid/query` - -### 2. Affected Projects - -Uses Nx's native `affected` commands to: - -- Build only changed projects: `nx affected --target=build` -- Test only changed projects: `nx affected --target=test` -- Lint only changed projects: `nx affected --target=lint` - -### 3. Release Process - -When changes are detected on master: - -1. **Try to reuse** build artifacts from CI (if available) -2. **Build** affected projects (only if artifacts not found) -3. **Test** affected projects -4. **Version** packages using Nx Release -5. **Generate** changelogs automatically -6. **Publish** to NPM and JSR -7. **Create** GitHub release - -## 🔧 Configuration Files - -### `.npmrc` - -```ini -registry=https://registry.npmjs.org/ -always-auth=true -@jsr:registry=https://npm.jsr.io/ -``` - -### `nx.json` (Release Configuration) - -```json -{ - "release": { - "projects": [ - "packages/react/remix", - "packages/react/router", - "packages/node/better-auth", - "packages/solid/query" - ], - "changelog": { - "projectChangelogs": { - "renderOptions": { - "authors": true, - "commitReferences": false, - "versionTitleDate": true, - "applyUsernameToAuthors": true - } - } - }, - "releaseTagPattern": "release/{version}", - "version": { - "preVersionCommand": "pnpm nx build @effectify/react-remix @effectify/react-router" - } - } -} +```bash +node --test scripts/release-policy-contract.test.mjs ``` -## 🚀 Usage +### Alpha: `.github/workflows/release-alpha.yml` -### Automatic Release +**Triggers:** pushes to `dev` and optional manual dispatch. -- Push changes to `master` branch -- The workflow automatically detects affected packages -- If changes are found, it triggers the release process +A normal run calculates projects affected across the GitHub push event's exact `before`-to-`github.sha` range, then intersects those exact project names with the seven-project release allowlist. Invalid or zero `before` SHAs safely fall back to the current commit's parent. If the intersection is empty, publication is skipped. Otherwise the workflow builds, tests, versions with Nx `--preid=alpha`, rebuilds the versioned packages, and publishes with npm `--tag=alpha`. -### Manual Release +Manual publish-only recovery requires an explicit comma-separated `projects` input. It publishes the selected existing manifests with `--tag=alpha` and skips version, changelog, and git mutation. -- Go to `Actions` tab in GitHub -- Select `🚀 Release & Publish` workflow -- Click `Run workflow` button +### Beta: `.github/workflows/cd.yml` -### Check Status +**Triggers:** pushes to `master` and optional manual dispatch. -- Go to `Actions` tab to see workflow status -- Check the `📊 CI Summary` for detailed results -- Review published packages in NPM and JSR +A normal run uses the same exact push-range and exact-membership affected-project policy as alpha, versions with Nx `--preid=beta`, and publishes with npm `--tag=beta`. It never publishes to npm's default tag. Pushes whose head commit contains `chore(release):` or `[skip release]` are skipped, preventing release-commit recursion. -## 🛠️ Troubleshooting +Manual publish-only recovery requires explicit existing project names and still publishes with `--tag=beta`; it skips version, changelog, and git mutation. -### Common Issues +### Stable: `.github/workflows/release-stable.yml` -1. **NPM Token Issues** +**Trigger:** manual dispatch only. The workflow has no push trigger. - - Ensure token has `Automation` type - - Check token permissions include publish access - - Verify token is not expired +The workflow always checks out `master`, fetches `origin/master`, and fails unless the checkout is the current remote commit. The `projects` input is required and is validated against all seven Nx release projects. -2. **JSR Token Issues** +#### Normal stable graduation - - Ensure JSR account has publish permissions - - Check if package name conflicts exist - - Verify JSR token is valid +1. Select one or more existing prerelease projects in the comma-separated `projects` input. +2. Leave `publish_only` disabled. +3. The workflow verifies the release-policy contract, exact checked-out HEAD equality with fetched `origin/master`, the selected-project allowlist, and npm authentication. +4. It builds and tests the selected projects, then runs React Router 8 tests, consolidation, readiness, and manifest verification. +5. Only after validation passes, Nx applies the relative `patch` specifier to the selected prereleases, producing their stable versions and release metadata. +6. Nx publishes only the selected projects without a prerelease dist-tag, so npm uses the stable default tag. -3. **Build Failures** +The workflow rejects a selected normal-mode project whose local manifest is already stable. This keeps graduation explicit and prevents an accidental extra patch release. - - Check if all dependencies are installed - - Verify TypeScript compilation - - Review test failures +#### Publish-only stable recovery -4. **No Release Triggered** - - Ensure changes are in release-configured projects - - Check if changes are in configuration files - - Verify branch is `master` +Use this only when selected stable versions already exist in the checked-out manifests but need publication retried: -## 🧪 Testing Workflows Locally +1. Enter the exact existing stable project names in `projects`. +2. Enable `publish_only`. +3. The workflow rejects missing versions, prerelease versions, unknown projects, and empty selections. +4. It builds, tests, and verifies before publishing the selected manifests. +5. It performs no version, changelog, tag, release commit, or git push mutation and supplies no prerelease npm dist-tag. -### Using Act (GitHub Actions Local Runner) +## Release safety checks -We've set up **act** to test GitHub Actions workflows locally before pushing to GitHub. - -#### Prerequisites +Before any Nx version or publish command, every release workflow runs: ```bash -# Install act (if not already installed) -brew install act - -# Install Docker (required for act) -# Download from https://www.docker.com/products/docker-desktop +node --test scripts/release-policy-contract.test.mjs ``` -#### Quick Testing +The contract rejects explicitly modeled structural regressions: a stable push trigger, missing beta or alpha prerelease flags, weakened project or current-`master` checks, known version/publish commands moving ahead of required validation, and channel documentation drifting from the workflows. -```bash -# Test all workflows -./scripts/test-workflows.sh - -# Test specific workflow -./scripts/test-workflows.sh ci -./scripts/test-workflows.sh release - -# List available workflows -./scripts/test-workflows.sh list - -# Get help -./scripts/test-workflows.sh help -``` - -#### Manual Testing with Act +React Router publication readiness is verified with the maintained React Router 8 project and example targets: ```bash -# List jobs in a workflow -act -W .github/workflows/ci.yml --list -act -W .github/workflows/release.yml --list - -# Run specific job -act -W .github/workflows/ci.yml -j build -act -W .github/workflows/ci.yml -j test - -# Run with M1/M2 Mac compatibility -act -W .github/workflows/ci.yml --container-architecture linux/amd64 - -# Run with local secrets -act -W .github/workflows/ci.yml --secret-file .secrets +pnpm nx test @effectify/react-router +pnpm nx run @effectify/react-router-example:migration:test +pnpm nx run @effectify/react-router-example:migration:verify +pnpm nx run @effectify/react-router-example:migration:manifest +pnpm nx run @effectify/react-router-example:consolidation:verify ``` -### Testing Nx Commands Locally - -```bash -# Check affected projects locally -pnpm nx show projects --affected --base=origin/master~1 --head=HEAD - -# Test release process locally -pnpm nx release --dry-run - -# Build affected projects locally -pnpm nx affected --target=build --base=origin/master~1 --head=HEAD - -# Test JSR publish locally (dry run) -cd packages/solid/query -pnpm dlx jsr publish --dry-run - -# Test JSR publish locally (real publish) -cd packages/solid/query -pnpm dlx jsr publish -``` - -### Local Testing Files - -- **`.actrc`**: Act configuration file -- **`.secrets`**: Local secrets for testing (not committed to git) -- **`scripts/test-workflows.sh`**: Helper script for testing workflows - -## 📚 Additional Resources - -- [Nx Release Documentation](https://nx.dev/nx-api/nx/documents/release) -- [Nx Affected Commands](https://nx.dev/nx-api/nx/documents/affected) -- [GitHub Actions Documentation](https://docs.github.com/en/actions) -- [NPM Publishing Guide](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry) -- [JSR Publishing Guide](https://jsr.io/docs/publishing) - -## 🎉 Benefits +## Recovery checklist -- ✅ **Automated**: No manual publishing required -- ✅ **Optimized**: Reuses build artifacts when possible -- ✅ **Efficient**: Only builds and tests affected projects -- ✅ **Reliable**: Comprehensive testing before release -- ✅ **Transparent**: Clear changelogs and release notes -- ✅ **Multi-platform**: Supports both NPM and JSR (Deno) -- ✅ **Scalable**: Easy to add new packages to release process -- ✅ **Separated**: Clear separation between CI and Release concerns -- ✅ **Fast**: Parallel execution and smart artifact reuse +- Confirm the workflow run is using the intended channel. +- Copy exact project names from the seven-project list above. +- For alpha or beta recovery, confirm the existing versions carry the matching prerelease suffix. +- For stable recovery, confirm every selected manifest version has no prerelease suffix. +- Use publish-only mode only to retry existing versions; use normal stable mode to graduate prereleases. +- Review the workflow summary and npm package pages after completion. diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6150e093..3992e9bf 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,80 +1,191 @@ -name: 🚀 CD +name: 🚀 Release Beta on: push: branches: [master] + workflow_dispatch: + inputs: + publish_only: + description: "Publish existing beta versions without versioning, changelog, or git changes" + required: true + type: boolean + default: false + projects: + description: "Comma-separated existing Nx release project names; required for publish-only recovery" + required: false + type: string -permissions: - contents: write # Needed for creating commits and tags - id-token: write # Needed for npm provenance authentication +concurrency: + group: release-beta + cancel-in-progress: false + +env: + DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/effectify" jobs: - release: - name: 🚀 Release + release-beta: + name: 🚀 Release Beta + if: ${{ github.event_name == 'workflow_dispatch' || (!contains(github.event.head_commit.message, 'chore(release):') && !contains(github.event.head_commit.message, '[skip release]')) }} runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.message, 'chore(release):') && !contains(github.event.head_commit.message, '[skip release]') }} + permissions: + contents: write + id-token: write + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: effectify + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - name: 📥 Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: - fetch-depth: 0 # Important for Nx to analyze git history - token: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} - name: 📦 Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: version: 10.14.0 - name: 🏗️ Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: 20 - registry-url: "https://registry.npmjs.org" + node-version: "24.19.0" cache: "pnpm" + registry-url: "https://registry.npmjs.org/" - name: 📦 Install dependencies run: pnpm install --frozen-lockfile - - name: ⚙️ Git Configuration + - name: 🛡️ Verify release policy contract + run: node --test scripts/release-policy-contract.test.mjs + + - name: 🔧 Configure Git + if: ${{ inputs.publish_only != true }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - name: 🔖 Version & Changelog - # Calculates versions, updates changelogs, creates commit and tag + - name: 🔍 Detect Affected Release Projects + id: affected env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PUBLISH_ONLY: ${{ inputs.publish_only || false }} + RECOVERY_PROJECTS: ${{ inputs.projects || '' }} + BEFORE_SHA: ${{ github.event.before }} + HEAD_SHA: ${{ github.sha }} run: | - # Ensure git user is configured - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + RELEASE_PROJECTS=$( + jq -r '.release.projects[]' nx.json | while read -r path; do + pnpm nx show project "$path" --json | jq -r '.name' + done | jq -Rsc 'split("\n") | map(select(length > 0)) | unique' + ) - # Run versioning (Nx modifies files locally) - pnpm nx release version --git-commit --git-tag + if [ "$PUBLISH_ONLY" = "true" ]; then + if [ -z "$RECOVERY_PROJECTS" ]; then + echo "publish-only recovery requires an explicit comma-separated projects input" >&2 + exit 1 + fi + SELECTED_PROJECTS=$(printf '%s' "$RECOVERY_PROJECTS" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed '/^$/d' | sort -u) + while IFS= read -r project; do + if ! printf '%s\n' "$RELEASE_PROJECTS" | jq -r '.[]' | grep -Fx -- "$project" >/dev/null; then + echo "Invalid release project: $project" >&2 + exit 1 + fi + done <<< "$SELECTED_PROJECTS" + echo "has_projects=true" >> "$GITHUB_OUTPUT" + echo "projects=$(printf '%s' "$SELECTED_PROJECTS" | paste -sd, -)" >> "$GITHUB_OUTPUT" + exit 0 + fi - # Manual Safety Net: Commit and Push - # 1. If Nx made a commit but didn't push -> We push. - # 2. If Nx modified files but didn't commit -> We commit and push. + ZERO_SHA="0000000000000000000000000000000000000000" + BEFORE="$BEFORE_SHA" + HEAD="$HEAD_SHA" + if ! git cat-file -e "${HEAD}^{commit}" 2>/dev/null; then + HEAD=$(git rev-parse HEAD) + fi + if [ -n "$BEFORE" ] && [ "$BEFORE" != "$ZERO_SHA" ] && git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then + BASE="$BEFORE" + elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then + BASE="HEAD^" + else + BASE="$HEAD" + fi - if [[ -n $(git status -s) ]]; then - echo "📝 Changes detected (Nx didn't commit). Committing manually..." - git add . - git commit -m "chore(release): publish [skip ci]" + AFFECTED_RAW=$(pnpm nx show projects --affected --base="$BASE" --head="$HEAD" --json 2>/dev/null || echo "[]") + AFFECTED_RELEASE_PROJECTS=$(echo "$AFFECTED_RAW" | jq -r --argjson release "$RELEASE_PROJECTS" '[.[] | select(. as $project | $release | index($project))] | unique | join(",")' 2>/dev/null || echo "") + if [ -z "$AFFECTED_RELEASE_PROJECTS" ] || [ "$AFFECTED_RELEASE_PROJECTS" = "null" ]; then + echo "has_projects=false" >> "$GITHUB_OUTPUT" + echo "projects=" >> "$GITHUB_OUTPUT" else - echo "✅ No uncommitted changes found (Nx likely committed)." + echo "has_projects=true" >> "$GITHUB_OUTPUT" + echo "projects=$AFFECTED_RELEASE_PROJECTS" >> "$GITHUB_OUTPUT" fi - echo "🚀 Pushing changes and tags to remote..." - git push origin HEAD --follow-tags + - name: 🏗️ Build Affected Projects + if: ${{ steps.affected.outputs.has_projects == 'true' }} + env: + PROJECTS: ${{ steps.affected.outputs.projects }} + run: pnpm nx run-many -t build "--projects=$PROJECTS" --parallel=3 - - name: 🏗️ Build - # Builds projects with the updated version before publishing - run: pnpm nx run-many -t build --all + - name: 🧪 Test Affected Projects + if: ${{ steps.affected.outputs.has_projects == 'true' }} + env: + PROJECTS: ${{ steps.affected.outputs.projects }} + run: pnpm nx run-many -t test "--projects=$PROJECTS" --parallel=3 --passWithNoTests - - name: 🚀 Publish + - name: ✅ Verify React Router 8 readiness + if: ${{ steps.affected.outputs.has_projects == 'true' }} + run: | + pnpm nx test @effectify/react-router + pnpm nx run @effectify/react-router-example:migration:test + pnpm nx run @effectify/react-router-example:migration:verify + pnpm nx run @effectify/react-router-example:migration:manifest + pnpm nx run @effectify/react-router-example:consolidation:verify + + - name: 🔐 Verify npm authentication + if: ${{ steps.affected.outputs.has_projects == 'true' }} + run: npm whoami + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: 🚀 Version, Changelog & Publish Beta + if: ${{ steps.affected.outputs.has_projects == 'true' }} env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + PROJECTS: ${{ steps.affected.outputs.projects }} + PUBLISH_ONLY: ${{ inputs.publish_only || false }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed for creating GitHub Releases - # Publishes packages to npm and creates GitHub Releases - run: pnpm nx release publish + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + if [ "$PUBLISH_ONLY" != "true" ]; then + pnpm nx release "--projects=$PROJECTS" --preid=beta --skip-publish + pnpm nx run-many -t build "--projects=$PROJECTS" --parallel=3 + fi + # Every beta publish is explicitly non-default, including recovery. + pnpm nx release publish "--projects=$PROJECTS" --tag=beta + + - name: 📊 Release Summary + if: always() + env: + HAS_PROJECTS: ${{ steps.affected.outputs.has_projects }} + PROJECTS: ${{ steps.affected.outputs.projects }} + PUBLISH_ONLY: ${{ inputs.publish_only || false }} + run: | + echo "## 🚀 Beta Release Summary" >> "$GITHUB_STEP_SUMMARY" + if [ "$HAS_PROJECTS" = "true" ]; then + echo "**Projects:** $PROJECTS" >> "$GITHUB_STEP_SUMMARY" + if [ "$PUBLISH_ONLY" = "true" ]; then + echo "**Mode:** publish-only recovery; selected existing manifests were built and published with the beta tag." >> "$GITHUB_STEP_SUMMARY" + fi + else + echo "⏭️ **Skipped - No affected projects**" >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ec0e667..b0c44f85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: 🧪 CI on: pull_request: - branches: [master, dev] + types: [opened, synchronize, reopened, ready_for_review] push: branches: [dev] @@ -15,115 +15,189 @@ env: NODE_OPTIONS: "--max-old-space-size=4096" jobs: + release-policy: + name: 🛡️ Release Policy Contract + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false + runs-on: ubuntu-latest + steps: + - name: 📥 Checkout + uses: actions/checkout@v5 + + - name: 🏗️ Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: "24.19.0" + package-manager-cache: false + + - name: 🛡️ Verify release policy contract + run: node --test scripts/release-policy-contract.test.mjs + # Lint and format check lint: name: 🔍 Lint & Format + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: 📥 Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: 📦 Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: version: 10.14.0 - name: 🏗️ Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: "20" + node-version: "24.19.0" cache: "pnpm" - name: 📦 Install dependencies run: pnpm install --frozen-lockfile - - name: 🔍 Run oxlint on affected projects + - name: 🎯 Resolve affected base + id: affected-base + shell: bash run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then BASE="origin/${{ github.base_ref }}" else - BASE="HEAD~1" + BEFORE="${{ github.event.before }}" + DEFAULT_BASE="origin/${{ github.event.repository.default_branch }}" + ZERO_SHA="0000000000000000000000000000000000000000" + if [[ -n "$BEFORE" && "$BEFORE" != "$ZERO_SHA" ]] && git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then + BASE="$BEFORE" + elif git cat-file -e "${DEFAULT_BASE}^{commit}" 2>/dev/null; then + BASE="$DEFAULT_BASE" + elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then + BASE="HEAD^" + else + BASE="HEAD" + fi fi - pnpm nx affected --target=lint --base=$BASE --head=HEAD --parallel=1 + echo "sha=$BASE" >> "$GITHUB_OUTPUT" + + - name: 🔍 Run oxlint on affected projects + run: pnpm nx affected --target=lint --base="${{ steps.affected-base.outputs.sha }}" --head=HEAD --parallel=1 - - name: 🎨 Check code formatting with dprint - run: pnpm exec dprint check || (echo "❌ Some files are not formatted. Run 'pnpm exec dprint fmt' to fix formatting." && exit 1) + - name: 🎨 Check changed files with oxfmt + run: pnpm nx run @effectify/repo:format:check --args="--base=${{ steps.affected-base.outputs.sha }} --head=HEAD" || (echo "❌ Some changed files are not formatted. Run 'pnpm format' to fix formatting." && exit 1) # Type checking typecheck: name: 🔍 Type Check + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: 📥 Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: 📦 Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: version: 10.14.0 - name: 🏗️ Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: "20" + node-version: "24.19.0" cache: "pnpm" - name: 📦 Install dependencies run: pnpm install --frozen-lockfile - - name: 🔍 Type check affected projects + - name: 🎯 Resolve affected base + id: affected-base + shell: bash run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then BASE="origin/${{ github.base_ref }}" else - BASE="HEAD~1" + BEFORE="${{ github.event.before }}" + DEFAULT_BASE="origin/${{ github.event.repository.default_branch }}" + ZERO_SHA="0000000000000000000000000000000000000000" + if [[ -n "$BEFORE" && "$BEFORE" != "$ZERO_SHA" ]] && git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then + BASE="$BEFORE" + elif git cat-file -e "${DEFAULT_BASE}^{commit}" 2>/dev/null; then + BASE="$DEFAULT_BASE" + elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then + BASE="HEAD^" + else + BASE="HEAD" + fi fi - pnpm nx affected --target=typecheck --base=$BASE --head=HEAD --parallel=1 --verbose + echo "sha=$BASE" >> "$GITHUB_OUTPUT" + + - name: 🔍 Type check affected projects + run: pnpm nx affected --target=typecheck --base="${{ steps.affected-base.outputs.sha }}" --head=HEAD --parallel=1 --verbose # Build affected projects build: name: 🏗️ Build + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ubuntu-latest outputs: has-build-artifacts: ${{ steps.build.outputs.has-artifacts }} steps: - name: 📥 Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: 📦 Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: version: 10.14.0 - name: 🏗️ Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: "20" + node-version: "24.19.0" cache: "pnpm" - name: 📦 Install dependencies run: pnpm install --frozen-lockfile + - name: 🎯 Resolve affected base + id: affected-base + shell: bash + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + BASE="origin/${{ github.base_ref }}" + else + BEFORE="${{ github.event.before }}" + DEFAULT_BASE="origin/${{ github.event.repository.default_branch }}" + ZERO_SHA="0000000000000000000000000000000000000000" + if [[ -n "$BEFORE" && "$BEFORE" != "$ZERO_SHA" ]] && git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then + BASE="$BEFORE" + elif git cat-file -e "${DEFAULT_BASE}^{commit}" 2>/dev/null; then + BASE="$DEFAULT_BASE" + elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then + BASE="HEAD^" + else + BASE="HEAD" + fi + fi + echo "sha=$BASE" >> "$GITHUB_OUTPUT" + - name: 🏗️ Build affected projects id: build run: | - BASE=$(git merge-base HEAD origin/dev) + BASE="${{ steps.affected-base.outputs.sha }}" # Build affected projects with caching - pnpm nx affected --target=build --base=$BASE --head=HEAD --parallel=1 --verbose + pnpm nx affected --target=build --base="$BASE" --head=HEAD --parallel=1 --verbose # Check if any projects were built - AFFECTED_PROJECTS=$(pnpm nx show projects --affected --base=$BASE --head=HEAD --json | jq -r '.[]' | tr '\n' ' ') + AFFECTED_PROJECTS=$(pnpm nx show projects --affected --base="$BASE" --head=HEAD --json | jq -r '.[]' | tr '\n' ' ') if [ -n "$AFFECTED_PROJECTS" ]; then echo "has-artifacts=true" >> $GITHUB_OUTPUT echo "📦 Built projects: $AFFECTED_PROJECTS" - + # Create build manifest for artifact upload echo "BUILT_PROJECTS=$AFFECTED_PROJECTS" >> $GITHUB_ENV else @@ -146,28 +220,51 @@ jobs: # Test affected projects test: name: 🧪 Test + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ubuntu-latest needs: [build] steps: - name: 📥 Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: 📦 Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: version: 10.14.0 - name: 🏗️ Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: "20" + node-version: "24.19.0" cache: "pnpm" - name: 📦 Install dependencies run: pnpm install --frozen-lockfile + - name: 🎯 Resolve affected base + id: affected-base + shell: bash + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + BASE="origin/${{ github.base_ref }}" + else + BEFORE="${{ github.event.before }}" + DEFAULT_BASE="origin/${{ github.event.repository.default_branch }}" + ZERO_SHA="0000000000000000000000000000000000000000" + if [[ -n "$BEFORE" && "$BEFORE" != "$ZERO_SHA" ]] && git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then + BASE="$BEFORE" + elif git cat-file -e "${DEFAULT_BASE}^{commit}" 2>/dev/null; then + BASE="$DEFAULT_BASE" + elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then + BASE="HEAD^" + else + BASE="HEAD" + fi + fi + echo "sha=$BASE" >> "$GITHUB_OUTPUT" + - name: 📦 Download build artifacts if: needs.build.outputs.has-build-artifacts == 'true' uses: actions/download-artifact@v4 @@ -175,22 +272,21 @@ jobs: name: build-artifacts-${{ github.sha }} - name: 🧪 Test affected projects - run: | - BASE=$(git merge-base HEAD origin/dev) - pnpm nx affected --target=test --base=$BASE --head=HEAD --parallel=1 --verbose --passWithNoTests + run: pnpm nx affected --target=test --base="${{ steps.affected-base.outputs.sha }}" --head=HEAD --parallel=1 --verbose --passWithNoTests # Summary job ci-summary: name: 📊 CI Summary runs-on: ubuntu-latest - needs: [lint, typecheck, build, test] - if: always() + needs: [release-policy, lint, typecheck, build, test] + if: always() && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) steps: - name: 📊 CI Summary run: | echo "## 🧪 CI Results Summary" >> $GITHUB_STEP_SUMMARY echo "| Job | Status | Details |" >> $GITHUB_STEP_SUMMARY echo "|-----|--------|---------|" >> $GITHUB_STEP_SUMMARY - echo "| 🔍 Lint & Format | ${{ needs.lint.result == 'success' && '✅ Success' || '❌ Failed' }} | Uses oxlint + dprint |" >> $GITHUB_STEP_SUMMARY + echo "| 🛡️ Release Policy | ${{ needs.release-policy.result == 'success' && '✅ Success' || '❌ Failed' }} | Enforces alpha, beta, and stable separation |" >> $GITHUB_STEP_SUMMARY + echo "| 🔍 Lint & Format | ${{ needs.lint.result == 'success' && '✅ Success' || '❌ Failed' }} | Uses oxlint + oxfmt |" >> $GITHUB_STEP_SUMMARY echo "| 🔍 Type Check | ${{ needs.typecheck.result == 'success' && '✅ Success' || '❌ Failed' }} | TypeScript compiler checks |" >> $GITHUB_STEP_SUMMARY echo "| 🏗️ Build | ${{ needs.build.result == 'success' && '✅ Success' || '❌ Failed' }} | Nx affected build |" >> $GITHUB_STEP_SUMMARY echo "| 🧪 Test | ${{ needs.test.result == 'success' && '✅ Success' || '❌ Failed' }} | Unit tests with Vitest |" >> $GITHUB_STEP_SUMMARY @@ -204,6 +300,6 @@ jobs: if [[ "${{ needs.lint.result }}" != "success" ]]; then echo "" >> $GITHUB_STEP_SUMMARY echo "### ❌ Linting Issues" >> $GITHUB_STEP_SUMMARY - echo "Please run 'pnpm exec dprint fmt' to fix formatting issues" >> $GITHUB_STEP_SUMMARY + echo "Please run 'pnpm format' to fix formatting issues" >> $GITHUB_STEP_SUMMARY fi # NEW: Add this step at the end of your job diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d2e11140..66c7bbff 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,19 +22,19 @@ jobs: runs-on: ubuntu-latest steps: - name: 📥 Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: 📦 Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: version: 10.14.0 - name: 🏗️ Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: "20" + node-version: "24.19.0" cache: "pnpm" - name: 📦 Install dependencies diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index c53ed9d2..f8e656ec 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -4,10 +4,20 @@ on: push: branches: [dev] workflow_dispatch: + inputs: + publish_only: + description: "Publish existing alpha versions without versioning, changelog, or git changes" + required: true + type: boolean + default: false + projects: + description: "Comma-separated existing Nx release project names; required for publish-only recovery" + required: false + type: string concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: release-alpha + cancel-in-progress: false env: DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/effectify" @@ -34,185 +44,139 @@ jobs: --health-timeout 5s --health-retries 5 steps: - # Setup - name: 📥 Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: - fetch-depth: 0 # Full history needed for nx affected + fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: 📦 Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: version: 10.14.0 - name: 🏗️ Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: "20" + node-version: "24.19.0" cache: "pnpm" registry-url: "https://registry.npmjs.org/" - name: 📦 Install dependencies run: pnpm install --frozen-lockfile + - name: 🛡️ Verify release policy contract + run: node --test scripts/release-policy-contract.test.mjs + - name: 🔧 Configure Git + if: ${{ inputs.publish_only != true }} run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" - # Detect affected projects (exit early if none) - name: 🔍 Detect Affected Release Projects id: affected + env: + PUBLISH_ONLY: ${{ inputs.publish_only || false }} + RECOVERY_PROJECTS: ${{ inputs.projects || '' }} + BEFORE_SHA: ${{ github.event.before }} + HEAD_SHA: ${{ github.sha }} run: | - # Get all release projects from nx.json (these are paths like "packages/solid/query") - RELEASE_PATHS=$(jq -r '.release.projects[]' nx.json | sort | uniq) - echo "📋 Release project paths:" - echo "$RELEASE_PATHS" - echo "" - - # Convert paths to project names - RELEASE_PROJECTS="" - for path in $RELEASE_PATHS; do - PROJECT_NAME=$(pnpm nx show project "$path" --json 2>/dev/null | jq -r '.name' 2>/dev/null) - if [ -n "$PROJECT_NAME" ] && [ "$PROJECT_NAME" != "null" ]; then - RELEASE_PROJECTS="$RELEASE_PROJECTS $PROJECT_NAME" + RELEASE_PROJECTS=$( + jq -r '.release.projects[]' nx.json | while read -r path; do + pnpm nx show project "$path" --json | jq -r '.name' + done | jq -Rsc 'split("\n") | map(select(length > 0)) | unique' + ) + + if [ "$PUBLISH_ONLY" = "true" ]; then + if [ -z "$RECOVERY_PROJECTS" ]; then + echo "publish-only recovery requires an explicit comma-separated projects input" >&2 + exit 1 fi - done - RELEASE_PROJECTS=$(echo "$RELEASE_PROJECTS" | tr ' ' '\n' | sort | uniq) - echo "📋 Release project names:" - echo "$RELEASE_PROJECTS" - echo "" - - # Get affected projects since last push to dev - AFFECTED_RAW=$(pnpm nx show projects --affected --base=origin/dev~1 --head=HEAD --json 2>/dev/null || echo "[]") - echo "🔍 All affected projects:" - echo "$AFFECTED_RAW" | jq -r '.[]' 2>/dev/null || echo "(none)" - echo "" + SELECTED_PROJECTS=$(printf '%s' "$RECOVERY_PROJECTS" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed '/^$/d' | sort -u) + while IFS= read -r project; do + if ! printf '%s\n' "$RELEASE_PROJECTS" | jq -r '.[]' | grep -Fx -- "$project" >/dev/null; then + echo "Invalid release project: $project" >&2 + exit 1 + fi + done <<< "$SELECTED_PROJECTS" + AFFECTED_RELEASE_PROJECTS=$(printf '%s' "$SELECTED_PROJECTS" | paste -sd, -) + echo "has_projects=true" >> "$GITHUB_OUTPUT" + echo "projects=$AFFECTED_RELEASE_PROJECTS" >> "$GITHUB_OUTPUT" + exit 0 + fi - # Filter affected projects that are in release list - AFFECTED_RELEASE_PROJECTS=$(echo "$AFFECTED_RAW" | jq -r --arg release "$RELEASE_PROJECTS" '[.[] | select(. as $p | $release | contains($p))] | join(",")' 2>/dev/null || echo "") + ZERO_SHA="0000000000000000000000000000000000000000" + BEFORE="$BEFORE_SHA" + HEAD="$HEAD_SHA" + if ! git cat-file -e "${HEAD}^{commit}" 2>/dev/null; then + HEAD=$(git rev-parse HEAD) + fi + if [ -n "$BEFORE" ] && [ "$BEFORE" != "$ZERO_SHA" ] && git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then + BASE="$BEFORE" + elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then + BASE="HEAD^" + else + BASE="$HEAD" + fi + AFFECTED_RAW=$(pnpm nx show projects --affected --base="$BASE" --head="$HEAD" --json 2>/dev/null || echo "[]") + AFFECTED_RELEASE_PROJECTS=$(echo "$AFFECTED_RAW" | jq -r --argjson release "$RELEASE_PROJECTS" '[.[] | select(. as $project | $release | index($project))] | unique | join(",")' 2>/dev/null || echo "") if [ -z "$AFFECTED_RELEASE_PROJECTS" ] || [ "$AFFECTED_RELEASE_PROJECTS" = "null" ]; then - echo "⚠️ No affected release projects found - skipping release" - echo "has_projects=false" >> $GITHUB_OUTPUT - echo "projects=" >> $GITHUB_OUTPUT - exit 0 + echo "has_projects=false" >> "$GITHUB_OUTPUT" + echo "projects=" >> "$GITHUB_OUTPUT" else - echo "✅ Affected release projects: $AFFECTED_RELEASE_PROJECTS" - echo "has_projects=true" >> $GITHUB_OUTPUT - echo "projects=$AFFECTED_RELEASE_PROJECTS" >> $GITHUB_OUTPUT + echo "has_projects=true" >> "$GITHUB_OUTPUT" + echo "projects=$AFFECTED_RELEASE_PROJECTS" >> "$GITHUB_OUTPUT" fi - # Build affected projects (skipped if no affected projects) - name: 🏗️ Build Affected Projects if: ${{ steps.affected.outputs.has_projects == 'true' }} - run: | - echo "Building: ${{ steps.affected.outputs.projects }}" - pnpm nx run-many -t build --projects=${{ steps.affected.outputs.projects }} --parallel=3 + env: + PROJECTS: ${{ steps.affected.outputs.projects }} + run: pnpm nx run-many -t build "--projects=$PROJECTS" --parallel=3 - name: 🧪 Test Affected Projects if: ${{ steps.affected.outputs.has_projects == 'true' }} - run: | - echo "Testing: ${{ steps.affected.outputs.projects }}" - pnpm nx run-many -t test --projects=${{ steps.affected.outputs.projects }} --parallel=3 --passWithNoTests + env: + PROJECTS: ${{ steps.affected.outputs.projects }} + run: pnpm nx run-many -t test "--projects=$PROJECTS" --parallel=3 --passWithNoTests - # Release: version bump + publish (skipped if no affected projects) - - name: 🚀 Version & Publish Alpha + - name: 🔐 Verify npm authentication if: ${{ steps.affected.outputs.has_projects == 'true' }} - run: | - PROJECTS="${{ steps.affected.outputs.projects }}" - echo "Releasing: $PROJECTS" - - # Version bump with preid=alpha → creates versions like 0.0.5-alpha.0 - pnpm nx release version --projects=$PROJECTS --preid=alpha - - # Rebuild with new versions (version bump updates package.json) - pnpm nx run-many -t build --projects=$PROJECTS --parallel=3 - - # Publish to npm with alpha tag - pnpm nx release publish --projects=$PROJECTS --tag=alpha - - # Generate changelog, create GitHub Releases and tags - # For independent versioning (multiple projects with different versions), - # iterate over each project and pass its specific version - for PROJECT in $(echo "$PROJECTS" | tr ',' '\n'); do - # Get project root path and version from package.json - PROJ_PATH=$(pnpm nx show project "$PROJECT" --json | jq -r '.root') - VERSION=$(cat "$PROJ_PATH/package.json" | jq -r '.version') - - echo "Generating changelog for $PROJECT@$VERSION" - pnpm nx release changelog --projects=$PROJECT --version=$VERSION --git-tag --git-push - done - - # Generate workspace-level CHANGELOG.md aggregating all package changelogs - # nx release changelog already creates packages/*/CHANGELOG.md and GitHub releases - { - echo "# Changelog" - echo "" - echo "All notable changes to this project will be documented in this file." - echo "" - echo "This changelog summarizes releases for the following packages:" - echo "" - for PROJECT in $(echo "$PROJECTS" | tr ',' '\n'); do - echo "- @$PROJECT" - done - echo "" - } > CHANGELOG.md - - for PROJECT in $(echo "$PROJECTS" | tr ',' '\n'); do - # Extract package name without scope (e.g., @effectify/react-query -> react-query) - PKG_NAME=$(echo "$PROJECT" | sed 's/^@effectify\///') - CHANGELOG_PATH="packages/$PKG_NAME/CHANGELOG.md" - - if [ -f "$CHANGELOG_PATH" ]; then - # Extract first version entry from package changelog - FIRST_ENTRY=$(awk '/^## [0-9]/ && !first {first=1; print} first && /^## [0-9]/ {exit}' "$CHANGELOG_PATH" 2>/dev/null || echo "") - if [ -n "$FIRST_ENTRY" ]; then - echo "## @$PROJECT" >> CHANGELOG.md - echo "" >> CHANGELOG.md - echo "$FIRST_ENTRY" >> CHANGELOG.md - echo "" >> CHANGELOG.md - fi - fi - done - - # Commit and push workspace changelog - if [ -s CHANGELOG.md ] && [ "$(wc -l < CHANGELOG.md)" -gt 10 ]; then - git add CHANGELOG.md - git commit -m "chore: update workspace changelog" || echo "No changes to workspace changelog" - git push origin HEAD - fi + run: npm whoami + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - # Push git tags and version commits - git push origin HEAD --follow-tags + - name: 🚀 Version, Changelog & Publish Alpha + if: ${{ steps.affected.outputs.has_projects == 'true' }} env: + PROJECTS: ${{ steps.affected.outputs.projects }} + PUBLISH_ONLY: ${{ inputs.publish_only || false }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_CONFIG_PROVENANCE: true + run: | + if [ "$PUBLISH_ONLY" != "true" ]; then + pnpm nx release "--projects=$PROJECTS" --preid=alpha --skip-publish + pnpm nx run-many -t build "--projects=$PROJECTS" --parallel=3 + fi + # Publish-only mode builds selected existing manifests and performs no git mutation. + pnpm nx release publish "--projects=$PROJECTS" --tag=alpha - # Summary - name: 📊 Release Summary if: always() + env: + HAS_PROJECTS: ${{ steps.affected.outputs.has_projects }} + PROJECTS: ${{ steps.affected.outputs.projects }} + PUBLISH_ONLY: ${{ inputs.publish_only || false }} run: | - echo "## 🚀 Alpha Release Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${{ steps.affected.outputs.has_projects }}" = "true" ]; then - echo "✅ **Release Completed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Projects:** ${{ steps.affected.outputs.projects }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY - echo "|------|--------|" >> $GITHUB_STEP_SUMMARY - echo "| Detect Affected | ✅ |" >> $GITHUB_STEP_SUMMARY - echo "| Build | ✅ |" >> $GITHUB_STEP_SUMMARY - echo "| Version Bump | ✅ With preid=alpha |" >> $GITHUB_STEP_SUMMARY - echo "| NPM Tag | alpha |" >> $GITHUB_STEP_SUMMARY - echo "| Changelog | ✅ Package + Workspace |" >> $GITHUB_STEP_SUMMARY - echo "| GitHub Release | ✅ Created |" >> $GITHUB_STEP_SUMMARY - echo "| Provenance | ✅ Enabled |" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "📦 Packages published with \"@alpha\" dist-tag and semver prerelease versions (x.x.x-alpha.x)." >> $GITHUB_STEP_SUMMARY + echo "## 🚀 Alpha Release Summary" >> "$GITHUB_STEP_SUMMARY" + if [ "$HAS_PROJECTS" = "true" ]; then + echo "**Projects:** $PROJECTS" >> "$GITHUB_STEP_SUMMARY" + if [ "$PUBLISH_ONLY" = "true" ]; then + echo "**Mode:** publish-only recovery; selected existing manifests were built and published with the alpha tag." >> "$GITHUB_STEP_SUMMARY" + fi else - echo "⏭️ **Skipped - No affected projects**" >> $GITHUB_STEP_SUMMARY + echo "⏭️ **Skipped - No affected projects**" >> "$GITHUB_STEP_SUMMARY" fi diff --git a/.github/workflows/release-stable.yml b/.github/workflows/release-stable.yml new file mode 100644 index 00000000..d40a3b14 --- /dev/null +++ b/.github/workflows/release-stable.yml @@ -0,0 +1,184 @@ +name: 🚀 Release Stable + +on: + workflow_dispatch: + inputs: + projects: + description: "Comma-separated Nx release project names to graduate or recover" + required: true + type: string + publish_only: + description: "Publish selected existing stable versions without version, tag, changelog, or git mutation" + required: true + type: boolean + default: false + +concurrency: + group: release-stable + cancel-in-progress: false + +env: + DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/effectify" + +jobs: + release-stable: + name: 🚀 Release Stable + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: effectify + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - name: 📥 Checkout current master + uses: actions/checkout@v5 + with: + ref: master + fetch-depth: 0 + token: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} + + - name: 🔒 Confirm current master + run: | + git fetch origin master --no-tags + test "$(git rev-parse HEAD)" = "$(git rev-parse origin/master)" || { + echo "Stable release checkout is not current origin/master" >&2 + exit 1 + } + + - name: 📦 Install pnpm + uses: pnpm/action-setup@v6 + with: + version: 10.14.0 + + - name: 🏗️ Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: "24.19.0" + cache: "pnpm" + registry-url: "https://registry.npmjs.org/" + + - name: 📦 Install dependencies + run: pnpm install --frozen-lockfile + + - name: 🛡️ Verify release policy contract + run: node --test scripts/release-policy-contract.test.mjs + + - name: 🔍 Validate Explicit Stable Projects + id: selected + env: + REQUESTED_PROJECTS: ${{ inputs.projects }} + PUBLISH_ONLY: ${{ inputs.publish_only }} + run: | + if [ -z "$REQUESTED_PROJECTS" ]; then + echo "Stable release requires explicit selected projects" >&2 + exit 1 + fi + + RELEASE_PROJECTS=$(jq -r '.release.projects[]' nx.json | while read -r path; do + pnpm nx show project "$path" --json | jq -r '.name' + done | sort -u) + SELECTED_PROJECTS=$(printf '%s' "$REQUESTED_PROJECTS" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed '/^$/d' | sort -u) + + if [ -z "$SELECTED_PROJECTS" ]; then + echo "Stable release requires at least one selected project" >&2 + exit 1 + fi + + while IFS= read -r project; do + if ! printf '%s\n' "$RELEASE_PROJECTS" | grep -Fx -- "$project" >/dev/null; then + echo "Invalid release project: $project" >&2 + exit 1 + fi + + PROJECT_ROOT=$(pnpm nx show project "$project" --json | jq -r '.root') + VERSION=$(jq -r '.version // empty' "$PROJECT_ROOT/package.json") + if [ -z "$VERSION" ]; then + echo "Release project has no manifest version: $project" >&2 + exit 1 + fi + + if [ "$PUBLISH_ONLY" = "true" ]; then + # publish-only recovery requires explicit selected existing stable projects. + if [[ "$VERSION" == *-* ]]; then + echo "Publish-only stable recovery rejects prerelease version $project@$VERSION" >&2 + exit 1 + fi + elif [[ "$VERSION" != *-* ]]; then + echo "Normal stable release only graduates selected prereleases: $project@$VERSION" >&2 + exit 1 + fi + done <<< "$SELECTED_PROJECTS" + + echo "projects=$(printf '%s' "$SELECTED_PROJECTS" | paste -sd, -)" >> "$GITHUB_OUTPUT" + + - name: 🔧 Configure Git for graduation + if: ${{ inputs.publish_only != true }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: 🔐 Verify npm authentication + run: npm whoami + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: 🏗️ Build Selected Projects + env: + PROJECTS: ${{ steps.selected.outputs.projects }} + run: pnpm nx run-many -t build "--projects=$PROJECTS" --parallel=3 + + - name: 🧪 Test Selected Projects + env: + PROJECTS: ${{ steps.selected.outputs.projects }} + run: pnpm nx run-many -t test "--projects=$PROJECTS" --parallel=3 --passWithNoTests + + - name: ✅ Verify React Router 8 readiness + run: | + pnpm nx test @effectify/react-router + pnpm nx run @effectify/react-router-example:migration:test + pnpm nx run @effectify/react-router-example:migration:verify + pnpm nx run @effectify/react-router-example:migration:manifest + pnpm nx run @effectify/react-router-example:consolidation:verify + + - name: 🔖 Graduate Selected Prereleases + if: ${{ inputs.publish_only != true }} + env: + PROJECTS: ${{ steps.selected.outputs.projects }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Relative patch removes the prerelease suffix without selecting unrequested projects. + run: pnpm nx release patch "--projects=$PROJECTS" --skip-publish + + - name: 🚀 Publish Stable + env: + PROJECTS: ${{ steps.selected.outputs.projects }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_CONFIG_PROVENANCE: true + # No prerelease dist-tag is supplied: npm's default stable tag is intentional. + run: pnpm nx release publish "--projects=$PROJECTS" + + - name: 📊 Release Summary + if: always() + env: + PROJECTS: ${{ steps.selected.outputs.projects }} + PUBLISH_ONLY: ${{ inputs.publish_only }} + run: | + echo "## 🚀 Stable Release Summary" >> "$GITHUB_STEP_SUMMARY" + echo "**Projects:** $PROJECTS" >> "$GITHUB_STEP_SUMMARY" + if [ "$PUBLISH_ONLY" = "true" ]; then + echo "**Mode:** publish-only recovery of selected existing stable versions; no version, tag, changelog, or git mutation was requested." >> "$GITHUB_STEP_SUMMARY" + else + echo "**Mode:** selected prereleases graduated with Nx relative patch." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..60ade1ae --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24.19.0 diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 00000000..8b6c08c1 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,36 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "semi": false, + "singleQuote": false, + "jsxSingleQuote": false, + "trailingComma": "all", + "arrowParens": "always", + "endOfLine": "lf", + "proseWrap": "preserve", + "embeddedLanguageFormatting": "off", + "sortImports": false, + "sortPackageJson": false, + "ignorePatterns": [ + "**/dist/**", + "**/build/**", + "**/.nx/**", + "**/node_modules/**", + "**/coverage/**", + "**/.verdaccio/**", + "**/.vscode/**", + "**/.trae/**", + "**/.husky/**", + "**/public/**", + "**/storybook-static/**", + "**/migrations.json", + "**/pnpm-lock.yaml", + "**/tsconfig.tsbuildinfo", + "**/sqlite.db", + "**/docker-compose.yml", + "**/routeTree.gen.ts", + "**/*.{less,vue,svelte,astro,yaml,yml,toml,graphql,gql,eta}" + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b8ab8c6d..2c2eb6d9 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,7 +2,6 @@ "recommendations": [ "nrwl.angular-console", "effectful-tech.effect-vscode", - "oxc.oxc-vscode", - "dprint.dprint" + "oxc.oxc-vscode" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 17160faa..a6c475a3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,16 +4,16 @@ "PATH": "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:${env:PATH}" }, "[javascript]": { - "editor.defaultFormatter": "dprint.dprint" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "[typescript]": { - "editor.defaultFormatter": "dprint.dprint" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "[json]": { - "editor.defaultFormatter": "dprint.dprint" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "[jsonc]": { - "editor.defaultFormatter": "dprint.dprint" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "editor.codeActionsOnSave": { "quickfix.oxc": "explicit", @@ -21,13 +21,10 @@ "source.fixAll.oxc": "explicit" }, "[javascriptreact]": { - "editor.defaultFormatter": "dprint.dprint" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "[typescriptreact]": { - "editor.defaultFormatter": "dprint.dprint" - }, - "[css]": { - "editor.defaultFormatter": "dprint.dprint" + "editor.defaultFormatter": "oxc.oxc-vscode" }, "editor.formatOnSave": true, "files.exclude": { @@ -56,12 +53,12 @@ "files.readonlyInclude": { "**/routeTree.gen.ts": true }, - "editor.defaultFormatter": "dprint.dprint", - "[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": { - "editor.defaultFormatter": "dprint.dprint" + "editor.defaultFormatter": "oxc.oxc-vscode", + "[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][markdown][mdx]": { + "editor.defaultFormatter": "oxc.oxc-vscode" }, "editor.formatOnPaste": true, "emmet.showExpandedAbbreviation": "never", - "typescript.native-preview.tsdk": "node_modules/@typescript/native-preview", + "typescript.native-preview.tsdk": "node_modules/@typescript/native", "js/ts.experimental.useTsgo": true -} \ No newline at end of file +} diff --git a/AGENTS.md b/AGENTS.md index 2ffeb067..7a1fbb54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,14 +24,16 @@ ## Effect-TS Pattern Discovery -This project uses effect-smol as the canonical source for Effect patterns. +This project uses the `main` branch of [Effect-TS/effect](https://github.com/Effect-TS/effect.git) as the canonical Effect v4 source. Effect v3 is maintained on the `v3` branch of that same repository. -| Skill | Description | Location | -| -------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------- | -| `effect-context-manager` | Setup & sync Effect v4 reference | [.agent/skills/effect-context-manager](.agent/skills/effect-context-manager/SKILL.md) | -| `effect-pattern-discovery` | Effect-TS patterns from effect-smol | [.agent/skills/effect-pattern-discovery](.agent/skills/effect-pattern-discovery/SKILL.md) | +| Skill | Description | Location | +| -------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------- | +| `effect-context-manager` | Setup & sync local reference clones | [.agent/skills/effect-context-manager](.agent/skills/effect-context-manager/SKILL.md) | +| `effect-pattern-discovery` | Effect-TS patterns from Effect v4 source | [.agent/skills/effect-pattern-discovery](.agent/skills/effect-pattern-discovery/SKILL.md) | -**Reference Directory**: `.effect-reference/` contains the effect-smol source code mounted as a git worktree. +**Effect Reference**: `.effect-reference/effect/` is an ignored, standalone, depth-1 clone of `https://github.com/Effect-TS/effect.git` on `main`. + +**Alchemy Reference**: `.effect-reference/alchemy/` is an ignored, standalone, depth-1 clone of [`alchemy-run/alchemy`](https://github.com/alchemy-run/alchemy.git) on `main`, and is the canonical Effect-based Alchemy next/alpha reference. [`alchemy-run/alchemy-async`](https://github.com/alchemy-run/alchemy-async) is the former async implementation. ## Hatchet + Effect Conventions diff --git a/CHANGELOG.md b/CHANGELOG.md index 69df628c..9c4d6f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,19 +4,74 @@ All notable changes to this project will be documented in this file. This changelog summarizes releases for the following packages: -- @@effectify/prisma -- @@effectify/react-query -- @@effectify/react-router-better-auth -- @@effectify/node-better-auth -- @@effectify/react-router -- @@effectify/react-remix -- @@effectify/solid-query -- @@effectify/hatchet +- @effectify/prisma +- @effectify/react-query +- @effectify/react-router-better-auth +- @effectify/node-better-auth +- @effectify/react-router +- @effectify/solid-query +- @effectify/hatchet -## @@effectify/prisma +## @effectify/hatchet + +## 0.1.0-alpha.5 (2026-07-12) + +## @effectify/node-better-auth + +## 0.5.12-alpha.1 (2026-07-12) + +## @effectify/prisma + +## 1.1.13-alpha.1 (2026-07-12) + +## @effectify/react-query + +## 1.0.0-alpha.7 (2026-07-12) + +## @effectify/react-router + +## 0.5.11-alpha.1 (2026-07-12) + +## @effectify/react-router-better-auth + +## 0.5.12-alpha.1 (2026-07-12) + +## @effectify/solid-query + +## 0.5.12-alpha.1 (2026-07-12) + +## @effectify/hatchet + +## 0.1.0-alpha.4 (2026-07-11) + +## @effectify/node-better-auth + +## 0.5.12-alpha.0 (2026-07-11) + +## @effectify/prisma + +## 1.1.13-alpha.0 (2026-07-11) + +## @effectify/react-query + +## 1.0.0-alpha.6 (2026-07-11) + +## @effectify/react-router + +## 0.5.11-alpha.0 (2026-07-11) + +## @effectify/react-router-better-auth + +## 0.5.12-alpha.0 (2026-07-11) + +## @effectify/solid-query + +## 0.5.12-alpha.0 (2026-07-11) + +## @effectify/prisma ## 2.0.0-alpha.3 (2026-04-27) -## @@effectify/hatchet +## @effectify/hatchet ## 0.1.0-alpha.3 (2026-04-27) diff --git a/README.md b/README.md index 827ae3df..1c22a615 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,58 @@ # Effectify -[](https://www.npmjs.com/search?q=%40effectify) +[](https://www.npmjs.com/search?q=%40effectify) [](https://devx-op.github.io/effectify/) -Monorepo of utilities for integrating [Effect](https://effect.website/) with different frameworks and libraries. +Effectify provides Effect integrations for React, Solid, authentication, Prisma, and Hatchet. -> **🚀 Effect v4 Alpha Support**: We are currently migrating packages to support Effect v4 beta. Alpha versions are available on npm with the `@alpha` tag. +> **Effect v4 RC:** The current workspace targets the Effect v4 release candidate (`effect@4.0.0-rc.111`). Prerelease packages are published on explicit npm tags and never replace the stable default by accident. + +## Choose a release channel + +| Channel | Trigger | npm tag | Use it for | +| ------- | ---------------------- | ------------------ | --------------------------------------- | +| Alpha | Push to `dev` | `alpha` | Earliest integration builds | +| Beta | Push to `master` | `beta` | Master-qualified prereleases | +| Stable | Manual stable workflow | default (`latest`) | Explicitly selected production releases | + +Install an explicit channel; do not rely on npm's default tag for prereleases. ## Packages -| Package | Version | Documentation | Description | -| -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------- | -| [@effectify/solid-query](https://www.npmjs.com/package/@effectify/solid-query) | [](https://www.npmjs.com/package/@effectify/solid-query) | [Docs](./packages/solid/query/README.md) | Integration of Effect with TanStack Query for Solid.js | -| [@effectify/react-query](https://www.npmjs.com/package/@effectify/react-query) | [](https://www.npmjs.com/package/@effectify/react-query) | [Docs](./packages/react/query/README.md) | Integration of Effect with TanStack Query for React | -| [@effectify/react-router](https://www.npmjs.com/package/@effectify/react-router) | [](https://www.npmjs.com/package/@effectify/react-router) | [Docs](./packages/react/router/README.md) | Integration of React Router with Effect for React applications | -| [@effectify/react-remix](https://www.npmjs.com/package/@effectify/react-remix) | [](https://www.npmjs.com/package/@effectify/react-remix) | [Docs](./packages/react/remix/README.md) | Integration of Remix with Effect for React applications | -| [@effectify/node-better-auth](https://www.npmjs.com/package/@effectify/node-better-auth) | [](https://www.npmjs.com/package/@effectify/node-better-auth) | [Docs](./packages/node/better-auth/README.md) | Integration of better-auth with Effect for Node.js applications | -| [@effectify/react-router-better-auth](https://www.npmjs.com/package/@effectify/react-router-better-auth) | [](https://www.npmjs.com/package/@effectify/react-router-better-auth) | [Docs](./packages/react/router-better-auth/) | Integration of React Router + better-auth with Effect for React applications | -| [@effectify/prisma](https://www.npmjs.com/package/@effectify/prisma) | [](https://www.npmjs.com/package/@effectify/prisma) | [Docs](./packages/prisma/README.md) | Prisma generator and runtime utilities for Effect | -| [@effectify/solid-effect-atom](https://www.npmjs.com/package/@effectify/solid-effect-atom) | [](https://www.npmjs.com/package/@effectify/solid-effect-atom) | [Docs](./packages/solid/effect-atom/README.md) | Reactive toolkit for Effect with SolidJS | +| Package | Documentation | Scope | +| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------ | +| [`@effectify/react-router`](https://www.npmjs.com/package/@effectify/react-router) | [Docs](./packages/react/router/README.md) | Maintained React Router 8 integration | +| [`@effectify/react-query`](https://www.npmjs.com/package/@effectify/react-query) | [Docs](./packages/react/query/README.md) | TanStack Query integration for React | +| [`@effectify/node-better-auth`](https://www.npmjs.com/package/@effectify/node-better-auth) | [Docs](./packages/node/better-auth/README.md) | better-auth integration for Node.js | +| [`@effectify/solid-query`](https://www.npmjs.com/package/@effectify/solid-query) | [Docs](./packages/solid/query/README.md) | TanStack Query integration for Solid | +| [`@effectify/react-router-better-auth`](https://www.npmjs.com/package/@effectify/react-router-better-auth) | [Usage reference](./packages/react/router-better-auth/tests/auth-guard.test.ts) | React Router 8 and better-auth integration | +| [`@effectify/prisma`](https://www.npmjs.com/package/@effectify/prisma) | [Docs](./packages/prisma/README.md) | Prisma generator and runtime utilities | +| [`@effectify/hatchet`](https://www.npmjs.com/package/@effectify/hatchet) | [Package](./packages/hatchet/) | Hatchet workflow integration | -## Alpha Installation (Effect v4) +The supported router surface is React Router 8 only. The Solid example uses Effect v4's `Atom` and `AtomRef` modules with the official [`@effect/atom-solid`](https://www.npmjs.com/package/@effect/atom-solid) bindings. -We are actively migrating packages to support Effect v4 beta. You can install alpha versions using the `@alpha` npm tag: +## Install alpha packages + +Every Nx release package is available through the explicit alpha channel when an alpha version has been published: ```bash -# npm -npm install @effectify/react-query@alpha -npm install @effectify/solid-query@alpha npm install @effectify/react-router@alpha +npm install @effectify/react-query@alpha npm install @effectify/node-better-auth@alpha -npm install @effectify/react-remix@alpha +npm install @effectify/solid-query@alpha +npm install @effectify/react-router-better-auth@alpha npm install @effectify/prisma@alpha - -# pnpm -pnpm add @effectify/react-query@alpha - -# yarn -yarn add @effectify/react-query@alpha +npm install @effectify/hatchet@alpha ``` -### Migration Status - -| Package | v4 Alpha Status | Stable Version | -| --------------------------- | --------------- | -------------- | -| @effectify/react-query | 🚧 In Progress | ✅ v3 | -| @effectify/solid-query | 🚧 In Progress | ✅ v3 | -| @effectify/react-router | 🚧 In Progress | ✅ v3 | -| @effectify/node-better-auth | 🚧 In Progress | ✅ v3 | -| @effectify/react-remix | 🚧 In Progress | ✅ v3 | -| @effectify/prisma | 🚧 In Progress | ✅ v3 | - -**Legend**: ✅ Available | 🚧 Migrating | ⏳ Pending - -### Effect v3 vs v4 - -- **Stable releases** (v3.x) continue to work with Effect v3.19.x -- **Alpha releases** (v4.x) require Effect v4 beta -- Both versions maintain the same API where possible - -For migration details, see the [Effect v4 Migration Guide](https://effect.website/docs/migration/v4). +Use the same package names with `pnpm add` or `yarn add` if those are your package managers. Alpha and beta releases require the current Effect v4 RC. Stable compatibility is documented by each package release. ## Development ### Requirements -- [pnpm](https://pnpm.io/) -- [Node.js](https://nodejs.org/) +- Node.js 24.19.0 +- pnpm 10.14.0 ### Commands @@ -74,23 +60,27 @@ For migration details, see the [Effect v4 Migration Guide](https://effect.websit # Install dependencies pnpm install -# Run example application -pnpm nx dev tanstack-solid-app +# Run the maintained Solid example +pnpm nx dev @effectify/solid-example -# Build all packages +# Build affected packages pnpm nx affected -t build -# Clean project -pnpm clean -``` +# Check or apply pinned formatting to changed files +pnpm format:check +pnpm format -### Release Management +# Verify React Router 8 consolidation and readiness +pnpm nx run @effectify/react-router-example:consolidation:verify +pnpm nx run @effectify/react-router-example:migration:manifest +pnpm nx run @effectify/react-router-example:migration:verify +``` -To skip a release for documentation updates or other non-release changes, include `[skip release]` in your commit message. +See [`.github/SETUP.md`](./.github/SETUP.md) for exact CI triggers, release behavior, and stable recovery. ## Credits & Inspiration -This project was inspired by the excellent educational content from [Lucas Barake](https://www.youtube.com/@lucas-barake), particularly his [video on Effect and TanStack Query](https://www.youtube.com/watch?v=zl4w3BQAoJM&t=1011s) which provides great insights into these technologies. +This project was inspired by the educational content from [Lucas Barake](https://www.youtube.com/@lucas-barake), particularly his [Effect and TanStack Query video](https://www.youtube.com/watch?v=zl4w3BQAoJM&t=1011s). ## License diff --git a/apps/docs/astro.config.ts b/apps/docs/astro.config.ts index efc45298..18c62f84 100644 --- a/apps/docs/astro.config.ts +++ b/apps/docs/astro.config.ts @@ -30,9 +30,7 @@ export default defineConfig({ }, { label: "Reference", - autogenerate: { - directory: "react/reference", - }, + items: [{ autogenerate: { directory: "react/reference" } }], }, ], }, @@ -52,9 +50,7 @@ export default defineConfig({ }, { label: "Reference", - autogenerate: { - directory: "solid/reference", - }, + items: [{ autogenerate: { directory: "solid/reference" } }], }, ], }, @@ -74,9 +70,7 @@ export default defineConfig({ }, { label: "Reference", - autogenerate: { - directory: "backend/reference", - }, + items: [{ autogenerate: { directory: "backend/reference" } }], }, ], }, @@ -96,9 +90,7 @@ export default defineConfig({ }, { label: "Reference", - autogenerate: { - directory: "universal/reference", - }, + items: [{ autogenerate: { directory: "universal/reference" } }], }, ], }, diff --git a/apps/docs/package.json b/apps/docs/package.json index 9709ecb4..3162cb90 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -20,7 +20,9 @@ "starlight-sidebar-topics": "catalog:", "starlight-sidebar-topics-dropdown": "catalog:" }, - "devDependencies": {}, + "devDependencies": { + "typescript": "catalog:" + }, "peerDependencies": {}, "optionalDependencies": {} } diff --git a/apps/docs/src/content/docs/es/solid/packages/solid-effect-atom.md b/apps/docs/src/content/docs/es/solid/packages/solid-effect-atom.md index 4d1efade..ec3c1ec5 100644 --- a/apps/docs/src/content/docs/es/solid/packages/solid-effect-atom.md +++ b/apps/docs/src/content/docs/es/solid/packages/solid-effect-atom.md @@ -1,25 +1,25 @@ --- -title: "@effectify/solid-effect-atom" -description: Herramientas reactivas para Effect con SolidJS +title: Atom de Effect v4 con SolidJS +description: Usa Atom y AtomRef de Effect v4 con los bindings oficiales para SolidJS sidebar: - label: "@effectify/solid-effect-atom" + label: Effect Atom para SolidJS order: 1 --- -Bindings de SolidJS para la primitiva `Atom` de Effect. Esta librería permite utilizar el estado reactivo de Effect (`Atom`) dentro de componentes SolidJS de manera eficiente y segura. +El paquete oficial `@effect/atom-solid` conecta los módulos principales `Atom` y `AtomRef` de Effect v4 con SolidJS. Proporciona accessors reactivos, setters, suscripciones y alcance de registros sin un paquete adaptador de Effectify. ## Instalación ```bash -npm install @effectify/solid-effect-atom @effect-atom/atom effect solid-js +npm install effect @effect/atom-solid solid-js ``` ## Configuración -Para usar los átomos, debes envolver tu aplicación (o la parte que los use) con `RegistryProvider`. Esto provee el contexto necesario para el registro de átomos. +Usa `RegistryProvider` cuando el estado de los átomos deba limitarse a un subárbol de Solid. Sin un provider, los hooks usan el registro independiente predeterminado. ```tsx -import { RegistryProvider } from "@effectify/solid-effect-atom" +import { RegistryProvider } from "@effect/atom-solid" function App() { return ( @@ -30,42 +30,42 @@ function App() { } ``` -## Uso Básico +## Uso básico -### Crear un Átomo +### Crear un átomo -Utiliza `Atom.make` del paquete `@effect-atom/atom`. +Importa `Atom` desde los módulos de reactividad de Effect v4. ```ts -import * as Atom from "@effect-atom/atom/Atom" +import * as Atom from "effect/unstable/reactivity/Atom" const counterAtom = Atom.make(0) ``` ### useAtom -Hook para leer y escribir un átomo. Similar a `createSignal` de Solid. +`useAtom` lee y escribe un átomo, de forma similar a `createSignal` de Solid. Los hooks reciben una función para respetar el modelo de propiedad reactiva de Solid. ```tsx -import { useAtom } from "@effectify/solid-effect-atom" +import { useAtom } from "@effect/atom-solid" function Counter() { - const [count, setCount] = useAtom(counterAtom) + const [count, setCount] = useAtom(() => counterAtom) - return + return } ``` ### useAtomValue -Hook para solo leer el valor de un átomo. Puedes pasar una función selectora para transformar el valor (computado). +Usa `useAtomValue` cuando un componente solo necesite un accessor reactivo. Un selector opcional permite derivar un valor. ```tsx -import { useAtomValue } from "@effectify/solid-effect-atom" +import { useAtomValue } from "@effect/atom-solid" function Display() { - const count = useAtomValue(counterAtom) - const doubled = useAtomValue(counterAtom, (n) => n * 2) + const count = useAtomValue(() => counterAtom) + const doubled = useAtomValue(() => counterAtom, (value) => value * 2) return (