diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c8c5dbd..7947e96 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ - + ## What & why @@ -8,8 +8,8 @@ - [ ] `pnpm typecheck` passes - [ ] `pnpm test` passes -- [ ] Verified in the running app (`pnpm dev`) — if this touches UI or behaviour -- [ ] Updated the relevant `specs/` file + `specs/PROGRESS.md` — if behaviour changed +- [ ] Verified in the running app (`pnpm dev`) - if this touches UI or behaviour +- [ ] Updated the relevant `specs/` file + `specs/PROGRESS.md` - if behaviour changed ## Notes diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cdda6ab --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: CI + +# Typecheck + tests on every push to main and every PR. Until now the only workflow was the +# tag-triggered release, so build/test rot (or a contributor's broken PR) was only discovered +# during a signed release run (IMPROVEMENT-PLAN 12.1). CONTRIBUTING.md promises `pnpm test` +# must pass before merge - this enforces it. + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + # macOS matches the release runner so the better-sqlite3 / node-pty electron-rebuild + # postinstall is exercised the same way it will be at release time. + runs-on: macos-14 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v5 + + - name: Set up Python 3.11 (node-gyp needs distutils) + uses: actions/setup-python@v6 + with: + python-version: '3.11' + + - uses: pnpm/action-setup@v6 + with: + version: 10 + + - uses: actions/setup-node@v5 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Typecheck + run: pnpm typecheck + + - name: Lint + run: pnpm lint + + - name: Test + run: pnpm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff4f3d5..a7c4b52 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,21 +24,37 @@ jobs: runs-on: macos-14 timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - # node-gyp 9 (used by @electron/rebuild when compiling native modules — e.g. node-pty + # Guard (tag pushes only): a release must be cut from a v* tag whose number matches + # package.json. Manual workflow_dispatch runs skip the guard but build with + # --publish never (see the build step) - a safe dry-run for testing the pipeline. + - name: Guard - tag matches package.json version + if: github.event_name == 'push' + run: | + if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then + echo "::error::Releases must be cut from a v* tag (got ${GITHUB_REF})."; exit 1 + fi + VERSION=$(grep -m1 '"version"' package.json | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') + TAG="${GITHUB_REF_NAME#v}" + if [ "${VERSION}" != "${TAG}" ]; then + echo "::error::package.json version (${VERSION}) does not match tag (${TAG})."; exit 1 + fi + echo "Releasing v${VERSION} from tag ${GITHUB_REF_NAME}." + + # node-gyp 9 (used by @electron/rebuild when compiling native modules - e.g. node-pty # for the cross-arch x64 build) imports the stdlib `distutils`, which Python 3.12 # removed (PEP 632). macos-14 runners default to 3.12, so pin 3.11 which still has it. - name: Set up Python 3.11 (node-gyp needs distutils) - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.11' - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v6 with: version: 10 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: 22 cache: pnpm @@ -67,11 +83,13 @@ jobs: # ~103MB arm64 DMG; Intel users the x64 DMG. electron-builder writes a single # latest-mac.yml listing both zips, and electron-updater fetches the slice that # matches the running machine. Smaller downloads than a combined universal binary. - run: pnpm exec electron-vite build && pnpm exec electron-builder --mac --arm64 --x64 --publish always + # Tag pushes publish; manual dispatch runs are dry-runs (build + sign only, artifacts + # uploaded by the next step) so the pipeline can be exercised without touching releases. + run: pnpm exec electron-vite build && pnpm exec electron-builder --mac --arm64 --x64 --publish ${{ github.event_name == 'push' && 'always' || 'never' }} - name: Upload artifacts (as backup) if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: mac-dmg path: | diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ddbd3e2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,15 @@ +# Generated / machine-managed +pnpm-lock.yaml +dist/ +out/ +node_modules/ +*.tsbuildinfo + +# Hand-formatted corpora a blanket `pnpm format` must not churn: +# specs are the project's source-of-truth documents; the workflows are the signed +# release pipeline; CHANGELOG follows keep-a-changelog conventions. +specs/ +.github/ +CHANGELOG.md +README.md +electron-builder.yml diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..0ac7c30 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "tabWidth": 2 +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c084ac..59a0bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,19 +6,86 @@ All notable changes to DevHarbor are documented here. The format is based on ## [Unreleased] -## [1.0.1] — 2026-06-07 +## [1.1.0] - 2026-06-14 + +A hardening + feature release driven by a comprehensive internal audit (see +`specs/IMPROVEMENT-PLAN.md`). + +> **Downgrading:** after running 1.1.0 once, secret env values are encrypted at rest and the +> database schema is upgraded. Going back to 1.0.x is not supported - export a backup first +> (Settings → Danger zone → Export) if you want a safety net. + +### Added +- **Menubar (tray) presence** - see aggregate state at a glance; start/stop any app, see its + ports, or stop everything without opening the window. Toggle in Settings. +- **Desktop notifications** when an app crashes (on by default) and, optionally, when an app + becomes ready. +- **Bulk import** - point DevHarbor at a folder of repos and register every detected project + in one pass (sidebar `+` menu, ⌘K, or the welcome screen). +- **Global log search (⌘⇧F)** - search every running task's logs at once; jump to the app. +- **Monorepo detection** - pnpm/yarn/npm workspaces are detected on add, with a one-click + "create a task per workspace package" option. +- **Launch at login** and per-app **start automatically** toggles. +- **Start all / Stop all** per folder, and keyboard reordering for folders (Move up / down). +- **Quit confirmation** that gracefully stops running servers (SIGTERM → grace → kill) before + exiting - auto-update installs included. +- **Port-conflict callout** - a crash caused by a busy port now names the port and, when + known, which app holds it. +- Update banner shows **release notes** before "Quit & install"; **Check for Updates…** and + **Open Logs Folder** in the Help menu; update checks repeat every 6 h on long-running Macs. +- Local **diagnostics log** (`~/Library/Logs/DevHarbor/`) for bug reports - local-only, in + keeping with the no-telemetry promise. +- Window size and position are remembered across launches. + +### Changed +- **Secret env values are encrypted at rest** (macOS Keychain-backed `safeStorage`); existing + plaintext secrets are migrated on first launch. +- Project `.env` files can no longer override env vars you set in DevHarbor, nor + process-control variables (`PATH`, `NODE_OPTIONS`, `DYLD_*`) - the UI is the source of + truth, and a checked-in `.env` can't hijack the spawned process. +- `.env` parsing now matches dotenv semantics: multiline quoted values (PEM keys), `export ` + prefixes, and trailing comments; `.env.development` / `.env.development.local` variants + load in the conventional order. +- Stopping a task now signals the **whole process tree** (graceful SIGTERM first), so dev + servers get a clean shutdown instead of skipping straight to a hard kill. +- Hardened packaged binaries: Electron fuses disable `ELECTRON_RUN_AS_NODE`, `NODE_OPTIONS`, + and `--inspect`; app code loads only from the asar archive. +- Keyboard + screen-reader support across the app: focus-trapped dialogs with Escape, + arrow-key menus and comboboxes, focusable sidebar rows, accessible status indicators, + screen-reader-readable logs, and a light terminal theme. +- Faster under load: batched process polling (one `ps`/`lsof` per tick instead of dozens of + forks), a global log-memory budget, render-throttled log filtering, and log streaming only + for visible tasks. + +### Fixed +- **Restart-on-change** now works (the file watcher had been silently inert after a + dependency major bump) - with regression tests. +- Apps that had ever run can be **removed** again; lifecycle errors (missing Node version, + moved folder, no tasks) now surface as messages from every view instead of failing silently. +- A failed start no longer leaves the app stuck on "Starting"; Stop is instant even while an + app is still starting; readiness probes time out instead of hanging forever. +- One-shot tasks (`exit` readiness) are no longer marked ready before they finish. +- Deleted task env vars no longer resurrect after a relaunch; task-scoped overrides of an + app-level key now save correctly. +- Database export/reset are WAL-safe (backups no longer miss recent changes); a corrupt + database produces a recovery dialog instead of a silent no-window launch. +- Env editor warns before discarding unsaved changes and confirms saves; long app names + truncate instead of breaking layouts; the welcome screen no longer flashes on startup for + existing users; many smaller fixes. + +## [1.0.1] - 2026-06-07 ### Changed - Canonical website domain is now **www.devharbor.app** (About panel, Help menu, repo homepage). -## [1.0.0] — 2026-06-07 +## [1.0.0] - 2026-06-07 First public stable release: code-signed, notarized, and auto-updating. ### Added - **Code-signed + notarized** macOS builds with hardened runtime; **per-architecture** (Apple Silicon + Intel) DMG/zip artifacts and GitHub-based **auto-update** via `electron-updater`. -- **Folders** in the sidebar — group apps, drag to reorder, rename/delete, collapse; a glowing dot marks collapsed folders that contain a running app. -- **Tags** — chip input with autocomplete in app settings; group the sidebar by tag, filter on the dashboard, and search tags in ⌘P. +- **Folders** in the sidebar - group apps, drag to reorder, rename/delete, collapse; a glowing dot marks collapsed folders that contain a running app. +- **Tags** - chip input with autocomplete in app settings; group the sidebar by tag, filter on the dashboard, and search tags in ⌘P. - **Group-by switcher** (Folder ⇄ Tag) and a **shared sort** (Name · Recently used · Running first) across the sidebar and dashboard. - **Folder picker** combobox in app settings (select existing or create new). - Open-source community files (CONTRIBUTING, Code of Conduct, Security policy, issue/PR templates). @@ -31,12 +98,12 @@ First public stable release: code-signed, notarized, and auto-updating. - Detail-page lifecycle buttons read **Start app / Stop app / Restart app**. ### Fixed -- Stopped apps now read **Stopped/Crashed** consistently — including after the teardown window and across app restarts (persisted from run history) — instead of flickering back to Idle. +- Stopped apps now read **Stopped/Crashed** consistently - including after the teardown window and across app restarts (persisted from run history) - instead of flickering back to Idle. - Single-instance relaunch always surfaces a window (no more "running but no window" dead state). - Smaller install: renderer libraries are bundled, not double-shipped as `node_modules` (asar ~40 MB → ~4 MB). - Numerous correctness fixes from an internal review (atomic app-add, env parsing, stale stats, navigation origin checks, folder-state drift). -## [0.1.0] — 2026-05-30 +## [0.1.0] - 2026-05-30 First public preview. macOS-only (Apple Silicon). @@ -50,7 +117,8 @@ First public preview. macOS-only (Apple Silicon). - Dashboard control room, folder organization, and a ⌘K command palette. - Local-only storage (SQLite). No accounts, no telemetry. -[Unreleased]: https://github.com/jainath/devharbor/compare/v1.0.1...HEAD +[Unreleased]: https://github.com/jainath/devharbor/compare/v1.1.0...HEAD +[1.1.0]: https://github.com/jainath/devharbor/compare/v1.0.1...v1.1.0 [1.0.1]: https://github.com/jainath/devharbor/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/jainath/devharbor/compare/v0.1.0...v1.0.0 [0.1.0]: https://github.com/jainath/devharbor/releases/tag/v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d0feda..73d0479 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing to DevHarbor Thanks for your interest in DevHarbor! This is a macOS-first desktop app for managing -local Node.js dev servers. Contributions of all kinds are welcome — bug reports, fixes, +local Node.js dev servers. Contributions of all kinds are welcome - bug reports, fixes, features, docs, and design feedback. By participating you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md). @@ -20,11 +20,11 @@ pnpm test # vitest ## Prerequisites -- **macOS** (Apple Silicon or Intel). The app is macOS-only today — it relies on +- **macOS** (Apple Silicon or Intel). The app is macOS-only today - it relies on `lsof`/`pgrep`, `open -a`, and Unix Node-manager paths. Windows/Linux are not supported yet (see the roadmap). - **Node 22+** (an `.nvmrc` pins the version) and **pnpm** (`npm i -g pnpm`). -- **Xcode Command Line Tools** (`xcode-select --install`) — needed to rebuild the native +- **Xcode Command Line Tools** (`xcode-select --install`) - needed to rebuild the native modules (`better-sqlite3`, `node-pty`). If `pnpm install` fails rebuilding native modules, see @@ -37,7 +37,7 @@ src/main/ Electron main process (services, IPC, DB, menu) src/preload/ contextBridge API surface src/renderer/ React UI (components, store, hooks) src/shared/ shared types + the typed IPC contract (ipc.ts) -specs/ the source of truth — read before non-trivial work +specs/ the source of truth - read before non-trivial work ``` ## The specs are the source of truth @@ -48,16 +48,16 @@ DevHarbor is **spec-driven**. Before non-trivial work, read the relevant file in [`specs/PROGRESS.md`](specs/PROGRESS.md) for the current build status. Quick map: -- [`specs/01-architecture.md`](specs/01-architecture.md) — process model, IPC, stack -- [`specs/02-data-model.md`](specs/02-data-model.md) — SQLite schema + core types -- [`specs/03-features.md`](specs/03-features.md) — every feature with acceptance criteria -- [`specs/04-ui.md`](specs/04-ui.md) — screens, layout, interactions +- [`specs/01-architecture.md`](specs/01-architecture.md) - process model, IPC, stack +- [`specs/02-data-model.md`](specs/02-data-model.md) - SQLite schema + core types +- [`specs/03-features.md`](specs/03-features.md) - every feature with acceptance criteria +- [`specs/04-ui.md`](specs/04-ui.md) - screens, layout, interactions ## Development workflow 1. **Fork** the repo and create a branch off `main`: `git checkout -b fix/short-description` -2. Make your change. Keep it focused — one logical change per PR. +2. Make your change. Keep it focused - one logical change per PR. 3. **Match the surrounding code.** Follow existing naming, comment density, and idioms. 4. Run the checks locally: ```bash @@ -76,7 +76,7 @@ Quick map: `sandbox: true`. Don't loosen these. - **Native modules** (`better-sqlite3`, `node-pty`) must be rebuilt for Electron's ABI (`pnpm rebuild`), not Node's. -- **Zustand selectors must return stable references** — `?? []` / `?? {}` inside a +- **Zustand selectors must return stable references** - `?? []` / `?? {}` inside a selector causes infinite re-render loops. Use module-level constants. ## Tests @@ -95,7 +95,7 @@ Quick map: ## Reporting bugs / requesting features Use the [issue templates](.github/ISSUE_TEMPLATE/). For security issues, **do not open a -public issue** — see [SECURITY.md](SECURITY.md). +public issue** - see [SECURITY.md](SECURITY.md). ## License @@ -104,4 +104,4 @@ By contributing, you agree that your contributions will be licensed under the pr submit the work under that license. Note the [trademark policy](TRADEMARK.md): the code is AGPL, but the **DevHarbor name and logo -are not** — forks must ship under a different name. +are not** - forks must ship under a different name. diff --git a/README.md b/README.md index 5d12c8f..d5f92b0 100644 --- a/README.md +++ b/README.md @@ -4,47 +4,66 @@ [![Platform: macOS](https://img.shields.io/badge/platform-macOS%20(Apple%20Silicon%20%2B%20Intel)-black.svg)](#install) [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) -**A harbor for your local dev servers.** macOS-first desktop app for managing local Node.js projects. Register your apps once, then start/stop/restart them, switch Node versions per project, edit env vars, watch logs, organize them into folders, and see what's running — all without leaving one window. +**A harbor for your local dev servers.** macOS-first desktop app for managing local Node.js projects. Register your apps once, then start/stop/restart them, switch Node versions per project, edit env vars, watch logs, organize them into folders, and see what's running, all without leaving one window. -Think of it like a "Postman for your local dev servers." Everything stays local — no accounts, no cloud, no telemetry. +Think of it like a "Postman for your local dev servers." Everything stays local: no accounts, no cloud, no telemetry. Website: [www.devharbor.app](https://www.devharbor.app) +## Highlights + +- **Register once, run forever** - point DevHarbor at a project (or **import a whole folder + of repos** at once); it detects the package manager, Node version, scripts, and `.env` + files. Monorepos get an offer to create a task per workspace package. +- **Multi-task apps** - services start in dependency order with readiness signals (port, + log regex, exit, delay) and stop gracefully in reverse - whole process tree, no orphans. +- **The right Node, every time** - per-app version resolution across nvm / fnm / Volta / + asdf / system, honoring `.nvmrc` / `engines.node`. +- **Live in the menubar** - see what's running, start/stop apps, and check ports without + opening the window; desktop notifications when something crashes. +- **Logs that work like a terminal** - xterm rendering with search, a regex filter view, + and **⌘⇧F to search across every running app at once**. Crash banners pin the last + output and call out port conflicts by name. +- **Layered env vars** - global → app → task, with `.env` watching and secret values + **encrypted at rest** (macOS Keychain). Your settings always win over a repo's `.env`. +- **Keyboard-first** - ⌘K palette, folders/tags, dashboard with live CPU/memory, per-app + auto-start + launch at login, signed/notarized builds with auto-update. + ## Install macOS (Apple Silicon or Intel). From [**Releases**](https://github.com/jainath/devharbor/releases), -download `devharbor--arm64.dmg` for Apple Silicon (M-series) or `…-x64.dmg` for Intel, +download `DevHarbor--arm64.dmg` for Apple Silicon (M-series) or `…-x64.dmg` for Intel, open it, and drag DevHarbor to Applications. (Not sure? Apple menu → About This Mac → "Chip" says "Apple" for arm64.) -> Releases are signed with a Developer ID and notarized by Apple, so they open normally — no right-click bypass needed. +> Releases are signed with a Developer ID and notarized by Apple, so they open normally - no right-click bypass needed. ## Contributing -Contributions welcome! Start with [CONTRIBUTING.md](CONTRIBUTING.md) — it covers dev setup +Contributions welcome! Start with [CONTRIBUTING.md](CONTRIBUTING.md) - it covers dev setup (`pnpm install && pnpm dev`), the spec-driven workflow, and the PR checklist. Please also read the [Code of Conduct](CODE_OF_CONDUCT.md). For security issues, see [SECURITY.md](SECURITY.md). ## Status -See [`specs/PROGRESS.md`](specs/PROGRESS.md) for the build-status matrix — that file is authoritative. +See [`specs/PROGRESS.md`](specs/PROGRESS.md) for the build-status matrix - that file is authoritative. The `specs/` folder is the source of truth for this project. See [`specs/WORKFLOW.md`](specs/WORKFLOW.md) for the rules of the road (read-before / update-before-code, step-back review after every change, etc.). ## Where to start -1. [`specs/WORKFLOW.md`](specs/WORKFLOW.md) — how we work -2. [`specs/PROGRESS.md`](specs/PROGRESS.md) — what's actually built right now -3. [`specs/00-overview.md`](specs/00-overview.md) — problem, goals, non-goals -4. [`specs/01-architecture.md`](specs/01-architecture.md) — tech stack, process model, IPC -5. [`specs/02-data-model.md`](specs/02-data-model.md) — SQLite schema and core types (incl. multi-task model) -6. [`specs/03-features.md`](specs/03-features.md) — every feature, with acceptance criteria -7. [`specs/04-ui.md`](specs/04-ui.md) — screens, layout, key interactions -8. [`specs/05-roadmap.md`](specs/05-roadmap.md) — phased delivery plan +1. [`specs/WORKFLOW.md`](specs/WORKFLOW.md) - how we work +2. [`specs/PROGRESS.md`](specs/PROGRESS.md) - what's actually built right now +3. [`specs/00-overview.md`](specs/00-overview.md) - problem, goals, non-goals +4. [`specs/01-architecture.md`](specs/01-architecture.md) - tech stack, process model, IPC +5. [`specs/02-data-model.md`](specs/02-data-model.md) - SQLite schema and core types (incl. multi-task model) +6. [`specs/03-features.md`](specs/03-features.md) - every feature, with acceptance criteria +7. [`specs/04-ui.md`](specs/04-ui.md) - screens, layout, key interactions +8. [`specs/05-roadmap.md`](specs/05-roadmap.md) - phased delivery plan ## Stack (as built) -Electron 33 + Vite + React 18 + TypeScript · Tailwind 3 (hand-rolled components; theme via semantic CSS variables driven by the **Radix Colors slate scale** — same palette family shadcn/ui, Linear, and Vercel use) · Zustand · better-sqlite3 · `@homebridge/node-pty-prebuilt-multiarch` · `@xterm/xterm` (+ fit, search, webgl) · chokidar · pidusage · `package-manager-detector` · cmdk · react-window + anser · electron-builder + electron-updater · Vitest. +Electron 33 + Vite + React 18 + TypeScript · Tailwind 3 (hand-rolled components; theme via semantic CSS variables driven by the **Radix Colors slate scale** - same palette family shadcn/ui, Linear, and Vercel use) · Zustand · better-sqlite3 · `@homebridge/node-pty-prebuilt-multiarch` · `@xterm/xterm` (+ fit, search, webgl) · chokidar · pidusage · `package-manager-detector` · cmdk · react-window + anser · electron-builder + electron-updater · Vitest. See [`specs/01-architecture.md`](specs/01-architecture.md) for the full rationale and [`specs/PROGRESS.md`](specs/PROGRESS.md) for current build status. @@ -52,9 +71,9 @@ See [`specs/01-architecture.md`](specs/01-architecture.md) for the full rational [GNU AGPL-3.0](LICENSE) © 2026 Jainath Ponnala -DevHarbor is free and open source. You may use, study, modify, and share it — but if you +DevHarbor is free and open source. You may use, study, modify, and share it - but if you distribute a modified version, or run a modified version as a network service, you must release your source under the same AGPL-3.0 license. See [`LICENSE`](LICENSE) for the full terms. -The **name "DevHarbor" and its logo are trademarks** and are *not* covered by the AGPL — see +The **name "DevHarbor" and its logo are trademarks** and are *not* covered by the AGPL - see [`TRADEMARK.md`](TRADEMARK.md). Forks are welcome, but please ship them under a different name. diff --git a/RELEASE.md b/RELEASE.md index 3c7066f..2ecf17b 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,7 +2,7 @@ DevHarbor ships as a **code-signed, notarized** macOS app and auto-updates from **GitHub Releases** via `electron-updater`. Releases are triggered by pushing a -`v*` tag — GitHub Actions builds, signs, notarizes, and publishes the artifacts. +`v*` tag - GitHub Actions builds, signs, notarizes, and publishes the artifacts. ## One-time setup @@ -55,19 +55,33 @@ git push origin main --tags ``` The `Release` workflow (`.github/workflows/release.yml`) runs on `macos-14`, builds -**arm64 + x64** (`--mac --arm64 --x64 --publish always`), signs + notarizes, and creates +**arm64 + x64** (`--mac --arm64 --x64`), signs + notarizes, and creates the GitHub Release with `*.dmg`, `*.zip`, `*.blockmap`, and `latest-mac.yml`. -> The git **tag** must match `package.json` `version` (prefixed with `v`) or -> `electron-builder` will reject the publish. +> A guard step fails the workflow unless it was triggered by a `v*` **tag whose number +> matches `package.json` `version`** - so a stale version bump is caught before any +> signing/publishing happens. + +### Dry-run the pipeline (no publish) + +Trigger the workflow manually (**Actions → Release → Run workflow**, any branch). Manual +dispatch runs skip the tag guard and build with `--publish never`: the full +build + sign + notarize path is exercised and the artifacts are uploaded to the workflow +run (not to a Release). Use this to validate toolchain changes - runner images, action +versions, certificate renewals - without burning a version number. + +### CI + +`.github/workflows/ci.yml` runs typecheck, ESLint, and the test suite on every push/PR to +`main` (also on `macos-14`, so the native-module rebuild path matches the release runner). ## Verify after publish 1. Download the `arm64` (Apple Silicon) or `x64` (Intel) `.dmg` from the Release on a - clean Mac — it should open **without** the right-click→Open bypass (proves notarization). + clean Mac - it should open **without** the right-click→Open bypass (proves notarization). 2. Auto-update: install the *previous* version, launch it, and confirm the in-app updater detects, downloads, and installs the new one. (Auto-update only works on - **signed** builds — `squirrel.mac` verifies the Developer ID signature.) + **signed** builds - `squirrel.mac` verifies the Developer ID signature.) ## Artifacts & channels diff --git a/SECURITY.md b/SECURITY.md index 27ad0de..485debd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,7 +11,7 @@ DevHarbor is in active development. Only the latest released version receives fi ## Reporting an issue -Please report security concerns **privately** — do **not** open a public GitHub issue. +Please report security concerns **privately** - do **not** open a public GitHub issue. - Preferred: GitHub's **[private vulnerability reporting](https://github.com/jainath/devharbor/security/advisories/new)** (repo → **Security** tab → **Report a vulnerability**). diff --git a/TRADEMARK.md b/TRADEMARK.md index df08302..6bc235b 100644 --- a/TRADEMARK.md +++ b/TRADEMARK.md @@ -13,7 +13,7 @@ licensed under the AGPL-3.0 and are **not** part of the open-source grant. - Use the Marks to **refer to** the official DevHarbor project (e.g. "compatible with DevHarbor", "based on DevHarbor"), as long as it's truthful and doesn't imply endorsement or affiliation. -- Fork and modify the source code under the AGPL — that's encouraged. +- Fork and modify the source code under the AGPL - that's encouraged. ## What you need to do when you fork or redistribute @@ -24,7 +24,7 @@ If you distribute a modified version (a fork, a rebuild, a derivative product), - Not use the DevHarbor logo or brand assets in your distribution. - Make clear it is your build, not the official project. -(Unmodified builds straight from this repository may keep the DevHarbor name — this restriction +(Unmodified builds straight from this repository may keep the DevHarbor name - this restriction is about *modified* or *competing* distributions.) ## Why diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist index f875a5b..0f6c71e 100644 --- a/build/entitlements.mac.plist +++ b/build/entitlements.mac.plist @@ -16,19 +16,14 @@ com.apple.security.cs.allow-dyld-environment-variables com.apple.security.cs.disable-library-validation - com.apple.security.cs.disable-executable-page-protection - com.apple.security.files.user-selected.read-write com.apple.security.inherit diff --git a/build/fuses.cjs b/build/fuses.cjs new file mode 100644 index 0000000..019f9dc --- /dev/null +++ b/build/fuses.cjs @@ -0,0 +1,33 @@ +const { flipFuses, FuseVersion, FuseV1Options } = require('@electron/fuses'); +const path = require('node:path'); + +/** + * electron-builder afterPack hook - runs AFTER packaging but BEFORE code-signing, so the + * fuse flips are covered by the signature. Disables the generic-Node-interpreter escape + * hatches that would otherwise let any local process execute arbitrary code under DevHarbor's + * signed + notarized + entitled identity (IMPROVEMENT-PLAN 6.2): + * - RunAsNode (ELECTRON_RUN_AS_NODE), NODE_OPTIONS, and --inspect → OFF + * - OnlyLoadAppFromAsar → ON (refuse to run loose app code) + * - Cookie encryption → ON + * + * EnableEmbeddedAsarIntegrityValidation is intentionally NOT flipped here: it requires the + * asar integrity header to be embedded and verified, which can fail launch if the toolchain + * doesn't embed it - enable only after a notarized build is confirmed to launch with it. + */ +exports.default = async function afterPack(context) { + const { electronPlatformName, appOutDir } = context; + if (electronPlatformName !== 'darwin') return; + + const appName = context.packager.appInfo.productFilename; // "DevHarbor" + const electronBinary = path.join(appOutDir, `${appName}.app`, 'Contents', 'MacOS', appName); + + await flipFuses(electronBinary, { + version: FuseVersion.V1, + resetAdHocDarwinSignature: true, // clear Electron's ad-hoc sig so electron-builder re-signs + [FuseV1Options.RunAsNode]: false, + [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, + [FuseV1Options.EnableNodeCliInspectArguments]: false, + [FuseV1Options.OnlyLoadAppFromAsar]: true, + [FuseV1Options.EnableCookieEncryption]: true + }); +}; diff --git a/build/notarize.cjs b/build/notarize.cjs index dfc6990..9631e7e 100644 --- a/build/notarize.cjs +++ b/build/notarize.cjs @@ -17,7 +17,7 @@ exports.default = async function notarizing(context) { if (!appleId || !appleIdPassword || !teamId) { console.warn( - '[notarize] APPLE_ID / APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID not set — skipping notarization.' + '[notarize] APPLE_ID / APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID not set - skipping notarization.' ); return; } diff --git a/electron-builder.yml b/electron-builder.yml index 0a4e6f8..07c5459 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -18,12 +18,12 @@ files: - "!postcss.config.{js,cjs}" - "!tailwind.config.{js,ts}" - "!{tsconfig.json,tsconfig.node.json,tsconfig.web.json,tsconfig.web.tsbuildinfo,tsconfig.node.tsbuildinfo}" - - "!{.eslintrc.cjs,.prettierignore,.prettierrc.yaml,CHANGELOG.md,README.md,LICENSE}" + - "!{eslint.config.mjs,.prettierrc.json,.prettierignore,vitest.config.ts,CHANGELOG.md,README.md,LICENSE}" - "!{.env,.env.*,.npmrc,pnpm-lock.yaml,.nvmrc,.gitignore}" asar: true -# Native modules MUST be unpacked from asar — they can't be loaded from inside a +# Native modules MUST be unpacked from asar - they can't be loaded from inside a # packed archive. electron-builder usually detects these automatically; we list # them explicitly for safety and clarity. asarUnpack: @@ -59,14 +59,20 @@ mac: CFBundleURLSchemes: - devharbor +# Harden Electron fuses on the packaged binary BEFORE signing (afterPack runs pre-sign), so +# the flips are covered by the code signature. See build/fuses.cjs. +afterPack: build/fuses.cjs + # Notarization: electron-builder reads APPLE_ID / APPLE_APP_SPECIFIC_PASSWORD / # APPLE_TEAM_ID env vars at build time. If they're set, the .app is automatically # notarized + stapled after signing. If unset, the build skips notarization with a -# warning — fine for local unsigned dev builds. +# warning - fine for local unsigned dev builds. afterSign: build/notarize.cjs dmg: - artifactName: ${name}-${version}-${arch}.dmg + # ${productName} (DevHarbor) - matches the zip naming so the user-facing dmg isn't the odd + # lowercase one out (IMPROVEMENT-PLAN 13.5). + artifactName: ${productName}-${version}-${arch}.dmg contents: - x: 130 y: 220 diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..07f686a --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,41 @@ +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import reactHooks from 'eslint-plugin-react-hooks'; + +/** + * Flat ESLint config. The repo previously had ZERO linting yet shipped 4 production + * `eslint-disable` comments that suppressed nothing (IMPROVEMENT-PLAN 12.2) - those now do + * their job. Kept intentionally lenient (most stylistic checks are warnings, not errors) so + * CI gates on real problems without forcing a mass refactor of an already-disciplined codebase. + */ +export default tseslint.config( + { + ignores: [ + 'dist/**', + 'out/**', + 'node_modules/**', + 'build/**', + 'specs/**', + '*.config.{js,cjs,mjs,ts}', + 'postcss.config.cjs', + '**/*.tsbuildinfo' + ] + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + plugins: { 'react-hooks': reactHooks }, + rules: { + ...reactHooks.configs.recommended.rules, + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrors: 'none' } + ], + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-empty-object-type': 'off', + 'no-empty': ['warn', { allowEmptyCatch: true }], + 'no-control-regex': 'off', + 'prefer-const': 'warn' + } + } +); diff --git a/package.json b/package.json index 3bb1c28..15d9693 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "devharbor", - "version": "1.0.1", + "version": "1.1.0", "private": true, - "description": "DevHarbor — a harbor for your local dev servers. Desktop app for managing local Node.js projects on macOS.", + "description": "DevHarbor - a harbor for your local dev servers. Desktop app for managing local Node.js projects on macOS.", "author": "Jainath Ponnala", "license": "AGPL-3.0-only", "homepage": "https://www.devharbor.app", @@ -21,6 +21,10 @@ "typecheck:node": "tsc --noEmit -p tsconfig.node.json", "typecheck:web": "tsc --noEmit -p tsconfig.web.json", "typecheck": "pnpm typecheck:node && pnpm typecheck:web", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", "test": "vitest run", "test:watch": "vitest", "rebuild": "electron-rebuild -f -w better-sqlite3,@homebridge/node-pty-prebuilt-multiarch", @@ -30,7 +34,7 @@ "pack:mac:universal": "electron-vite build && electron-builder --mac --universal" }, "comments": { - "dependencies": "ONLY main-process / native modules that electron-vite externalizes belong here — electron-builder ships every production dependency into the asar as raw node_modules. Renderer-only libs (react, lucide-react, xterm, etc.) are bundled into out/renderer by Vite, so they live in devDependencies to avoid being shipped twice (lucide-react alone is 3,500+ files)." + "dependencies": "ONLY main-process / native modules that electron-vite externalizes belong here - electron-builder ships every production dependency into the asar as raw node_modules. Renderer-only libs (react, lucide-react, xterm, etc.) are bundled into out/renderer by Vite, so they live in devDependencies to avoid being shipped twice (lucide-react alone is 3,500+ files)." }, "dependencies": { "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", @@ -38,16 +42,20 @@ "chokidar": "^5.0.0", "electron-updater": "^6.8.3", "package-manager-detector": "^1.6.0", + "picomatch": "^4.0.4", "pidusage": "^4.0.1", "semver": "^7.8.1", "tree-kill": "^1.2.2", "ulid": "^2.3.0" }, "devDependencies": { + "@electron/fuses": "^1.8.0", "@electron/notarize": "^3.1.1", "@electron/rebuild": "^3.7.0", + "@eslint/js": "^9.39.4", "@types/better-sqlite3": "^7.6.11", "@types/node": "^22.9.0", + "@types/picomatch": "^3.0.2", "@types/pidusage": "^2.0.5", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", @@ -66,14 +74,18 @@ "electron": "^33.2.0", "electron-builder": "^25.1.8", "electron-vite": "^2.3.0", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^5.2.0", "lucide-react": "^0.460.0", "postcss": "^8.4.49", + "prettier": "^3.8.4", "react": "^18.3.1", "react-dom": "^18.3.1", "react-window": "^2.2.7", "tailwind-merge": "^2.5.4", "tailwindcss": "^3.4.15", "typescript": "^5.6.3", + "typescript-eslint": "^8.61.0", "vite": "^5.4.11", "vitest": "^2.1.9", "zustand": "^5.0.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e33a9e3..4c65f4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: package-manager-detector: specifier: ^1.6.0 version: 1.6.0 + picomatch: + specifier: ^4.0.4 + version: 4.0.4 pidusage: specifier: ^4.0.1 version: 4.0.1 @@ -36,18 +39,27 @@ importers: specifier: ^2.3.0 version: 2.4.0 devDependencies: + '@electron/fuses': + specifier: ^1.8.0 + version: 1.8.0 '@electron/notarize': specifier: ^3.1.1 version: 3.1.1 '@electron/rebuild': specifier: ^3.7.0 version: 3.7.2 + '@eslint/js': + specifier: ^9.39.4 + version: 9.39.4 '@types/better-sqlite3': specifier: ^7.6.11 version: 7.6.13 '@types/node': specifier: ^22.9.0 version: 22.19.19 + '@types/picomatch': + specifier: ^3.0.2 + version: 3.0.2 '@types/pidusage': specifier: ^2.0.5 version: 2.0.5 @@ -102,12 +114,21 @@ importers: electron-vite: specifier: ^2.3.0 version: 2.3.0(vite@5.4.21(@types/node@22.19.19)) + eslint: + specifier: ^9.39.4 + version: 9.39.4(jiti@1.21.7) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.39.4(jiti@1.21.7)) lucide-react: specifier: ^0.460.0 version: 0.460.0(react@18.3.1) postcss: specifier: ^8.4.49 version: 8.5.15 + prettier: + specifier: ^3.8.4 + version: 3.8.4 react: specifier: ^18.3.1 version: 18.3.1 @@ -126,6 +147,9 @@ importers: typescript: specifier: ^5.6.3 version: 5.9.3 + typescript-eslint: + specifier: ^8.61.0 + version: 8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) vite: specifier: ^5.4.11 version: 5.4.21(@types/node@22.19.19) @@ -243,6 +267,10 @@ packages: engines: {node: '>=10.12.0'} hasBin: true + '@electron/fuses@1.8.0': + resolution: {integrity: sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==} + hasBin: true + '@electron/get@2.0.3': resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} engines: {node: '>=12'} @@ -418,6 +446,44 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} @@ -425,6 +491,26 @@ packages: resolution: {integrity: sha512-ccQ60nMcbEGrQh0U9E6x0ajW9qJNeazpcM/9CH6J8leyNtJgb+gu24WTBAfBUVeO486ZhscnaxLEITI2HXwhow==} engines: {node: '>=18.0.0 <25.0.0'} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -854,6 +940,9 @@ packages: '@types/http-cache-semantics@4.2.0': resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -866,6 +955,9 @@ packages: '@types/node@22.19.19': resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + '@types/picomatch@3.0.2': + resolution: {integrity: sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA==} + '@types/pidusage@2.0.5': resolution: {integrity: sha512-MIiyZI4/MK9UGUXWt0jJcCZhVw7YdhBuTOuqP/BjuLDLZ2PmmViMIQgZiWxtaMicQfAz/kMrZ5T7PKxFSkTeUA==} @@ -899,6 +991,65 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typescript-eslint/eslint-plugin@8.61.0': + resolution: {integrity: sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.61.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.61.0': + resolution: {integrity: sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.61.0': + resolution: {integrity: sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.61.0': + resolution: {integrity: sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.61.0': + resolution: {integrity: sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.61.0': + resolution: {integrity: sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.61.0': + resolution: {integrity: sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.61.0': + resolution: {integrity: sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.61.0': + resolution: {integrity: sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.61.0': + resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -953,6 +1104,16 @@ packages: abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -1173,6 +1334,10 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -1348,6 +1513,9 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -1505,9 +1673,61 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -1538,6 +1758,9 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1553,6 +1776,10 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -1563,6 +1790,17 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -1666,6 +1904,10 @@ packages: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -1745,6 +1987,18 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1853,6 +2107,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -1877,6 +2134,10 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1884,6 +2145,10 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -1903,6 +2168,9 @@ packages: lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.union@4.6.0: resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} @@ -2067,6 +2335,9 @@ packages: napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.4: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} @@ -2130,6 +2401,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -2142,6 +2417,10 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} @@ -2152,6 +2431,14 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -2261,6 +2548,15 @@ packages: deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.4: + resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} + engines: {node: '>=14'} + hasBin: true + proc-log@2.0.1: resolution: {integrity: sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -2387,6 +2683,10 @@ packages: resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -2565,6 +2865,10 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + sucrase@3.35.1: resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} engines: {node: '>=16 || 14 >=14.17'} @@ -2655,6 +2959,12 @@ packages: truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -2664,10 +2974,21 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} + typescript-eslint@8.61.0: + resolution: {integrity: sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -2812,6 +3133,10 @@ packages: wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3008,6 +3333,12 @@ snapshots: glob: 7.2.3 minimatch: 3.1.5 + '@electron/fuses@1.8.0': + dependencies: + chalk: 4.1.2 + fs-extra: 9.1.0 + minimist: 1.2.8 + '@electron/get@2.0.3': dependencies: debug: 4.4.3 @@ -3185,6 +3516,52 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@1.21.7))': + dependencies: + eslint: 9.39.4(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + '@gar/promisify@1.1.3': {} '@homebridge/node-pty-prebuilt-multiarch@0.13.1': @@ -3192,6 +3569,22 @@ snapshots: node-addon-api: 7.1.1 prebuild-install: 7.1.3 + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3546,6 +3939,8 @@ snapshots: '@types/http-cache-semantics@4.2.0': {} + '@types/json-schema@7.0.15': {} + '@types/keyv@3.1.4': dependencies: '@types/node': 22.19.19 @@ -3560,6 +3955,8 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/picomatch@3.0.2': {} + '@types/pidusage@2.0.5': {} '@types/plist@3.0.5': @@ -3600,6 +3997,97 @@ snapshots: '@types/node': 22.19.19 optional: true + '@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.61.0 + '@typescript-eslint/type-utils': 8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.61.0 + eslint: 9.39.4(jiti@1.21.7) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.61.0 + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.61.0 + debug: 4.4.3 + eslint: 9.39.4(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.61.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3) + '@typescript-eslint/types': 8.61.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.61.0': + dependencies: + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/visitor-keys': 8.61.0 + + '@typescript-eslint/tsconfig-utils@8.61.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4(jiti@1.21.7) + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.61.0': {} + + '@typescript-eslint/typescript-estree@8.61.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.61.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3) + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/visitor-keys': 8.61.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.1 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.61.0 + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3) + eslint: 9.39.4(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.61.0': + dependencies: + '@typescript-eslint/types': 8.61.0 + eslint-visitor-keys: 5.0.1 + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@22.19.19))': dependencies: '@babel/core': 7.29.0 @@ -3664,6 +4152,12 @@ snapshots: abbrev@1.1.1: {} + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + agent-base@6.0.2: dependencies: debug: 4.4.3 @@ -3974,6 +4468,8 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + callsites@3.1.0: {} + camelcase-css@2.0.1: {} caniuse-lite@1.0.30001793: {} @@ -4136,6 +4632,8 @@ snapshots: deep-extend@0.6.0: {} + deep-is@0.1.4: {} + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -4361,13 +4859,86 @@ snapshots: escalade@3.2.0: {} - escape-string-regexp@4.0.0: - optional: true + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.39.4(jiti@1.21.7)): + dependencies: + eslint: 9.39.4(jiti@1.21.7) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 + esutils@2.0.3: {} + expand-template@2.0.3: {} expect-type@1.3.0: {} @@ -4399,6 +4970,8 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-levenshtein@2.0.6: {} + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -4411,6 +4984,10 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + file-uri-to-path@1.0.0: {} filelist@1.0.6: @@ -4421,6 +4998,18 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -4559,6 +5148,8 @@ snapshots: serialize-error: 7.0.1 optional: true + globals@14.0.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -4658,6 +5249,15 @@ snapshots: ieee754@1.2.1: {} + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -4737,6 +5337,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-safe@5.0.1: optional: true @@ -4762,10 +5364,19 @@ snapshots: dependencies: readable-stream: 2.3.8 + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.defaults@4.2.0: {} lodash.difference@4.5.0: {} @@ -4778,6 +5389,8 @@ snapshots: lodash.isplainobject@4.0.6: {} + lodash.merge@4.6.2: {} + lodash.union@4.6.0: {} lodash@4.18.1: {} @@ -4936,6 +5549,8 @@ snapshots: napi-build-utils@2.0.0: {} + natural-compare@1.4.0: {} + negotiator@0.6.4: {} node-abi@3.92.0: @@ -5000,6 +5615,15 @@ snapshots: dependencies: mimic-fn: 2.1.0 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + ora@5.4.1: dependencies: bl: 4.1.0 @@ -5018,6 +5642,10 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-map@4.0.0: dependencies: aggregate-error: 3.1.0 @@ -5026,6 +5654,12 @@ snapshots: package-manager-detector@1.6.0: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -5117,6 +5751,10 @@ snapshots: tar-fs: 2.1.4 tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} + + prettier@3.8.4: {} + proc-log@2.0.1: {} process-nextick-args@2.0.1: {} @@ -5236,6 +5874,8 @@ snapshots: resolve-alpn@1.2.1: {} + resolve-from@4.0.0: {} + resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -5435,6 +6075,8 @@ snapshots: strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} + sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -5557,6 +6199,10 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-interface-checker@0.1.13: {} tslib@2.8.1: {} @@ -5565,9 +6211,24 @@ snapshots: dependencies: safe-buffer: 5.2.1 + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-fest@0.13.1: optional: true + typescript-eslint@8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.61.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.61.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.4(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} ulid@2.4.0: {} @@ -5701,6 +6362,8 @@ snapshots: dependencies: string-width: 4.2.3 + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 diff --git a/specs/00-overview.md b/specs/00-overview.md index 8d87796..762cde2 100644 --- a/specs/00-overview.md +++ b/specs/00-overview.md @@ -1,4 +1,4 @@ -# 00 — Overview +# 00 - Overview ## Problem @@ -8,19 +8,19 @@ Developers juggling several local Node.js services hit the same friction every d - "Which Node version does this repo want again?" → `nvm use`, hope `.nvmrc` is right. - Editing `.env` in a text editor, restarting by hand, forgetting which port a service is on. - No single view of "what is currently running on my machine." -- Switching between npm / yarn / pnpm projects — easy to use the wrong one. +- Switching between npm / yarn / pnpm projects - easy to use the wrong one. ## Vision One desktop app that knows about all your local Node.js projects and gives you a calm, dense, professional interface to run them. -The product the user has in their head: "Postman, but for local dev servers." Not literally Postman — we are not an API client — but the same *feel*: a sidebar of registered things, a main area showing the active one, sensible defaults, keyboard-driven, dev-tool aesthetic. +The product the user has in their head: "Postman, but for local dev servers." Not literally Postman - we are not an API client - but the same *feel*: a sidebar of registered things, a main area showing the active one, sensible defaults, keyboard-driven, dev-tool aesthetic. ## Primary user A working developer on macOS who: -- Maintains 3–20 local Node.js projects +- Maintains 3-20 local Node.js projects - Uses some combination of npm, yarn, and pnpm - Has nvm (or fnm, or Volta, or asdf) installed - Wants tighter feedback loops and less terminal-tab archaeology @@ -30,11 +30,11 @@ A working developer on macOS who: 1. **Register** local app directories by browsing to them. 2. **Detect** Node version, package manager, and available scripts automatically; allow overrides. 3. **Run** the chosen script with the correct Node version and package manager. -4. **Stop** apps cleanly — entire process tree, no orphans. -5. **Env vars** — global defaults + per-app overrides, layered, with `.env` file integration. -6. **Logs** — live stdout/stderr per app, real terminal rendering, searchable history. -7. **Dashboard** — at-a-glance view of running apps, their ports, CPU/RAM. -8. **Dev-friendly extras** — Cmd-K palette, port detection with clickable links, restart-on-change, `.env` file watching, crash recovery actions. +4. **Stop** apps cleanly - entire process tree, no orphans. +5. **Env vars** - global defaults + per-app overrides, layered, with `.env` file integration. +6. **Logs** - live stdout/stderr per app, real terminal rendering, searchable history. +7. **Dashboard** - at-a-glance view of running apps, their ports, CPU/RAM. +8. **Dev-friendly extras** - Cmd-K palette, port detection with clickable links, restart-on-change, `.env` file watching, crash recovery actions. ## Non-goals (explicitly out of scope for v1) diff --git a/specs/01-architecture.md b/specs/01-architecture.md index 5b12157..bf2439c 100644 --- a/specs/01-architecture.md +++ b/specs/01-architecture.md @@ -1,4 +1,4 @@ -# 01 — Architecture +# 01 - Architecture ## Tech stack @@ -9,7 +9,7 @@ | Packaging | electron-builder | Mature signing/notarization/DMG/auto-update. Pairs cleanly with electron-vite. | | UI | React 18 + TypeScript | Default. | | Styling | Tailwind CSS | Dense, dev-tool aesthetic; pairs with shadcn. | -| Components | shadcn/ui (on Radix) | We own the source — easy to make dense + customise. | +| Components | shadcn/ui (on Radix) | We own the source - easy to make dense + customise. | | Renderer state | Zustand | Tiny, no Provider gymnastics, scales for this app size. | | IPC types | Shared `types/ipc.ts` + `electron-trpc` (deferred) | Start hand-rolled with strict types; revisit trpc if churn is high. | | Process spawn | `@lydell/node-pty` (prebuilt for Electron) | Real TTY → ANSI colors and TUI redraws work. No `electron-rebuild` pain. | @@ -172,7 +172,7 @@ The preload re-exports an `api` object whose methods are 1:1 with these channel A single SQLite file at `~/Library/Application Support/devharbor/devharbor.db` (resolved via `app.getPath('userData')`). Schema in [`02-data-model.md`](02-data-model.md). Migrations live in `src/main/db/migrations/` and run in order on startup. -Logs are **not** persisted to SQLite by default — they live in the ring buffer. Optional per-app "save logs to disk" writes a rotating file to `userData/logs//.log`. +Logs are **not** persisted to SQLite by default - they live in the ring buffer. Optional per-app "save logs to disk" writes a rotating file to `userData/logs//.log`. ## How we run a child process (the canonical sequence) @@ -181,7 +181,7 @@ Logs are **not** persisted to SQLite by default — they live in the ring buffer 1. Look up `App` row, including resolved Node version, package manager, working dir, script. 2. `NodeResolver.resolveBinPath(app.nodeVersion)` → absolute path like `/Users/.../.nvm/versions/node/v20.11.0/bin`. 3. Build the env: - - Start from a clean object (NOT inheriting full `process.env` — too leaky). + - Start from a clean object (NOT inheriting full `process.env` - too leaky). - Inject a sanitized base: `HOME`, `USER`, `LANG`, `TMPDIR`, `SHELL`, `TERM=xterm-256color`. - Prepend the resolved Node bin dir to `PATH`, then the user's standard `PATH` from a one-time login-shell probe. - Layer in global env vars from `EnvStore`. @@ -202,11 +202,11 @@ Logs are **not** persisted to SQLite by default — they live in the ring buffer ## Failure & recovery -- App quits while children are running → on next boot, scan the DB for "was running" flags; we can't reattach to PTYs but we can show the user "these apps were running last time — restart?" +- App quits while children are running → on next boot, scan the DB for "was running" flags; we can't reattach to PTYs but we can show the user "these apps were running last time - restart?" - PTY native module fails to load → fall back to plain `child_process.spawn` with `FORCE_COLOR=1`. UI shows a warning banner. - SQLite corruption → on open failure, back up the file to `*.corrupt.` and create a fresh DB. Surface a one-time toast. - Node version requested but not installed → block start; show "Install v20.11.0 with nvm" with a copy-to-clipboard command. Do not auto-install. ## Auto-update -`electron-updater` configured against a GitHub releases feed (or S3 later). Code signing + notarization wired from day one — see `04-ui.md` for the in-app update banner. +`electron-updater` configured against a GitHub releases feed (or S3 later). Code signing + notarization wired from day one - see `04-ui.md` for the in-app update banner. diff --git a/specs/02-data-model.md b/specs/02-data-model.md index 2e23d64..53af43e 100644 --- a/specs/02-data-model.md +++ b/specs/02-data-model.md @@ -1,4 +1,4 @@ -# 02 — Data Model +# 02 - Data Model All persistent state lives in one SQLite file at `userData/devharbor.db`. In-memory state (running processes, ring buffers, pidusage samples) is **not** persisted. @@ -16,12 +16,13 @@ One row per registered project. | `color` | TEXT | Hex like `#7aa2f7`, for the sidebar dot; auto-assigned, user-editable | | `icon` | TEXT | Optional emoji or short tag | | `node_version_pref` | TEXT | One of: `auto` (use `.nvmrc`/`.node-version`/`engines.node`), `system`, or an explicit version like `20.11.0` | -| `package_manager` | TEXT | `npm` \| `yarn` \| `pnpm` \| `bun` — null means "detect each time" | -| `default_script` | TEXT | e.g. `dev` — remembered between sessions | +| `package_manager` | TEXT | `npm` \| `yarn` \| `pnpm` \| `bun` - null means "detect each time" | +| `default_script` | TEXT | e.g. `dev` - remembered between sessions | | `custom_command` | TEXT | If set, used instead of ` run