diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d06a058..a7e50106 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,10 +21,15 @@ jobs: run: | TAG=${GITHUB_REF#refs/tags/v} PKG=$(node -p "require('./package.json').version") + PY=$(grep -m1 '^version' refactron-py/pyproject.toml | sed -E 's/version\s*=\s*"([^"]+)"/\1/') if [ "$TAG" != "$PKG" ]; then echo "Tag $TAG does not match package.json version $PKG" exit 1 fi + if [ "$TAG" != "$PY" ]; then + echo "Tag $TAG does not match refactron-py/pyproject.toml version $PY" + exit 1 + fi echo "version=$TAG" >> $GITHUB_OUTPUT test-before-release: @@ -65,9 +70,32 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + publish-pypi: + name: Publish to PyPI + needs: test-before-release + runs-on: ubuntu-latest + environment: + name: pypi-production + url: https://pypi.org/p/refactron + permissions: + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Build wrapper + working-directory: refactron-py + run: | + python -m pip install --upgrade build + python -m build + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: refactron-py/dist + github-release: name: Create GitHub Release - needs: publish-npm + needs: [publish-npm, publish-pypi] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index f929509d..92bb1262 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,8 @@ __pycache__/ *.pyc .pytest_cache/ bench/*-loc/ + +# Python wrapper build artifacts (refactron-py) +refactron-py/dist/ +refactron-py/build/ +refactron-py/**/*.egg-info/ diff --git a/.npmignore b/.npmignore index 154906a4..3c076de9 100644 --- a/.npmignore +++ b/.npmignore @@ -21,6 +21,7 @@ vitest.config* coverage/ .nyc_output/ node_modules/ +tape/ # Env & secrets .env diff --git a/CHANGELOG.md b/CHANGELOG.md index c63ed057..78a8e9f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,51 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [0.2.0] — 2026-05-15 + +First public release of the v2.0 deterministic-refactoring rebuild. + +### Added +- **Engine** — 10 deterministic AST transforms (5 Python via LibCST, 5 TypeScript via ts-morph) with cross-file preconditions +- **3-gate verification** — syntax + imports + tests on a shadow tree, atomic batch write or rollback (PRs #7, #8, #11, #13, #15) +- **Documentation engine** — Step 4 of the pipeline; the only LLM-touching component, runs only on already-verified diffs (PR #15) +- **5 LLM providers** for `document`: Ollama (local default — for the trust-conscious), Groq (BYOK fast), OpenAI, Anthropic, Backend (managed via api.refactron.dev for Pro users) (PR #22) +- **CLI output redesign** — by-file findings with code excerpts (analyze), per-file unified diffs (run --dry-run), gate-by-gate progress + structured failure surface (run --apply) (PR #19) +- **Authentication** — OAuth device flow with `REFACTRON_TOKEN` env var support; long-lived API keys for stay-logged-in semantics (PRs #13, #25) +- **`.refactronrc.json` config** — cosmiconfig + ajv schema validation; `transforms`, `exclude`, `testCmd`, `confidence`, `dryRun`, `documentation` (PRs #13, #20, #22) +- **Performance** — per-file parallelization in plan step, 3× speedup on python-legacy-mini (PR #23) +- **Mintlify documentation site** with full transform catalog, safety model diagram, FAQ, citations +- **Reproducible perf bench** infrastructure at `bench/` + +### Changed +- REPL output history rendered via Ink `` to eliminate whole-screen flicker (PR #24) +- Spinner reduced from 80ms tick + per-char shimmer to 250ms tick + single brand color (~50 → ~4 ANSI escapes/sec) (PR #24) +- `RefactronRc.documentation.provider` defaults to `'backend'` so authenticated Pro users get LLM docs out-of-the-box (PR #22) + +### Fixed +- REPL `clear` now wipes the terminal viewport, not just React state (PR #24) +- REPL `document` defaults to the active session's analyze target (was reading stale `last-apply.json` from cwd) (PR #22) +- `RefactronRc.exclude` field is now wired into discovery (was dead code since Week 5) (PR #20) +- Run `--apply ` parser stops eating the path argument (PR #17) +- REPL `document` output no longer vanishes into Ink's render buffer (PR #18) +- Verify failure surface no longer drops vitest's FAIL section (was front-slicing 4000 chars) (PR #19) +- Findings rendered in source order within each file, not detector-emission order (PR #20) +- CHANGELOG written next to the changed files' project marker, not to cwd (PR #22) +- Cross-platform: Windows path separators normalized in formatters; Node 18 execa.timedOut wall-clock derivation (PR #19, #20) + +### Documentation +- SECURITY.md with disclosure policy + threat model +- 30-second demo GIF in README +- 13 docs site pages (Safety Model, 10 transform pages, CLI Reference, Configuration, FAQ, Why No LLM) +- ADRs 1–10 covering every weekly architecture decision + +### Honest limitations +- No Ruby / Go / Rust adapters in 0.2; multi-language is post-launch +- Documentation engine requires a reachable LLM provider (graceful skip otherwise) +- Self-analysis on Refactron's own repo fails the test gate by design (mutating fixtures breaks meta-tests) — see `docs/known-limitations` + +--- + ## [0.1.0-beta.2] — 2026-04-05 ### Added diff --git a/README.md b/README.md index ba474a85..6299bba9 100644 --- a/README.md +++ b/README.md @@ -1,287 +1,110 @@ # Refactron -> **Alpha — do not use in production.** -> Refactron is mid-rebuild on the v2.0 architecture. The npm package on the registry is the older blast-radius engine; the `main` branch is the in-flight v2.0 surface. The golden end-to-end test is intentionally red until the Week 4 refactoring engine lands. - -> Safety-first refactoring — finds, fixes, and verifies code changes are safe before touching the filesystem. +Refactron finds legacy patterns in your Python and TypeScript code, refactors them deterministically, and proves nothing broke before writing a single byte. [![npm version](https://img.shields.io/npm/v/refactron)](https://www.npmjs.com/package/refactron) -[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) +[![npm downloads](https://img.shields.io/npm/dm/refactron)](https://www.npmjs.com/package/refactron) [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen)](https://nodejs.org) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) +[![CI](https://github.com/Refactron-ai/Refactron_Lib_TS/actions/workflows/ci.yml/badge.svg)](https://github.com/Refactron-ai/Refactron_Lib_TS/actions/workflows/ci.yml) ---- - -## What is Refactron? - -Refactron is a TypeScript CLI that analyzes your codebase for issues, automatically fixes what it can, and **verifies every change is safe before writing a single byte to disk**. - -The core differentiator: **Blast Radius**. Every issue carries a mandatory impact score (0–100) computed from transitive import and call graphs. The verification engine scales its strictness based on this score — a one-liner fix in an isolated utility gets a quick syntax check, while a change to a widely-imported core module triggers syntax + import + full test suite verification. - -``` -refactron analyze src/ -refactron autofix . --verify -``` - ---- - -## Features - -- **Blast Radius Scoring** — every issue gets a 0–100 impact score across 5 levels (trivial → critical), computed from transitive import and call graphs -- **Verification Gate** — changes are verified (syntax, imports, tests) before being written; strictness scales with blast radius -- **Atomic Writes** — all file writes use temp-file-then-rename; partial writes never happen -- **Backup & Rollback** — every session is backed up; `refactron rollback` restores the previous state -- **14 Auto-fixers** — unused imports, trailing whitespace, dead code, missing type hints, sort imports, and more -- **7 Analyzers** — security (SQL injection, eval, hardcoded secrets), complexity, code smell, dead code, type hints, dependencies, performance -- **Python + TypeScript** — language-agnostic engine with pluggable `ILanguageAdapter` -- **Ink Terminal UI** — interactive issue browser with blast radius graph, diff view, and verification progress +![Refactron in action](docs/assets/demo.gif) --- -## Installation +## Install + first refactor ```bash -npm install -g refactron +npm install -g refactron@0.2.0 +cd your-project && refactron login +refactron analyze . +refactron run --apply ``` -Requires Node.js 18+ and (for Python analysis) Python 3.8+. +Or via PyPI: `pip install refactron` (Python wrapper around the npm package). --- -## Quick Start - -```bash -# Analyze your project -refactron analyze src/ - -# Preview fixes without writing anything -refactron autofix . --dry-run - -# Fix with full verification gate -refactron autofix . --verify - -# Check session status -refactron status - -# Undo last applied fixes -refactron rollback +## The 3-gate safety model + +```mermaid +flowchart LR + Plan[RefactorPlan] --> G1{Gate 1
Syntax} + G1 -- pass --> G2{Gate 2
Imports} + G2 -- pass --> G3{Gate 3
Tests} + G3 -- pass --> AW[Atomic batch write] + G1 -- fail --> Reject[✗ Reject — your tree untouched] + G2 -- fail --> Reject + G3 -- fail --> Reject ``` ---- - -## Commands - -| Command | Description | -|---|---| -| `analyze [target]` | Scan files and report issues with blast radius scores | -| `autofix [target]` | Fix issues with verification before writing | -| `verify [file]` | Verify a specific file is safe to modify | -| `status` | Show current session state and fix counts | -| `rollback` | Restore files from last session backup | -| `diff [target]` | Display unified diff for a pending fix | - -### Options - -| Flag | Description | -|---|---| -| `--fail-on ` | Exit non-zero if issues at `critical`, `high`, `medium`, or `low` found | -| `--dry-run` | Preview fixes without writing to disk | -| `--verify` | Require verification gate before applying any fix | -| `--format ` | Output format: `terminal` (default), `json`, `sarif` | +Every refactor passes three gates before any byte is written: (1) the new content re-parses cleanly; (2) all imports resolve; (3) your project's full test suite passes on a shadow tree. Atomic write or rollback — never partial state. See [`docs/concepts/safety-model.mdx`](docs/concepts/safety-model.mdx) for the full breakdown. --- -## How Blast Radius Works +## Transform catalog -Every `CodeIssue` carries a mandatory `BlastRadius` object: - -```typescript -interface BlastRadius { - affectedFiles: string[]; // files transitively depending on the changed file - affectedFunctions: string[]; // functions in the call chain - affectedTestFiles: string[]; // test files that cover affected code - score: number; // 0–100 weighted impact score - level: BlastLevel; // trivial | low | medium | high | critical -} -``` - -**Score formula:** `files (40%) + functions (40%) + test coverage gap (20%)` - -**Verification escalation by blast level:** - -| Level | Score | Checks Run | +| Transform | Language | Description | |---|---|---| -| trivial | 0 | syntax only | -| low | 1–19 | syntax + imports + tests (45s timeout) | -| medium | 20–49 | syntax + imports + tests (45s timeout) | -| high | 50–74 | syntax + imports + tests (45s timeout) | -| critical | 75–100 | syntax + imports + tests (120s timeout) | +| [`callback_to_async_await`](docs/transforms/callback-to-async-await.mdx) | Python | Convert trailing-callback functions into async functions that return the result | +| [`format_to_fstring`](docs/transforms/format-to-fstring.mdx) | Python | Convert old-style `%`-formatting and `.format()` calls into f-strings | +| [`class_to_dataclass`](docs/transforms/class-to-dataclass.mdx) | Python | Promote pure data-holder classes (trivial `__init__`) to `@dataclass` | +| [`manual_typecheck_to_hints`](docs/transforms/manual-typecheck-to-hints.mdx) | Python | Promote `isinstance`-chain dispatch into a `Union[...]` annotation on the parameter | +| [`deprecated_api_requests_to_httpx`](docs/transforms/deprecated-api-requests-to-httpx.mdx) | Python | Migrate the `requests` library to the modern `httpx` equivalent | +| [`promise_chains_to_async`](docs/transforms/promise-chains-to-async.mdx) | TypeScript | Convert `.then()` chains into async/await with named bindings per stage | +| [`promise_constructor_to_async`](docs/transforms/promise-constructor-to-async.mdx) | TypeScript | Replace `new Promise((resolve) => resolve(value))` with an async function returning the value | +| [`var_to_const_let`](docs/transforms/var-to-const-let.mdx) | TypeScript | Replace `var` declarations with `const` (or `let` if reassigned) per binding | +| [`commonjs_to_esm`](docs/transforms/commonjs-to-esm.mdx) | TypeScript | Migrate CommonJS `require` / `module.exports` to ES module `import` / `export` | +| [`implicit_any`](docs/transforms/implicit-any.mdx) | TypeScript | Annotate untyped parameters when call-site inference yields a single primitive | --- -## Analyzers +## Honest limitations -| Analyzer | Issues Detected | -|---|---| -| Security | SQL injection, `eval()`, hardcoded secrets, `exec()` | -| Complexity | Cyclomatic complexity above threshold (default: 10) | -| Code Smell | Long methods, god objects | -| Dead Code | Unreachable code after `return`/`raise`/`break` | -| Type Hints | Missing return type annotations, explicit `any` | -| Dependencies | Unused imports | -| Performance | List concatenation in loops, `await` inside loops | +- Python and TypeScript only at v0.2; no Ruby/Go/Rust adapters yet. +- Documentation engine requires a reachable LLM provider (graceful skip otherwise — refactor still ships). +- Self-analysis on Refactron's own repo fails the test gate by design (mutating the bundled fixtures breaks meta-tests). See [Self-test paradox](#self-test-paradox) below. +- Public extension API for custom transforms is post-launch. +- Test gate is bound by your project's own suite — runs `npm test` / `pytest` in a shadow tree. ---- +### Self-test paradox -## Auto-fixers - -| Fixer | What it fixes | -|---|---| -| `unused-imports` | Removes unused import statements | -| `trailing-whitespace` | Strips trailing whitespace from all lines | -| `dead-code` | Removes unreachable code | -| `sort-imports` | Sorts imports alphabetically | -| `normalize-quotes` | Normalizes quote style | -| `type-hints` | Adds `-> None` return type annotations | -| `docstrings` | Inserts placeholder docstrings | -| `simplify-boolean` | Simplifies `x == True` → `x` | -| `unused-variables` | Removes unused variable declarations | -| `fix-indentation` | Converts tabs to 4-space indentation | -| `missing-commas` | Adds missing trailing commas | -| `remove-debug` | Removes debug/print statements | -| `magic-numbers` | Flags magic numbers for manual review | -| `convert-fstring` | Flags `%`-format strings for manual conversion | +If you `git clone` Refactron and run `refactron run --apply` on the repo itself, the test gate **will** fail and **no files will be written**. That's working as designed — the meta-tests exercise transforms on `fixtures/python-legacy-mini/` and `fixtures/ts-legacy-mini/`, which are deliberately full of legacy patterns. Refactoring them invalidates the tests' expected inputs, the verification engine catches the regression, and the write is refused — exactly what would happen on any project where a refactor breaks downstream tests. ---- - -## Architecture - -``` -src/ -├── core/ -│ ├── models.ts # All types — LOCKED (CodeIssue, BlastRadius, etc.) -│ ├── config.ts # RefactronConfig + YAML loader -│ └── orchestrator.ts # Full pipeline coordinator -├── adapters/ -│ ├── interface.ts # ILanguageAdapter — LOCKED -│ ├── registry.ts # Language auto-detection -│ ├── python/ # Python adapter (subprocess) -│ └── typescript/ # TypeScript adapter (compiler API) -├── analysis/ -│ ├── blast-radius.ts # Transitive impact scoring -│ ├── import-graph.ts # File-level reverse import graph -│ ├── call-graph.ts # Function-level call graph -│ ├── temporal.ts # Git history intelligence -│ ├── engine.ts # Analysis orchestrator -│ └── analyzers/ # 7 language-agnostic analyzers -├── verification/ -│ ├── engine.ts # Blast-radius-aware check selection -│ ├── atomic-writer.ts # Temp → rename safe writes -│ └── checks/ # syntax, imports, test-gate -├── autofix/ -│ ├── engine.ts # AutoFix orchestrator -│ └── fixers/ # 14 fixers -├── pipeline/ -│ ├── session.ts # State machine -│ ├── store.ts # .refactron/ persistence -│ └── queue.ts # Fix queue management -├── infrastructure/ -│ ├── backup.ts # Pre-write backup + rollback -│ ├── diff.ts # Unified diff generation -│ └── git.ts # Git log + co-change analysis -├── cli/ -│ ├── index.ts # Fast-path dispatcher (<10ms for --version) -│ ├── app.tsx # Command router -│ └── commands/ # 6 Ink command components -└── ui/ # Ink terminal UI components -``` - ---- +To self-analyze without triggering this, exclude the fixtures via `.refactronrc.json`: -## Configuration - -Create a `refactron.yaml` in your project root: - -```yaml -version: 2 -analyzers: - complexity: - enabled: true - threshold: 10 # cyclomatic complexity limit - security: - enabled: true - code_smell: - enabled: true - max_method_lines: 50 - dead_code: - enabled: true - type_hints: - enabled: true - dependencies: - enabled: true - performance: - enabled: true -verification: - timeout_seconds: 45 - critical_timeout_seconds: 120 -autofix: - dry_run: false - require_verification: true -output: - format: terminal # terminal | json | sarif - fail_on: null # critical | high | medium | low | null +```json +{ "exclude": ["fixtures/**"] } ``` --- -## Development - -```bash -git clone https://github.com/Refactron-ai/Refactron_Lib_TS.git -cd Refactron_Lib_TS -npm install - -npm run build # compile TypeScript -npm test # run 45 tests -npm run typecheck # type check only -npm run lint # ESLint -npm run format # Prettier -npm run test:watch # watch mode -``` - -### Adding a Language Adapter +## Performance -Implement `ILanguageAdapter` from `src/adapters/interface.ts` and register it in `src/adapters/registry.ts`. The interface is locked — all language-specific logic must stay inside the adapter. +Reproducible benchmark on Apple M2, Node 24: -### Adding a Fixer +| Tree size | Files | Median analyze | Range | +|---|---|---|---| +| 10k LOC | 448 | 1.31s | 1.16s – 1.64s | +| 100k LOC | 4,465 | 20.58s | 14.99s – 38.65s | -Extend `BaseFixer` from `src/autofix/fixers/base.ts`, declare `supportedIssueTypes`, implement `fix()`, and register it in `src/autofix/engine.ts`. +Run it yourself: `bash bench/run-bench.sh`. --- -## Known Limitations +## Documentation -### Running Refactron on the Refactron repo (self-test paradox) +Full documentation: [docs.refactron.dev](https://docs.refactron.dev) (or [`docs/`](docs/) in this repo). -If you `git clone` Refactron and run `refactron run --apply` on the repo -itself, the test gate **will** fail and **no files will be written**. - -That's working as designed. Refactron's own test suite includes meta-tests -that exercise the transforms on `fixtures/python-legacy-mini/` and -`fixtures/ts-legacy-mini/` — fixtures that are deliberately full of legacy -patterns. Running the transforms on those fixtures produces refactored code, -which then no longer matches what the meta-tests expect as input. The -verification engine catches the regression and refuses to write — exactly -what would happen on any project where a refactor breaks downstream tests. - -To self-analyze without triggering this, exclude the fixtures via -`.refactronrc.json`: +--- -```json -{ "exclude": ["fixtures/**"] } -``` +## Citations -`refactron analyze .` then returns `No findings.` and `run --apply .` -becomes a no-op. +- Opdyke 1992 — *Refactoring Object-Oriented Frameworks* (UIUC PhD thesis) — academic foundation for behavior-preserving refactoring +- Brunsfeld 2018 — *Tree-sitter: a new parsing system for programming tools* (Strange Loop) — analysis layer +- Instagram engineering — [LibCST](https://github.com/Instagram/LibCST) — Python codemod foundation +- Microsoft / TypeScript team — [ts-morph](https://github.com/dsherret/ts-morph) — TypeScript AST transforms +- Wang et al. ICSE 2018 — *Towards Refactoring-Aware Regression Test Selection* — coverage-of-changed-surface insight --- diff --git a/SECURITY.md b/SECURITY.md index d4227d5f..1becdd90 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,40 +1,48 @@ # Security Policy -## Supported Versions +## Reporting a vulnerability -| Version | Supported | -|---|---| -| 0.1.0-beta.1 | ✅ Active | +**Do not open a public GitHub issue for security vulnerabilities.** ---- +Please report privately via either: -## Reporting a Vulnerability +- Email: `omsherikar0229@gmail.com` +- GitHub Security Advisory: https://github.com/Refactron-ai/Refactron_Lib_TS/security/advisories/new -**Do not open a public GitHub issue for security vulnerabilities.** +You will receive an acknowledgement within **72 hours**. For high or critical issues we will coordinate disclosure with you and aim to ship a patched release before any public details are published. Please give us a reasonable embargo window before disclosing publicly. + +When reporting, include: a description, reproduction steps, the affected version, and the potential impact. + +## Supported versions + +| Version | Status | +| ------------ | ---------------------------------------------- | +| `0.2.x` | Supported — security fixes will be backported. | +| `0.1.x-beta` | **Not supported.** Please upgrade to `0.2.x`. | + +## Threat model + +Refactron is a deterministic refactoring engine. The core pipeline (analyze → plan → transform → verify) contains **no LLM in the critical path**. Every change committed to disk originates from a registered transform with documented preconditions and is gated by three deterministic verifiers (syntax, imports, tests). The published surface cannot generate code that wasn't produced by a reviewed transform — there is no path by which a hallucinated or hostile model response can rewrite a user file. + +The `document` step (Step 4 of the pipeline, in `src/document/`) is the only LLM-touching component. It runs **only on already-verified diffs** and produces docstrings, commit messages, and CHANGELOG entries — never executable code that participates in verification. The worst-case outcome of a malicious or hallucinated LLM response is an incorrect docstring or a misleading changelog line; the underlying refactor remains correct because it was verified before the LLM was ever consulted. + +Document-side mitigations: + +- **Secret redaction** — `src/document/redact.ts` strips API keys, bearer tokens, and `.env`-style assignments from prompts before they leave the process. +- **Provider-error fallback** — if the LLM call fails or returns garbage, the refactor stays applied; only the documentation step is skipped or marked degraded. -Email: **security@refactron.dev** +Atomic write guarantees: refactor outputs are written by `writeBatchAtomic` in `src/verify/atomic-batch-writer.ts`, which writes every `FileChange` to a sibling temp file and then renames atomically. On any rename failure, the remaining temps are unlinked. There is no partial-write state — a refactor plan either commits in full or leaves the working tree untouched. -Include: -- A description of the vulnerability -- Steps to reproduce -- Potential impact -- Any suggested fix (optional) +## Subprocess safety -You will receive an acknowledgement within 48 hours. We aim to release a patch within 14 days of confirmation. +All subprocess invocations use `execa(cmd, [args], opts)` array-form. There is no `child_process.exec` and no string interpolation into command strings; `shell: true` is **not** used for any tool-invoked command. The 10 `execa` call sites in `src/` (Python sidecar, vitest runner, pytest runner, Python interpreter probes, the verifier runner, and the transform runner) all pass arguments as arrays. -We will credit you in the release notes unless you prefer to remain anonymous. +**One intentional exception**: when the user supplies a `testCmd` in `.refactronrc.json` (or via `--test-cmd`), Refactron runs it through `sh -c` (`src/verify/runners/detect.ts:27`). This is the entire purpose of the field — users need to express things like `vitest run --testNamePattern foo` or chained pipelines. The trust boundary is the `.refactronrc.json` file: **a hostile `.refactronrc.json` in a repository can run arbitrary shell commands inside the verifier's shadow tree**, equivalent to running that repository's own test suite. Refactron is therefore no more or less safe than running `npm test` (or `pytest`) on an untrusted repository. Treat unfamiliar `.refactronrc.json` files with the same caution you would treat unfamiliar `package.json` `scripts` blocks. ---- +## Atomic-write guarantees -## Scope +`writeBatchAtomic` (`src/verify/atomic-batch-writer.ts`) uses [`write-file-atomic`](https://www.npmjs.com/package/write-file-atomic), which performs a POSIX `rename(2)` on Unix and `MoveFileExW` with `MOVEFILE_REPLACE_EXISTING` on Windows. Each file in a `RefactorPlan` is written to a temp file first; only after every temp file is fsync'd does the writer perform the renames. If any rename fails, the remaining temp files are unlinked and the plan is reported as failed — the working tree is never left half-converted. -In scope: -- Remote code execution via crafted input files -- Path traversal in file analysis or fix application -- Secrets leaking through analysis output -- Dependency vulnerabilities with known CVEs +## Known dependency advisories -Out of scope: -- Issues requiring physical access to the machine -- Social engineering -- Bugs in `--dry-run` output only (no filesystem writes) +As of the `0.2.x` release, `npm audit` reports **0 vulnerabilities** of any severity in the production or development dependency graph. diff --git a/dev-docs/decisions/10-week-8-launch.md b/dev-docs/decisions/10-week-8-launch.md new file mode 100644 index 00000000..e3092218 --- /dev/null +++ b/dev-docs/decisions/10-week-8-launch.md @@ -0,0 +1,125 @@ +# ADR 010 — Week 8 Launch (npm + PyPI + Show HN) + +## Status +Accepted, 2026-05-14 (Week 8, Day 55). + +## Context +Through Week 7 the v2.0 engine + CLI shipped functional, with measured perf +under target on 10k and 100k LOC trees and the output-redesign work landed. +The remaining gap before v0.2.0 reaches public users is distribution: the +package needs to actually be installable from npm and PyPI, the docs site +needs to be reachable, and the launch surface (Show HN) needs to be ready +to absorb the first wave of comments without typing answers under time +pressure. + +Week 8 is the publication week. Days 50-55 are subagent-driven preparation +(packaging, docs, checklists, response templates); Day 56 is the user-driven +publish + Show HN post + 12-hour engagement window. This ADR records the +decisions that shape that arc. + +## Decision + +### 1. Version is `0.2.0`, not `1.0.0` or `2.0.0` +The previous public version on npm was `0.1.0-beta.2`. Bumping to a small, +non-1.0 number signals "first public iteration of v2.0; not yet 1.0-stable". +1.0 is reserved for after real beta validation surfaces from HN + post-launch +users. The 2.0.0 alternative was rejected because it overclaims maturity — +the v2.0 *engine* is new, but the *user-facing contract* (CLI surface, +config schema) is too young to promise breaking-change discipline. + +### 2. Defer gauntlets #2-#5 +Week 7 planned five external gauntlet runs as the validation gate for 1.0. +We're shipping after gauntlet #1 only. Show HN itself becomes the external +validation surface — every issue/comment from the launch thread is a +gauntlet observation, and the resulting bug-fix cycles roll into Week 9+ +triage. This saves 2-3 weeks of slip waiting for ≥3 external developers to +commit time, and trades it for a noisier feedback channel that's +self-organizing. + +### 3. PyPI strategy: thin wrapper, not rewrite +Legacy `refactron` on PyPI is at v1.0.15 — a complete independent Python +implementation from before the TS rewrite. It has ~3 500 historical installs +and represents an existing user surface we don't want to abandon. The new +`refactron-py/` package is a thin wrapper that shells out to the npm CLI: +it preserves the `pip install refactron` install path while collapsing +maintenance to one engine (the TS one). Trade-off: Python-only users now +need Node.js installed locally. Mitigation: the wrapper's first invocation +detects missing `refactron` on PATH and offers to run `npm install -g +refactron` (with a confirmation prompt — never silent installs). + +### 4. Mintlify for docs hosting +The docs site config already lives at `docs/mint.json` from earlier weeks. +Mintlify renders MDX with built-in navigation, search, and dark mode — all +of which would be a week of work to replicate on a custom Next.js app. +Deployment is `npx mintlify deploy` from the repo root, which is a manual +command (no GitHub Action exists for Mintlify auto-deploy on push). +Trade-off: ship-day publish has a manual step that has to be remembered and +sequenced before the Show HN post; mitigation is its line item in the +pre-ship checklist. + +### 5. Demo GIF via vhs (not asciinema or animated SVG) +vhs (Charm) produces small GIFs (~1-3 MB for a 30s recording) that render +inline in GitHub README, npm package page, and PyPI long_description. +asciinema requires a JS player which doesn't render on PyPI; animated SVG +support is patchy across npm's package page. vhs's GIF output is the lowest +common denominator that works everywhere we publish. Trade-off: re-recording +requires the user to install vhs locally (no CI render path was set up +this week — flagged for Day 53 follow-up but pushed to user). + +### 6. `publishConfig.access: public` + `provenance: true` +Both encoded in `package.json` even though `release.yml` already passes +`--provenance` to `npm publish`. Encoding the intent in package.json +documents it in the canonical place + survives the manual-publish path if +the GH Action is bypassed for any reason. SLSA provenance attestation is a +launch credibility signal — HN readers checking the npm page will see the +"verified provenance" badge, which matters for a tool whose pitch is +"correctness". + +### 7. Day 56 is user-driven +Subagents prepared everything Day 50-55: packaging, docs, ADRs, checklists, +response templates, smoke-tests. Day 56 itself — running `npm publish` and +`twine upload`, posting the Show HN, answering comments in real time — is +not subagent-appropriate. Reasons: latency-sensitive (HN comments need +human-paced replies), requires brand voice (the response templates are +written in Om's voice and the live thread needs to sound the same), and +requires Om to be on the hook for what's said publicly. The pre-ship +checklist (`pre-ship-checklist.md`) splits subagent-verifiable items from +user-only items so Day 56 morning is a clear handoff. + +## Consequences +- v0.2.0 ships on npm + PyPI on Day 56 with provenance attestation and a + rendered demo GIF (assuming Om completes the vhs render before T-1h). +- Show HN comment-thread responses are pre-frozen and consistent in tone + across the 12-hour engagement window. +- Python users get continuity of the `pip install refactron` install path + without forcing the engine team to maintain two implementations. +- Future PyPI re-implementation (full native Python engine) is not blocked + — the wrapper gives us a graceful fallback if user feedback says the + Node.js dependency is a deal-breaker. +- Gauntlets #2-#5 are now Week 9+ work; resourcing them depends on what + Show HN surfaces (e.g. if 5 users self-report similar bugs, gauntlets + become unnecessary; if zero users engage substantively, gauntlets + become essential). +- Mintlify is now a launch-critical dependency — if Mintlify has an outage + on Day 56, the docs site link in Show HN 404s. Mitigation: the README on + GitHub stays a self-contained fallback that covers install + first-run. + +## Future work (deferred from Week 8) +- **CI-based vhs rendering** so demo.gif regenerates on every CLI surface + change instead of requiring a local install. +- **Public extension API** for transforms (Show HN Q&A #7 promises + "post-launch"). Wait for ≥3 transform requests to land in issues before + designing the surface. +- **Cross-package monorepo refactors** (Show HN Q&A #8 v0.3 roadmap item). +- **Native Python engine reconsideration** if Node.js dependency feedback + warrants it (Decision 3 trade-off). +- **Gauntlets #2-#5** if Show HN engagement is too thin to substitute. + +## References +- Source-of-truth: `dev-docs/Refactron_Detailed_Execution_Plan.md` §Week 8. +- Pre-ship checklist: [`dev-docs/launch/pre-ship-checklist.md`](../launch/pre-ship-checklist.md). +- Show HN response templates: [`dev-docs/launch/show-hn-responses.md`](../launch/show-hn-responses.md). +- Docs deploy notes: [`dev-docs/launch/deploy-docs.md`](../launch/deploy-docs.md). +- Demo recording notes: [`dev-docs/launch/recording-the-demo.md`](../launch/recording-the-demo.md). +- Bench evidence cited at launch: [`bench/results-2026-05-14.txt`](../../bench/results-2026-05-14.txt). +- LOCKED contract: `src/contracts.ts` (untouched). diff --git a/dev-docs/launch/deploy-docs.md b/dev-docs/launch/deploy-docs.md new file mode 100644 index 00000000..4ef99680 --- /dev/null +++ b/dev-docs/launch/deploy-docs.md @@ -0,0 +1,96 @@ +# Deploying the Mintlify docs site + +The Refactron docs site at `docs/` is built and hosted by Mintlify. This document +describes the **manual deploy** procedure for the launch. + +## Why manual (and not a GitHub Action)? + +Mintlify offers a GitHub-app integration that auto-deploys on push to `main`. +That is the long-term path. For the v0.2 launch we are deferring the GH Action +because: + +- Auto-deploy from `main` would re-render the live docs every time we land an + unrelated PR (CI burn, build queue noise). +- The Mintlify GH App requires a one-time install + admin approval on the + `Refactron-ai` org, which is a separate ticket. +- Manual `npx mintlify deploy` is a single command run from `docs/`. Safer + for the launch window where we want a human-in-the-loop on the staging→live + flip. + +We will revisit the auto-deploy path post-launch (track in a separate issue). + +## Prerequisites + +- Node.js 18+ (matches the Refactron CLI requirement). +- `mintlify` CLI invoked via `npx` — no global install needed. +- A Mintlify account with admin access on the `refactron` project. + +Run this once on your laptop: + +```bash +npx mintlify@latest --version # caches the CLI +npx mintlify@latest login # opens a browser; pick the refactron project +``` + +## Local preview + +Always preview before deploying. From the repo root: + +```bash +cd docs +npx mintlify@latest dev +# → http://localhost:3000 +``` + +This serves the site exactly as it will render in production. Click through +every page added or modified in your branch. Specifically check: + +- `concepts/safety-model` renders the mermaid 3-gate diagram. +- `transforms/index` cards link correctly to the per-transform pages. +- `cli/reference` tables render and CodeGroup tabs switch. +- All anchor links inside `faq.mdx` and `concepts/why-no-llm.mdx` point + somewhere real. + +## Deploy + +Once the local preview looks right and your branch is merged to `main`: + +```bash +cd docs +npx mintlify@latest deploy +``` + +Mintlify will: + +1. Validate `mint.json` against its schema. +2. Build the site. +3. Push to the live `refactron` project at https://docs.refactron.dev (or + whatever custom domain is configured in the Mintlify dashboard). + +Deploy typically takes 30-60 seconds. The CLI prints the live URL on +success. + +## Smoke check after deploy + +Open the live URL and click through: + +- Homepage / Introduction loads. +- The new nav groups are present: Concepts → Safety Model, Transforms, + Configuration → .refactronrc.json, FAQ. +- Mermaid diagram on Safety Model renders (not raw markdown). +- One transform page (e.g. `transforms/var-to-const-let`) renders with the + CodeGroup before/after intact. + +## Rollback + +If a deploy goes wrong, the Mintlify dashboard exposes a one-click rollback +to the previous build. Use it; do not hot-patch via another deploy unless +you've previewed locally. + +## Future work + +- Install the Mintlify GitHub App on `Refactron-ai/Refactron_Lib_TS` and switch + to auto-deploy on `main`. Open as a separate issue post-launch so the + configuration change can be reviewed independently. +- Add a docs-only PR check that runs `npx mintlify@latest broken-links` against + changed pages. diff --git a/dev-docs/launch/pre-ship-checklist.md b/dev-docs/launch/pre-ship-checklist.md new file mode 100644 index 00000000..d6a1f58a --- /dev/null +++ b/dev-docs/launch/pre-ship-checklist.md @@ -0,0 +1,32 @@ +# Refactron v0.2.0 — Pre-Ship Checklist + +Run through this in order on Day 56 morning before T-2h smoke. Anything failing +aborts ship until fixed. + +## Subagent-verifiable (Day 55 closes these) + +- [x] `npm run typecheck` exits 0 +- [x] `npm run lint` exits 0 +- [x] `npm run format:check` exits 0 +- [x] `npm run build` exits 0 +- [x] `npm test` exits 0 (Python-subprocess parallel-load flake passes in isolation — acceptable) +- [x] `npm pack --dry-run` shows `refactron-0.2.0.tgz` with expected file list (no `.env`, `node_modules/`, `fixtures/`, `tests/`, `dev-docs/`, `tape/`, `bench/`, `docs/`, `refactron-py/`) +- [x] `npm audit --audit-level=high` exits 0 +- [x] `cd refactron-py && python3 -m build && twine check dist/*` PASSES +- [x] Local clean-room: macOS host install of npm tarball returns `--version` 0.2.0 +- [x] CHANGELOG `[0.2.0]` entry present and dated +- [x] README displays demo GIF reference (the file may still be a placeholder pending vhs render) + +## User-only (Om verifies on ship day) + +- [ ] `npm whoami` returns Om's account +- [ ] npm 2FA on +- [ ] `pip install -e refactron-py/` works locally in a fresh venv +- [ ] README renders cleanly on GitHub mobile (HN traffic skews mobile) +- [ ] `vhs tape/demo.tape` rendered → `docs/assets/demo.gif` exists, under 5 MB, under 30s, no audio +- [ ] Docs site live at docs.refactron.dev +- [ ] Git tag `v0.2.0` ready to push (`git tag v0.2.0`; do not `push origin v0.2.0` until Day 56 T-1h) +- [ ] PyPI `refactron` namespace owner has authorized Om's account for upload + (legacy v1.0.15 owner — confirm before T-1h or PyPI publish blocks) +- [ ] Discord/Slack/Twitter cleared for the 12-hour engagement window +- [ ] Phone fully charged diff --git a/dev-docs/launch/recording-the-demo.md b/dev-docs/launch/recording-the-demo.md new file mode 100644 index 00000000..7e94c9e5 --- /dev/null +++ b/dev-docs/launch/recording-the-demo.md @@ -0,0 +1,55 @@ +# Recording the README demo GIF + +The README references `docs/assets/demo.gif`, which is generated from +`tape/demo.tape` using [vhs](https://github.com/charmbracelet/vhs). + +Generation is deferred to the maintainer because vhs is a one-time Homebrew +install and the recording requires a built `dist/` on `PATH`. + +## One-time setup + +```bash +brew install vhs +``` + +vhs depends on `ffmpeg` and `ttyd`; Homebrew pulls those in automatically. + +## Recording + +From the repo root: + +```bash +# 1. Build the CLI so the global `refactron` symbol points at this tree. +npm run build + +# 2. Make the local build globally available (or skip if it already is). +npm install -g . + +# 3. Render the GIF. +vhs tape/demo.tape + +# 4. Verify size — must be under 5 MB for the README. +ls -lh docs/assets/demo.gif +``` + +If the GIF exceeds 5 MB, edit `tape/demo.tape`: + +- Lower `Width` to `720` or `Height` to `480`. +- Lower `FontSize` to `12`. +- Drop the `refactron document --apply` step (the launch GIF can show the + refactor flow without docs; users find `document` via the docs site). + +## Cleanup + +After recording, optionally: + +```bash +npm uninstall -g refactron # if you don't want the local build globally +``` + +## Why this isn't automated in CI + +vhs needs a real PTY and a Homebrew-managed binary stack; running it inside +GitHub Actions reliably is more setup than the once-per-launch cadence +justifies. The tape script is checked in, so anyone with vhs locally can +re-render in under a minute. diff --git a/dev-docs/launch/show-hn-responses.md b/dev-docs/launch/show-hn-responses.md new file mode 100644 index 00000000..72c4dc1d --- /dev/null +++ b/dev-docs/launch/show-hn-responses.md @@ -0,0 +1,170 @@ +# Show HN — pre-baked response templates + +These are frozen, pre-thought answers to the questions most likely to come up +in the Show HN comment thread on Day 56. The intent is to avoid typing answers +under time pressure and to keep tone consistent across the 12-hour window. + +Edit lightly to fit the actual phrasing of the comment, but do not improvise +new claims — every number and citation here is the one that's already in +README, FAQ, or `bench/results-2026-05-14.txt`. + +--- + +## 1. Why not Cursor / Copilot? + +Fair question — they're great at fluent text, which is a different problem. +The catch with using an LLM for refactors is non-determinism and no +verify-before-write: NYU's "Asleep at the Keyboard?" (Pearce et al., 2022) +found 40% of Copilot's security-relevant completions had vulnerabilities. +Refactron is the opposite shape — deterministic AST transforms with three +gates (syntax, imports, tests) before any file is written. LLMs are still in +the pipeline at Step 4 for documentation generation, where fluency matters +more than formal correctness. + +— om + +--- + +## 2. Why no Rust adapter? + +Honest answer: TS + LibCST already gives multi-language reach with one engine +team. Adding Rust means rebuilding the semantic layer from scratch — there's +no LibCST-equivalent for Rust today, and tree-sitter gives you a syntax tree +without the type/scope info the verification gate needs. That's roughly a +year of catch-up work for a language I haven't seen 10 user requests for yet. +Happy to prioritize when there's clear demand — open an issue with your use +case. + +— om + +--- + +## 3. Doesn't ESLint do this? + +ESLint is excellent at single-file syntactic issues and auto-fixes — I use it +on this repo. The difference is scope: Refactron's transforms are cross-file +and semantic (e.g. `callback_to_async_await` walks every caller of the +function before deciding it's safe to transform), and every change runs +through three gates — syntax + imports + tests — before being written to +disk. ESLint's autofix model is "edit then trust"; Refactron's is +"plan then verify then write". Different tools, different jobs. + +— om + +--- + +## 4. What about Comby? + +Comby is great for structural search/replace and I've used it for one-off +codemods. The gap is type info and verification: Comby matches on syntax +patterns, doesn't know your imports or call graph, and doesn't run your +tests after the rewrite. Refactron is the refactor + verify pipeline for +when you need the change to land green. + +— om + +--- + +## 5. How is this different from jscodeshift? + +Same AST-codemod tradition, the difference is verification-first. jscodeshift +runs the transform and leaves verifying it to you; Refactron's three gates +run automatically and refuse to write if any fail. Maintenance picture +matters too — jscodeshift's last release was v17 in 2023 and Meta hasn't +shipped against it much in 2024+; the babel-plugin-codemod community fork is +alive but separate. Refactron's transform contract is `Refactorer` in +`src/contracts.ts` if you want to compare shape. + +— om + +--- + +## 6. Why no LLM in the engine? + +The numbers convinced me. NYU 40% Copilot vulnerability rate; ACM 92.45% LLM +test-generation failure rate; UTSA 19.7% AI-assisted code-review false +positives; Stripe's 2024 dev survey reporting 42% of AI-generated code is +discarded; Stack Overflow's 2024 survey showing 62% of developers don't +trust AI output. LLMs are great at fluent text — that's why Refactron uses +them at Step 4 for documentation. They're not great at formal correctness +with semantic-preservation guarantees, which is the whole job of a +refactoring engine. + +— om + +--- + +## 7. Can I write my own transform? + +For now, by forking. The internal contract is `Refactorer` in +`src/contracts.ts` — each transform implements +`plan(symbol, snapshot) → RefactorPlan | null` and is registered in the +engine. A public extension API is on the roadmap post-launch; I want to +shake out the contract shape against real users before freezing it. If you +have a transform you'd want to ship, open an issue and I'll help wire it. + +— om + +--- + +## 8. Does it work on monorepos? + +Yes, with one caveat. The verifier walks up to the nearest `package.json` / +`pyproject.toml` and runs the configured `testCmd` from that package's root +— so per-package refactors verify against per-package tests. Cross-package +refactors (one transform that touches packages A and B and needs both +test suites green) are out of scope at v0.2 and on the v0.3 roadmap. + +— om + +--- + +## 9. What's the perf? + +Measured on Apple M2, Node 24, 5 iterations per size, raw evidence in +`bench/results-2026-05-14.txt`. 10k LOC (448 files) — 1.31s median analyze. +100k LOC (4 465 files) — 20.58s median analyze. The plan step runs ~3× +faster than v0.1 after Week 7's per-file parallelization. 500k LOC isn't in +the public bench yet (fixture generation alone is ~30s); runnable locally +with `SIZES=500000 bash bench/run-bench.sh`. + +— om + +--- + +## 10. Why MIT? + +Wide adoption matters more than monetization at v0.2. The commercial side +sits in the managed-LLM "Backend" provider used at Step 4 (documentation) — +Pro users get api.refactron.dev with hosted models and rate limits; everyone +else uses their own keys or skips Step 4. The engine itself is and stays +MIT — that's the part I want every developer running locally. + +— om + +--- + +## 11. What about the obvious bug Z? (catch-all) + +Thanks — open an issue at github.com/Refactron-ai/Refactron_Lib_TS/issues +with a minimal repro and I'll triage. The 3-gate verifier means the worst +case is "refactor refused with logs", not "broken tree" — but I want to +know about every false negative so the gate set can grow. + +— om + +--- + +## Tone discipline + +- **Under 5 sentences.** HN respects brevity. Cut adjectives, not nouns. +- **Never defend; engage.** Find the agreeable kernel of the question first + ("fair question", "honest answer"), then substantiate. +- **When you don't know, say so + a timeline.** "I don't know — will dig in + tomorrow and reply" beats hand-waving every time. +- **No all-caps. No exclamation marks.** No "revolutionary", + "cutting-edge", "game-changing", "blazingly fast", or any other + marketing adjective. Cite the artifact (paper, file path, bench run) + instead. +- **Sign as `om`** (lowercase) — matches HN handle conventions and reads as + a person, not a brand. diff --git a/docs/assets/demo.gif b/docs/assets/demo.gif new file mode 100644 index 00000000..3525a2e9 Binary files /dev/null and b/docs/assets/demo.gif differ diff --git a/docs/cli/reference.mdx b/docs/cli/reference.mdx new file mode 100644 index 00000000..fac1e46c --- /dev/null +++ b/docs/cli/reference.mdx @@ -0,0 +1,255 @@ +--- +title: CLI Reference +description: Every Refactron command, flag, and exit code. +--- + +Refactron is a single binary, `refactron`, with two execution modes: + +- **One-shot:** `refactron [flags]` runs the command and exits. +- **Interactive REPL:** `refactron` (no command) drops you into a `❯` prompt that accepts the same verb names. + +The verbs in this reference work in both modes unless noted otherwise. + +## `analyze` + +```bash +refactron analyze [target] [flags] +``` + +Read-only scan. Detects transform patterns across the project, prints findings, creates a session under `.refactron/work-sessions/`, and exits. Never mutates anything. + +| Flag | Type | Default | Description | +|---|---|---|---| +| `--json` | bool | false | Emit findings as JSON to stdout instead of the formatted report. | +| `--confidence` | `high \| medium \| low` | `high` | Minimum analyzer confidence to surface. Lower = noisier. | +| `--fail-on` | `high \| medium \| low` | none | Exit non-zero if any finding meets or exceeds this confidence. | +| `--graph=` | path | none | Also write the analysis report (with import/call graphs) as JSON to ``. | + + +```bash one-shot +refactron analyze src/ +refactron analyze . --json > report.json +refactron analyze . --fail-on high # CI gate +``` + +```bash REPL +❯ analyze . +❯ analyze src/ +``` + + +**Exit codes:** `0` clean; `1` `--fail-on` matched; `2` flag parse error. + +--- + +## `run` + +```bash +refactron run [target] [flags] +``` + +Plan + verify + (optionally) apply transforms. Without `--apply`, runs as a dry-run and prints unified diffs. + +| Flag | Type | Default | Description | +|---|---|---|---| +| `--apply` | bool | false | After verification passes, write the changes atomically. | +| `--dry-run` | bool | true | Show the diff without writing. Mutually exclusive with `--apply`. | +| `--transforms=` | csv or `all` | `all` | Limit to specific [transform IDs](/transforms). | +| `--confidence` | `high \| medium \| low` | `high` | Analyzer confidence threshold (overrides `.refactronrc.json`). | +| `--test-cmd` | string | auto-detect | Override the test runner used by Gate 3 (overrides `.refactronrc.json`). | +| `--diff-context=` | int | 30 | Context lines per file in dry-run output. | +| `--files=` | glob | none | Scope the dry-run preview to a subset of planned changes. | +| `--json` | bool | false | Emit `{ mode, plan }` as JSON. | + + +```bash one-shot +refactron run --dry-run # see what would change +refactron run --apply # verify + write +refactron run --transforms=var_to_const_let,implicit_any --apply +refactron run src/auth/ --apply # scoped to a directory +``` + +```bash REPL +❯ run # dry-run +❯ run --apply +❯ run --transforms=callback_to_async_await --apply +``` + + +**Exit codes:** `0` plan applied (or dry-run printed); `1` verification failed (gate rejected); `2` flag parse error or missing path. + +--- + +## `document` + +```bash +refactron document [target] [flags] +``` + +Reads `.refactron/last-apply.json` (the snapshot from the most recent `run --apply`), generates docstrings + a CHANGELOG entry via the configured LLM provider, and either prints (`--dry-run`, default) or writes (`--apply`). + +This is the only command that touches an LLM, and it operates **only on already-verified diffs** — the worst-case is a wrong docstring, never broken code. + +| Flag | Type | Default | Description | +|---|---|---|---| +| `--apply` | bool | false | Insert generated docstrings into source files; append CHANGELOG.md. | +| `--no-cache` | bool | false | Bypass the prompt/response cache under `.refactron/cache/llm/`. | +| `--json` | bool | false | Emit the `DocPatch` as JSON. | +| `--provider=` | `backend \| ollama \| openai \| anthropic \| groq` | `backend` | Override `.refactronrc.json`'s `documentation.provider`. | +| `--model=` | string | provider default | Override the model name. | + + +```bash one-shot +refactron document # dry-run, prints DocPatch +refactron document --apply # write docstrings + CHANGELOG +refactron document --provider=ollama --model=qwen2.5-coder:7b --apply +``` + +```bash REPL +❯ document +❯ document --apply +``` + + +**Exit codes:** `0` success (dry-run or apply); `7` not authenticated; `8` no `.refactron/last-apply.json` snapshot — run `run --apply` first; `9` provider configuration error (missing API key, factory threw); `10` flag parse error or internal error. + +--- + +## `init` + +```bash +refactron init [target] +``` + +Scaffold a `.refactronrc.json` template into the target directory (default: `.`). Errors if a config already exists. + +| Flag | Type | Default | Description | +|---|---|---|---| +| `[target]` | path | `.` | Directory to write `.refactronrc.json` into. | + + +```bash one-shot +refactron init +refactron init my-project/ +``` + +```bash REPL +❯ init +``` + + +**Exit codes:** `0` created; `1` config already exists. + +--- + +## `login` + +```bash +refactron login +``` + +Open the OAuth 2.0 Device Authorization flow in the browser. On success, credentials are written to `~/.refactron/credentials.json`. + +REPL only (also runs automatically when an unauthenticated user invokes any auth-gated command). + +--- + +## `logout` + +```bash +refactron logout +``` + +Remove `~/.refactron/credentials.json`. The next auth-gated command will prompt for re-login. + +--- + +## `auth` + +```bash +refactron auth +``` + +Print authentication status — current account, token age, refresh-token presence — without modifying anything. + +--- + +## `status` + +```bash +❯ status +``` + +Print the active session's summary (id, target, finding count, severity histogram, age). If no session is active in this REPL, falls back to the most recent saved session under `.refactron/work-sessions/`. + +REPL only. + +--- + +## `session` + +```bash +❯ session list +❯ session +``` + +`session list` prints all saved sessions newest-first. `session ` loads a previously-saved session and makes it active in this REPL — subsequent `run` / `document` calls operate against it. + +REPL only. + +--- + +## `rollback` + +```bash +❯ rollback +``` + +Print rollback guidance — Refactron's atomic batch writes don't keep an in-CLI undo log; use `git checkout` to restore. The command exists as a discoverable hint surface. + +REPL only. + +--- + +## `help` / `?` + +```bash +❯ help +❯ ? +``` + +Print the inline help table for all REPL verbs. + +REPL only. + +--- + +## `clear` + +```bash +❯ clear +``` + +Clear the terminal viewport. + +REPL only. + +--- + +## `exit` / `quit` + +```bash +❯ exit +❯ quit +``` + +Leave the REPL. + +--- + +## Global behaviour + +- **`refactron --version` / `-v`** — print the version and exit (fast path, < 10ms target). +- **`refactron --help` / `-h`** — print the top-level command list and exit. +- Authentication is required for `analyze`, `run`, `document`, `init` — these commands return exit code `7` and prompt for login when credentials are missing or expired. +- A `.refactronrc.json` in the project root is auto-loaded; CLI flags always override its values. diff --git a/docs/concepts/safety-model.mdx b/docs/concepts/safety-model.mdx new file mode 100644 index 00000000..3c2b8ab6 --- /dev/null +++ b/docs/concepts/safety-model.mdx @@ -0,0 +1,103 @@ +--- +title: Safety Model +description: Three verification gates run before any byte hits your filesystem. Atomic batch writes guarantee all-or-nothing. +icon: shield-check +--- + +Refactron's moat is what happens **before** a write, not how clever the rewrite is. Every refactor plan passes through three verification gates and a single atomic batch-write step. If any gate rejects, your working tree is untouched. + +> **Inviolable Rule #4:** Verification runs before every write. Atomic write protocol always. + +## The 3 gates + +```mermaid +flowchart LR + Plan[RefactorPlan] --> G1{Gate 1
Syntax} + G1 -- pass --> G2{Gate 2
Imports} + G2 -- pass --> G3{Gate 3
Tests} + G3 -- pass --> AW[Atomic batch write] + G1 -- fail --> Reject[Reject — your tree untouched] + G2 -- fail --> Reject + G3 -- fail --> Reject +``` + +Each gate operates on the **proposed-new-content** string for every `FileChange` in the plan. Nothing in the gate path reads or writes your real working tree until the final atomic step. + +--- + +## Gate 1 — Syntax + +Re-parse the new content for each `FileChange` in the plan. + +- **Python:** LibCST parses the proposed source. A failure means a malformed transformation slipped past the refactorer (a bug we want to catch). +- **TypeScript:** ts-morph collects diagnostics on the proposed source. Compiler diagnostics with category `Error` reject the file. + +If any file fails to parse, the gate rejects with a clear `blockingReason` naming the offending file. The plan is dropped; nothing else runs. + +**Typical wall-clock:** ~50ms for a small batch. + +--- + +## Gate 2 — Imports + +Build a dependency graph from the proposed-new-content trees and resolve every import. + +- **Python:** collect `import X` and `from X import Y` statements; resolve against `sys.path` plus the project tree. +- **TypeScript:** ts-morph `getImportDeclarations()` plus `getModuleSpecifierSourceFile()` for each import. + +**Reject conditions:** + +- A newly-introduced import in the new content does not resolve. +- An import that previously resolved in a changed file now fails (e.g. transform deleted a re-exported symbol). + +This catches the most common refactor-induced breakage: the rewrite is locally valid but quietly orphans a downstream module. + +**Typical wall-clock:** ~50ms. + +--- + +## Gate 3 — Tests + +The most expensive gate, and the only one that runs **your** code. + +1. **Build a shadow tree** — a temp directory copy of the project root. Unchanged files are hardlinked (cheap, instant). Files in the plan are written from the proposed new content. +2. **Detect the test runner** by looking for a config file in the project root: + - `vitest.config.{ts,js,mjs}` → `vitest run` + - `jest.config.{ts,js,cjs,json}` → `jest` + - `pyproject.toml` with `[tool.pytest.*]` or `pytest.ini` → `pytest` + - User can override via `.refactronrc.json` `testCmd: ""`. +3. **Run the baseline first** — execute the runner against the **unchanged** copy. If the baseline already fails, abort with `your tests don't pass before refactoring` rather than blame the refactor for pre-existing failures. +4. **Run the mutated** — same runner, same temp tree, with the proposed changes in place. Any non-zero exit rejects the plan. + +**Typical wall-clock:** dominated by the user's own suite size. The default per-gate timeout is 45s; raise via `verification.timeout_seconds` for slower suites. + +--- + +## Atomic batch write protocol + +Once all three gates pass, the verifier hands a list of `(path, newContent)` pairs to `writeBatchAtomic`. Each write uses `write-file-atomic`: + +1. Write the new content to a sibling temp file (`.refactron-tmp-`). +2. `fsync` the temp file. +3. POSIX `rename` (`MoveFileExW` on Windows) the temp over the real path. This rename is atomic at the filesystem layer. + +**The contract:** either every change in the plan commits, or none do. There is no intermediate state where one file has the new content and another still holds the old. If a write fails midway, the verifier aborts the batch and the partially-written temp files are cleaned up — your real tree never sees the inconsistent intermediate. + +--- + +## Cross-file preconditions + +Some transforms need to know whether **other files in the project** depend on the surface they're about to mutate. These transforms walk the import graph for the target file and skip themselves rather than break a cross-file caller: + +- [`callback_to_async_await`](/transforms/callback-to-async-await) — skips when an external file imports the function and passes a callback at the call site. +- [`deprecated_api_requests_to_httpx`](/transforms/deprecated-api-requests-to-httpx) — skips when an external file references `.requests` (including `unittest.mock.patch` string targets). + +The cross-file check runs during planning (before Gate 1) so the transform never produces a plan it knows would orphan a caller. + +--- + +## Citations + +The three-gate model traces back to Bill Opdyke's 1992 PhD thesis on behaviour-preserving refactoring at UIUC — the original formal treatment of preconditions before automated source transformation. + +- Opdyke, William F. *Refactoring Object-Oriented Frameworks.* PhD thesis, University of Illinois at Urbana-Champaign, 1992. [PDF](https://www.cs.umd.edu/users/atif/Refactoring.pdf) diff --git a/docs/concepts/why-no-llm.mdx b/docs/concepts/why-no-llm.mdx new file mode 100644 index 00000000..899454fc --- /dev/null +++ b/docs/concepts/why-no-llm.mdx @@ -0,0 +1,61 @@ +--- +title: Why No LLM +description: Refactron is the only modern refactoring tool where AI never touches your code. This is on purpose. +icon: brain-circuit +--- + +**Refactron is the only modern refactoring tool where AI never touches your code.** + +The engine — analyzers, transforms, verifiers, atomic writers — is 100% deterministic. The only place an LLM appears is the optional `document` command, which generates docstrings on top of an already-verified diff. Nothing the LLM produces can ever break your build. + +This is a design choice, not a limitation. It's the choice the published research has made for us. + +## The five facts + +### 1. ~40% of Copilot programs contain exploitable security vulnerabilities + +NYU 2022 study, "Asleep at the Keyboard? Assessing the Security of GitHub Copilot's Code Contributions." Across 89 hand-built scenarios from MITRE's Top 25 CWEs, **40%** of Copilot's top suggestions contained a vulnerability. arXiv: [2108.09293](https://arxiv.org/abs/2108.09293). + +### 2. 92.45% of Copilot-generated tests fail or are broken when there's no existing suite + +ACM AST 2024 study, "An Empirical Study on the Usage of Automated Tools for Testing Code Generated by Copilot." When asked to generate unit tests for code without a pre-existing test infrastructure, **92.45%** of the generated tests were unrunnable or failed. DOI: [10.1145/3644032.3644443](https://doi.org/10.1145/3644032.3644443). + +### 3. 19.7% of LLM-recommended packages are fabricated + +UTSA / Virginia Tech / University of Oklahoma joint 2024 study, "We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs." Across 16 LLMs, **19.7%** of recommended package names did not exist on PyPI / npm. arXiv: [2406.10279](https://arxiv.org/abs/2406.10279). + +### 4. 33% of developer time goes to tech debt + +Stripe 2018 [Developer Coefficient](https://stripe.com/files/reports/the-developer-coefficient.pdf) report — surveyed 1,000+ developers and found 33% of weekly hours go to maintenance / refactoring / fighting legacy code, not new feature work. + +### 5. 62% of developers cite tech debt as their #1 frustration + +Stack Overflow [2024 Developer Survey](https://survey.stackoverflow.co/2024/) — across 65,000 respondents, "technical debt" was the most-cited frustration in professional work. + +## So where does AI fit? + +In documentation, not in the engine. + +Refactron's `document` command runs **after** verification has already passed. It receives the verified diff, generates a docstring per touched function and a CHANGELOG entry, and writes them through the same atomic batch writer. The worst-case failure mode is a wrong docstring — never broken code, never an introduced vulnerability, never a hallucinated import. + +LLMs are excellent at fluent natural language. They are not yet reliable at formal correctness. We deploy them where they're good and keep them away from where they're not. + +## Compared to + +| Tool | Deterministic engine | Verifies before write | Multi-language | Actively maintained | +|---|---|---|---|---| +| **Refactron** | yes | yes (3 gates) | Python + TypeScript | yes | +| Cursor | no (LLM) | no | many | yes | +| GitHub Copilot | no (LLM) | no | many | yes | +| Greptile | no (LLM-assisted) | partial | many | yes | +| jscodeshift | yes | no | TypeScript / JavaScript | abandoned by Meta 2024 ([#587](https://github.com/facebook/jscodeshift/issues/587)) | +| OpenRewrite | yes | partial (visitor preconditions) | Java-first | yes | +| Comby | yes | no (structural search/replace) | many | yes | + +## Citations + +- Opdyke, William F. *Refactoring Object-Oriented Frameworks.* PhD thesis, UIUC, 1992. [PDF](https://www.cs.umd.edu/users/atif/Refactoring.pdf) +- Roberts, Donald B. *Practical Analysis for Refactoring.* PhD thesis, UIUC, 1999. +- Wang et al. ICSE 2018, "How Practitioners Perceive Automated Bug Report Management Techniques." +- Brunsfeld, Max. "Tree-sitter: a new parsing system for programming tools." Strange Loop 2018. +- Instagram engineering blog, "[Static analysis at scale: Meta's approach](https://engineering.fb.com/)" — and the [LibCST launch post](https://engineering.fb.com/2019/12/12/security/glean/) (2019), the underlying parser Refactron uses for Python. diff --git a/docs/configuration/refactronrc.mdx b/docs/configuration/refactronrc.mdx new file mode 100644 index 00000000..a5243259 --- /dev/null +++ b/docs/configuration/refactronrc.mdx @@ -0,0 +1,84 @@ +--- +title: .refactronrc.json +description: Project-level configuration file. CLI flags always override values here. +--- + +All Refactron CLI configuration lives in `.refactronrc.json` at your project root. Every field is optional — defaults are applied when a key is missing, and any key the CLI flag explicitly sets wins over the file. + +Run `refactron init` to scaffold a starter file. + +## Schema + +| Field | Type | Default | Description | +|---|---|---|---| +| `transforms` | `string[]` | `["all"]` | `["all"]` enables every transform, or list specific [transform IDs](/transforms). | +| `exclude` | `string[]` | `[]` | Gitignore-style globs to skip during analysis (e.g. `["dist", "node_modules"]`). | +| `testCmd` | `string \| null` | `null` (auto-detect) | Override the test runner Gate 3 invokes. Auto-detection looks for `vitest.config.*`, `jest.config.*`, `pytest.ini`, or a `[tool.pytest.*]` block in `pyproject.toml`. | +| `confidence` | `"high" \| "medium" \| "low"` | `"high"` | Minimum analyzer confidence to surface findings. Lower = noisier. | +| `dryRun` | `boolean` | `true` | Default mode for `run` when neither `--apply` nor `--dry-run` is passed. | +| `documentation.provider` | `"backend" \| "ollama" \| "openai" \| "anthropic" \| "groq"` | `"backend"` | LLM provider for the `document` command. `"backend"` uses your stored Refactron credentials (Pro plan); the others are opt-in. | +| `documentation.model` | `string` | `"llama-3.3-70b-versatile"` | Model name passed to the provider. | +| `documentation.endpoint` | `string \| null` | `null` | Override the provider URL (e.g. self-hosted Ollama or a custom OpenAI-compatible gateway). | +| `documentation.tokenBudget` | `integer (256-32000)` | `4000` | Max tokens per LLM call. Lower = cheaper, more truncation. | +| `documentation.redactPatterns` | `string[]` | `[]` | Extra regex patterns to strip from prompts before they leave your machine. The built-in set already covers AWS keys, OpenAI / Anthropic / GitHub tokens, JWTs, and generic `.env` style assignments. | +| `documentation.cache` | `boolean` | `true` | Cache prompt → response under `.refactron/cache/llm/`. Bypass once with `--no-cache`. | + +## Example + + +```json .refactronrc.json +{ + "$schema": "https://refactron.dev/schema/refactronrc.json", + "transforms": ["all"], + "exclude": ["node_modules", "dist", ".venv", "__pycache__"], + "testCmd": null, + "confidence": "high", + "dryRun": true, + "documentation": { + "provider": "backend", + "model": "llama-3.3-70b-versatile", + "endpoint": null, + "tokenBudget": 4000, + "redactPatterns": [], + "cache": true + } +} +``` + +```json minimal.json +{ + "transforms": ["var_to_const_let", "implicit_any"], + "exclude": ["dist"] +} +``` + +```json local-ollama.json +{ + "transforms": ["all"], + "documentation": { + "provider": "ollama", + "model": "qwen2.5-coder:7b", + "endpoint": "http://localhost:11434" + } +} +``` + + +## File search order + +Refactron uses [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig) and looks for, in order: + +1. `.refactronrc` +2. `.refactronrc.json` +3. `.refactronrc.yaml` +4. `refactron.config.js` + +The first match wins. We recommend `.refactronrc.json` for editor JSON-schema support. + +## Validation + +Configs are validated against a JSON Schema 7 schema with [Ajv 8](https://ajv.js.org/). Unknown fields, wrong types, and out-of-range values cause the CLI to exit with code `2` and a clear error message — no silent ignoring. + + +The legacy `refactron.yaml` ([Configuration overview](/configuration)) is still parsed for backwards compatibility but new keys land here in `.refactronrc.json`. The two files are merged when both exist; flags override either. + diff --git a/docs/faq.mdx b/docs/faq.mdx new file mode 100644 index 00000000..dac172dd --- /dev/null +++ b/docs/faq.mdx @@ -0,0 +1,100 @@ +--- +title: FAQ +description: Frequently-asked questions about Refactron — what it is, what it isn't, and why. +--- + +## Why not Cursor or Copilot? + +Both are LLM-driven and non-deterministic — the same input may yield a different output, and neither verifies its proposed change against your tests before suggesting it. NYU's 2022 study found ~40% of Copilot suggestions across MITRE Top-25 CWE scenarios contained an exploitable vulnerability. + +Refactron is the opposite: a deterministic engine where every change passes [three verification gates](/concepts/safety-model) before a single byte is written. You don't have to trust it — you can read the same plan twice and get the same diff. + +See [Why No LLM](/concepts/why-no-llm) for the full case. + +--- + +## Why no Rust / Go / Ruby support? + +Each new language is a fully-implemented adapter — parser, detector, refactorer, verifier path. Picking Python and TypeScript first gives one engine team broad coverage: TypeScript reaches the entire JavaScript world; Python covers most of the data / scripting / ML surface. + +Adding more languages is a roadmap question of demand and contributor bandwidth. If you'd use Refactron in a Go monorepo, [open an issue](https://github.com/Refactron-ai/Refactron_Lib_TS/issues) — concrete demand moves it up the priority list. + +--- + +## Doesn't ESLint / Pyflakes already do this? + +ESLint and Pyflakes **report** issues. A handful have `--fix` modes, but they don't verify the fix against your test suite before writing. + +Refactron **transforms and verifies** — every change is parsed, re-imported, and tested in a shadow tree before disk gets touched. The two are complementary: keep your linter for style; add Refactron for legacy-pattern migration with safety guarantees. + +--- + +## What about Comby? + +[Comby](https://comby.dev/) is excellent for structural search-and-replace. It works on parse trees, not regex — that's a real advantage over `sed`. But it has no type information and no verification gate. If your replacement breaks an import or fails a test, you find out at runtime. + +Refactron's transforms know the type of each parameter (TypeScript) or import target (Python) and refuse to apply when the rewrite would invalidate a downstream caller. + +--- + +## How is this different from jscodeshift? + +Same AST-codemod tradition, different priorities. jscodeshift gives you a programmatic API to write your own transforms — Refactron ships ten verified transforms with cross-file precondition checks and a baked-in test gate. + +Meta archived active development on jscodeshift in 2024 ([issue #587](https://github.com/facebook/jscodeshift/issues/587)). The library still works, but it's no longer maintained by its original team. + +--- + +## What about OpenRewrite? + +[OpenRewrite](https://docs.openrewrite.org/) is the gold standard in the JVM world — Java-first, Maven / Gradle integration, recipe ecosystem. But it's heavy (Java runtime, Spring-style configuration) and Python / TypeScript adoption has lagged. + +Refactron is positioned for Python and TypeScript codebases where OpenRewrite isn't a fit. + +--- + +## Why no LLM in the engine itself? + +LLMs are great at fluent natural language. They are not yet reliable at formal correctness: + +- ~40% of Copilot suggestions contain vulnerabilities (NYU 2022). +- 92.45% of LLM-generated tests fail when there's no existing suite (ACM AST 2024). +- 19.7% of LLM-recommended packages are fabricated (UTSA / VTech / UOklahoma 2024). + +Refactoring is a formal-correctness problem. We use the LLM only in the [`document` command](/cli/reference#document), which generates docstrings and CHANGELOG text on top of an already-verified diff. The worst-case is a wrong sentence — never a broken build. + +Full citations on the [Why No LLM](/concepts/why-no-llm) page. + +--- + +## Can I write my own transform? + +A public extension API is on the roadmap for v0.3+. For v0.2, the supported path is to fork the repository and add your transform under `src/transform/transforms//`, following the existing patterns (and tests under `tests/unit/transform/transforms/`). + +If you have a transform you'd want maintained upstream, open an issue with the before/after examples — we triage these case-by-case. + +--- + +## Does it work on monorepos? + +Yes. The verification gate runs in the package being refactored — Gate 3 detects the local test runner via config-file presence (`vitest.config.*`, `jest.config.*`, `pyproject.toml` / `pytest.ini`), and you can override per-package via `.refactronrc.json` `testCmd`. + +The cross-file precondition checks scan the import graph rooted at the file being transformed, which works correctly across package boundaries inside the same repo. + +--- + +## What's the perf? + +Refactron tracks performance via the `bench/` infrastructure in the repo. On a 4,465-file, ~100k-LOC project, `analyze` runs in ~20 seconds on an Apple M2 (median of 5 runs). `run --apply` adds the verification gate cost — typically dominated by your own test suite's wall-clock. + +--- + +## Why MIT? + +At v0.2, wide adoption matters more than monetization. MIT removes friction for evaluation, internal tooling, and downstream forks. + +--- + +## Why 0.2 and not 1.0? + +The engine has only had one round of self-gauntlet validation (Refactron refactoring its own codebase under load). 1.0 will land after we've seen the patterns of bug reports from external usage and addressed them. Show HN feedback informs that timeline. diff --git a/docs/mint.json b/docs/mint.json index 5e5ec39c..39e24389 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -46,21 +46,40 @@ ], "navigation": [ { - "group": "Getting Started", - "pages": ["introduction", "quickstart", "configuration"] + "group": "Get Started", + "pages": ["introduction", "quickstart"] }, { - "group": "Core Concepts", + "group": "Concepts", "pages": [ + "concepts/safety-model", + "concepts/why-no-llm", "concepts/blast-radius", "concepts/verification", "concepts/sessions", "concepts/temporal-analysis" ] }, + { + "group": "Transforms", + "pages": [ + "transforms/index", + "transforms/callback-to-async-await", + "transforms/format-to-fstring", + "transforms/manual-typecheck-to-hints", + "transforms/deprecated-api-requests-to-httpx", + "transforms/class-to-dataclass", + "transforms/var-to-const-let", + "transforms/promise-chains-to-async", + "transforms/implicit-any", + "transforms/commonjs-to-esm", + "transforms/promise-constructor-to-async" + ] + }, { "group": "CLI", "pages": [ + "cli/reference", "cli/overview", "cli/analyze", "cli/issue-browser", @@ -70,10 +89,18 @@ "cli/other-commands" ] }, + { + "group": "Configuration", + "pages": ["configuration", "configuration/refactronrc"] + }, { "group": "Languages", "pages": ["languages/python", "languages/typescript"] }, + { + "group": "FAQ", + "pages": ["faq"] + }, { "group": "API Reference", "pages": ["api/overview", "api/models", "api/adapters", "api/orchestrator"] diff --git a/docs/transforms/callback-to-async-await.mdx b/docs/transforms/callback-to-async-await.mdx new file mode 100644 index 00000000..38ceb153 --- /dev/null +++ b/docs/transforms/callback-to-async-await.mdx @@ -0,0 +1,71 @@ +--- +title: callback_to_async_await +description: Convert trailing-callback Python functions into async functions that return the result. +--- + +**Transform ID:** `callback_to_async_await` +**Language:** Python + +## What it does + +Detects functions whose last positional parameter is a callback (named `callback`, `cb`, or `done`) that is invoked exactly once at the tail of every control-flow branch. Rewrites the function as `async def`, drops the callback parameter, and `return`s what the callback was being passed. + +## Detector pattern + +The detector lives at `src/analyze/detectors/python/callback-detector.ts`. It scans for `def` statements where the last positional parameter name matches the callback alias set, then walks the function body to verify the call shape. + +## Preconditions + +1. The callback parameter is the **last positional** parameter of the function. +2. The function is **not already async** (`async def`). +3. The function is **not a generator** (no `yield` / `yield from`). +4. The callback is invoked **exactly once**, on the tail of every reachable branch. +5. **Cross-file:** no external file imports this function and passes a callback at the call site (would orphan that caller). The transform skips itself when an `import ` followed by `.(... cb)` or `from import ` followed by `(... cb)` is found in the project. + +## Before / after + + +```python before.py +def fetch_user(user_id, callback): + """Simulate fetching a user record and pass it to ``callback``.""" + result = {"id": user_id, "name": "user-%d" % user_id} + callback(result) + + +def save_data(payload, done): + """Simulate persisting ``payload`` then invoking ``done`` with a receipt.""" + receipt = {"ok": True, "size": len(payload)} + done(receipt) +``` + +```python after.py +async def fetch_user(user_id): + """Simulate fetching a user record and pass it to ``callback``.""" + result = {"id": user_id, "name": "user-%d" % user_id} + return result + + +async def save_data(payload): + """Simulate persisting ``payload`` then invoking ``done`` with a receipt.""" + receipt = {"ok": True, "size": len(payload)} + return receipt +``` + + +## Edge cases handled + +- Callback aliases: `callback`, `cb`, `done`. +- Preserves preceding decorators (e.g. `@retry`). +- Preserves type hints on the remaining parameters. + +## Edge cases NOT handled (skip via precondition) + +- Callback is not the last positional parameter (e.g. `def f(callback, x)`). +- Function is a generator (`yield`). +- Callback is called more than once (multiple branches each invoking it). +- Function is already declared `async`. +- An external file in the project calls this function passing a callback argument. + + +Actual emitted form may differ slightly per the transform's idempotency rules — run `refactron run --dry-run` to see the exact diff for your code. + diff --git a/docs/transforms/class-to-dataclass.mdx b/docs/transforms/class-to-dataclass.mdx new file mode 100644 index 00000000..153cf7de --- /dev/null +++ b/docs/transforms/class-to-dataclass.mdx @@ -0,0 +1,74 @@ +--- +title: class_to_dataclass +description: Promote pure data-holder classes (trivial __init__) to @dataclass. +--- + +**Transform ID:** `class_to_dataclass` +**Language:** Python + +## What it does + +Detects classes whose `__init__` method only does `self. = ` assignments and rewrites them as `@dataclass`. Each former init parameter becomes a typed field (`Any` when the original parameter has no annotation). + +## Detector pattern + +The detector at `src/analyze/detectors/python/dataclass-detector.ts` walks `ClassDef` nodes, accepts only those with a single `__init__` method, and verifies every body statement is an `Assign(targets=[Attribute(self, name)], value=Name(name))` shape. + +## Preconditions + +1. The class has exactly one method, and it is `__init__`. +2. The `__init__` body is purely `self. = ` lines (no defaults, no transformations like `self.x = x * 2`). +3. The class has **no `__slots__`** declaration. +4. The class does **not inherit** from a parent with a custom `__init__`. +5. Init parameter names match the assignment target names (no `self.public = _private` redirection). + +## Before / after + + +```python before.py +class User: + def __init__(self, id, name, email): + self.id = id + self.name = name + self.email = email + + +class Address: + def __init__(self, street, city, zip): + self.street = street + self.city = city + self.zip = zip +``` + +```python after.py +from dataclasses import dataclass +from typing import Any + + +@dataclass +class User: + id: Any + name: Any + email: Any + + +@dataclass +class Address: + street: Any + city: Any + zip: Any +``` + + +## Edge cases handled + +- Adds `from dataclasses import dataclass` and `from typing import Any` if missing. +- Preserves class docstrings and decorators that precede the class. +- Multiple convertible classes per file are all converted in one pass. + +## Edge cases NOT handled (skip via precondition) + +- Class has any method besides `__init__` (the transform refuses to promote — a dataclass with custom methods is fine in principle, but inferring `eq`/`hash`/`repr` interactions safely is out of scope for v0.2). +- Init body contains expressions (`self.id = id * 2`, `self.full_name = first + " " + last`). +- Init has default arguments (`def __init__(self, x=0)`) — supported by dataclass via `field(default=…)` but not auto-inferred here. +- Class uses `__slots__`. diff --git a/docs/transforms/commonjs-to-esm.mdx b/docs/transforms/commonjs-to-esm.mdx new file mode 100644 index 00000000..62a773b0 --- /dev/null +++ b/docs/transforms/commonjs-to-esm.mdx @@ -0,0 +1,68 @@ +--- +title: commonjs_to_esm +description: Migrate CommonJS require / module.exports to ES module import / export. +--- + +**Transform ID:** `commonjs_to_esm` +**Language:** TypeScript + +## What it does + +Rewrites the CommonJS module surface into ES modules: + +- `const x = require('m')` → `import x from 'm'` (with `node:` prefix for Node builtins). +- `const { a, b } = require('m')` → `import { a, b } from 'm'`. +- `module.exports = X` (identifier) → `export default X`. +- `module.exports = { a, b }` (object literal) → named `export { a, b }`. + +## Detector pattern + +The detector at `src/analyze/detectors/typescript/commonjs-detector.ts` finds ts-morph `CallExpression` nodes with the callee `require` and `BinaryExpression` nodes with `module.exports` on the left. + +## Preconditions + +1. The file does **not use `__dirname` or `__filename`** — these are CommonJS-only globals; ESM requires `import.meta.url` translation which is out of scope. +2. **No dynamic require** (`require(name)` where `name` is not a string literal) — ESM imports must be static. +3. The file is not a `.cjs` extension (explicitly opted into CommonJS by the user). +4. No `require.resolve(...)`, `require.cache`, or other Node CJS-specific APIs. + +## Before / after + + +```ts before.ts +// Legacy CommonJS interop. +const path = require('path'); + +module.exports = { + join: (a, b) => path.join(a, b), +}; +``` + +```ts after.ts +import path from 'node:path'; + +const join = (a, b) => path.join(a, b); + +export { join }; +``` + + +## Edge cases handled + +- Node builtins (`path`, `fs`, `os`, `crypto`, etc.) get the modern `node:` specifier prefix. +- Package specifiers (`lodash`, `express`) are kept unprefixed. +- Relative specifiers (`'./utils'`) are kept unprefixed. +- Destructured require (`const { join } = require('path')`) becomes named import. +- `module.exports = identifier` becomes `export default identifier`. +- `module.exports = { a, b }` becomes `export { a, b }`. + +## Edge cases NOT handled (skip via precondition) + +- File uses `__dirname` / `__filename` (precondition `node-globals`). +- Dynamic `require(name)` where `name` is not a literal (precondition `dynamic-require`). +- `.cjs` files (precondition `cjs-extension`). +- `module.exports.x = ...` style (top-level mutation of the exports object — defer to a manual migration). + + +The actual reordering of declarations between input and output may differ — `module.exports = { join }` references a `join` defined inline; the rewrite hoists the `join` const ahead of the named `export`. Run `refactron run --dry-run` for the exact diff in your code. + diff --git a/docs/transforms/deprecated-api-requests-to-httpx.mdx b/docs/transforms/deprecated-api-requests-to-httpx.mdx new file mode 100644 index 00000000..2f817801 --- /dev/null +++ b/docs/transforms/deprecated-api-requests-to-httpx.mdx @@ -0,0 +1,86 @@ +--- +title: deprecated_api_requests_to_httpx +description: Migrate Python's requests library to the modern httpx equivalent. +--- + +**Transform ID:** `deprecated_api_requests_to_httpx` +**Language:** Python + +## What it does + +Rewrites `requests` imports and call sites to the API-compatible `httpx` equivalent. The mapping is hardcoded — `requests` and `httpx` share the same `get`/`post`/`put`/`delete`/`head`/`patch`/`options` surface and identical kwargs (`url`, `timeout`, `json`, `headers`, etc.). + +## Detector pattern + +The detector at `src/analyze/detectors/python/deprecated-api-detector.ts` looks for: + +- `import requests` +- `from requests import ` +- `requests.(...)` call expressions + +## Preconditions + +1. `httpx` is **not already imported** in the file (avoid double-imports / shadowing). +2. **Cross-file:** no external file in the project references `.requests` in any context. The check covers: + - `import this_module; print(this_module.requests)` (attribute reads). + - `from unittest.mock import patch; patch("this_module.requests.get", ...)` (string-target mock patches — these would silently break post-rename). + +## Before / after + + +```python before.py +import requests + + +def fetch_json(url): + try: + response = requests.get(url, timeout=5) + return response.json() + except Exception as exc: + return {"error": str(exc)} + + +def submit(url, payload): + try: + response = requests.post(url, json=payload, timeout=5) + return response.json() + except Exception as exc: + return {"error": str(exc)} +``` + +```python after.py +import httpx + + +def fetch_json(url): + try: + response = httpx.get(url, timeout=5) + return response.json() + except Exception as exc: + return {"error": str(exc)} + + +def submit(url, payload): + try: + response = httpx.post(url, json=payload, timeout=5) + return response.json() + except Exception as exc: + return {"error": str(exc)} +``` + + +## Edge cases handled + +- Both `import requests` and `from requests import get` forms. +- All HTTP-method call sites (`requests.get`, `.post`, `.put`, `.delete`, etc.). +- Cross-file string-target mocks (`patch("module.requests.get", ...)`). + +## Edge cases NOT handled (skip via precondition) + +- File already imports `httpx` (manual migration probably in progress). +- An external file in the project patches or references `.requests`. +- Code that uses `requests`-specific surfaces not in `httpx` (e.g. `requests.utils.requote_uri`, `requests.models.Response` constructed by hand). The transform is conservative and skips files where it cannot prove the mapping is total. + + +After the transform applies, you must add `httpx` to your dependencies (`pip install httpx` or `pyproject.toml`). Refactron does not modify your dependency manifest. + diff --git a/docs/transforms/format-to-fstring.mdx b/docs/transforms/format-to-fstring.mdx new file mode 100644 index 00000000..9a5bd333 --- /dev/null +++ b/docs/transforms/format-to-fstring.mdx @@ -0,0 +1,63 @@ +--- +title: format_to_fstring +description: Convert old-style %-formatting and .format() calls into f-strings. +--- + +**Transform ID:** `format_to_fstring` +**Language:** Python + +## What it does + +Rewrites legacy string-formatting expressions to PEP 498 f-strings. Both the `%` operator (`"hello %s" % name`) and `str.format()` calls (`"hello {}".format(name)`) are converted in place. + +## Detector pattern + +The detector at `src/analyze/detectors/python/format-detector.ts` finds `BinaryOperator(left=String, op='%', right=...)` and `Call(func=Attribute(value=String, attr='format'), ...)` nodes via LibCST. + +## Preconditions + +1. The expression is one of the two recognised forms above. +2. The substituted values are simple references (names or attribute accesses) — LibCST's `ConvertFormatStringCommand` skips patterns it cannot losslessly inline. +3. The string literal is not an existing f-string. + +## Before / after + + +```python before.py +def greet(name): + # Old percent-formatting style. + return "hello %s" % name + + +def format_value(x): + # Old str.format style. + return "value is {}".format(x) +``` + +```python after.py +def greet(name): + # Old percent-formatting style. + return f"hello {name}" + + +def format_value(x): + # Old str.format style. + return f"value is {x}" +``` + + +## Edge cases handled + +- Both `%` and `.format()` styles in the same file. +- Returns `null` (no change) for plain strings without format operators. +- Survives complex `.format()` patterns that the underlying `ConvertFormatStringCommand` cannot handle — the file is left unchanged rather than crashed. + +## Edge cases NOT handled (skip via precondition) + +- Nested `.format()` calls inside dict/list literals (per ADR-006, complex shapes may be skipped by LibCST's converter). +- Format strings with chained attribute access in the substitution slot (e.g. `"{}".format(a.b.c)`). +- Conditional formats (`format` called via `getattr` or aliased). + + +When the underlying converter cannot safely produce an equivalent f-string, the transform reports preconditions but leaves the source untouched — it never emits a half-converted file. + diff --git a/docs/transforms/implicit-any.mdx b/docs/transforms/implicit-any.mdx new file mode 100644 index 00000000..56a76736 --- /dev/null +++ b/docs/transforms/implicit-any.mdx @@ -0,0 +1,59 @@ +--- +title: implicit_any +description: Annotate untyped parameters when call-site inference yields a single primitive. +--- + +**Transform ID:** `implicit_any` +**Language:** TypeScript + +## What it does + +Finds parameters and return positions where ts-morph reports `getType().isAny()` (i.e. an implicit any). For each such parameter, the transform looks at every call site of the enclosing function and annotates **only** when every call site passes the same primitive type (`number`, `string`, `boolean`). + +## Detector pattern + +The detector at `src/analyze/detectors/typescript/implicit-any-detector.ts` enumerates ts-morph `ParameterDeclaration` nodes whose `getType().isAny()` returns true and have no explicit type node. + +## Preconditions + +1. The function has at least one call site in the project. +2. Every call site passes a value of the **same** primitive type for the target parameter. +3. The parameter doesn't already have a type annotation. +4. The function isn't a class method overridden by a subclass with a different signature (would change the public contract). + +## Before / after + + +```ts before.ts +// Implicit-any parameters. +export function add(a, b) { + return a + b; +} + +// Single call site elsewhere in the project: +// const r = add(1, 2); +``` + +```ts after.ts +// Implicit-any parameters. +export function add(a: number, b: number) { + return a + b; +} + +// Single call site elsewhere in the project: +// const r = add(1, 2); +``` + + +## Edge cases handled + +- Mixed already-annotated and bare parameters in the same signature — only bare ones are touched. +- Multiple call sites passing the same primitive (e.g. `add(1, 2)` and `add(3, 4)` both → `number`). +- Returns are also annotated when ts-morph infers a single concrete type from the body. + +## Edge cases NOT handled (skip via precondition) + +- Call sites pass diverging primitives (e.g. `id(1); id('a');` → precondition `diverging-types:id:x`). +- The function has zero call sites in the project (no signal to infer from). +- Call site passes a complex object / generic type (only primitives are considered safe). +- Parameter is destructured (`function f({ x, y })`). diff --git a/docs/transforms/index.mdx b/docs/transforms/index.mdx new file mode 100644 index 00000000..cb860a72 --- /dev/null +++ b/docs/transforms/index.mdx @@ -0,0 +1,62 @@ +--- +title: Transforms +description: The ten verified transforms shipped in Refactron 0.2 — five for Python, five for TypeScript. +--- + +Refactron ships a hand-curated catalog of ten transforms. Every transform has been hardened against precondition edge cases, runs through the [three verification gates](/concepts/safety-model), and either commits atomically or skips itself. + +You select transforms via the `--transforms` flag (`run --transforms=var_to_const_let,implicit_any`) or the `transforms` array in [`.refactronrc.json`](/configuration/refactronrc). The default is `['all']`. + +## Catalog + +| Transform ID | Language | What it does | +|---|---|---| +| `callback_to_async_await` | Python | Trailing-callback function → `async def` returning the result | +| `format_to_fstring` | Python | `%`-format and `.format()` calls → f-strings | +| `manual_typecheck_to_hints` | Python | `isinstance` chain dispatching one parameter → `Union[...]` annotation | +| `deprecated_api_requests_to_httpx` | Python | `requests` imports / calls → `httpx` | +| `class_to_dataclass` | Python | Pure `__init__`-assignment classes → `@dataclass` | +| `var_to_const_let` | TypeScript | `var` → `const` (or `let` if reassigned) per binding | +| `promise_chains_to_async` | TypeScript | `.then(...).then(...)` chains → `async`/`await` | +| `implicit_any` | TypeScript | Add explicit parameter types when all call sites pass the same primitive | +| `commonjs_to_esm` | TypeScript | `require` / `module.exports` → ES module `import` / `export` | +| `promise_constructor_to_async` | TypeScript | `new Promise((resolve) => resolve(x))` → `async` returning `x` | + +## Browse the catalog + + + + Python · trailing-callback → `async def` + + + Python · `%` and `.format()` → f-string + + + Python · `isinstance` chain → `Union[...]` + + + Python · `requests` → `httpx` + + + Python · plain class → `@dataclass` + + + TypeScript · `var` → `const` / `let` + + + TypeScript · `.then().then()` → `async`/`await` + + + TypeScript · annotate inferred-primitive parameters + + + TypeScript · `require` / `module.exports` → ESM + + + TypeScript · `new Promise(...)` → `async` + + + + +Every transform's "after" output passes [Gate 1 (syntax)](/concepts/safety-model#gate-1-syntax) by construction — the refactorer rebuilds the source from a parsed CST/AST. Gates 2 and 3 (imports + tests) are what catch regressions the local rewrite couldn't predict. + diff --git a/docs/transforms/manual-typecheck-to-hints.mdx b/docs/transforms/manual-typecheck-to-hints.mdx new file mode 100644 index 00000000..e67dafbe --- /dev/null +++ b/docs/transforms/manual-typecheck-to-hints.mdx @@ -0,0 +1,87 @@ +--- +title: manual_typecheck_to_hints +description: Promote isinstance-chain dispatch into a Union[...] type annotation on the parameter. +--- + +**Transform ID:** `manual_typecheck_to_hints` +**Language:** Python + +## What it does + +Detects `isinstance(x, T1) / elif isinstance(x, T2) / ...` chains where every branch discriminates the **same** parameter `x`, then annotates the parameter with `Union[T1, T2, ...]` and adds `from typing import Union` if needed. The chain itself is preserved (the runtime check still runs); only the parameter signature is enriched. + +## Detector pattern + +The detector at `src/analyze/detectors/python/typecheck-detector.ts` walks `If` / `Elif` chains looking for `Call(func=Name("isinstance"), args=[Name(param), Name(type)])` shapes and groups by parameter name. + +## Preconditions + +1. The function has at least one parameter without an existing type annotation. +2. Every `isinstance` branch in the chain discriminates the **same single parameter**. +3. The chain effectively dispatches the function body — i.e. the bare-`else` is either absent or raises. +4. The parameter is not already annotated. +5. The chain references at least two distinct types (a single `isinstance` is not informative enough to be worth annotating). + +## Before / after + + +```python before.py +import math + + +class Circle: + def __init__(self, radius): + self.radius = radius + + +class Square: + def __init__(self, side): + self.side = side + + +def area(shape): + if isinstance(shape, Circle): + return math.pi * shape.radius * shape.radius + elif isinstance(shape, Square): + return shape.side * shape.side + else: + raise TypeError("unknown shape: %r" % shape) +``` + +```python after.py +import math +from typing import Union + + +class Circle: + def __init__(self, radius): + self.radius = radius + + +class Square: + def __init__(self, side): + self.side = side + + +def area(shape: Union[Circle, Square]): + if isinstance(shape, Circle): + return math.pi * shape.radius * shape.radius + elif isinstance(shape, Square): + return shape.side * shape.side + else: + raise TypeError("unknown shape: %r" % shape) +``` + + +## Edge cases handled + +- Adds `from typing import Union` when not already imported. +- Preserves the runtime `isinstance` chain — purely additive in semantics. +- Records satisfied preconditions as `annotated::` for traceability. + +## Edge cases NOT handled (skip via precondition) + +- Parameter is already annotated (`def handle(x: int)`). +- Chain discriminates more than one parameter (e.g. `isinstance(x, int)` then `isinstance(y, str)`). +- Chain contains a single type only. +- The discriminated parameter doesn't exist in the function signature (defensive check). diff --git a/docs/transforms/promise-chains-to-async.mdx b/docs/transforms/promise-chains-to-async.mdx new file mode 100644 index 00000000..200e6952 --- /dev/null +++ b/docs/transforms/promise-chains-to-async.mdx @@ -0,0 +1,77 @@ +--- +title: promise_chains_to_async +description: Convert .then() chains into async/await with named bindings per stage. +--- + +**Transform ID:** `promise_chains_to_async` +**Language:** TypeScript + +## What it does + +Rewrites `fn(...).then(a => ...).then(b => ...)` chains as `async` functions with `await` and named const bindings for each intermediate value. Both flat (`a().then(b).then(c)`) and nested (`a().then(x => b(x).then(y => y))`) shapes are handled. + +## Detector pattern + +The detector at `src/analyze/detectors/typescript/promise-chain-detector.ts` walks ts-morph `CallExpression` nodes whose `expression` is a `PropertyAccessExpression` named `then`, climbing the chain to identify the originating call. + +## Preconditions + +1. The function body returns the chain (or the chain is the only statement). Mid-function chains used for side effects only are skipped. +2. **No `.catch(...)` in the chain.** The transform doesn't synthesize `try`/`catch` blocks — pre-existing error handling would change semantics. +3. **No `Promise.all` / `Promise.race` / `Promise.allSettled` / `Promise.any`** inside the chain — combinator semantics aren't trivially rewritable as sequential awaits. +4. The enclosing function returns a Promise (so adding `async` is type-preserving). + +## Before / after + + +```ts before.ts +async function fetchUser(id) { + return Promise.resolve({ id: id, name: 'user-' + id }); +} + +async function fetchPosts(userId) { + return Promise.resolve([ + { id: 1, userId: userId, title: 'first' }, + { id: 2, userId: userId, title: 'second' }, + ]); +} + +export function loadProfile(id) { + return fetchUser(id).then((u) => + fetchPosts(u.id).then((posts) => ({ user: u, posts: posts })), + ); +} +``` + +```ts after.ts +async function fetchUser(id) { + return Promise.resolve({ id: id, name: 'user-' + id }); +} + +async function fetchPosts(userId) { + return Promise.resolve([ + { id: 1, userId: userId, title: 'first' }, + { id: 2, userId: userId, title: 'second' }, + ]); +} + +export async function loadProfile(id) { + const u = await fetchUser(id); + const posts = await fetchPosts(u.id); + return { user: u, posts: posts }; +} +``` + + +## Edge cases handled + +- Flat chains (`a().then(b).then(c)`). +- Nested chains (`a().then(x => b(x).then(y => y))`). +- Preserves the enclosing function's exports / signature shape. + +## Edge cases NOT handled (skip via precondition) + +- Chain contains `.catch(...)` (preconditions: `no-catch`). +- Chain contains `Promise.all` / `Promise.race` / `Promise.allSettled` / `Promise.any` (precondition: `no-promise-combinator`). +- Chain handler returns void / does side effects only. +- Multiple parallel chains on the same value. diff --git a/docs/transforms/promise-constructor-to-async.mdx b/docs/transforms/promise-constructor-to-async.mdx new file mode 100644 index 00000000..3cf68937 --- /dev/null +++ b/docs/transforms/promise-constructor-to-async.mdx @@ -0,0 +1,51 @@ +--- +title: promise_constructor_to_async +description: Replace new Promise((resolve) => resolve(value)) with an async function returning the value. +--- + +**Transform ID:** `promise_constructor_to_async` +**Language:** TypeScript + +## What it does + +Detects `new Promise((resolve) => resolve(x))` (and equivalent block-bodied executors with a single `resolve(...)` call) and folds them into the enclosing function as `async`/`return x`. The `Promise` constructor wrapper disappears. + +## Detector pattern + +The detector at `src/analyze/detectors/typescript/promise-constructor-detector.ts` walks ts-morph `NewExpression` nodes with `Promise` as the callee, then inspects the executor arrow function body for a single resolve-only path. + +## Preconditions + +1. The executor's resolve path is **synchronous** — no `setTimeout`, `setInterval`, `process.nextTick`, `queueMicrotask`, `addEventListener`, or other async-escape mechanism (precondition `no-async-escape`). +2. **Single resolve / reject path** — the executor calls `resolve` (or `reject`) at most once, and not from inside multiple branches (precondition `single-resolve`). +3. No `try`/`catch` straddling the resolve call. +4. The enclosing function returns the `new Promise(...)` directly. + +## Before / after + + +```ts before.ts +function makePromise(value: number) { + return new Promise((resolve) => resolve(value)); +} +``` + +```ts after.ts +async function makePromise(value: number) { + return value; +} +``` + + +## Edge cases handled + +- Arrow-body executor (`(resolve) => resolve(x)`) and block-body single-statement executor (`(resolve) => { resolve(x); }`). +- Preserves the function name, parameters, and parameter type annotations. +- Promotes the function declaration to `async`. + +## Edge cases NOT handled (skip via precondition) + +- Executor uses `setTimeout` / event listeners to defer resolve (precondition `no-async-escape`). The `delayedValue` fixture is intentionally an example of this — `new Promise((resolve) => setTimeout(() => resolve(value), ms))` is left alone because folding it would lose the delay semantics. +- Multiple `resolve(...)` calls across branches (precondition `single-resolve`). +- Executor calls `reject(...)` — the rewrite would need to throw, and bare `throw` inside an async function changes the rejection semantics in ways that may surprise downstream `.catch` handlers. +- `new Promise((resolve, reject) => ...)` where both are used. diff --git a/docs/transforms/var-to-const-let.mdx b/docs/transforms/var-to-const-let.mdx new file mode 100644 index 00000000..c06d8ff9 --- /dev/null +++ b/docs/transforms/var-to-const-let.mdx @@ -0,0 +1,74 @@ +--- +title: var_to_const_let +description: Replace var declarations with const (or let if reassigned) per binding. +--- + +**Transform ID:** `var_to_const_let` +**Language:** TypeScript + +## What it does + +For every `var` declaration in the file, decide per binding: + +- **`const`** if the binding is never reassigned after initialization. +- **`let`** if the binding is reassigned at least once. + +Each binding is judged independently — multiple `var` statements in one file may map to a mix of `const` and `let`. + +## Detector pattern + +The detector at `src/analyze/detectors/typescript/var-detector.ts` collects all `VariableStatement` nodes with the `var` keyword via ts-morph and then walks the surrounding scope to count reassignments per binding name. + +## Preconditions + +1. No binding in the file is **referenced before its declaration** (relying on `var` hoisting). If hoisting is used anywhere, the entire file is skipped — the transform refuses to break working hoisted code. +2. No `var` is declared inside a `with` statement (legacy scope rules differ from `let`/`const`). +3. The file contains at least one `var` statement. + +## Before / after + + +```ts before.ts +// Legacy var-style declarations. +export function sumTo(n) { + var counter = 0; + for (var i = 1; i <= n; i++) { + counter = counter + i; + } + return counter; +} + +export function makeGreeting(name) { + var greeting = 'hi'; + return greeting + ', ' + name; +} +``` + +```ts after.ts +// Legacy var-style declarations. +export function sumTo(n) { + let counter = 0; + for (let i = 1; i <= n; i++) { + counter = counter + i; + } + return counter; +} + +export function makeGreeting(name) { + const greeting = 'hi'; + return greeting + ', ' + name; +} +``` + + +## Edge cases handled + +- Mixed const/let outcomes inside a single file based on each binding's mutability. +- Multiple independent `var`s in one file (`var a = 1; var b = 2; b = b + 1;` → `const a = 1; let b = 2; b = b + 1;`). +- `for (var i = 0; ...)` becomes `for (let i = 0; ...)` when `i` is mutated by the loop step. + +## Edge cases NOT handled (skip via precondition) + +- File relies on hoisting (e.g. `console.log(x); var x = 1;`). +- `var` declared inside a `with` block. +- `.cjs` files (handled by [`commonjs_to_esm`](/transforms/commonjs-to-esm) when relevant; `var` inside CommonJS modules has different historical patterns). diff --git a/package-lock.json b/package-lock.json index c3436e5f..b5adcc51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "refactron", - "version": "0.1.0-beta.2", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "refactron", - "version": "0.1.0-beta.2", + "version": "0.2.0", "license": "MIT", "dependencies": { "ajv": "^8.20.0", @@ -28,6 +28,7 @@ "tree-sitter-python": "^0.21.0", "tree-sitter-typescript": "^0.21.0", "ts-morph": "^22.0.0", + "typescript": "^5.4.0", "write-file-atomic": "^5.0.1" }, "bin": { @@ -42,12 +43,11 @@ "@types/write-file-atomic": "^4.0.3", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", - "@vitest/coverage-v8": "^1.0.0", + "@vitest/coverage-v8": "^3.2.4", "eslint": "^8.0.0", "prettier": "^3.0.0", "rimraf": "^5.0.0", - "typescript": "^5.4.0", - "vitest": "^1.0.0" + "vitest": "^3.2.4" }, "engines": { "node": ">=18.0.0" @@ -144,11 +144,14 @@ } }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -161,9 +164,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -174,13 +177,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -191,13 +194,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -208,13 +211,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -225,13 +228,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], @@ -242,13 +245,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -259,13 +262,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -276,13 +279,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -293,13 +296,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -310,13 +313,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -327,13 +330,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -344,13 +347,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -361,13 +364,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -378,13 +381,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -395,13 +398,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -412,13 +415,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -429,13 +432,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -446,13 +449,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -463,13 +483,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -480,13 +517,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -497,13 +551,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -514,13 +568,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -531,13 +585,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -548,7 +602,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -819,28 +873,15 @@ } }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -926,9 +967,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", "cpu": [ "arm" ], @@ -940,9 +981,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", "cpu": [ "arm64" ], @@ -954,9 +995,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", "cpu": [ "arm64" ], @@ -968,9 +1009,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", "cpu": [ "x64" ], @@ -982,9 +1023,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", "cpu": [ "arm64" ], @@ -996,9 +1037,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", "cpu": [ "x64" ], @@ -1010,9 +1051,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", "cpu": [ "arm" ], @@ -1024,9 +1065,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", "cpu": [ "arm" ], @@ -1038,9 +1079,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", "cpu": [ "arm64" ], @@ -1052,9 +1093,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", "cpu": [ "arm64" ], @@ -1066,9 +1107,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", "cpu": [ "loong64" ], @@ -1080,9 +1121,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", "cpu": [ "loong64" ], @@ -1094,9 +1135,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", "cpu": [ "ppc64" ], @@ -1108,9 +1149,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", "cpu": [ "ppc64" ], @@ -1122,9 +1163,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", "cpu": [ "riscv64" ], @@ -1136,9 +1177,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", "cpu": [ "riscv64" ], @@ -1150,9 +1191,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", "cpu": [ "s390x" ], @@ -1164,9 +1205,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", "cpu": [ "x64" ], @@ -1178,9 +1219,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", "cpu": [ "x64" ], @@ -1192,9 +1233,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", "cpu": [ "x64" ], @@ -1206,9 +1247,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", "cpu": [ "arm64" ], @@ -1220,9 +1261,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", "cpu": [ "arm64" ], @@ -1234,9 +1275,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", "cpu": [ "ia32" ], @@ -1248,9 +1289,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", "cpu": [ "x64" ], @@ -1262,9 +1303,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", "cpu": [ "x64" ], @@ -1275,13 +1316,6 @@ "win32" ] }, - "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, "node_modules/@ts-morph/common": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.23.0.tgz", @@ -1294,6 +1328,24 @@ "path-browserify": "^1.0.1" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/diff": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.3.tgz", @@ -1302,9 +1354,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -1571,131 +1623,149 @@ "license": "ISC" }, "node_modules/@vitest/coverage-v8": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", - "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.4", + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.4", - "istanbul-reports": "^3.1.6", - "magic-string": "^0.30.5", - "magicast": "^0.3.3", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0" + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.6.1" + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, "node_modules/@vitest/expect": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", - "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "chai": "^4.3.10" + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", - "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "1.6.1", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" }, "funding": { "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" + "tinyrainbow": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12.20" + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", - "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", - "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^2.2.0" + "tinyspy": "^4.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", - "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -1724,19 +1794,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", - "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -1806,15 +1863,34 @@ } }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", + "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/auto-bind": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", @@ -1874,22 +1950,20 @@ } }, "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, "license": "MIT", "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=18" } }, "node_modules/chalk": { @@ -1905,16 +1979,13 @@ } }, "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/cli-boxes": { @@ -2084,13 +2155,6 @@ "dev": true, "license": "MIT" }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, "node_modules/convert-to-spaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", @@ -2166,14 +2230,11 @@ } }, "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, "engines": { "node": ">=6" } @@ -2194,16 +2255,6 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2272,6 +2323,13 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-toolkit": { "version": "1.45.1", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", @@ -2283,9 +2341,9 @@ ] }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2293,32 +2351,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/escape-string-regexp": { @@ -2609,6 +2670,16 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2859,16 +2930,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", @@ -3389,23 +3450,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, - "node_modules/local-pkg": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.3", - "pkg-types": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3442,14 +3486,11 @@ } }, "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/lru-cache": { "version": "10.4.3", @@ -3574,26 +3615,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mlly": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", - "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.16.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.3" - } - }, - "node_modules/mlly/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3602,9 +3623,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -3865,20 +3886,20 @@ } }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/picocolors": { @@ -3899,29 +3920,10 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/pkg-types/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "dev": true, "funding": [ { @@ -3973,34 +3975,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4043,13 +4017,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, "node_modules/react-reconciler": { "version": "0.29.2", "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", @@ -4157,9 +4124,9 @@ } }, "node_modules/rollup": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", - "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", "dev": true, "license": "MIT", "dependencies": { @@ -4173,34 +4140,41 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.1", - "@rollup/rollup-android-arm64": "4.60.1", - "@rollup/rollup-darwin-arm64": "4.60.1", - "@rollup/rollup-darwin-x64": "4.60.1", - "@rollup/rollup-freebsd-arm64": "4.60.1", - "@rollup/rollup-freebsd-x64": "4.60.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", - "@rollup/rollup-linux-arm-musleabihf": "4.60.1", - "@rollup/rollup-linux-arm64-gnu": "4.60.1", - "@rollup/rollup-linux-arm64-musl": "4.60.1", - "@rollup/rollup-linux-loong64-gnu": "4.60.1", - "@rollup/rollup-linux-loong64-musl": "4.60.1", - "@rollup/rollup-linux-ppc64-gnu": "4.60.1", - "@rollup/rollup-linux-ppc64-musl": "4.60.1", - "@rollup/rollup-linux-riscv64-gnu": "4.60.1", - "@rollup/rollup-linux-riscv64-musl": "4.60.1", - "@rollup/rollup-linux-s390x-gnu": "4.60.1", - "@rollup/rollup-linux-x64-gnu": "4.60.1", - "@rollup/rollup-linux-x64-musl": "4.60.1", - "@rollup/rollup-openbsd-x64": "4.60.1", - "@rollup/rollup-openharmony-arm64": "4.60.1", - "@rollup/rollup-win32-arm64-msvc": "4.60.1", - "@rollup/rollup-win32-ia32-msvc": "4.60.1", - "@rollup/rollup-win32-x64-gnu": "4.60.1", - "@rollup/rollup-win32-x64-msvc": "4.60.1", + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" } }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4496,9 +4470,9 @@ } }, "node_modules/strip-literal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", - "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", "dev": true, "license": "MIT", "dependencies": { @@ -4529,64 +4503,57 @@ } }, "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^10.4.1", + "minimatch": "^10.2.2" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "18 || 20 || >=22" } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/text-table": { @@ -4603,10 +4570,75 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", "engines": { @@ -4614,9 +4646,9 @@ } }, "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "license": "MIT", "engines": { @@ -4726,16 +4758,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -4753,7 +4775,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -4763,13 +4784,6 @@ "node": ">=14.17" } }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true, - "license": "MIT" - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -4788,21 +4802,24 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -4811,19 +4828,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -4844,74 +4867,115 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, "node_modules/vite-node": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", - "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/vitest": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", - "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "dependencies": { - "@vitest/expect": "1.6.1", - "@vitest/runner": "1.6.1", - "@vitest/snapshot": "1.6.1", - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.1", - "why-is-node-running": "^2.2.2" + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.1", - "@vitest/ui": "1.6.1", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, @@ -4919,6 +4983,9 @@ "@edge-runtime/vm": { "optional": true }, + "@types/debug": { + "optional": true + }, "@types/node": { "optional": true }, @@ -4936,6 +5003,19 @@ } } }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index e350c468..38cd7cfa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "refactron", - "version": "0.1.0-beta.2", + "version": "0.2.0", "description": "Safety-first refactoring — finds, fixes, and verifies changes are safe before touching the filesystem", + "author": "Om Sherikar ", "bin": { "refactron": "./dist/cli/index.js" }, @@ -58,8 +59,13 @@ "dist/", "README.md", "LICENSE", - "CHANGELOG.md" + "CHANGELOG.md", + "SECURITY.md" ], + "publishConfig": { + "access": "public", + "provenance": true + }, "dependencies": { "ajv": "^8.20.0", "chalk": "^5.0.0", @@ -80,6 +86,7 @@ "tree-sitter-python": "^0.21.0", "tree-sitter-typescript": "^0.21.0", "ts-morph": "^22.0.0", + "typescript": "^5.4.0", "write-file-atomic": "^5.0.1" }, "devDependencies": { @@ -91,12 +98,11 @@ "@types/write-file-atomic": "^4.0.3", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", - "@vitest/coverage-v8": "^1.0.0", + "@vitest/coverage-v8": "^3.2.4", "eslint": "^8.0.0", "prettier": "^3.0.0", "rimraf": "^5.0.0", - "typescript": "^5.4.0", - "vitest": "^1.0.0" + "vitest": "^3.2.4" }, "license": "MIT" } diff --git a/refactron-py/LICENSE b/refactron-py/LICENSE new file mode 100644 index 00000000..28599897 --- /dev/null +++ b/refactron-py/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Refactron AI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/refactron-py/README.md b/refactron-py/README.md new file mode 100644 index 00000000..9ec37022 --- /dev/null +++ b/refactron-py/README.md @@ -0,0 +1,26 @@ +# refactron (Python wrapper) + +This is a thin Python wrapper around the [npm `refactron` package](https://npmjs.com/package/refactron). +It exists so users with a Python-first toolchain can `pip install refactron` +and get the CLI on their PATH without manually installing Node. + +## Install + +```bash +pip install refactron +``` + +Requires Node.js 18+ on your PATH. If `refactron` isn't already installed +via npm, this wrapper will run `npm install -g refactron` on first use. + +## Usage + +Identical to the npm package: + +```bash +refactron analyze . +refactron run --apply +refactron document --apply +``` + +Full docs: https://docs.refactron.dev diff --git a/refactron-py/pyproject.toml b/refactron-py/pyproject.toml new file mode 100644 index 00000000..ec282754 --- /dev/null +++ b/refactron-py/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "refactron" +version = "0.2.0" +description = "Safety-first refactoring CLI for Python and TypeScript (Python wrapper around the npm package)" +readme = "README.md" +requires-python = ">=3.8" +license = { text = "MIT" } +authors = [{ name = "Om Sherikar", email = "omsherikar0229@gmail.com" }] +keywords = ["refactoring", "python", "typescript", "ast", "cli"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Code Generators", +] +dependencies = [] + +[project.scripts] +refactron = "refactron.cli:main" + +[project.urls] +Homepage = "https://github.com/Refactron-ai/Refactron_Lib_TS" +Documentation = "https://docs.refactron.dev" +Issues = "https://github.com/Refactron-ai/Refactron_Lib_TS/issues" diff --git a/refactron-py/refactron/__init__.py b/refactron-py/refactron/__init__.py new file mode 100644 index 00000000..543a68aa --- /dev/null +++ b/refactron-py/refactron/__init__.py @@ -0,0 +1,7 @@ +"""Python wrapper around the Refactron npm CLI. + +This package is a thin shim — all real work happens in the npm @refactron +package. Installing this gives you the `refactron` command on your PATH +even if you only have Python tooling. +""" +__version__ = "0.2.0" diff --git a/refactron-py/refactron/cli.py b/refactron-py/refactron/cli.py new file mode 100644 index 00000000..fcf7c9f2 --- /dev/null +++ b/refactron-py/refactron/cli.py @@ -0,0 +1,76 @@ +"""Detect Node.js, ensure the npm refactron CLI is available, then exec it.""" +from __future__ import annotations + +import os +import shutil +import subprocess +import sys + + +REQUIRED_NODE_MAJOR = 18 +NPM_PACKAGE = "refactron" + + +def _have(cmd: str) -> bool: + return shutil.which(cmd) is not None + + +def _node_version_ok() -> bool: + try: + out = subprocess.check_output(["node", "--version"], text=True).strip() + # "v20.11.1" -> 20 + major = int(out.lstrip("v").split(".")[0]) + return major >= REQUIRED_NODE_MAJOR + except (subprocess.SubprocessError, ValueError, OSError): + return False + + +def _ensure_npm_cli() -> str: + """Return the absolute path to the `refactron` CLI executable. + + If `refactron` is on PATH, use it. Otherwise install via `npm install -g` + (best-effort) and re-check. If still missing, raise.""" + if _have("refactron"): + return shutil.which("refactron") # type: ignore[return-value] + + # Try a one-shot global install via npm. + if _have("npm"): + subprocess.check_call(["npm", "install", "-g", NPM_PACKAGE], stdout=sys.stderr) + if _have("refactron"): + return shutil.which("refactron") # type: ignore[return-value] + + raise SystemExit( + "refactron: the underlying Node CLI is not installed. " + "Run `npm install -g refactron` or use `npx refactron` directly." + ) + + +def main() -> "int | None": + if not _have("node"): + print( + "refactron: Node.js 18+ is required. Install Node from https://nodejs.org " + "or use a version manager like nvm.", + file=sys.stderr, + ) + return 1 + if not _node_version_ok(): + print( + f"refactron: Node.js {REQUIRED_NODE_MAJOR}+ required (you have an older version).", + file=sys.stderr, + ) + return 1 + + cli = _ensure_npm_cli() + # exec replaces this process so signals (Ctrl+C) and exit codes pass + # through cleanly. On Windows os.execvp is shimmed; if it fails we fall + # back to subprocess.run so the user still gets a usable CLI. + args = [cli, *sys.argv[1:]] + try: + os.execvp(cli, args) + except OSError: + completed = subprocess.run(args) + return completed.returncode + + +if __name__ == "__main__": + sys.exit(main() or 0) diff --git a/tape/demo.tape b/tape/demo.tape new file mode 100644 index 00000000..e0ca6ced --- /dev/null +++ b/tape/demo.tape @@ -0,0 +1,52 @@ +# tape/demo.tape — terminal recording for the README demo GIF. +# Generate: brew install vhs && vhs tape/demo.tape + +Output docs/assets/demo.gif +Set FontSize 14 +Set Width 960 +Set Height 600 +Set Theme "Dracula" +Set TypingSpeed 50ms +Set PlaybackSpeed 1.0 + +# Use a copy of the python-legacy-mini fixture as the demo target. +Hide +Type "cp -R fixtures/python-legacy-mini /tmp/refactron-demo && cd /tmp/refactron-demo" +Enter +Sleep 500ms +Show + +# `refactron login` boots the app. If you're not authenticated it shows the +# OAuth device-flow UI; if you are (as on this host), it skips straight to +# the REPL. From the REPL the verbs are bare (analyze, run, document), no +# `refactron ` prefix. +Type "refactron login" +Enter +Sleep 3s + +Type "analyze ." +Enter +Sleep 4s + +Type "run --dry-run ." +Enter +Sleep 5s + +Type "run --apply ." +Enter +Sleep 6s + +Type "document --apply" +Enter +Sleep 4s + +Type "exit" +Enter +Sleep 500ms + +# Clean up before the recording ends so subsequent reruns from the same +# checkout don't see the mutated fixture. +Hide +Type "cd - && rm -rf /tmp/refactron-demo" +Enter +Show