diff --git a/.codex/environments/environment.toml b/.codex/environments/environment.toml
index 0949a0a..a7367df 100644
--- a/.codex/environments/environment.toml
+++ b/.codex/environments/environment.toml
@@ -4,8 +4,6 @@ name = "react"
[setup]
script = '''
-mise trust -y
-eval "$(mise env -s bash)"
-mise install
-pnpm i --frozen-lockfile
+vp env install
+vp install --frozen-lockfile
'''
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index c25997b..a050833 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -10,6 +10,6 @@
-- [ ] `pnpm -s cicheck`
+- [ ] `vp run cicheck`
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 24676ab..89ccf81 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,11 +11,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- - uses: pnpm/action-setup@v6
- - uses: actions/setup-node@v6
+ - uses: voidzero-dev/setup-vp@cab1ab9eaff52d4857875f4f2c016e36af8f2c76 # v1.22.0
with:
- node-version: 24
- cache: 'pnpm'
+ node-version-file: .node-version
+ cache: true
+ run-install: false
- name: Validate PR title (Conventional Commits)
if: github.event_name == 'pull_request'
@@ -34,5 +34,5 @@ jobs:
}
NODE
- - run: pnpm install
- - run: pnpm -s cicheck
+ - run: vp install --frozen-lockfile
+ - run: vp run cicheck
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index df3daec..415b229 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -13,36 +13,34 @@ jobs:
concurrency: publish-${{ github.ref }}
steps:
- uses: actions/checkout@v6
- - uses: actions/setup-node@v6
+ - uses: voidzero-dev/setup-vp@cab1ab9eaff52d4857875f4f2c016e36af8f2c76 # v1.22.0
with:
- node-version: 24
- - uses: pnpm/action-setup@v6
- - run: pnpm install
- - run: pnpm -s cicheck
- - name: Ensure npm CLI supports trusted publishing
- run: |
- npm i -g npm@11.5.1
- npm --version
-
+ node-version-file: .node-version
+ cache: true
+ run-install: false
+ - run: vp install --frozen-lockfile
+ - run: vp run cicheck
- name: Detect already-published version
id: npmcheck
run: |
set -euo pipefail
NAME=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
- echo "name=$NAME" >> "$GITHUB_OUTPUT"
- echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if npm view "$NAME@$VERSION" version >/dev/null 2>&1; then
echo "published=true" >> "$GITHUB_OUTPUT"
else
echo "published=false" >> "$GITHUB_OUTPUT"
fi
+ - name: Pack publishable tarball (resolve pnpm catalogs)
+ if: steps.npmcheck.outputs.published != 'true'
+ run: pnpm pack --out "$RUNNER_TEMP/sigrea-package.tgz"
+
- name: npm publish (OIDC trusted publishing)
if: steps.npmcheck.outputs.published != 'true'
- run: npm publish --access public
+ run: npm publish "$RUNNER_TEMP/sigrea-package.tgz" --access public
- name: Sync GitHub Release notes via changelogen
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: pnpm exec changelogen gh release ${{ github.ref_name }} --token "${GITHUB_TOKEN}"
+ run: vp exec changelogen gh release ${{ github.ref_name }} --token "${GITHUB_TOKEN}"
diff --git a/.gitignore b/.gitignore
index 6761199..75e7eb9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,6 @@ node_modules
dist
coverage
.DS_Store
+.vitest/
+
+.vite-hooks/_
diff --git a/.node-version b/.node-version
new file mode 100644
index 0000000..1dda03d
--- /dev/null
+++ b/.node-version
@@ -0,0 +1 @@
+26.13.0
diff --git a/.vite-hooks/pre-commit b/.vite-hooks/pre-commit
new file mode 100755
index 0000000..0ef6019
--- /dev/null
+++ b/.vite-hooks/pre-commit
@@ -0,0 +1,4 @@
+#!/bin/sh
+set -e
+vp staged
+vp test run
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 94cef68..07f8702 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,17 +4,24 @@ Thank you for contributing! This project uses a PR-based workflow with required
## Requirements
-Node.js >= 24 and pnpm >= 10.
-TypeScript strict mode, Biome for formatting, Vitest for tests.
+Install [Vite+](https://viteplus.dev/guide/). Node.js and pnpm versions are pinned
+in `.node-version` and `package.json`.
-## Scripts
+```sh
+vp env install
+vp install --frozen-lockfile
+```
-`pnpm typecheck` — run TypeScript checks
-`pnpm test` — run unit tests
-`pnpm build` — build the library via unbuild
-`pnpm format` — check formatting (no writes)
-`pnpm format:fix` — apply formatting
-`pnpm -s cicheck` — run test, typecheck, build, smoke, and format checks
+## Commands
+
+- `vp check` — format, lint, and TypeScript checks
+- `vp test run` — unit tests
+- `vp test run --coverage` — coverage
+- `vp pack` — ESM/CJS bundles and declarations
+- `vp fmt` — format source files
+- `vp run cicheck` — checks, tests, packaging, and smoke checks
+
+The pre-commit hook fixes staged files and runs the test suite.
## Commit Convention
@@ -28,15 +35,15 @@ Changelogen reads Conventional Commits directly, so please keep commit messages
## Pull Requests
Ensure the following before requesting review:
-`pnpm -s cicheck` passes and the PR title follows Conventional Commits.
+`vp run cicheck` passes and the PR title follows Conventional Commits.
## Release Workflow
This repository uses [changelogen](https://github.com/unjs/changelogen) to update `CHANGELOG.md` and create the release commit plus tag. Releases use an explicit version so maintainers do not depend on an inferred bump.
-1. Ensure `main` is up to date and clean. Run `mise run notes` if you want to preview the generated notes without touching the tree.
-2. Run `SIGREA_RELEASE_VERSION=x.y.z mise run release_version`. This runs `pnpm -s cicheck`, updates the changelog, amends the release commit if formatting changes are needed, and creates the annotated `vX.Y.Z` tag.
-3. Push the commit and tag together with `mise run push_release`. The task uses `git push origin main --follow-tags`.
-4. Tag pushes trigger `.github/workflows/publish.yml`. The job runs on the `release` environment, installs dependencies, runs `pnpm -s cicheck`, publishes to npm with OIDC trusted publishing, and then syncs the GitHub Release body with `pnpm exec changelogen gh release`.
+1. Ensure `main` is up to date and clean. Run `vp run notes` if you want to preview the generated notes without touching the tree.
+2. Run `SIGREA_RELEASE_VERSION=x.y.z vp run release_version`. This runs `vp run cicheck`, updates the changelog, amends the release commit if formatting changes are needed, and creates the annotated `vX.Y.Z` tag.
+3. Push the commit and tag together with `vp run push_release`. The task uses `git push origin main --follow-tags`.
+4. Tag pushes run `.github/workflows/publish.yml` in the `release` environment and publish to npm through OIDC.
If the publish workflow fails, fix the root cause and re-run the job from the GitHub Actions UI. Avoid creating a new tag unless you intend to cut a new release. If you must roll back, delete the tag locally and remotely, revert the release commit, and start over from step 1.
diff --git a/README.md b/README.md
index fb5138e..559a304 100644
--- a/README.md
+++ b/README.md
@@ -351,20 +351,18 @@ createRoot(document.getElementById("root")!).render();
## Development
-This repo targets Node.js 24 or later.
+Install [Vite+](https://viteplus.dev/guide/), then run:
-- `pnpm install` installs dependencies.
-- `pnpm test` runs the Vitest suite once.
-- `pnpm typecheck` runs TypeScript checks.
-- `pnpm test:coverage` collects coverage.
-- `pnpm build` builds CJS and ESM bundles with unbuild.
-- `pnpm -s cicheck` runs the local CI chain.
-- `pnpm dev` launches the playground counter demo.
+```sh
+vp env install
+vp install --frozen-lockfile
+vp run cicheck
+```
-If you use mise, run `mise trust -y` once before using mise tasks.
+Run `vp run dev` to start the playground.
See [CONTRIBUTING.md](https://github.com/sigrea/react/blob/main/CONTRIBUTING.md)
-for workflow details.
+for individual commands and the release workflow.
## License
diff --git a/biome.json b/biome.json
deleted file mode 100644
index fd154e8..0000000
--- a/biome.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
- "linter": {
- "enabled": true,
- "rules": {
- "suspicious": {
- "noConfusingVoidType": "off"
- }
- }
- },
- "formatter": { "enabled": true },
- "files": {
- "ignore": ["dist/**"]
- }
-}
diff --git a/build.config.ts b/build.config.ts
deleted file mode 100644
index dd613ee..0000000
--- a/build.config.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { defineBuildConfig } from "unbuild";
-
-const clientDirective = '"use client";';
-
-export default defineBuildConfig({
- entries: ["index"],
- clean: true,
- declaration: true,
- rollup: {
- emitCJS: true,
- output: {
- banner: clientDirective,
- },
- },
-});
diff --git a/changelog.config.ts b/changelog.config.ts
index 8564cf3..a4c53b1 100644
--- a/changelog.config.ts
+++ b/changelog.config.ts
@@ -1,6 +1,6 @@
import type { ChangelogConfig } from "changelogen";
-const config: ChangelogConfig = {
+const config: Partial = {
repo: "sigrea/react",
output: "CHANGELOG.md",
types: {
diff --git a/lefthook.yml b/lefthook.yml
deleted file mode 100644
index b4df625..0000000
--- a/lefthook.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-pre-commit:
- parallel: false
- commands:
- biome:
- run: pnpm exec biome check --write {staged_files}
- stage_fixed: true
- glob: "*.{ts,tsx,js,jsx,json}"
- test:
- run: pnpm test
diff --git a/mise.toml b/mise.toml
deleted file mode 100644
index 00ff26d..0000000
--- a/mise.toml
+++ /dev/null
@@ -1,31 +0,0 @@
-[tools]
-node = "24"
-pnpm = "10.0.0"
-
-[tasks.notes]
-description = "Preview changelog notes (no file changes)"
-run = "pnpm exec changelogen --no-output"
-
-[tasks.release_version]
-description = "Cut release commit + annotated tag for SIGREA_RELEASE_VERSION"
-run = '''
-test "$(git rev-parse --abbrev-ref HEAD)" = "main"
-test -n "${SIGREA_RELEASE_VERSION:-}" || { echo "SIGREA_RELEASE_VERSION is required, for example SIGREA_RELEASE_VERSION=0.7.1 mise run release_version." >&2; exit 1; }
-test -z "$(git status --porcelain)" || { echo "Git status must be clean before release." >&2; git status --short; exit 1; }
-pnpm -s cicheck
-test -z "$(git status --porcelain)" || { echo "cicheck changed files." >&2; git status --short; exit 1; }
-pnpm exec changelogen --clean --release -r "$SIGREA_RELEASE_VERSION"
-pnpm format:fix
-if ! git diff --quiet; then
- git add -A
- git commit --amend --no-edit
- git tag -fa "v$SIGREA_RELEASE_VERSION" -m "v$SIGREA_RELEASE_VERSION"
-fi
-test "$(git cat-file -t "v$SIGREA_RELEASE_VERSION")" = "tag"
-test -z "$(git status --porcelain)" || { echo "Release changed files unexpectedly." >&2; git status --short; exit 1; }
-'''
-
-[tasks.push_release]
-description = "Push main + tags (triggers publish.yml)"
-confirm = "Push main and tags to origin?"
-run = 'test "$(git rev-parse --abbrev-ref HEAD)" = "main" && git push origin main --follow-tags'
diff --git a/package.json b/package.json
index 35e7630..22d0eed 100644
--- a/package.json
+++ b/package.json
@@ -2,73 +2,79 @@
"name": "@sigrea/react",
"version": "0.7.1",
"description": "React hooks for Sigrea molecules and signals.",
- "license": "MIT",
- "type": "module",
- "packageManager": "pnpm@10.0.0",
- "publishConfig": {
- "access": "public"
- },
- "repository": {
- "type": "git",
- "url": "git+https://github.com/sigrea/react.git"
- },
+ "keywords": [
+ "molecule",
+ "react",
+ "reactivity",
+ "signals",
+ "typescript"
+ ],
"homepage": "https://github.com/sigrea/react#readme",
"bugs": {
"url": "https://github.com/sigrea/react/issues"
},
- "engines": {
- "node": ">=24"
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/sigrea/react.git"
},
+ "files": [
+ "dist"
+ ],
+ "type": "module",
"sideEffects": false,
+ "main": "./dist/index.cjs",
+ "types": "./dist/index.d.ts",
"exports": {
".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.mjs",
- "require": "./dist/index.cjs"
+ "import": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.mjs"
+ },
+ "require": {
+ "types": "./dist/index.d.cts",
+ "default": "./dist/index.cjs"
+ }
}
},
- "main": "./dist/index.cjs",
- "types": "./dist/index.d.ts",
- "files": ["dist"],
- "keywords": ["signals", "reactivity", "react", "molecule", "typescript"],
+ "publishConfig": {
+ "access": "public"
+ },
"scripts": {
- "dev": "vite --config playground/vite.config.ts",
- "build": "unbuild",
- "prepack": "unbuild",
+ "dev": "vp dev --config playground/vite.config.ts",
+ "prepack": "vp pack",
"smoke": "node --input-type=module -e \"const mod = await import('@sigrea/react'); for (const key of ['useMolecule', 'useSignal']) { if (typeof mod[key] !== 'function') throw new Error(key + ' export is missing'); }\" && node -e \"const mod = require('@sigrea/react'); for (const key of ['useMolecule', 'useSignal']) { if (typeof mod[key] !== 'function') throw new Error(key + ' export is missing'); }\"",
- "changelog": "changelogen",
- "test": "vitest run",
- "test:coverage": "vitest --coverage",
- "typecheck": "tsc -p tsconfig.json --noEmit",
- "format": "biome check .",
- "format:fix": "biome check --write .",
- "cicheck": "pnpm -s test && pnpm -s typecheck && pnpm -s build && pnpm -s smoke && pnpm -s format"
- },
- "peerDependencies": {
- "@sigrea/core": "^0.7.0",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
+ "cicheck": "vp check && vp test run && vp pack && vp run smoke",
+ "prepare": "vp config --no-agent",
+ "playground:build": "vp build --config playground/vite.config.ts",
+ "notes": "vp exec changelogen --no-output",
+ "release_version": "sh scripts/release-version.sh",
+ "push_release": "sh scripts/push-release.sh"
},
"devDependencies": {
- "@biomejs/biome": "1.9.4",
+ "@arethetypeswrong/core": "^0.18.5",
"@sigrea/core": "^0.7.0",
+ "@types/node": "^24.19.2",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
- "@vitejs/plugin-react": "^4.3.3",
- "@vitest/coverage-v8": "^3.2.4",
- "baseline-browser-mapping": "^2.10.30",
+ "@vitejs/plugin-react": "6.1.0",
+ "@vitest/coverage-v8": "catalog:",
"changelogen": "^0.6.2",
"jsdom": "^24.1.3",
- "lefthook": "1.13.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "tsx": "^4.20.5",
"typescript": "5.9.3",
- "unbuild": "3.6.1",
- "vite": "^5.4.6",
- "vitest": "^3.2.4"
+ "vite": "catalog:",
+ "vite-plus": "catalog:",
+ "vitest": "catalog:"
+ },
+ "peerDependencies": {
+ "@sigrea/core": "^0.7.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "engines": {
+ "node": ">=24"
},
- "pnpm": {
- "onlyBuiltDependencies": ["lefthook", "@biomejs/biome"]
- }
+ "packageManager": "pnpm@12.16.1"
}
diff --git a/packages/__tests__/useComputed.test.ts b/packages/__tests__/useComputed.test.ts
index 02b922c..f487595 100644
--- a/packages/__tests__/useComputed.test.ts
+++ b/packages/__tests__/useComputed.test.ts
@@ -1,5 +1,5 @@
import { act, createElement } from "react";
-import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test";
import { computed, signal } from "@sigrea/core";
diff --git a/packages/__tests__/useDeepSignal.test.ts b/packages/__tests__/useDeepSignal.test.ts
index bf923a1..7c8a866 100644
--- a/packages/__tests__/useDeepSignal.test.ts
+++ b/packages/__tests__/useDeepSignal.test.ts
@@ -1,5 +1,5 @@
import { act, createElement } from "react";
-import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test";
import { deepSignal } from "@sigrea/core";
diff --git a/packages/__tests__/useMolecule.mount-lifecycle.test.ts b/packages/__tests__/useMolecule.mount-lifecycle.test.ts
index 79d5099..5f2738b 100644
--- a/packages/__tests__/useMolecule.mount-lifecycle.test.ts
+++ b/packages/__tests__/useMolecule.mount-lifecycle.test.ts
@@ -1,5 +1,12 @@
import { createElement } from "react";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import {
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ vi,
+} from "vite-plus/test";
import {
type MoleculeInstance,
diff --git a/packages/__tests__/useMolecule.ssr.test.tsx b/packages/__tests__/useMolecule.ssr.test.tsx
index 0e4917b..dcccc5e 100644
--- a/packages/__tests__/useMolecule.ssr.test.tsx
+++ b/packages/__tests__/useMolecule.ssr.test.tsx
@@ -1,6 +1,5 @@
// @vitest-environment node
-// @ts-expect-error This node-only test imports a built-in module without node types in the package tsconfig.
import { PassThrough } from "node:stream";
import { Suspense, createElement, use } from "react";
import {
@@ -8,7 +7,7 @@ import {
renderToReadableStream,
renderToString,
} from "react-dom/server";
-import { describe, expect, it, vi } from "vitest";
+import { describe, expect, it, vi } from "vite-plus/test";
import {
molecule,
diff --git a/packages/__tests__/useMolecule.strict-mode.test.ts b/packages/__tests__/useMolecule.strict-mode.test.ts
index 48bbe27..a06029f 100644
--- a/packages/__tests__/useMolecule.strict-mode.test.ts
+++ b/packages/__tests__/useMolecule.strict-mode.test.ts
@@ -1,5 +1,12 @@
import { StrictMode, createElement } from "react";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import {
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ vi,
+} from "vite-plus/test";
import {
computed,
diff --git a/packages/__tests__/useMolecule.test.ts b/packages/__tests__/useMolecule.test.ts
index 5fd1f4d..22ab46f 100644
--- a/packages/__tests__/useMolecule.test.ts
+++ b/packages/__tests__/useMolecule.test.ts
@@ -1,5 +1,12 @@
import { createElement } from "react";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import {
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ vi,
+} from "vite-plus/test";
import {
type MoleculeInstance,
diff --git a/packages/__tests__/useSignal.test.ts b/packages/__tests__/useSignal.test.ts
index bde06c0..e322bdc 100644
--- a/packages/__tests__/useSignal.test.ts
+++ b/packages/__tests__/useSignal.test.ts
@@ -1,5 +1,5 @@
import { act, createElement } from "react";
-import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test";
import { computed, readonly, signal } from "@sigrea/core";
diff --git a/packages/__tests__/useSnapshot.test.ts b/packages/__tests__/useSnapshot.test.ts
index 44d4cfc..6d0a9e5 100644
--- a/packages/__tests__/useSnapshot.test.ts
+++ b/packages/__tests__/useSnapshot.test.ts
@@ -1,5 +1,12 @@
import { act, createElement } from "react";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import {
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ vi,
+} from "vite-plus/test";
import type { SnapshotHandler } from "@sigrea/core";
import { createSignalHandler, signal } from "@sigrea/core";
diff --git a/playground/index.html b/playground/index.html
index 8e4e76c..b4816a7 100644
--- a/playground/index.html
+++ b/playground/index.html
@@ -10,4 +10,3 @@