feat: perry dev — V1 watch mode (auto-recompile + relaunch on save)#126
feat: perry dev — V1 watch mode (auto-recompile + relaunch on save)#126proggeramlug merged 2 commits intoPerryTS:mainfrom
Conversation
…0.5.143) New `perry dev <entry.ts>` subcommand: watches the nearest project root (package.json / perry.toml, falls back to entry's parent) recursively via the `notify` crate, debounces 300ms, recompiles on any .ts|.tsx|.mts|.cts|.json|.toml change, kills the running child, and relaunches the fresh binary. Ignores node_modules, target, .git, dist, build, .perry-dev, .perry-cache. Output defaults to .perry-dev/<stem>. Args after `--` forward to the child (perry dev src/main.ts -- --port 3000). Smoke-tested: initial build ~15s (cold auto-optimize), post-edit rebuild ~330ms (hot libs cached). 8 unit tests cover the pure helpers (is_trigger_path, is_relevant, find_project_root). tempfile added as dev-dep. V2 follow-ups planned: in-memory AST cache + per-module .o reuse for incremental compilation. (Rebased onto main and bumped from v0.5.142 → v0.5.143; the 0.5.142 slot was already taken by two fixes on main — fs/roundtrip cross-platform path and async-closure Promise return — between PR author and merge.)
PR PerryTS#126 shipped the `perry dev` watch-mode subcommand but not the docs page. Adding it on the contributor's behalf as part of the merge so the CLI reference stays in sync with what ships. Covers: usage examples, flag table, mechanics (project-root discovery, debounce, kill-then-rebuild order), trigger extension allowlist and ignored-directory list, benchmark table, and the V2 incremental-cache roadmap called out in the PR description.
17ca849 to
0d80146
Compare
Surfaced by PR #126 (first external contributor): main shipped two v0.5.142 commits while the PR was open, and the PR's own v0.5.142 bump turned into a merge conflict that had nothing to do with the feature. Fix the rule rather than the symptom: external contributors leave `[workspace.package] version`, the `**Current Version:**` line in CLAUDE.md, and the "Recent Changes" entry alone. Maintainer folds that metadata in at merge time via rebase + amend. No code change, no version bump for this commit — it's pure policy documentation.
|
Merged. Thanks enormously, @TheHypnoo — this is Perry's first external PR and a genuinely solid one to start with. The debounced-watcher design, the pure-helper testing split, and the Two small notes on the merge mechanics so the force-push history doesn't look mysterious:
Off the back of your PR we also carved out a repo policy (coming shortly in a follow-up commit): external contributors should leave The V2 follow-ups you called out in the PR (in-memory AST cache, per-module Thanks again for the excellent bring-up. |
First inbound external PR (#126) landed today, and a second substantial one (#127) is in flight — time to make the project legible as a real community project instead of a solo push. Adds: - CONTRIBUTING.md — build-from-source prereqs derived from .github/workflows/test.yml, PR guidelines (including the "don't bump version or touch CLAUDE.md" rule we carved out this week), conventional- commit guidance as loose convention, how to claim an issue, pointers to good-first-issue / help-wanted labels. - CODE_OF_CONDUCT.md — Contributor Covenant 2.1 verbatim, contact email set to ralph@skelpo.com. Fetched directly from the EthicalSource canon. - .github/ISSUE_TEMPLATE/bug_report.md — requires Perry version, target, host OS, and a minimal repro. - .github/ISSUE_TEMPLATE/feature_request.md — problem / proposed solution / alternatives / scope estimate. - .github/ISSUE_TEMPLATE/config.yml — disables blank issues, routes questions to Discussions and security reports to email. - .github/pull_request_template.md — leads with "do NOT bump version or edit CLAUDE.md" so the next contributor doesn't retrace #126's version-collision friction. Explicitly NOT doing (decided earlier in the design pass): - MAINTAINERS.md — skipped until there's a real list beyond "Ralph, everything". - All Contributors bot — defer until contributor volume justifies the badge wall + permanent bot webhook. - Community docs page inside mdBook — would duplicate CONTRIBUTING.md and go stale. No DCO / CLA required; commit style is guidance-only, not enforced. No code or build-config changes.
Summary
New
perry dev <entry.ts>subcommand: watches the project tree, recompiles on save, and relaunches the compiled binary. V1 — no caching, just "save → fast restart".The goal is to close the gap with Node's
tsx watch/ Vite's dev UX for Perry users, without waiting for the deeper incremental-compilation work. This PR is the minimal shippable slice; two larger follow-ups are called out at the bottom.What's in scope
One commit (v0.5.142), self-contained:
crates/perry/src/commands/dev.rs— new subcommand. Uses thenotifycrate (already in workspace deps) with a 300 ms debounce window so editor "save storms" collapse into one build.Watches the nearest project root discovered by walking up from the entry file until a
package.jsonorperry.tomlis found; falls back to the entry's parent directory. Extra roots accepted via--watch.Trigger extensions:
.ts | .tsx | .mts | .cts | .json | .toml. Ignored directories (never watched, never retrigger):node_modules,target,.git,dist,build,.perry-dev,.perry-cache.Rebuilds by calling
super::compile::run()directly — no subprocess, no startup overhead. Kills the previous child before overwriting the binary (avoids the link step racing a running process on macOS/Linux and hard-failing on Windows).Output defaults to
.perry-dev/<stem>so dev artifacts don't pollute the CWD. Args after--forward to the child:perry dev src/main.ts -- --port 3000.8 unit tests for the pure helpers:
is_trigger_path— extension allowlist +IGNORED_DIRStraversal (the filter most likely to silently break the feature).is_relevant—Modify/Create/Removepass,Accessand errors rejected.find_project_root— findspackage.json/perry.toml, falls back tostartwhen no marker exists.tempfileadded as a[dev-dependencies](not shipped in release).Wiring:
Commands::Dev(DevArgs)variant,"dev"added to the legacy-invocation matcher soperry file.tsstill works. No telemetry on dev (sessions trigger many recompiles — would be noisy).Benchmark
Smoke-tested locally on
/tmp/perry-dev-smoke/main.ts(trivialconsole.log):The 45× speedup on hot rebuilds comes entirely from Perry's existing auto-optimize library cache — nothing in this PR re-engineers the build pipeline. That's also the ceiling: a trivial one-file program rebuilds in ~330 ms because that's the cost of LLVM codegen + link for one
.tsfile with libs already on disk. Real projects with multiple modules will recompile all of them on every change, which is where the follow-up work pays off.What's explicitly NOT in this PR
V1 is deliberately thin — ship something useful today, iterate.
.ocache on disk — only re-codegen the changed module, relink the rest. Planned for V2 (this is where the real win is for non-trivial projects).saveState/loadStatehook so long-running dev sessions don't lose in-memory counters etc. on rebuild). Needs design work; not in scope.Happy to fold V2 into this PR instead of a follow-up if that's preferred — let me know. Splitting it that way because the caching work touches the compile pipeline and is materially more invasive than what's here.
Test plan
cargo test -p perry --bin perry commands::dev— 8/8 passcargo check -p perry— clean (only pre-existing warnings).ts, observe rebuild log + relaunched child stdoutperry dev nonexistent.ts→ clear error, no panicperry dev src/main.ts -- --port 3000→ child receives args