mirror of
https://github.com/logseq/logseq.git
synced 2026-05-18 01:42:19 +00:00
enhance(skill): update logseq-repl
This commit is contained in:
@@ -1,33 +1,120 @@
|
||||
---
|
||||
name: logseq-repl
|
||||
description: Start and coordinate Logseq development REPL workflows for the Desktop renderer `:app`, Electron main-process `:electron`, and `:db-worker-node` runtimes while sharing a single `yarn watch` process.
|
||||
description: Start and coordinate Logseq development REPL workflows for the Desktop renderer `:app`, Electron main-process `:electron`, and `:db-worker-node` runtimes through one unified workflow.
|
||||
---
|
||||
|
||||
# Logseq REPL workflows
|
||||
# Logseq REPL Workflow
|
||||
|
||||
Use this skill when the user needs a Logseq development REPL for:
|
||||
|
||||
- Desktop renderer `:app`
|
||||
- Electron main process `:electron`
|
||||
- `:db-worker-node`
|
||||
- or any combination of them
|
||||
- any combination of those runtimes
|
||||
|
||||
This skill keeps both workflows coordinated through `<repo>/tmp/logseq-repl/` so they do not start multiple different REPL types at the same time with competing `shadow-cljs` servers.
|
||||
The workflow uses one shared state directory: `<repo>/tmp/logseq-repl/`.
|
||||
|
||||
## Required preflight cleanup
|
||||
## Scripts
|
||||
|
||||
Before starting or attaching any REPL, run both cleanup scripts:
|
||||
Start everything:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/cleanup-desktop-app-repl.sh
|
||||
.agents/skills/logseq-repl/scripts/cleanup-db-worker-node-repl.sh
|
||||
./scripts/start-repl.sh --repo demo
|
||||
```
|
||||
|
||||
They stop only skill-managed Desktop, `db-worker-node`, and shared `shadow-cljs` processes. They do **not** guarantee a clean environment if another manually started or externally managed Logseq/shadow-cljs session is still running.
|
||||
Clean up everything:
|
||||
|
||||
## Required post-cleanup port audit
|
||||
```bash
|
||||
./scripts/cleanup-repl.sh
|
||||
```
|
||||
|
||||
Immediately after cleanup, verify the standard ports:
|
||||
Verify all REPL targets after startup:
|
||||
|
||||
```bash
|
||||
./scripts/verify-repls.sh
|
||||
```
|
||||
|
||||
`start-repl.sh` starts:
|
||||
|
||||
1. shared `pnpm watch`
|
||||
2. Desktop dev app via `pnpm dev-electron-app`
|
||||
3. `db-worker-node` via `node ./static/db-worker-node.js --repo <name>`
|
||||
|
||||
`start-repl.sh` is a small shell wrapper around `start-repl.py`. The Python script verifies that `:app`, `:electron`, and `:db-worker-node` runtimes are live, runs `verify-repls.sh` to connect to each target REPL and print a small result, then prints attach commands. It does not attach an interactive REPL by itself and exits after verification.
|
||||
|
||||
`cleanup-repl.sh` stops all workflow-managed processes without requiring a target selection. It also removes legacy state files from older split workflows and attempts to stop repo-owned listeners on the standard REPL ports.
|
||||
|
||||
`verify-repls.sh` connects to `app`, `electron`, and `db-worker-node` with `pnpm exec shadow-cljs cljs-repl`, evaluates one target-specific expression in each REPL, and prints the output.
|
||||
|
||||
## Standard Flow
|
||||
|
||||
Before starting or attaching:
|
||||
|
||||
```bash
|
||||
./scripts/cleanup-repl.sh
|
||||
```
|
||||
|
||||
Then start all runtimes:
|
||||
|
||||
```bash
|
||||
./scripts/start-repl.sh --repo demo
|
||||
```
|
||||
|
||||
Attach only to the target you need:
|
||||
|
||||
```bash
|
||||
pnpm exec shadow-cljs cljs-repl app
|
||||
pnpm exec shadow-cljs cljs-repl electron
|
||||
pnpm exec shadow-cljs cljs-repl db-worker-node
|
||||
```
|
||||
|
||||
## Runtime Selection
|
||||
|
||||
- Need DOM, UI, renderer state, or page rendering? Use `:app`.
|
||||
- Need Electron main process APIs, menus, window lifecycle, or main-process filesystem behavior? Use `:electron`.
|
||||
- Need Node worker behavior or db-worker-node code paths? Use `:db-worker-node`.
|
||||
|
||||
Runtime reminders:
|
||||
|
||||
- `:app` = Electron renderer
|
||||
- `:electron` = Electron main process
|
||||
- `:db-worker` = browser worker
|
||||
- `:db-worker-node` = Node worker
|
||||
|
||||
## Readiness Model
|
||||
|
||||
Keep these separate:
|
||||
|
||||
1. watch alive: a `shadow-cljs` server or `pnpm watch` process exists
|
||||
2. build ready: the target build completed successfully
|
||||
3. runtime attached: a live JS runtime is connected for `:app`, `:electron`, or `:db-worker-node`
|
||||
|
||||
If runtime count is `0`, do not attach yet. Fix runtime startup first.
|
||||
|
||||
Check runtime counts:
|
||||
|
||||
```bash
|
||||
pnpm exec shadow-cljs clj-eval "(do (require '[shadow.cljs.devtools.api :as api]) (println {:app (count (api/repl-runtimes :app)) :electron (count (api/repl-runtimes :electron)) :db-worker-node (count (api/repl-runtimes :db-worker-node))}))"
|
||||
```
|
||||
|
||||
Interpretation:
|
||||
|
||||
- `:app > 0` means a Desktop renderer runtime is attached
|
||||
- `:electron > 0` means an Electron main-process runtime is attached
|
||||
- `:db-worker-node > 0` means a worker-node runtime is attached
|
||||
- `0` means not ready, even if watch/build logs look healthy
|
||||
|
||||
## Logs
|
||||
|
||||
Look here first:
|
||||
|
||||
- `<repo>/tmp/logseq-repl/shared-shadow-watch.log`
|
||||
- `<repo>/tmp/logseq-repl/desktop-electron.log`
|
||||
- `<repo>/tmp/logseq-repl/db-worker-node.log`
|
||||
|
||||
## Port Audit
|
||||
|
||||
After cleanup, verify standard ports if startup still reports conflicts:
|
||||
|
||||
```bash
|
||||
lsof -nP -iTCP:8701 -sTCP:LISTEN
|
||||
@@ -40,127 +127,15 @@ lsof -nP -iTCP:9631 -sTCP:LISTEN
|
||||
Interpretation:
|
||||
|
||||
- no listeners: clean enough to continue
|
||||
- listeners after cleanup: external conflict first
|
||||
- listeners only after startup: expected if owned by the workflow you just started
|
||||
- listeners after cleanup: resolve the external conflict first
|
||||
- listeners only after startup: expected if owned by the workflow
|
||||
|
||||
If listeners remain right after cleanup, stop and resolve that conflict before trusting later startup results.
|
||||
|
||||
## Readiness model: watch vs build vs runtime
|
||||
|
||||
Keep these separate:
|
||||
|
||||
1. **watch alive** — a `shadow-cljs` server or `yarn watch` process exists
|
||||
2. **build ready** — the target build completed successfully
|
||||
3. **runtime attached** — a live JS runtime is connected for `:app`, `:electron`, or `:db-worker-node`
|
||||
|
||||
Common failure mode:
|
||||
|
||||
- `yarn watch` is alive
|
||||
- logs say `Build completed`
|
||||
- runtime count is still `0`
|
||||
|
||||
If runtime count is `0`, do **not** attach yet. Fix runtime startup first.
|
||||
|
||||
## Cheat sheet
|
||||
|
||||
### Pick the right runtime
|
||||
|
||||
- Need DOM, UI, renderer state, page rendering? Use **Desktop app `:app` REPL**.
|
||||
- Need Node worker behavior or db-worker-node code paths? Use **db-worker-node REPL**.
|
||||
- Need Electron main process APIs, menus, window lifecycle, or main-process filesystem code? Use **Electron main-process `:electron` REPL**.
|
||||
|
||||
Runtime reminders:
|
||||
|
||||
- `:app` = Electron renderer
|
||||
- `:electron` = Electron main process
|
||||
- `:db-worker` = browser worker
|
||||
- `:db-worker-node` = Node worker
|
||||
|
||||
### Fast paths
|
||||
## Non-Interactive Verification Examples
|
||||
|
||||
Desktop `:app`:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-desktop-app-repl.sh --no-repl
|
||||
npx shadow-cljs cljs-repl app
|
||||
```
|
||||
|
||||
Electron `:electron`:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-desktop-app-repl.sh --no-repl
|
||||
npx shadow-cljs cljs-repl electron
|
||||
```
|
||||
|
||||
`db-worker-node`:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-db-worker-node-repl.sh --repo demo --no-repl
|
||||
npx shadow-cljs cljs-repl db-worker-node
|
||||
```
|
||||
|
||||
Multiple runtimes:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-desktop-app-repl.sh --no-repl
|
||||
.agents/skills/logseq-repl/scripts/start-db-worker-node-repl.sh --repo demo --no-repl
|
||||
npx shadow-cljs cljs-repl app
|
||||
npx shadow-cljs cljs-repl electron
|
||||
npx shadow-cljs cljs-repl db-worker-node
|
||||
```
|
||||
|
||||
### Preflight for Desktop `:app`
|
||||
|
||||
- close browser dev app instances before starting Desktop `:app`
|
||||
- keep the Desktop window as the only renderer runtime you want to attach to
|
||||
|
||||
Why: the start script expects exactly one live `:app` runtime.
|
||||
|
||||
### Shared watch and logs
|
||||
|
||||
Both workflows share one `yarn watch` process.
|
||||
|
||||
Look here first:
|
||||
|
||||
- real shared watch log: `<repo>/tmp/logseq-repl/shared-shadow-watch.log`
|
||||
|
||||
Workflow-local files may exist but may stay mostly empty when shared watch is reused.
|
||||
|
||||
### Editor attach helpers
|
||||
|
||||
```clojure
|
||||
(shadow.user/cljs-repl)
|
||||
(shadow.user/electron-repl)
|
||||
(shadow.user/worker-node-repl)
|
||||
```
|
||||
|
||||
### Runtime-count health checks
|
||||
|
||||
Before attaching, verify that the intended runtime count is non-zero.
|
||||
|
||||
Check all runtimes at once:
|
||||
|
||||
```bash
|
||||
npx shadow-cljs clj-eval "(do (require '[shadow.cljs.devtools.api :as api]) (println {:app (count (api/repl-runtimes :app)) :electron (count (api/repl-runtimes :electron)) :db-worker-node (count (api/repl-runtimes :db-worker-node))}))"
|
||||
```
|
||||
|
||||
Interpretation:
|
||||
|
||||
- `:app > 0` means a Desktop renderer runtime is really attached
|
||||
- `:electron > 0` means the Electron main-process runtime is really attached
|
||||
- `:db-worker-node > 0` means the worker-node runtime is really attached
|
||||
- `0` means **not ready yet**, even if watch/build logs look healthy
|
||||
|
||||
Do not run `npx shadow-cljs cljs-repl <build>` until the intended runtime count is non-zero.
|
||||
|
||||
### Non-interactive verification examples
|
||||
|
||||
Prefer heredocs over complex `printf` quoting.
|
||||
|
||||
Desktop `:app`:
|
||||
|
||||
```bash
|
||||
cat <<'EOF' | npx shadow-cljs cljs-repl app
|
||||
cat <<'EOF' | pnpm exec shadow-cljs cljs-repl app
|
||||
(prn {:runtime :app :document? (some? js/document) :title (.-title js/document)})
|
||||
:cljs/quit
|
||||
EOF
|
||||
@@ -169,7 +144,7 @@ EOF
|
||||
Electron `:electron`:
|
||||
|
||||
```bash
|
||||
cat <<'EOF' | npx shadow-cljs cljs-repl electron
|
||||
cat <<'EOF' | pnpm exec shadow-cljs cljs-repl electron
|
||||
(prn {:runtime :electron :process? (some? js/process) :type (.-type js/process)})
|
||||
:cljs/quit
|
||||
EOF
|
||||
@@ -178,360 +153,45 @@ EOF
|
||||
`db-worker-node`:
|
||||
|
||||
```bash
|
||||
cat <<'EOF' | npx shadow-cljs cljs-repl db-worker-node
|
||||
cat <<'EOF' | pnpm exec shadow-cljs cljs-repl db-worker-node
|
||||
(prn {:runtime :db-worker-node :process? (some? js/process) :platform (.-platform js/process)})
|
||||
:cljs/quit
|
||||
EOF
|
||||
```
|
||||
|
||||
### Cleanup both workflows
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/cleanup-desktop-app-repl.sh
|
||||
.agents/skills/logseq-repl/scripts/cleanup-db-worker-node-repl.sh
|
||||
```
|
||||
|
||||
Expected behavior:
|
||||
|
||||
- cleaning one workflow keeps shared watch alive if the other is still active
|
||||
- cleaning the last active workflow stops shared watch
|
||||
|
||||
---
|
||||
|
||||
## Details and troubleshooting
|
||||
|
||||
### Desktop app `:app` REPL
|
||||
|
||||
Scripts:
|
||||
|
||||
- `scripts/start-desktop-app-repl.sh`
|
||||
- `scripts/cleanup-desktop-app-repl.sh`
|
||||
|
||||
Standard start:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-desktop-app-repl.sh
|
||||
```
|
||||
|
||||
Start without attaching:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-desktop-app-repl.sh --no-repl
|
||||
```
|
||||
|
||||
What it does:
|
||||
|
||||
1. starts or reuses `yarn watch`
|
||||
2. waits for `:app` and `:electron` builds plus nREPL
|
||||
3. starts or reuses `yarn dev-electron-app`
|
||||
4. verifies one live `:app` runtime
|
||||
5. runs a DOM smoke test
|
||||
6. attaches `npx shadow-cljs cljs-repl app` unless `--no-repl` is used
|
||||
|
||||
### Electron main-process `:electron` REPL
|
||||
|
||||
Use the same Desktop startup script because the Electron main process comes from the same `yarn dev-electron-app` session and shared `yarn watch`.
|
||||
|
||||
Standard start without attaching:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-desktop-app-repl.sh --no-repl
|
||||
```
|
||||
|
||||
Then attach `:electron`:
|
||||
|
||||
```bash
|
||||
npx shadow-cljs cljs-repl electron
|
||||
```
|
||||
|
||||
What it does:
|
||||
|
||||
1. starts or reuses `yarn watch`
|
||||
2. waits for `:app` and `:electron` builds plus nREPL
|
||||
3. starts or reuses `yarn dev-electron-app`
|
||||
4. verifies the Desktop app is alive via the renderer smoke test
|
||||
5. lets you attach `npx shadow-cljs cljs-repl electron`
|
||||
|
||||
Notes:
|
||||
|
||||
- there is no separate Electron main-process start script today; reuse `start-desktop-app-repl.sh`
|
||||
- `:electron` and `:app` can both be attached against the same Desktop dev app session
|
||||
- **watch ready is not enough**; `:electron` runtime count must be non-zero before attaching
|
||||
- if `npx shadow-cljs cljs-repl electron` says `No available JS runtime`, first inspect runtime counts; do not keep retrying blindly
|
||||
|
||||
### db-worker-node REPL
|
||||
|
||||
Scripts:
|
||||
|
||||
- `scripts/start-db-worker-node-repl.sh`
|
||||
- `scripts/cleanup-db-worker-node-repl.sh`
|
||||
|
||||
Standard start:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-db-worker-node-repl.sh --repo demo
|
||||
```
|
||||
|
||||
Start without attaching:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-db-worker-node-repl.sh --repo demo --no-repl
|
||||
```
|
||||
|
||||
Pass extra runtime args:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-db-worker-node-repl.sh --repo demo -- --create-empty-db
|
||||
```
|
||||
|
||||
What it does:
|
||||
|
||||
1. starts or reuses shared `yarn watch`
|
||||
2. waits for the `:db-worker-node` build
|
||||
3. starts or reuses `node ./static/db-worker-node.js --repo <name>`
|
||||
4. runs a REPL connectivity check
|
||||
5. attaches `npx shadow-cljs cljs-repl db-worker-node` unless `--no-repl` is used
|
||||
|
||||
### Run multiple runtimes together
|
||||
|
||||
Use one terminal or editor session per runtime.
|
||||
|
||||
Examples:
|
||||
|
||||
- `:app` + `:electron`: start Desktop once with `--no-repl`, then attach each runtime separately
|
||||
- `:app` + `:db-worker-node`: start both workflows with `--no-repl`, then attach separately
|
||||
- `:electron` + `:db-worker-node`: start Desktop once plus db-worker-node once, then attach separately
|
||||
- all three: one shared Desktop/watch workflow plus one db-worker-node workflow
|
||||
|
||||
Shared `yarn watch` is expected here. Do not rely on one interactive REPL session to cover multiple runtimes for you.
|
||||
|
||||
### Editor attach
|
||||
|
||||
If the start scripts already launched `yarn watch`, nREPL should be on `localhost:8701`.
|
||||
|
||||
#### Desktop `:app`
|
||||
|
||||
Direct shadow-cljs/editor attach:
|
||||
|
||||
- Host: `localhost`
|
||||
- Port: `8701`
|
||||
- Build: `:app`
|
||||
|
||||
CLJ-first attach:
|
||||
|
||||
```clojure
|
||||
(shadow.cljs.devtools.api/repl :app)
|
||||
```
|
||||
|
||||
Helper:
|
||||
## Editor Attach Helpers
|
||||
|
||||
```clojure
|
||||
(shadow.user/cljs-repl)
|
||||
```
|
||||
|
||||
#### Electron `:electron`
|
||||
|
||||
Direct shadow-cljs/editor attach:
|
||||
|
||||
- Host: `localhost`
|
||||
- Port: `8701`
|
||||
- Build: `:electron`
|
||||
|
||||
CLJ-first attach:
|
||||
|
||||
```clojure
|
||||
(shadow.cljs.devtools.api/repl :electron)
|
||||
```
|
||||
|
||||
Helper:
|
||||
|
||||
```clojure
|
||||
(shadow.user/electron-repl)
|
||||
```
|
||||
|
||||
`shadow.user/electron-repl` ensures `:electron` is watched, then attaches to the Electron main-process runtime.
|
||||
|
||||
#### `db-worker-node`
|
||||
|
||||
Direct shadow-cljs/editor attach:
|
||||
|
||||
- Host: `localhost`
|
||||
- Port: `8701`
|
||||
- Build: `:db-worker-node`
|
||||
|
||||
CLJ-first attach:
|
||||
|
||||
```clojure
|
||||
(shadow.cljs.devtools.api/repl :db-worker-node)
|
||||
```
|
||||
|
||||
Helper:
|
||||
|
||||
```clojure
|
||||
(shadow.user/worker-node-repl)
|
||||
```
|
||||
|
||||
`shadow.user/worker-node-repl` picks the first available `:db-worker-node` runtime.
|
||||
## Troubleshooting
|
||||
|
||||
### Troubleshooting
|
||||
Failure triage order:
|
||||
|
||||
#### Failure triage order
|
||||
1. inspect `tmp/logseq-repl/shared-shadow-watch.log`
|
||||
2. inspect `tmp/logseq-repl/desktop-electron.log`
|
||||
3. inspect `tmp/logseq-repl/db-worker-node.log`
|
||||
4. inspect standard port listeners with `lsof`
|
||||
5. inspect runtime counts with `shadow.cljs.devtools.api/repl-runtimes`
|
||||
|
||||
When startup or attach fails, inspect evidence in this order before retrying:
|
||||
Common cases:
|
||||
|
||||
1. `tmp/logseq-repl/shared-shadow-watch.log`
|
||||
2. `tmp/desktop-app-repl/desktop-electron.log` or `tmp/db-worker-node-repl/*`
|
||||
3. port listeners via `lsof`
|
||||
4. runtime counts via `shadow.cljs.devtools.api/repl-runtimes`
|
||||
- `No available JS runtime`: the build may be ready, but the runtime has not connected. Check runtime counts before retrying attach.
|
||||
- multiple `:app` runtimes: close browser dev app instances so only the Desktop renderer remains.
|
||||
- ports already in use after cleanup: another Logseq/shadow-cljs dev session is still running.
|
||||
- `db-worker-node` repo mismatch: rerun `start-repl.sh --repo <name>`; it restarts the worker runtime for the requested repo.
|
||||
|
||||
Do not jump from a failed attach straight to another attach attempt.
|
||||
## Recommended Response Pattern
|
||||
|
||||
#### `start-desktop-app-repl.sh` timed out or said watch is not ready
|
||||
|
||||
Possible causes:
|
||||
|
||||
- external port conflict after cleanup
|
||||
- watch alive but expected ports or log patterns differ from the script assumptions
|
||||
- builds completed but runtime count is still `0`
|
||||
|
||||
Triage:
|
||||
|
||||
1. read `tmp/logseq-repl/shared-shadow-watch.log`
|
||||
2. rerun the port audit
|
||||
3. check runtime counts
|
||||
4. if runtime counts are all `0`, debug app startup next instead of attaching
|
||||
|
||||
#### Desktop `:app`: `No available JS runtime`
|
||||
|
||||
Cause: the Desktop renderer is not connected yet, or the Desktop window is not open.
|
||||
|
||||
Fix:
|
||||
|
||||
1. run `scripts/start-desktop-app-repl.sh --no-repl`
|
||||
2. confirm the Desktop window is open
|
||||
3. confirm `:app` runtime count is non-zero
|
||||
4. only then attach from the script or editor
|
||||
|
||||
#### Desktop startup: ports already in use (`8701`, `9630`, `3001`, `3002`)
|
||||
|
||||
Cause: another Logseq/shadow-cljs dev session is already running, often one that was not started by these skill scripts.
|
||||
|
||||
How to confirm:
|
||||
|
||||
```bash
|
||||
lsof -nP -iTCP:8701 -sTCP:LISTEN
|
||||
lsof -nP -iTCP:9630 -sTCP:LISTEN
|
||||
lsof -nP -iTCP:3001 -sTCP:LISTEN
|
||||
lsof -nP -iTCP:3002 -sTCP:LISTEN
|
||||
```
|
||||
|
||||
If those listeners are not the PIDs tracked under `tmp/logseq-repl/`, `tmp/desktop-app-repl/`, or `tmp/db-worker-node-repl/`, the cleanup scripts will not stop them.
|
||||
|
||||
Fix:
|
||||
|
||||
1. stop the conflicting manually started dev session
|
||||
2. rerun both cleanup scripts
|
||||
3. rerun the port audit
|
||||
4. retry `start-desktop-app-repl.sh` only after ports are clean
|
||||
|
||||
#### Desktop `:app`: wrong runtime attached
|
||||
|
||||
Cause: browser and Desktop renderer runtimes are both alive.
|
||||
|
||||
Fix: close the browser dev app and retry.
|
||||
|
||||
#### Desktop `:app`: connected to `:electron` by mistake
|
||||
|
||||
Symptom: DOM/browser globals are missing.
|
||||
|
||||
Fix: reconnect to `:app`, not `:electron`.
|
||||
|
||||
#### Electron `:electron`: `No available JS runtime`
|
||||
|
||||
Cause: the Electron main process is not connected yet, or `yarn dev-electron-app` has not finished starting.
|
||||
|
||||
Fix:
|
||||
|
||||
1. run `scripts/start-desktop-app-repl.sh --no-repl`
|
||||
2. confirm the Desktop dev app is still open
|
||||
3. confirm `:electron` runtime count is non-zero
|
||||
4. only then run `npx shadow-cljs cljs-repl electron`
|
||||
|
||||
If build logs look healthy but `:electron` runtime count stays `0`, debug the Desktop app startup instead of retrying the attach command.
|
||||
|
||||
#### Electron desktop app exits quickly
|
||||
|
||||
Symptoms may include:
|
||||
|
||||
- `yarn dev-electron-app` exits almost immediately
|
||||
- `gulp electron` reports incomplete async completion
|
||||
- no `:app` or `:electron` runtime ever appears
|
||||
|
||||
Triage:
|
||||
|
||||
1. inspect `tmp/desktop-app-repl/desktop-electron.log`
|
||||
2. inspect `tmp/logseq-repl/shared-shadow-watch.log`
|
||||
3. inspect port conflicts
|
||||
|
||||
Do not attempt `cljs-repl electron` until runtime count is non-zero.
|
||||
|
||||
#### Electron `:electron`: browser globals are missing
|
||||
|
||||
Expected: `js/process` exists, but `js/document` usually does not.
|
||||
|
||||
Fix: if you need DOM/browser APIs, reconnect to `:app` instead.
|
||||
|
||||
#### `db-worker-node`: `shadow-cljs already running in project`
|
||||
|
||||
Cause: stale or conflicting shadow/db-worker-node processes.
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/cleanup-db-worker-node-repl.sh
|
||||
.agents/skills/logseq-repl/scripts/start-db-worker-node-repl.sh
|
||||
```
|
||||
|
||||
Then confirm `:db-worker-node` runtime count is non-zero before attaching.
|
||||
|
||||
#### `db-worker-node`: `repo is required`
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-db-worker-node-repl.sh --repo <name>
|
||||
```
|
||||
|
||||
#### `db-worker-node`: `No available JS runtime`
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/cleanup-db-worker-node-repl.sh
|
||||
.agents/skills/logseq-repl/scripts/start-db-worker-node-repl.sh --repo <name>
|
||||
```
|
||||
|
||||
Then confirm runtime count before attaching.
|
||||
|
||||
#### `db-worker-node`: runtime/module startup errors
|
||||
|
||||
Rebuild runtime artifacts first:
|
||||
|
||||
```bash
|
||||
yarn db-worker-node:release:bundle
|
||||
```
|
||||
|
||||
## Recommended response pattern
|
||||
|
||||
When helping a user connect to one or more REPLs:
|
||||
When helping a user connect to a REPL:
|
||||
|
||||
1. identify whether they need `:app`, `:electron`, `:db-worker-node`, or a combination
|
||||
2. run both cleanup scripts
|
||||
3. run the post-cleanup port audit; if standard ports are still occupied, treat that as an external conflict first
|
||||
4. if they need Desktop `:app`, close browser dev app instances first
|
||||
5. start the needed workflow: `start-desktop-app-repl.sh` for `:app`/`:electron`, `start-db-worker-node-repl.sh` for `:db-worker-node`
|
||||
6. if they need multiple runtimes, start each workflow with `--no-repl`
|
||||
7. verify runtime counts before attaching; do not assume watch/build readiness implies runtime readiness
|
||||
8. attach from the matching build or helper: `shadow.user/cljs-repl`, `shadow.user/electron-repl`, or `shadow.user/worker-node-repl`
|
||||
9. if something fails, inspect `tmp/logseq-repl/shared-shadow-watch.log` first, then use port checks, runtime counts, and app logs to triage instead of repeatedly retrying attach commands
|
||||
10. run the matching cleanup script when finished
|
||||
2. run `cleanup-repl.sh`
|
||||
3. if standard ports remain occupied, resolve that conflict first
|
||||
4. run `start-repl.sh --repo <name>`
|
||||
5. verify runtime counts if attach fails
|
||||
6. attach to the matching build or helper
|
||||
7. run `cleanup-repl.sh` when finished
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DEFAULT_REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
REPO_ROOT="${REPO_ROOT:-$DEFAULT_REPO_ROOT}"
|
||||
FORCE_KILL=0
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Stop processes started by start-db-worker-node-repl.sh.
|
||||
|
||||
Usage:
|
||||
cleanup-db-worker-node-repl.sh [options]
|
||||
|
||||
Options:
|
||||
--repo-root <path> Logseq repository root (default: auto-detect from script location)
|
||||
--force Use SIGKILL if process does not stop gracefully
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--repo-root)
|
||||
shift
|
||||
REPO_ROOT="${1:?missing value for --repo-root}"
|
||||
;;
|
||||
--force)
|
||||
FORCE_KILL=1
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
LOG_DIR="$REPO_ROOT/tmp/db-worker-node-repl"
|
||||
DESKTOP_LOG_DIR="$REPO_ROOT/tmp/desktop-app-repl"
|
||||
SHARED_LOG_DIR="$REPO_ROOT/tmp/logseq-repl"
|
||||
SHADOW_PID_FILE="$LOG_DIR/shadow-db-worker-node.pid"
|
||||
DB_PID_FILE="$LOG_DIR/db-worker-node.pid"
|
||||
DB_REPO_FILE="$LOG_DIR/db-worker-node.repo"
|
||||
DESKTOP_SHADOW_PID_FILE="$DESKTOP_LOG_DIR/shadow-watch.pid"
|
||||
SHARED_SHADOW_PID_FILE="$SHARED_LOG_DIR/shared-shadow-watch.pid"
|
||||
|
||||
is_running_pid() {
|
||||
local pid="$1"
|
||||
[[ "$pid" =~ ^[0-9]+$ ]] && kill -0 "$pid" 2>/dev/null
|
||||
}
|
||||
|
||||
read_pid() {
|
||||
local file="$1"
|
||||
if [[ -f "$file" ]]; then
|
||||
tr -d '[:space:]' < "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
stop_by_pid_file() {
|
||||
local pid_file="$1"
|
||||
local label="$2"
|
||||
|
||||
local pid
|
||||
pid="$(read_pid "$pid_file" || true)"
|
||||
|
||||
if [[ -z "${pid:-}" ]]; then
|
||||
echo "$label: no pid file, nothing to stop"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! is_running_pid "$pid"; then
|
||||
echo "$label: process already stopped (pid=$pid)"
|
||||
rm -f "$pid_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "$label: stopping pid=$pid"
|
||||
kill "$pid" 2>/dev/null || true
|
||||
|
||||
local i
|
||||
for ((i=0; i<10; i++)); do
|
||||
if ! is_running_pid "$pid"; then
|
||||
echo "$label: stopped"
|
||||
rm -f "$pid_file"
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [[ "$FORCE_KILL" -eq 1 ]]; then
|
||||
echo "$label: force killing pid=$pid"
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
if is_running_pid "$pid"; then
|
||||
echo "$label: failed to stop pid=$pid" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$label: stopped"
|
||||
rm -f "$pid_file"
|
||||
}
|
||||
|
||||
stop_shadow_watch() {
|
||||
local own_pid shared_pid other_pid
|
||||
own_pid="$(read_pid "$SHADOW_PID_FILE" || true)"
|
||||
shared_pid="$(read_pid "$SHARED_SHADOW_PID_FILE" || true)"
|
||||
other_pid="$(read_pid "$DESKTOP_SHADOW_PID_FILE" || true)"
|
||||
|
||||
if [[ -n "${own_pid:-}" && -n "${other_pid:-}" && "$own_pid" == "$other_pid" ]] && is_running_pid "$other_pid"; then
|
||||
echo "shadow-cljs watch: shared with other workflows, leaving it running"
|
||||
rm -f "$SHADOW_PID_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${shared_pid:-}" && -n "${other_pid:-}" && "$shared_pid" == "$other_pid" ]] && is_running_pid "$other_pid"; then
|
||||
echo "shadow-cljs watch: shared with other workflows, leaving it running"
|
||||
rm -f "$SHADOW_PID_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -f "$SHARED_SHADOW_PID_FILE" ]]; then
|
||||
stop_by_pid_file "$SHARED_SHADOW_PID_FILE" "shadow-cljs watch"
|
||||
rm -f "$SHADOW_PID_FILE"
|
||||
else
|
||||
stop_by_pid_file "$SHADOW_PID_FILE" "shadow-cljs watch"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ ! -d "$LOG_DIR" && ! -d "$SHARED_LOG_DIR" ]]; then
|
||||
echo "State directory not found: $LOG_DIR"
|
||||
echo "Nothing to clean up."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
stop_by_pid_file "$DB_PID_FILE" "db-worker-node"
|
||||
rm -f "$DB_REPO_FILE"
|
||||
stop_shadow_watch
|
||||
|
||||
echo "Cleanup done."
|
||||
@@ -8,14 +8,14 @@ FORCE_KILL=0
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Stop processes started by start-desktop-app-repl.sh.
|
||||
Stop all processes started by start-repl.sh.
|
||||
|
||||
Usage:
|
||||
cleanup-desktop-app-repl.sh [options]
|
||||
cleanup-repl.sh [options]
|
||||
|
||||
Options:
|
||||
--repo-root <path> Logseq repository root (default: auto-detect from script location)
|
||||
--force Use SIGKILL if process does not stop gracefully
|
||||
--force Use SIGKILL if a process does not stop gracefully
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
@@ -42,13 +42,9 @@ while [[ $# -gt 0 ]]; do
|
||||
shift
|
||||
done
|
||||
|
||||
LOG_DIR="$REPO_ROOT/tmp/desktop-app-repl"
|
||||
DB_WORKER_LOG_DIR="$REPO_ROOT/tmp/db-worker-node-repl"
|
||||
SHARED_LOG_DIR="$REPO_ROOT/tmp/logseq-repl"
|
||||
SHADOW_PID_FILE="$LOG_DIR/shadow-watch.pid"
|
||||
DESKTOP_PID_FILE="$LOG_DIR/desktop-electron.pid"
|
||||
DB_WORKER_SHADOW_PID_FILE="$DB_WORKER_LOG_DIR/shadow-db-worker-node.pid"
|
||||
SHARED_SHADOW_PID_FILE="$SHARED_LOG_DIR/shared-shadow-watch.pid"
|
||||
LOG_DIR="$REPO_ROOT/tmp/logseq-repl"
|
||||
LEGACY_DESKTOP_LOG_DIR="$REPO_ROOT/tmp/desktop-app-repl"
|
||||
LEGACY_DB_LOG_DIR="$REPO_ROOT/tmp/db-worker-node-repl"
|
||||
|
||||
is_running_pid() {
|
||||
local pid="$1"
|
||||
@@ -94,7 +90,7 @@ stop_by_pid_file() {
|
||||
pid="$(read_pid "$pid_file" || true)"
|
||||
|
||||
if [[ -z "${pid:-}" ]]; then
|
||||
echo "$label: no pid file, nothing to stop"
|
||||
rm -f "$pid_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -132,39 +128,47 @@ stop_by_pid_file() {
|
||||
rm -f "$pid_file"
|
||||
}
|
||||
|
||||
stop_shadow_watch() {
|
||||
local own_pid shared_pid other_pid
|
||||
own_pid="$(read_pid "$SHADOW_PID_FILE" || true)"
|
||||
shared_pid="$(read_pid "$SHARED_SHADOW_PID_FILE" || true)"
|
||||
other_pid="$(read_pid "$DB_WORKER_SHADOW_PID_FILE" || true)"
|
||||
|
||||
if [[ -n "${own_pid:-}" && -n "${other_pid:-}" && "$own_pid" == "$other_pid" ]] && is_running_pid "$other_pid"; then
|
||||
echo "shadow-cljs watch: shared with other workflows, leaving it running"
|
||||
rm -f "$SHADOW_PID_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${shared_pid:-}" && -n "${other_pid:-}" && "$shared_pid" == "$other_pid" ]] && is_running_pid "$other_pid"; then
|
||||
echo "shadow-cljs watch: shared with other workflows, leaving it running"
|
||||
rm -f "$SHADOW_PID_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -f "$SHARED_SHADOW_PID_FILE" ]]; then
|
||||
stop_by_pid_file "$SHARED_SHADOW_PID_FILE" "shadow-cljs watch"
|
||||
rm -f "$SHADOW_PID_FILE"
|
||||
else
|
||||
stop_by_pid_file "$SHADOW_PID_FILE" "shadow-cljs watch"
|
||||
fi
|
||||
repo_owns_pid() {
|
||||
local pid="$1"
|
||||
local cwd
|
||||
cwd="$(lsof -nP -a -p "$pid" -d cwd 2>/dev/null | awk 'NR > 1 {print $NF; exit}' || true)"
|
||||
[[ "$cwd" == "$REPO_ROOT" ]]
|
||||
}
|
||||
|
||||
if [[ ! -d "$LOG_DIR" && ! -d "$SHARED_LOG_DIR" ]]; then
|
||||
stop_repo_port_listener() {
|
||||
local port="$1"
|
||||
local pid
|
||||
|
||||
while read -r pid; do
|
||||
[[ -n "${pid:-}" ]] || continue
|
||||
if is_running_pid "$pid" && repo_owns_pid "$pid"; then
|
||||
echo "port $port listener: stopping pid=$pid"
|
||||
signal_process_group TERM "$pid"
|
||||
fi
|
||||
done < <(lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)
|
||||
}
|
||||
|
||||
if [[ ! -d "$LOG_DIR" && ! -d "$LEGACY_DESKTOP_LOG_DIR" && ! -d "$LEGACY_DB_LOG_DIR" ]]; then
|
||||
echo "State directory not found: $LOG_DIR"
|
||||
echo "Nothing to clean up."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
stop_by_pid_file "$DESKTOP_PID_FILE" "desktop-electron"
|
||||
stop_shadow_watch
|
||||
stop_by_pid_file "$LOG_DIR/db-worker-node.pid" "db-worker-node"
|
||||
stop_by_pid_file "$LOG_DIR/desktop-electron.pid" "desktop-electron"
|
||||
stop_by_pid_file "$LOG_DIR/shared-shadow-watch.pid" "shadow-cljs watch"
|
||||
|
||||
stop_by_pid_file "$LEGACY_DB_LOG_DIR/db-worker-node.pid" "legacy db-worker-node"
|
||||
stop_by_pid_file "$LEGACY_DB_LOG_DIR/shadow-db-worker-node.pid" "legacy shadow-cljs watch"
|
||||
stop_by_pid_file "$LEGACY_DESKTOP_LOG_DIR/desktop-electron.pid" "legacy desktop-electron"
|
||||
stop_by_pid_file "$LEGACY_DESKTOP_LOG_DIR/shadow-watch.pid" "legacy shadow-cljs watch"
|
||||
|
||||
rm -f "$LOG_DIR/db-worker-node.repo" "$LEGACY_DB_LOG_DIR/db-worker-node.repo"
|
||||
|
||||
for port in 8701 3001 3002 9630 9631; do
|
||||
stop_repo_port_listener "$port"
|
||||
done
|
||||
|
||||
rm -f "$LOG_DIR"/*.pid "$LEGACY_DB_LOG_DIR"/*.pid "$LEGACY_DESKTOP_LOG_DIR"/*.pid 2>/dev/null || true
|
||||
|
||||
echo "Cleanup done."
|
||||
@@ -51,7 +51,7 @@ logseq_repl_runtime_count() {
|
||||
local output
|
||||
|
||||
pushd "$repo_root" >/dev/null
|
||||
if ! output="$(npx shadow-cljs clj-eval "(do (require '[shadow.cljs.devtools.api :as api]) (println (count (api/repl-runtimes :$build_name))))" 2>&1)"; then
|
||||
if ! output="$(pnpm exec shadow-cljs clj-eval "(do (require '[shadow.cljs.devtools.api :as api]) (println (count (api/repl-runtimes :$build_name))))" 2>&1)"; then
|
||||
popd >/dev/null
|
||||
echo "Error: failed to inspect :$build_name runtimes." >&2
|
||||
echo "--- clj-eval output ---" >&2
|
||||
|
||||
@@ -1,324 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DEFAULT_REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
REPO_ROOT="${REPO_ROOT:-$DEFAULT_REPO_ROOT}"
|
||||
DB_REPO="${DB_REPO:-demo}"
|
||||
ATTACH_REPL=1
|
||||
EXTRA_NODE_ARGS=()
|
||||
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Start or reuse db-worker-node REPL workflow.
|
||||
|
||||
Usage:
|
||||
start-db-worker-node-repl.sh [options] [-- <extra db-worker-node args>]
|
||||
|
||||
Options:
|
||||
--repo <name> Graph repo name passed to db-worker-node (default: demo)
|
||||
--repo-root <path> Logseq repository root (default: auto-detect from script location)
|
||||
--no-repl Do not attach `shadow-cljs cljs-repl` after startup
|
||||
--repl Force attach REPL (default behavior)
|
||||
-h, --help Show this help
|
||||
|
||||
Examples:
|
||||
./start-db-worker-node-repl.sh
|
||||
./start-db-worker-node-repl.sh --repo demo --no-repl
|
||||
./start-db-worker-node-repl.sh --repo demo -- --create-empty-db
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--repo)
|
||||
shift
|
||||
DB_REPO="${1:?missing value for --repo}"
|
||||
;;
|
||||
--repo-root)
|
||||
shift
|
||||
REPO_ROOT="${1:?missing value for --repo-root}"
|
||||
;;
|
||||
--no-repl)
|
||||
ATTACH_REPL=0
|
||||
;;
|
||||
--repl)
|
||||
ATTACH_REPL=1
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
EXTRA_NODE_ARGS+=("$@")
|
||||
break
|
||||
;;
|
||||
*)
|
||||
EXTRA_NODE_ARGS+=("$1")
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ ! -d "$REPO_ROOT" ]]; then
|
||||
echo "Error: repo root not found: $REPO_ROOT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v npx >/dev/null 2>&1; then
|
||||
echo "Error: npx not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v yarn >/dev/null 2>&1; then
|
||||
echo "Error: yarn not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo "Error: node not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LOG_DIR="$REPO_ROOT/tmp/db-worker-node-repl"
|
||||
SHARED_LOG_DIR="$REPO_ROOT/tmp/logseq-repl"
|
||||
mkdir -p "$LOG_DIR"
|
||||
mkdir -p "$SHARED_LOG_DIR"
|
||||
|
||||
if ! logseq_repl_require_clean_standard_ports; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SHADOW_PID_FILE="$LOG_DIR/shadow-db-worker-node.pid"
|
||||
DB_PID_FILE="$LOG_DIR/db-worker-node.pid"
|
||||
DB_REPO_FILE="$LOG_DIR/db-worker-node.repo"
|
||||
SHADOW_LOG="$LOG_DIR/shadow-db-worker-node.log"
|
||||
DB_LOG="$LOG_DIR/db-worker-node.log"
|
||||
SHARED_SHADOW_PID_FILE="$SHARED_LOG_DIR/shared-shadow-watch.pid"
|
||||
SHARED_SHADOW_LOG="$SHARED_LOG_DIR/shared-shadow-watch.log"
|
||||
|
||||
wait_for_log_pattern() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
local timeout_seconds="$3"
|
||||
|
||||
local i
|
||||
for ((i=0; i<timeout_seconds; i++)); do
|
||||
if [[ -f "$file" ]] && grep -q "$pattern" "$file"; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_shadow_build_ready() {
|
||||
local file="$1"
|
||||
local timeout_seconds="$2"
|
||||
|
||||
local i
|
||||
for ((i=0; i<timeout_seconds; i++)); do
|
||||
if [[ -f "$file" ]] && grep -q "\[:db-worker-node\] Build completed\." "$file"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -f "$file" ]] && grep -q "\[:db-worker-node\] Build failure\." "$file"; then
|
||||
echo "Error: shadow-cljs reported build failure. Check $file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Warning: did not observe db-worker-node build completion within timeout." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
start_background_command() {
|
||||
local log_file="$1"
|
||||
shift
|
||||
|
||||
pushd "$REPO_ROOT" >/dev/null
|
||||
if command -v setsid >/dev/null 2>&1; then
|
||||
nohup setsid "$@" > "$log_file" 2>&1 &
|
||||
else
|
||||
nohup "$@" > "$log_file" 2>&1 &
|
||||
fi
|
||||
local pid=$!
|
||||
popd >/dev/null
|
||||
|
||||
echo "$pid"
|
||||
}
|
||||
|
||||
ensure_shadow_watch() {
|
||||
local existing_pid shared_pid
|
||||
existing_pid="$(logseq_repl_read_pid "$SHADOW_PID_FILE" || true)"
|
||||
|
||||
if [[ -n "${existing_pid:-}" ]] && logseq_repl_is_running_pid "$existing_pid"; then
|
||||
echo "Reusing shared shadow-cljs watch (pid=$existing_pid)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
shared_pid="$(logseq_repl_read_pid "$SHARED_SHADOW_PID_FILE" || true)"
|
||||
if [[ -n "${shared_pid:-}" ]] && logseq_repl_is_running_pid "$shared_pid"; then
|
||||
echo "$shared_pid" > "$SHADOW_PID_FILE"
|
||||
: > "$SHADOW_LOG"
|
||||
echo "Reusing shared shadow-cljs watch (pid=$shared_pid)"
|
||||
if wait_for_shadow_build_ready "$SHARED_SHADOW_LOG" 120; then
|
||||
echo "shadow-cljs db-worker-node build is ready"
|
||||
else
|
||||
echo "Error: shadow-cljs build is not ready. Check $SHARED_SHADOW_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Starting shared shadow-cljs watch via yarn watch ..."
|
||||
local shadow_pid
|
||||
shadow_pid="$(start_background_command "$SHARED_SHADOW_LOG" yarn watch)"
|
||||
|
||||
echo "$shadow_pid" > "$SHARED_SHADOW_PID_FILE"
|
||||
echo "$shadow_pid" > "$SHADOW_PID_FILE"
|
||||
: > "$SHADOW_LOG"
|
||||
sleep 1
|
||||
|
||||
if ! logseq_repl_is_running_pid "$shadow_pid"; then
|
||||
echo "Error: yarn watch exited early. Check $SHARED_SHADOW_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if wait_for_log_pattern "$SHARED_SHADOW_LOG" "watching build :db-worker-node\\|\\[:db-worker-node\\] Build completed\\." 45; then
|
||||
echo "shadow-cljs watch is ready (pid=$shadow_pid)"
|
||||
else
|
||||
echo "Warning: did not observe watch-ready log within timeout. Continuing anyway." >&2
|
||||
fi
|
||||
|
||||
if wait_for_shadow_build_ready "$SHARED_SHADOW_LOG" 120; then
|
||||
echo "shadow-cljs db-worker-node build is ready"
|
||||
else
|
||||
echo "Error: shadow-cljs build is not ready. Check $SHARED_SHADOW_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_db_worker_node() {
|
||||
local existing_pid existing_repo
|
||||
existing_pid="$(logseq_repl_read_pid "$DB_PID_FILE" || true)"
|
||||
existing_repo="$(logseq_repl_read_pid "$DB_REPO_FILE" || true)"
|
||||
|
||||
if [[ -n "${existing_pid:-}" ]] && logseq_repl_is_running_pid "$existing_pid"; then
|
||||
if [[ "$existing_repo" == "$DB_REPO" ]]; then
|
||||
echo "Reusing db-worker-node runtime (pid=$existing_pid, repo=$DB_REPO)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Stopping existing db-worker-node (pid=$existing_pid) due to repo mismatch"
|
||||
kill "$existing_pid" 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
echo "Starting db-worker-node (repo=$DB_REPO) ..."
|
||||
pushd "$REPO_ROOT" >/dev/null
|
||||
local node_cmd=(node ./static/db-worker-node.js --repo "$DB_REPO")
|
||||
if (( ${#EXTRA_NODE_ARGS[@]} > 0 )); then
|
||||
node_cmd+=("${EXTRA_NODE_ARGS[@]}")
|
||||
fi
|
||||
nohup "${node_cmd[@]}" > "$DB_LOG" 2>&1 &
|
||||
local db_pid=$!
|
||||
popd >/dev/null
|
||||
|
||||
echo "$db_pid" > "$DB_PID_FILE"
|
||||
echo "$DB_REPO" > "$DB_REPO_FILE"
|
||||
sleep 1
|
||||
|
||||
if ! logseq_repl_is_running_pid "$db_pid"; then
|
||||
echo "Error: db-worker-node exited early. Check $DB_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "db-worker-node is running (pid=$db_pid, repo=$DB_REPO)"
|
||||
}
|
||||
|
||||
ensure_worker_node_runtime() {
|
||||
local runtime_count second
|
||||
|
||||
for ((second=0; second<60; second++)); do
|
||||
runtime_count="$(logseq_repl_runtime_count "$REPO_ROOT" db-worker-node)"
|
||||
|
||||
if [[ "$runtime_count" != "0" ]]; then
|
||||
echo "Detected live :db-worker-node runtime count: $runtime_count"
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Error: expected a live :db-worker-node runtime, but runtime count stayed 0." >&2
|
||||
echo "Check $DB_LOG and $SHARED_SHADOW_LOG before retrying." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
verify_repl_connectivity() {
|
||||
echo "Verifying CLJS REPL connectivity ..."
|
||||
|
||||
local repl_output
|
||||
pushd "$REPO_ROOT" >/dev/null
|
||||
if repl_output="$(printf '(+ 1 2)\n:cljs/quit\n' | npx shadow-cljs cljs-repl db-worker-node 2>&1)"; then
|
||||
popd >/dev/null
|
||||
else
|
||||
local repl_status=$?
|
||||
popd >/dev/null
|
||||
echo "Error: failed to run REPL connectivity check (exit=$repl_status)." >&2
|
||||
echo "--- REPL output ---" >&2
|
||||
echo "$repl_output" >&2
|
||||
echo "-------------------" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$repl_output" != *"shadow-cljs - connected to server"* ]]; then
|
||||
echo "Error: REPL check did not report successful server connection." >&2
|
||||
echo "--- REPL output ---" >&2
|
||||
echo "$repl_output" >&2
|
||||
echo "-------------------" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$repl_output" != *$'cljs.user=> 3'* ]]; then
|
||||
echo "Error: REPL check did not produce expected evaluation result." >&2
|
||||
echo "--- REPL output ---" >&2
|
||||
echo "$repl_output" >&2
|
||||
echo "-------------------" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "REPL connectivity check passed"
|
||||
}
|
||||
|
||||
ensure_shadow_watch
|
||||
ensure_db_worker_node
|
||||
ensure_worker_node_runtime
|
||||
verify_repl_connectivity
|
||||
|
||||
echo
|
||||
|
||||
echo "Logs:"
|
||||
echo " shared shadow-cljs: $SHARED_SHADOW_LOG"
|
||||
echo " db-worker-node: $DB_LOG"
|
||||
echo " workflow state file: $SHADOW_LOG"
|
||||
echo "PID files:"
|
||||
echo " $SHADOW_PID_FILE"
|
||||
echo " $DB_PID_FILE"
|
||||
echo
|
||||
|
||||
if [[ "$ATTACH_REPL" -eq 1 ]]; then
|
||||
echo "Attaching CLJS REPL: npx shadow-cljs cljs-repl db-worker-node"
|
||||
pushd "$REPO_ROOT" >/dev/null
|
||||
npx shadow-cljs cljs-repl db-worker-node
|
||||
popd >/dev/null
|
||||
else
|
||||
echo "Startup complete. REPL attach skipped (--no-repl)."
|
||||
fi
|
||||
@@ -1,298 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DEFAULT_REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
REPO_ROOT="${REPO_ROOT:-$DEFAULT_REPO_ROOT}"
|
||||
ATTACH_REPL=1
|
||||
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Start or reuse the Desktop app `:app` REPL workflow.
|
||||
|
||||
Usage:
|
||||
start-desktop-app-repl.sh [options]
|
||||
|
||||
Options:
|
||||
--repo-root <path> Logseq repository root (default: auto-detect from script location)
|
||||
--no-repl Do not attach `shadow-cljs cljs-repl app` after startup
|
||||
--repl Force attach REPL (default behavior)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--repo-root)
|
||||
shift
|
||||
REPO_ROOT="${1:?missing value for --repo-root}"
|
||||
;;
|
||||
--no-repl)
|
||||
ATTACH_REPL=0
|
||||
;;
|
||||
--repl)
|
||||
ATTACH_REPL=1
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ ! -d "$REPO_ROOT" ]]; then
|
||||
echo "Error: repo root not found: $REPO_ROOT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v yarn >/dev/null 2>&1; then
|
||||
echo "Error: yarn not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v npx >/dev/null 2>&1; then
|
||||
echo "Error: npx not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LOG_DIR="$REPO_ROOT/tmp/desktop-app-repl"
|
||||
SHARED_LOG_DIR="$REPO_ROOT/tmp/logseq-repl"
|
||||
mkdir -p "$LOG_DIR"
|
||||
mkdir -p "$SHARED_LOG_DIR"
|
||||
|
||||
if ! logseq_repl_require_clean_standard_ports; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SHADOW_PID_FILE="$LOG_DIR/shadow-watch.pid"
|
||||
DESKTOP_PID_FILE="$LOG_DIR/desktop-electron.pid"
|
||||
SHADOW_LOG="$LOG_DIR/shadow-watch.log"
|
||||
DESKTOP_LOG="$LOG_DIR/desktop-electron.log"
|
||||
SHARED_SHADOW_PID_FILE="$SHARED_LOG_DIR/shared-shadow-watch.pid"
|
||||
SHARED_SHADOW_LOG="$SHARED_LOG_DIR/shared-shadow-watch.log"
|
||||
|
||||
wait_for_all_patterns() {
|
||||
local file="$1"
|
||||
local timeout_seconds="$2"
|
||||
shift 2
|
||||
|
||||
local second pattern all_found
|
||||
for ((second=0; second<timeout_seconds; second++)); do
|
||||
all_found=1
|
||||
for pattern in "$@"; do
|
||||
if [[ ! -f "$file" ]] || ! grep -Fq "$pattern" "$file"; then
|
||||
all_found=0
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$all_found" -eq 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_any_pattern() {
|
||||
local file="$1"
|
||||
local timeout_seconds="$2"
|
||||
shift 2
|
||||
|
||||
local second pattern
|
||||
for ((second=0; second<timeout_seconds; second++)); do
|
||||
if [[ -f "$file" ]]; then
|
||||
for pattern in "$@"; do
|
||||
if grep -Fq "$pattern" "$file"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
start_background_command() {
|
||||
local log_file="$1"
|
||||
shift
|
||||
|
||||
pushd "$REPO_ROOT" >/dev/null
|
||||
if command -v setsid >/dev/null 2>&1; then
|
||||
nohup setsid "$@" > "$log_file" 2>&1 &
|
||||
else
|
||||
nohup "$@" > "$log_file" 2>&1 &
|
||||
fi
|
||||
local pid=$!
|
||||
popd >/dev/null
|
||||
|
||||
echo "$pid"
|
||||
}
|
||||
|
||||
ensure_shadow_watch() {
|
||||
local existing_pid shared_pid
|
||||
existing_pid="$(logseq_repl_read_pid "$SHADOW_PID_FILE" || true)"
|
||||
|
||||
if [[ -n "${existing_pid:-}" ]] && logseq_repl_is_running_pid "$existing_pid"; then
|
||||
echo "Reusing shared shadow-cljs watch (pid=$existing_pid)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
shared_pid="$(logseq_repl_read_pid "$SHARED_SHADOW_PID_FILE" || true)"
|
||||
if [[ -n "${shared_pid:-}" ]] && logseq_repl_is_running_pid "$shared_pid"; then
|
||||
echo "$shared_pid" > "$SHADOW_PID_FILE"
|
||||
: > "$SHADOW_LOG"
|
||||
echo "Reusing shared shadow-cljs watch (pid=$shared_pid)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Starting shadow-cljs watch via yarn watch ..."
|
||||
local shadow_pid
|
||||
shadow_pid="$(start_background_command "$SHARED_SHADOW_LOG" yarn watch)"
|
||||
echo "$shadow_pid" > "$SHARED_SHADOW_PID_FILE"
|
||||
echo "$shadow_pid" > "$SHADOW_PID_FILE"
|
||||
: > "$SHADOW_LOG"
|
||||
sleep 1
|
||||
|
||||
if ! logseq_repl_is_running_pid "$shadow_pid"; then
|
||||
echo "Error: yarn watch exited early. Check $SHARED_SHADOW_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if wait_for_all_patterns "$SHARED_SHADOW_LOG" 180 \
|
||||
"[:electron] Build completed." \
|
||||
"[:app] Build completed."; then
|
||||
echo "shadow-cljs watch builds are ready (pid=$shadow_pid)"
|
||||
else
|
||||
echo "Error: yarn watch did not finish the :app/:electron builds in time. Check $SHARED_SHADOW_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_desktop_app() {
|
||||
local existing_pid
|
||||
existing_pid="$(read_pid "$DESKTOP_PID_FILE" || true)"
|
||||
|
||||
if [[ -n "${existing_pid:-}" ]] && is_running_pid "$existing_pid"; then
|
||||
echo "Reusing Desktop dev app (pid=$existing_pid)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Starting Desktop dev app via yarn dev-electron-app ..."
|
||||
local desktop_pid
|
||||
desktop_pid="$(start_background_command "$DESKTOP_LOG" yarn dev-electron-app)"
|
||||
echo "$desktop_pid" > "$DESKTOP_PID_FILE"
|
||||
sleep 1
|
||||
|
||||
if ! is_running_pid "$desktop_pid"; then
|
||||
echo "Error: yarn dev-electron-app exited early. Check $DESKTOP_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if wait_for_any_pattern "$DESKTOP_LOG" 120 "shadow-cljs - #" "Logseq App("; then
|
||||
echo "Desktop dev app is running (pid=$desktop_pid)"
|
||||
else
|
||||
echo "Error: Desktop dev app did not report startup in time. Check $DESKTOP_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_single_app_runtime() {
|
||||
local runtime_count second
|
||||
|
||||
for ((second=0; second<60; second++)); do
|
||||
runtime_count="$(logseq_repl_runtime_count "$REPO_ROOT" app)"
|
||||
|
||||
if [[ "$runtime_count" == "1" ]]; then
|
||||
echo "Detected exactly one live :app runtime"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$runtime_count" != "0" ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [[ "$runtime_count" == "0" ]]; then
|
||||
echo "Error: Expected exactly one live :app runtime, found 0 after waiting for the Desktop renderer to connect." >&2
|
||||
echo "Check $DESKTOP_LOG and $SHARED_SHADOW_LOG, make sure the Desktop window is fully open, then retry." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Error: Expected exactly one live :app runtime, found $runtime_count." >&2
|
||||
echo "Close the browser dev app so only the Desktop renderer remains, then retry." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
verify_renderer_smoke_test() {
|
||||
echo "Verifying Desktop renderer smoke test ..."
|
||||
|
||||
local smoke_output
|
||||
pushd "$REPO_ROOT" >/dev/null
|
||||
if ! smoke_output="$(printf '(some? js/document)\n(.-title js/document)\n:cljs/quit\n' | npx shadow-cljs cljs-repl app 2>&1)"; then
|
||||
popd >/dev/null
|
||||
echo "Error: smoke test REPL command failed." >&2
|
||||
echo "--- smoke output ---" >&2
|
||||
echo "$smoke_output" >&2
|
||||
echo "--------------------" >&2
|
||||
exit 1
|
||||
fi
|
||||
popd >/dev/null
|
||||
|
||||
if [[ "$smoke_output" != *"shadow-cljs - connected to server"* ]]; then
|
||||
echo "Error: smoke test did not connect to shadow-cljs." >&2
|
||||
echo "--- smoke output ---" >&2
|
||||
echo "$smoke_output" >&2
|
||||
echo "--------------------" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$smoke_output" != *$'cljs.user=> true'* ]]; then
|
||||
echo "Error: smoke test did not confirm js/document." >&2
|
||||
echo "--- smoke output ---" >&2
|
||||
echo "$smoke_output" >&2
|
||||
echo "--------------------" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Desktop renderer smoke test passed"
|
||||
}
|
||||
|
||||
ensure_shadow_watch
|
||||
ensure_desktop_app
|
||||
ensure_single_app_runtime
|
||||
verify_renderer_smoke_test
|
||||
|
||||
echo
|
||||
|
||||
echo "Logs:"
|
||||
echo " shared shadow-cljs: $SHARED_SHADOW_LOG"
|
||||
echo " desktop-app: $DESKTOP_LOG"
|
||||
echo " workflow state file: $SHADOW_LOG"
|
||||
echo "PID files:"
|
||||
echo " $SHADOW_PID_FILE"
|
||||
echo " $DESKTOP_PID_FILE"
|
||||
echo
|
||||
|
||||
if [[ "$ATTACH_REPL" -eq 1 ]]; then
|
||||
echo "Attaching CLJS REPL: npx shadow-cljs cljs-repl app"
|
||||
pushd "$REPO_ROOT" >/dev/null
|
||||
npx shadow-cljs cljs-repl app
|
||||
popd >/dev/null
|
||||
else
|
||||
echo "Startup complete. REPL attach skipped (--no-repl)."
|
||||
fi
|
||||
342
.agents/skills/logseq-repl/scripts/start-repl.py
Executable file
342
.agents/skills/logseq-repl/scripts/start-repl.py
Executable file
@@ -0,0 +1,342 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
DEFAULT_REPO_ROOT = SCRIPT_DIR.parents[3]
|
||||
STANDARD_PORTS = (8701, 3001, 3002, 9630, 9631)
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(line_buffering=True)
|
||||
sys.stderr.reconfigure(line_buffering=True)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="start-repl.sh",
|
||||
description="Start the unified Logseq REPL workflow.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=(
|
||||
"This starts shared pnpm watch, the Desktop dev app, and the\n"
|
||||
"db-worker-node runtime, verifies the REPL targets, and exits."
|
||||
),
|
||||
)
|
||||
parser.add_argument("--repo", default=os.environ.get("DB_REPO", "demo"),
|
||||
help="Graph repo name passed to db-worker-node (default: demo)")
|
||||
parser.add_argument("--repo-root", default=os.environ.get("REPO_ROOT", str(DEFAULT_REPO_ROOT)),
|
||||
help="Logseq repository root (default: auto-detect from script location)")
|
||||
parser.add_argument("extra_node_args", nargs=argparse.REMAINDER,
|
||||
help="Arguments after -- are passed to db-worker-node")
|
||||
args = parser.parse_args()
|
||||
if args.extra_node_args and args.extra_node_args[0] == "--":
|
||||
args.extra_node_args = args.extra_node_args[1:]
|
||||
return args
|
||||
|
||||
|
||||
def require_command(name):
|
||||
if subprocess.run(["/usr/bin/env", "sh", "-c", f"command -v {name} >/dev/null 2>&1"]).returncode != 0:
|
||||
raise SystemExit(f"Error: {name} not found in PATH")
|
||||
|
||||
|
||||
def read_pid(path):
|
||||
try:
|
||||
text = path.read_text().strip()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
return int(text) if re.fullmatch(r"\d+", text) else None
|
||||
|
||||
|
||||
def is_running(pid):
|
||||
if not pid:
|
||||
return False
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def write_pid(path, pid):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(f"{pid}\n")
|
||||
|
||||
|
||||
def wait_for_patterns(path, timeout, patterns, all_required=True):
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if path.exists():
|
||||
text = path.read_text(errors="replace")
|
||||
if all_required and all(pattern in text for pattern in patterns):
|
||||
return True
|
||||
if not all_required and any(pattern in text for pattern in patterns):
|
||||
return True
|
||||
time.sleep(1)
|
||||
return False
|
||||
|
||||
|
||||
def process_group_kwargs():
|
||||
if os.name == "nt":
|
||||
return {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP}
|
||||
return {"start_new_session": True}
|
||||
|
||||
|
||||
def start_process(repo_root, log_path, command):
|
||||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
log_file = log_path.open("wb")
|
||||
return subprocess.Popen(
|
||||
command,
|
||||
cwd=repo_root,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=log_file,
|
||||
stderr=subprocess.STDOUT,
|
||||
**process_group_kwargs(),
|
||||
)
|
||||
|
||||
|
||||
def port_listener_pid(port):
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
["lsof", "-nP", f"-iTCP:{port}", "-sTCP:LISTEN", "-t"],
|
||||
text=True,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||
return None
|
||||
for line in output.splitlines():
|
||||
if line.strip().isdigit():
|
||||
return int(line.strip())
|
||||
return None
|
||||
|
||||
|
||||
def has_managed_processes(paths):
|
||||
return any(is_running(read_pid(path)) for path in paths)
|
||||
|
||||
|
||||
def require_clean_ports():
|
||||
had_conflict = False
|
||||
for port in STANDARD_PORTS:
|
||||
pid = port_listener_pid(port)
|
||||
if pid:
|
||||
had_conflict = True
|
||||
print(f"Port {port} is already listening (pid={pid})", file=sys.stderr)
|
||||
if had_conflict:
|
||||
print("Error: standard Logseq REPL ports are still occupied after cleanup.", file=sys.stderr)
|
||||
print("Resolve the external conflict first, then retry.", file=sys.stderr)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def ensure_shadow_watch(repo_root, shadow_pid_file, shadow_log):
|
||||
pid = read_pid(shadow_pid_file)
|
||||
if is_running(pid):
|
||||
print(f"Reusing shared shadow-cljs watch (pid={pid})")
|
||||
return pid
|
||||
|
||||
print("Starting shared shadow-cljs watch via pnpm watch ...")
|
||||
process = start_process(repo_root, shadow_log, ["pnpm", "watch"])
|
||||
write_pid(shadow_pid_file, process.pid)
|
||||
time.sleep(1)
|
||||
|
||||
if process.poll() is not None:
|
||||
raise SystemExit(f"Error: pnpm watch exited early. Check {shadow_log}")
|
||||
|
||||
if not wait_for_patterns(shadow_log, 180, [
|
||||
"[:electron] Build completed.",
|
||||
"[:app] Build completed.",
|
||||
"[:db-worker-node] Build completed.",
|
||||
]):
|
||||
raise SystemExit(f"Error: pnpm watch did not finish the expected builds in time. Check {shadow_log}")
|
||||
|
||||
listener_pid = port_listener_pid(8701)
|
||||
if listener_pid and is_running(listener_pid):
|
||||
write_pid(shadow_pid_file, listener_pid)
|
||||
pid = listener_pid
|
||||
else:
|
||||
pid = process.pid
|
||||
|
||||
print("shadow-cljs watch builds are ready")
|
||||
return pid
|
||||
|
||||
|
||||
def ensure_desktop_app(repo_root, desktop_pid_file, desktop_log):
|
||||
pid = read_pid(desktop_pid_file)
|
||||
if is_running(pid):
|
||||
print(f"Reusing Desktop dev app (pid={pid})")
|
||||
return pid
|
||||
|
||||
print("Starting Desktop dev app via pnpm dev-electron-app ...")
|
||||
process = start_process(repo_root, desktop_log, ["pnpm", "dev-electron-app"])
|
||||
write_pid(desktop_pid_file, process.pid)
|
||||
time.sleep(1)
|
||||
|
||||
if process.poll() is not None:
|
||||
raise SystemExit(f"Error: pnpm dev-electron-app exited early. Check {desktop_log}")
|
||||
|
||||
if not wait_for_patterns(desktop_log, 120, ["shadow-cljs - #", "Logseq App("], all_required=False):
|
||||
raise SystemExit(f"Error: Desktop dev app did not report startup in time. Check {desktop_log}")
|
||||
|
||||
print(f"Desktop dev app is running (pid={process.pid})")
|
||||
return process.pid
|
||||
|
||||
|
||||
def ensure_db_worker_node(repo_root, db_pid_file, db_repo_file, db_log, repo, extra_args):
|
||||
pid = read_pid(db_pid_file)
|
||||
existing_repo = db_repo_file.read_text().strip() if db_repo_file.exists() else None
|
||||
|
||||
if is_running(pid):
|
||||
if existing_repo == repo:
|
||||
print(f"Reusing db-worker-node runtime (pid={pid}, repo={repo})")
|
||||
return pid
|
||||
print(f"Stopping existing db-worker-node (pid={pid}) due to repo mismatch")
|
||||
terminate_pid(pid)
|
||||
time.sleep(1)
|
||||
|
||||
print(f"Starting db-worker-node (repo={repo}) ...")
|
||||
process = start_process(
|
||||
repo_root,
|
||||
db_log,
|
||||
["node", "./static/db-worker-node.js", "--repo", repo, *extra_args],
|
||||
)
|
||||
write_pid(db_pid_file, process.pid)
|
||||
db_repo_file.write_text(f"{repo}\n")
|
||||
time.sleep(1)
|
||||
|
||||
if process.poll() is not None:
|
||||
raise SystemExit(f"Error: db-worker-node exited early. Check {db_log}")
|
||||
|
||||
print(f"db-worker-node is running (pid={process.pid}, repo={repo})")
|
||||
return process.pid
|
||||
|
||||
|
||||
def runtime_count(repo_root, build_name):
|
||||
form = f"(do (require '[shadow.cljs.devtools.api :as api]) (println (count (api/repl-runtimes :{build_name}))))"
|
||||
proc = subprocess.run(
|
||||
["pnpm", "exec", "shadow-cljs", "clj-eval", form],
|
||||
cwd=repo_root,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
raise SystemExit(
|
||||
f"Error: failed to inspect :{build_name} runtimes.\n"
|
||||
f"--- clj-eval output ---\n{proc.stdout}{proc.stderr}\n-----------------------"
|
||||
)
|
||||
matches = re.findall(r"^(\d+)$", proc.stdout + proc.stderr, flags=re.MULTILINE)
|
||||
if not matches:
|
||||
raise SystemExit(
|
||||
f"Error: could not parse :{build_name} runtime count.\n"
|
||||
f"--- clj-eval output ---\n{proc.stdout}{proc.stderr}\n-----------------------"
|
||||
)
|
||||
return int(matches[-1])
|
||||
|
||||
|
||||
def wait_for_runtime_count(repo_root, build_name, expected, timeout):
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
count = runtime_count(repo_root, build_name)
|
||||
if expected == "exactly-one":
|
||||
if count == 1:
|
||||
print(f"Detected exactly one live :{build_name} runtime")
|
||||
return
|
||||
if count != 0:
|
||||
raise SystemExit(f"Error: Expected exactly one live :{build_name} runtime, found {count}.")
|
||||
elif expected == "nonzero" and count != 0:
|
||||
print(f"Detected live :{build_name} runtime count: {count}")
|
||||
return
|
||||
time.sleep(1)
|
||||
|
||||
if expected == "exactly-one":
|
||||
raise SystemExit(f"Error: Expected exactly one live :{build_name} runtime, found 0 after waiting.")
|
||||
raise SystemExit(f"Error: expected a live :{build_name} runtime, but runtime count stayed 0.")
|
||||
|
||||
|
||||
def verify_repls(repo_root):
|
||||
subprocess.run([str(SCRIPT_DIR / "verify-repls.sh"), "--repo-root", str(repo_root)], check=True)
|
||||
|
||||
|
||||
def print_summary(shadow_log, desktop_log, db_log, shadow_pid_file, desktop_pid_file, db_pid_file):
|
||||
print()
|
||||
print("Logs:")
|
||||
print(f" shared shadow-cljs: {shadow_log}")
|
||||
print(f" desktop-app: {desktop_log}")
|
||||
print(f" db-worker-node: {db_log}")
|
||||
print("PID files:")
|
||||
print(f" {shadow_pid_file}")
|
||||
print(f" {desktop_pid_file}")
|
||||
print(f" {db_pid_file}")
|
||||
print()
|
||||
print("Attach commands:")
|
||||
print(" pnpm exec shadow-cljs cljs-repl app")
|
||||
print(" pnpm exec shadow-cljs cljs-repl electron")
|
||||
print(" pnpm exec shadow-cljs cljs-repl db-worker-node")
|
||||
print()
|
||||
print("Startup complete. Attach to the needed REPL manually.")
|
||||
|
||||
|
||||
def terminate_pid(pid):
|
||||
if not pid:
|
||||
return
|
||||
try:
|
||||
if os.name == "nt":
|
||||
os.kill(pid, signal.CTRL_BREAK_EVENT)
|
||||
else:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
repo_root = Path(args.repo_root).resolve()
|
||||
if not repo_root.is_dir():
|
||||
raise SystemExit(f"Error: repo root not found: {repo_root}")
|
||||
|
||||
require_command("pnpm")
|
||||
require_command("node")
|
||||
|
||||
log_dir = repo_root / "tmp" / "logseq-repl"
|
||||
legacy_desktop_dir = repo_root / "tmp" / "desktop-app-repl"
|
||||
legacy_db_dir = repo_root / "tmp" / "db-worker-node-repl"
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
shadow_pid_file = log_dir / "shared-shadow-watch.pid"
|
||||
desktop_pid_file = log_dir / "desktop-electron.pid"
|
||||
db_pid_file = log_dir / "db-worker-node.pid"
|
||||
db_repo_file = log_dir / "db-worker-node.repo"
|
||||
shadow_log = log_dir / "shared-shadow-watch.log"
|
||||
desktop_log = log_dir / "desktop-electron.log"
|
||||
db_log = log_dir / "db-worker-node.log"
|
||||
|
||||
managed_pid_files = [
|
||||
shadow_pid_file,
|
||||
desktop_pid_file,
|
||||
db_pid_file,
|
||||
legacy_desktop_dir / "shadow-watch.pid",
|
||||
legacy_desktop_dir / "desktop-electron.pid",
|
||||
legacy_db_dir / "shadow-db-worker-node.pid",
|
||||
legacy_db_dir / "db-worker-node.pid",
|
||||
]
|
||||
|
||||
if not has_managed_processes(managed_pid_files) and not require_clean_ports():
|
||||
return 1
|
||||
|
||||
ensure_shadow_watch(repo_root, shadow_pid_file, shadow_log)
|
||||
ensure_desktop_app(repo_root, desktop_pid_file, desktop_log)
|
||||
ensure_db_worker_node(repo_root, db_pid_file, db_repo_file, db_log, args.repo, args.extra_node_args)
|
||||
wait_for_runtime_count(repo_root, "app", "exactly-one", 60)
|
||||
wait_for_runtime_count(repo_root, "electron", "nonzero", 60)
|
||||
wait_for_runtime_count(repo_root, "db-worker-node", "nonzero", 60)
|
||||
verify_repls(repo_root)
|
||||
print_summary(shadow_log, desktop_log, db_log, shadow_pid_file, desktop_pid_file, db_pid_file)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
5
.agents/skills/logseq-repl/scripts/start-repl.sh
Executable file
5
.agents/skills/logseq-repl/scripts/start-repl.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
exec python3 "$SCRIPT_DIR/start-repl.py" "$@"
|
||||
86
.agents/skills/logseq-repl/scripts/verify-repls.sh
Executable file
86
.agents/skills/logseq-repl/scripts/verify-repls.sh
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DEFAULT_REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
REPO_ROOT="${REPO_ROOT:-$DEFAULT_REPO_ROOT}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Verify that all Logseq CLJS REPL targets are usable.
|
||||
|
||||
Usage:
|
||||
verify-repls.sh [options]
|
||||
|
||||
Options:
|
||||
--repo-root <path> Logseq repository root (default: auto-detect from script location)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--repo-root)
|
||||
shift
|
||||
REPO_ROOT="${1:?missing value for --repo-root}"
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ ! -d "$REPO_ROOT" ]]; then
|
||||
echo "Error: repo root not found: $REPO_ROOT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "Error: pnpm not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_target() {
|
||||
local target="$1"
|
||||
local form="$2"
|
||||
|
||||
echo "Checking :$target ..."
|
||||
|
||||
local repl_output
|
||||
pushd "$REPO_ROOT" >/dev/null
|
||||
if ! repl_output="$(printf '%s\n' "$form" | pnpm exec shadow-cljs cljs-repl "$target" 2>&1)"; then
|
||||
popd >/dev/null
|
||||
echo "Error: REPL verification failed for :$target." >&2
|
||||
echo "--- :$target output ---" >&2
|
||||
echo "$repl_output" >&2
|
||||
echo "-----------------------" >&2
|
||||
return 1
|
||||
fi
|
||||
popd >/dev/null
|
||||
|
||||
if [[ "$repl_output" != *"shadow-cljs - connected to server"* ]]; then
|
||||
echo "Error: REPL verification did not connect for :$target." >&2
|
||||
echo "--- :$target output ---" >&2
|
||||
echo "$repl_output" >&2
|
||||
echo "-----------------------" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "--- :$target result ---"
|
||||
echo "$repl_output"
|
||||
echo "-----------------------"
|
||||
echo "REPL verification passed for :$target"
|
||||
}
|
||||
|
||||
echo "Verifying CLJS REPL targets ..."
|
||||
verify_target app "(prn {:runtime :app :document? (some? js/document)})"
|
||||
verify_target electron "(prn {:runtime :electron :process? (some? js/process) :type (some-> js/process .-type)})"
|
||||
verify_target db-worker-node "(prn {:runtime :db-worker-node :process? (some? js/process) :platform (some-> js/process .-platform)})"
|
||||
echo "All CLJS REPL targets verified."
|
||||
@@ -1,345 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_DIR="$(cd "$TEST_DIR/.." && pwd)"
|
||||
SKILLS_ROOT="$(cd "$SKILL_DIR/.." && pwd)"
|
||||
START_SCRIPT="$SKILL_DIR/scripts/start-db-worker-node-repl.sh"
|
||||
CLEANUP_SCRIPT="$SKILL_DIR/scripts/cleanup-db-worker-node-repl.sh"
|
||||
DESKTOP_START_SCRIPT="$SKILL_DIR/scripts/start-desktop-app-repl.sh"
|
||||
DESKTOP_CLEANUP_SCRIPT="$SKILL_DIR/scripts/cleanup-desktop-app-repl.sh"
|
||||
SKILL_FILE="$SKILL_DIR/SKILL.md"
|
||||
CURRENT_SKILL_NAME="$(basename "$SKILL_DIR")"
|
||||
ORIGINAL_PATH="$PATH"
|
||||
|
||||
# shellcheck source=./test-lib.sh
|
||||
source "$TEST_DIR/test-lib.sh"
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
|
||||
create_fake_env() {
|
||||
TEST_ROOT="$(mktemp -d)"
|
||||
REPO_ROOT="$TEST_ROOT/repo"
|
||||
BIN_DIR="$TEST_ROOT/bin"
|
||||
CMD_LOG="$TEST_ROOT/commands.log"
|
||||
|
||||
mkdir -p "$REPO_ROOT/static" "$BIN_DIR"
|
||||
: > "$CMD_LOG"
|
||||
|
||||
cat > "$BIN_DIR/yarn" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "yarn $*" >> "$FAKE_CMD_LOG"
|
||||
|
||||
case "${1:-}" in
|
||||
watch)
|
||||
echo "shadow-cljs - nREPL server started on port 8701"
|
||||
echo "[:electron] Build completed."
|
||||
echo "[:app] Build completed."
|
||||
echo "[:db-worker-node] Build completed."
|
||||
while true; do sleep 1; done
|
||||
;;
|
||||
dev-electron-app)
|
||||
echo "17:12:00.841 › Logseq App(2.0.1) Starting..."
|
||||
echo "shadow-cljs - #6 ready!"
|
||||
while true; do sleep 1; done
|
||||
;;
|
||||
*)
|
||||
echo "Unexpected yarn command: $*" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
|
||||
cat > "$BIN_DIR/npx" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
input=""
|
||||
if [[ "${1:-}" == "shadow-cljs" && "${2:-}" == "cljs-repl" ]] && [[ -p /dev/stdin ]]; then
|
||||
input="$(cat)"
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "shadow-cljs" && "${2:-}" == "clj-eval" ]]; then
|
||||
echo "npx-clj-eval $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
echo "${FAKE_APP_RUNTIME_COUNT:-1}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "shadow-cljs" && "${2:-}" == "cljs-repl" && "${3:-}" == "app" ]]; then
|
||||
if [[ "$input" == *"(.-title js/document)"* ]]; then
|
||||
echo "npx-app-smoke $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
echo "cljs.user=> true"
|
||||
echo 'cljs.user=> "Logseq"'
|
||||
echo "cljs.user=>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "npx-app-attach $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
echo "cljs.user=>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "shadow-cljs" && "${2:-}" == "cljs-repl" && "${3:-}" == "db-worker-node" ]]; then
|
||||
if [[ "$input" == *"(+ 1 2)"* ]]; then
|
||||
echo "npx-db-smoke $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
echo "cljs.user=> 3"
|
||||
echo "cljs.user=>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "npx-db-attach $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
echo "cljs.user=>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Unexpected npx command: $*" >&2
|
||||
exit 1
|
||||
EOF
|
||||
|
||||
cat > "$BIN_DIR/node" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "node $*" >> "$FAKE_CMD_LOG"
|
||||
while true; do sleep 1; done
|
||||
EOF
|
||||
|
||||
cat > "$BIN_DIR/setsid" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
exec "$@"
|
||||
EOF
|
||||
|
||||
chmod +x "$BIN_DIR/yarn" "$BIN_DIR/npx" "$BIN_DIR/node" "$BIN_DIR/setsid"
|
||||
|
||||
export PATH="$BIN_DIR:$ORIGINAL_PATH"
|
||||
export FAKE_CMD_LOG="$CMD_LOG"
|
||||
export FAKE_APP_RUNTIME_COUNT="${FAKE_APP_RUNTIME_COUNT:-1}"
|
||||
}
|
||||
|
||||
cleanup_fake_env() {
|
||||
if [[ -n "${REPO_ROOT:-}" ]]; then
|
||||
local pid_file
|
||||
for pid_file in \
|
||||
"$REPO_ROOT"/tmp/db-worker-node-repl/*.pid \
|
||||
"$REPO_ROOT"/tmp/desktop-app-repl/*.pid \
|
||||
"$REPO_ROOT"/tmp/logseq-repl/*.pid; do
|
||||
[[ -e "$pid_file" ]] || continue
|
||||
local pid
|
||||
pid="$(tr -d '[:space:]' < "$pid_file")"
|
||||
if [[ "$pid" =~ ^[0-9]+$ ]]; then
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
PATH="$ORIGINAL_PATH"
|
||||
unset FAKE_CMD_LOG FAKE_APP_RUNTIME_COUNT || true
|
||||
|
||||
if [[ -n "${TEST_ROOT:-}" && -d "$TEST_ROOT" ]]; then
|
||||
rm -rf "$TEST_ROOT"
|
||||
fi
|
||||
}
|
||||
|
||||
scripts_exist_and_skill_is_renamed_test() {
|
||||
assert_equals "logseq-repl" "$CURRENT_SKILL_NAME"
|
||||
assert_file_exists "$START_SCRIPT"
|
||||
assert_file_exists "$CLEANUP_SCRIPT"
|
||||
assert_file_exists "$DESKTOP_START_SCRIPT"
|
||||
assert_file_exists "$DESKTOP_CLEANUP_SCRIPT"
|
||||
}
|
||||
|
||||
start_no_repl_launches_shared_shadow_and_runtime_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo --no-repl > "$TEST_ROOT/output.log" 2>&1
|
||||
|
||||
assert_contains "Startup complete. REPL attach skipped (--no-repl)." "$TEST_ROOT/output.log"
|
||||
assert_contains "shared shadow-cljs: $REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.log" "$TEST_ROOT/output.log"
|
||||
assert_contains "db-worker-node: $REPO_ROOT/tmp/db-worker-node-repl/db-worker-node.log" "$TEST_ROOT/output.log"
|
||||
assert_contains "yarn watch" "$CMD_LOG"
|
||||
assert_contains "node ./static/db-worker-node.js --repo demo" "$CMD_LOG"
|
||||
assert_contains "npx-db-smoke shadow-cljs cljs-repl db-worker-node" "$CMD_LOG"
|
||||
|
||||
assert_file_exists "$REPO_ROOT/tmp/db-worker-node-repl/shadow-db-worker-node.pid"
|
||||
assert_file_exists "$REPO_ROOT/tmp/db-worker-node-repl/db-worker-node.pid"
|
||||
assert_file_exists "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.pid"
|
||||
assert_file_exists "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.log"
|
||||
}
|
||||
|
||||
start_reuses_existing_db_worker_runtime_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo --no-repl > "$TEST_ROOT/first.log" 2>&1
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo --no-repl > "$TEST_ROOT/second.log" 2>&1
|
||||
|
||||
local watch_count node_count
|
||||
watch_count="$(grep -c '^yarn watch$' "$CMD_LOG")"
|
||||
node_count="$(grep -c '^node ./static/db-worker-node.js --repo demo$' "$CMD_LOG")"
|
||||
|
||||
assert_equals "1" "$watch_count"
|
||||
assert_equals "1" "$node_count"
|
||||
assert_contains "Reusing shared shadow-cljs watch" "$TEST_ROOT/second.log"
|
||||
assert_contains "Reusing db-worker-node runtime" "$TEST_ROOT/second.log"
|
||||
}
|
||||
|
||||
desktop_and_db_worker_share_single_shadow_watch_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$DESKTOP_START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/desktop.log" 2>&1
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo --no-repl > "$TEST_ROOT/db.log" 2>&1
|
||||
|
||||
local watch_count desktop_shadow_pid db_shadow_pid
|
||||
watch_count="$(grep -c '^yarn watch$' "$CMD_LOG")"
|
||||
desktop_shadow_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/desktop-app-repl/shadow-watch.pid")"
|
||||
db_shadow_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/db-worker-node-repl/shadow-db-worker-node.pid")"
|
||||
|
||||
assert_equals "1" "$watch_count"
|
||||
assert_equals "$desktop_shadow_pid" "$db_shadow_pid"
|
||||
assert_contains "Reusing shared shadow-cljs watch" "$TEST_ROOT/db.log"
|
||||
}
|
||||
|
||||
cleanup_db_worker_keeps_shared_watch_for_desktop_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$DESKTOP_START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/desktop.log" 2>&1
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo --no-repl > "$TEST_ROOT/db.log" 2>&1
|
||||
|
||||
local watch_pid runtime_pid
|
||||
watch_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.pid")"
|
||||
runtime_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/db-worker-node-repl/db-worker-node.pid")"
|
||||
|
||||
bash "$CLEANUP_SCRIPT" --repo-root "$REPO_ROOT" > "$TEST_ROOT/cleanup-db.log" 2>&1
|
||||
|
||||
if ! kill -0 "$watch_pid" 2>/dev/null; then
|
||||
fail "expected shared watch to keep running while desktop workflow is active"
|
||||
fi
|
||||
|
||||
if kill -0 "$runtime_pid" 2>/dev/null; then
|
||||
fail "expected db-worker-node runtime to stop during cleanup"
|
||||
fi
|
||||
|
||||
assert_contains "shadow-cljs watch: shared with other workflows, leaving it running" "$TEST_ROOT/cleanup-db.log"
|
||||
assert_file_exists "$REPO_ROOT/tmp/desktop-app-repl/shadow-watch.pid"
|
||||
assert_not_exists "$REPO_ROOT/tmp/db-worker-node-repl/shadow-db-worker-node.pid"
|
||||
}
|
||||
|
||||
cleanup_desktop_keeps_shared_watch_for_db_worker_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$DESKTOP_START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/desktop.log" 2>&1
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo --no-repl > "$TEST_ROOT/db.log" 2>&1
|
||||
|
||||
local watch_pid electron_pid
|
||||
watch_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.pid")"
|
||||
electron_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/desktop-app-repl/desktop-electron.pid")"
|
||||
|
||||
bash "$DESKTOP_CLEANUP_SCRIPT" --repo-root "$REPO_ROOT" > "$TEST_ROOT/cleanup-desktop.log" 2>&1
|
||||
|
||||
if ! kill -0 "$watch_pid" 2>/dev/null; then
|
||||
fail "expected shared watch to keep running while db-worker workflow is active"
|
||||
fi
|
||||
|
||||
if kill -0 "$electron_pid" 2>/dev/null; then
|
||||
fail "expected desktop electron process to stop during cleanup"
|
||||
fi
|
||||
|
||||
assert_contains "shadow-cljs watch: shared with other workflows, leaving it running" "$TEST_ROOT/cleanup-desktop.log"
|
||||
assert_file_exists "$REPO_ROOT/tmp/db-worker-node-repl/shadow-db-worker-node.pid"
|
||||
assert_not_exists "$REPO_ROOT/tmp/desktop-app-repl/shadow-watch.pid"
|
||||
}
|
||||
|
||||
shared_watch_stops_after_last_cleanup_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$DESKTOP_START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/desktop.log" 2>&1
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo --no-repl > "$TEST_ROOT/db.log" 2>&1
|
||||
|
||||
local watch_pid
|
||||
watch_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.pid")"
|
||||
|
||||
bash "$CLEANUP_SCRIPT" --repo-root "$REPO_ROOT" > "$TEST_ROOT/cleanup-db.log" 2>&1
|
||||
bash "$DESKTOP_CLEANUP_SCRIPT" --repo-root "$REPO_ROOT" > "$TEST_ROOT/cleanup-desktop.log" 2>&1
|
||||
|
||||
if kill -0 "$watch_pid" 2>/dev/null; then
|
||||
fail "expected shared watch to stop after the last workflow cleaned up"
|
||||
fi
|
||||
|
||||
assert_not_exists "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.pid"
|
||||
}
|
||||
|
||||
start_attaches_repl_by_default_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo < /dev/null > "$TEST_ROOT/output.log" 2>&1
|
||||
|
||||
assert_contains "Attaching CLJS REPL: npx shadow-cljs cljs-repl db-worker-node" "$TEST_ROOT/output.log"
|
||||
assert_contains "npx-db-attach shadow-cljs cljs-repl db-worker-node" "$CMD_LOG"
|
||||
}
|
||||
|
||||
help_and_docs_are_portable_test() {
|
||||
local temp_dir start_help_file cleanup_help_file path_pattern
|
||||
temp_dir="$(mktemp -d)"
|
||||
start_help_file="$temp_dir/start-help.txt"
|
||||
cleanup_help_file="$temp_dir/cleanup-help.txt"
|
||||
path_pattern="$(portable_path_pattern)"
|
||||
|
||||
bash "$START_SCRIPT" --help > "$start_help_file"
|
||||
bash "$CLEANUP_SCRIPT" --help > "$cleanup_help_file"
|
||||
|
||||
assert_not_matches "$path_pattern" "$start_help_file"
|
||||
assert_not_matches "$path_pattern" "$cleanup_help_file"
|
||||
assert_not_matches "$path_pattern" "$SKILL_FILE"
|
||||
assert_contains "name: logseq-repl" "$SKILL_FILE"
|
||||
assert_contains 'Desktop app `:app` REPL' "$SKILL_FILE"
|
||||
assert_contains 'Electron main-process `:electron` REPL' "$SKILL_FILE"
|
||||
assert_contains "db-worker-node REPL" "$SKILL_FILE"
|
||||
assert_contains "multiple different REPL types at the same time" "$SKILL_FILE"
|
||||
assert_contains "tmp/logseq-repl" "$SKILL_FILE"
|
||||
assert_contains 'Run multiple runtimes together' "$SKILL_FILE"
|
||||
assert_contains "shared-shadow-watch.log" "$SKILL_FILE"
|
||||
assert_contains "shadow.user/electron-repl" "$SKILL_FILE"
|
||||
assert_contains "shadow.user/worker-node-repl" "$SKILL_FILE"
|
||||
assert_contains "Non-interactive verification examples" "$SKILL_FILE"
|
||||
assert_contains "Cleanup both workflows" "$SKILL_FILE"
|
||||
|
||||
rm -rf "$temp_dir"
|
||||
}
|
||||
|
||||
obsolete_skill_directories_removed_test() {
|
||||
assert_not_exists "$SKILLS_ROOT/desktop-app-repl"
|
||||
assert_not_exists "$SKILLS_ROOT/db-worker-node-repl"
|
||||
}
|
||||
|
||||
run_test "scripts exist and skill is renamed" scripts_exist_and_skill_is_renamed_test
|
||||
run_test "start --no-repl launches shared shadow and runtime" start_no_repl_launches_shared_shadow_and_runtime_test
|
||||
run_test "start reuses existing db-worker runtime" start_reuses_existing_db_worker_runtime_test
|
||||
run_test "desktop and db-worker share a single shadow watch" desktop_and_db_worker_share_single_shadow_watch_test
|
||||
run_test "cleanup db-worker keeps shared watch for desktop" cleanup_db_worker_keeps_shared_watch_for_desktop_test
|
||||
run_test "cleanup desktop keeps shared watch for db-worker" cleanup_desktop_keeps_shared_watch_for_db_worker_test
|
||||
run_test "shared watch stops after last cleanup" shared_watch_stops_after_last_cleanup_test
|
||||
run_test "start attaches repl by default" start_attaches_repl_by_default_test
|
||||
run_test "help and docs are portable" help_and_docs_are_portable_test
|
||||
run_test "obsolete skill directories removed" obsolete_skill_directories_removed_test
|
||||
|
||||
echo
|
||||
if [[ "$FAIL_COUNT" -gt 0 ]]; then
|
||||
echo "$FAIL_COUNT test(s) failed; $PASS_COUNT passed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All $PASS_COUNT test(s) passed."
|
||||
@@ -1,395 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_DIR="$(cd "$TEST_DIR/.." && pwd)"
|
||||
SKILLS_ROOT="$(cd "$SKILL_DIR/.." && pwd)"
|
||||
START_SCRIPT="$SKILL_DIR/scripts/start-desktop-app-repl.sh"
|
||||
CLEANUP_SCRIPT="$SKILL_DIR/scripts/cleanup-desktop-app-repl.sh"
|
||||
SKILL_FILE="$SKILL_DIR/SKILL.md"
|
||||
CURRENT_SKILL_NAME="$(basename "$SKILL_DIR")"
|
||||
ORIGINAL_PATH="$PATH"
|
||||
|
||||
# shellcheck source=./test-lib.sh
|
||||
source "$TEST_DIR/test-lib.sh"
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
|
||||
create_fake_env() {
|
||||
TEST_ROOT="$(mktemp -d)"
|
||||
REPO_ROOT="$TEST_ROOT/repo"
|
||||
BIN_DIR="$TEST_ROOT/bin"
|
||||
CMD_LOG="$TEST_ROOT/commands.log"
|
||||
|
||||
mkdir -p "$REPO_ROOT/static" "$BIN_DIR"
|
||||
: > "$CMD_LOG"
|
||||
|
||||
cat > "$BIN_DIR/yarn" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "yarn $*" >> "$FAKE_CMD_LOG"
|
||||
|
||||
case "${1:-}" in
|
||||
watch)
|
||||
echo "shadow-cljs - nREPL server started on port 8701"
|
||||
echo "[:electron] Build completed."
|
||||
echo "[:app] Build completed."
|
||||
while true; do sleep 1; done
|
||||
;;
|
||||
dev-electron-app)
|
||||
echo "17:12:00.841 › Logseq App(2.0.1) Starting..."
|
||||
echo "shadow-cljs - #6 ready!"
|
||||
while true; do sleep 1; done
|
||||
;;
|
||||
*)
|
||||
echo "Unexpected yarn command: $*" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
|
||||
cat > "$BIN_DIR/npx" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
input=""
|
||||
if [[ "${1:-}" == "shadow-cljs" && "${2:-}" == "cljs-repl" && ( "${3:-}" == "app" || "${3:-}" == "electron" ) ]] && [[ -p /dev/stdin ]]; then
|
||||
input="$(cat)"
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "shadow-cljs" && "${2:-}" == "clj-eval" ]]; then
|
||||
echo "npx-clj-eval $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
echo "${FAKE_APP_RUNTIME_COUNT:-1}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "shadow-cljs" && "${2:-}" == "cljs-repl" && "${3:-}" == "app" ]]; then
|
||||
if [[ "$input" == *"(.-title js/document)"* ]]; then
|
||||
echo "npx-smoke $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
if [[ "${FAKE_SMOKE_TEST_FAIL:-0}" == "1" ]]; then
|
||||
echo "cljs.user=> false"
|
||||
echo "cljs.user=> \"Wrong\""
|
||||
echo "cljs.user=>"
|
||||
exit 0
|
||||
fi
|
||||
echo "cljs.user=> true"
|
||||
echo "cljs.user=> \"${FAKE_SMOKE_TEST_TITLE:-Logseq}\""
|
||||
echo "cljs.user=>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "npx-attach $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
echo "cljs.user=>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "shadow-cljs" && "${2:-}" == "cljs-repl" && "${3:-}" == "electron" ]]; then
|
||||
if [[ "$input" == *":runtime :electron"* ]]; then
|
||||
echo "npx-electron-smoke $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
echo "cljs.user=> {:runtime :electron, :process? true, :type \"browser\"}"
|
||||
echo "cljs.user=>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "npx-electron-attach $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
echo "cljs.user=>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Unexpected npx command: $*" >&2
|
||||
exit 1
|
||||
EOF
|
||||
|
||||
cat > "$BIN_DIR/setsid" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
exec "$@"
|
||||
EOF
|
||||
|
||||
chmod +x "$BIN_DIR/yarn" "$BIN_DIR/npx" "$BIN_DIR/setsid"
|
||||
|
||||
export PATH="$BIN_DIR:$ORIGINAL_PATH"
|
||||
export FAKE_CMD_LOG="$CMD_LOG"
|
||||
export FAKE_APP_RUNTIME_COUNT="${FAKE_APP_RUNTIME_COUNT:-1}"
|
||||
export FAKE_SMOKE_TEST_FAIL="${FAKE_SMOKE_TEST_FAIL:-0}"
|
||||
export FAKE_SMOKE_TEST_TITLE="${FAKE_SMOKE_TEST_TITLE:-Logseq}"
|
||||
}
|
||||
|
||||
link_system_tool() {
|
||||
local name="$1"
|
||||
local target
|
||||
target="$(command -v "$name")"
|
||||
ln -s "$target" "$SYSTEM_BIN_DIR/$name"
|
||||
}
|
||||
|
||||
create_fake_env_without_setsid() {
|
||||
create_fake_env
|
||||
|
||||
SYSTEM_BIN_DIR="$TEST_ROOT/system-bin"
|
||||
mkdir -p "$SYSTEM_BIN_DIR"
|
||||
rm -f "$BIN_DIR/setsid"
|
||||
|
||||
local tool
|
||||
for tool in bash awk cat dirname grep mkdir nohup ps rm sleep tr; do
|
||||
link_system_tool "$tool"
|
||||
done
|
||||
|
||||
export PATH="$BIN_DIR:$SYSTEM_BIN_DIR"
|
||||
}
|
||||
|
||||
cleanup_fake_env() {
|
||||
if [[ -n "${REPO_ROOT:-}" && -d "$REPO_ROOT/tmp/desktop-app-repl" ]]; then
|
||||
local pid_file
|
||||
for pid_file in "$REPO_ROOT"/tmp/desktop-app-repl/*.pid; do
|
||||
[[ -e "$pid_file" ]] || continue
|
||||
local pid
|
||||
pid="$(tr -d '[:space:]' < "$pid_file")"
|
||||
if [[ "$pid" =~ ^[0-9]+$ ]]; then
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
PATH="$ORIGINAL_PATH"
|
||||
unset FAKE_CMD_LOG FAKE_APP_RUNTIME_COUNT FAKE_SMOKE_TEST_FAIL FAKE_SMOKE_TEST_TITLE || true
|
||||
|
||||
if [[ -n "${TEST_ROOT:-}" && -d "$TEST_ROOT" ]]; then
|
||||
rm -rf "$TEST_ROOT"
|
||||
fi
|
||||
}
|
||||
|
||||
scripts_exist_and_skill_is_renamed_test() {
|
||||
assert_equals "logseq-repl" "$CURRENT_SKILL_NAME"
|
||||
assert_file_exists "$START_SCRIPT"
|
||||
assert_file_exists "$CLEANUP_SCRIPT"
|
||||
}
|
||||
|
||||
start_no_repl_launches_watch_and_electron_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/output.log" 2>&1
|
||||
|
||||
assert_contains "Startup complete. REPL attach skipped (--no-repl)." "$TEST_ROOT/output.log"
|
||||
assert_contains "shared shadow-cljs: $REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.log" "$TEST_ROOT/output.log"
|
||||
assert_contains "desktop-app: $REPO_ROOT/tmp/desktop-app-repl/desktop-electron.log" "$TEST_ROOT/output.log"
|
||||
assert_contains "yarn watch" "$CMD_LOG"
|
||||
assert_contains "yarn dev-electron-app" "$CMD_LOG"
|
||||
assert_contains "npx-clj-eval shadow-cljs clj-eval" "$CMD_LOG"
|
||||
assert_contains "npx-smoke shadow-cljs cljs-repl app" "$CMD_LOG"
|
||||
|
||||
assert_file_exists "$REPO_ROOT/tmp/desktop-app-repl/shadow-watch.pid"
|
||||
assert_file_exists "$REPO_ROOT/tmp/desktop-app-repl/desktop-electron.pid"
|
||||
assert_file_exists "$REPO_ROOT/tmp/desktop-app-repl/shadow-watch.log"
|
||||
assert_file_exists "$REPO_ROOT/tmp/desktop-app-repl/desktop-electron.log"
|
||||
assert_file_exists "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.pid"
|
||||
assert_file_exists "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.log"
|
||||
}
|
||||
|
||||
start_reuses_running_processes_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/first.log" 2>&1
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/second.log" 2>&1
|
||||
|
||||
local watch_count
|
||||
watch_count="$(grep -c '^yarn watch$' "$CMD_LOG")"
|
||||
local electron_count
|
||||
electron_count="$(grep -c '^yarn dev-electron-app$' "$CMD_LOG")"
|
||||
|
||||
assert_equals "1" "$watch_count"
|
||||
assert_equals "1" "$electron_count"
|
||||
assert_contains "Reusing shared shadow-cljs watch" "$TEST_ROOT/second.log"
|
||||
assert_contains "Reusing Desktop dev app" "$TEST_ROOT/second.log"
|
||||
}
|
||||
|
||||
start_fails_when_multiple_app_runtimes_exist_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
export FAKE_APP_RUNTIME_COUNT=2
|
||||
|
||||
if bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/output.log" 2>&1; then
|
||||
fail "expected start script to fail when multiple :app runtimes exist"
|
||||
fi
|
||||
|
||||
assert_contains "Expected exactly one live :app runtime" "$TEST_ROOT/output.log"
|
||||
assert_contains "Close the browser dev app" "$TEST_ROOT/output.log"
|
||||
}
|
||||
|
||||
start_attaches_repl_by_default_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" < /dev/null > "$TEST_ROOT/output.log" 2>&1
|
||||
|
||||
assert_contains "Attaching CLJS REPL: npx shadow-cljs cljs-repl app" "$TEST_ROOT/output.log"
|
||||
assert_contains "npx-attach shadow-cljs cljs-repl app" "$CMD_LOG"
|
||||
}
|
||||
|
||||
electron_attach_works_after_desktop_start_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/start.log" 2>&1
|
||||
printf '(prn {:runtime :electron :process? (some? js/process) :type (.-type js/process)})\n:cljs/quit\n' | npx shadow-cljs cljs-repl electron > "$TEST_ROOT/electron.log" 2>&1
|
||||
|
||||
assert_contains "Startup complete. REPL attach skipped (--no-repl)." "$TEST_ROOT/start.log"
|
||||
assert_contains "shadow-cljs - connected to server" "$TEST_ROOT/electron.log"
|
||||
assert_contains ":runtime :electron" "$TEST_ROOT/electron.log"
|
||||
assert_contains "npx-electron-smoke shadow-cljs cljs-repl electron" "$CMD_LOG"
|
||||
}
|
||||
|
||||
start_works_without_setsid_test() {
|
||||
create_fake_env_without_setsid
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/output.log" 2>&1
|
||||
|
||||
assert_contains "Startup complete. REPL attach skipped (--no-repl)." "$TEST_ROOT/output.log"
|
||||
assert_contains "yarn watch" "$CMD_LOG"
|
||||
assert_contains "yarn dev-electron-app" "$CMD_LOG"
|
||||
}
|
||||
|
||||
start_accepts_non_logseq_window_title_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
export FAKE_SMOKE_TEST_TITLE="My Graph"
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/output.log" 2>&1
|
||||
|
||||
assert_contains "Desktop renderer smoke test passed" "$TEST_ROOT/output.log"
|
||||
assert_contains "npx-smoke shadow-cljs cljs-repl app" "$CMD_LOG"
|
||||
}
|
||||
|
||||
cleanup_stops_tracked_processes_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --no-repl > "$TEST_ROOT/start.log" 2>&1
|
||||
|
||||
local shadow_pid
|
||||
shadow_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/desktop-app-repl/shadow-watch.pid")"
|
||||
local electron_pid
|
||||
electron_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/desktop-app-repl/desktop-electron.pid")"
|
||||
|
||||
bash "$CLEANUP_SCRIPT" --repo-root "$REPO_ROOT" > "$TEST_ROOT/cleanup.log" 2>&1
|
||||
|
||||
if kill -0 "$shadow_pid" 2>/dev/null; then
|
||||
fail "expected shadow watch pid to be stopped"
|
||||
fi
|
||||
|
||||
if kill -0 "$electron_pid" 2>/dev/null; then
|
||||
fail "expected desktop electron pid to be stopped"
|
||||
fi
|
||||
|
||||
assert_not_exists "$REPO_ROOT/tmp/desktop-app-repl/shadow-watch.pid"
|
||||
assert_not_exists "$REPO_ROOT/tmp/desktop-app-repl/desktop-electron.pid"
|
||||
assert_not_exists "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.pid"
|
||||
assert_contains "Cleanup done." "$TEST_ROOT/cleanup.log"
|
||||
}
|
||||
|
||||
cleanup_does_not_signal_own_process_group_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
local bash_env_file fake_pid fake_pgid
|
||||
fake_pid=424242
|
||||
fake_pgid=777777
|
||||
mkdir -p "$REPO_ROOT/tmp/desktop-app-repl"
|
||||
echo "$fake_pid" > "$REPO_ROOT/tmp/desktop-app-repl/desktop-electron.pid"
|
||||
|
||||
cat > "$BIN_DIR/ps" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "\${1:-}" == "-o" && "\${2:-}" == "pgid=" && "\${3:-}" == "-p" ]]; then
|
||||
echo " $fake_pgid"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec /bin/ps "\$@"
|
||||
EOF
|
||||
|
||||
bash_env_file="$TEST_ROOT/bash-env"
|
||||
cat > "$bash_env_file" <<EOF
|
||||
kill() {
|
||||
echo "kill \$*" >> "$CMD_LOG"
|
||||
return 0
|
||||
}
|
||||
EOF
|
||||
|
||||
chmod +x "$BIN_DIR/ps"
|
||||
|
||||
BASH_ENV="$bash_env_file" bash "$CLEANUP_SCRIPT" --repo-root "$REPO_ROOT" > "$TEST_ROOT/cleanup-own-pgid.log" 2>&1 || true
|
||||
|
||||
if grep -Eq '^kill -TERM -[0-9]+' "$CMD_LOG"; then
|
||||
fail "expected cleanup not to signal its own process group"
|
||||
fi
|
||||
}
|
||||
|
||||
help_and_docs_are_portable_test() {
|
||||
local temp_dir start_help_file cleanup_help_file path_pattern
|
||||
temp_dir="$(mktemp -d)"
|
||||
start_help_file="$temp_dir/start-help.txt"
|
||||
cleanup_help_file="$temp_dir/cleanup-help.txt"
|
||||
path_pattern="$(portable_path_pattern)"
|
||||
|
||||
bash "$START_SCRIPT" --help > "$start_help_file"
|
||||
bash "$CLEANUP_SCRIPT" --help > "$cleanup_help_file"
|
||||
|
||||
assert_not_matches "$path_pattern" "$start_help_file"
|
||||
assert_not_matches "$path_pattern" "$cleanup_help_file"
|
||||
assert_not_matches "$path_pattern" "$SKILL_FILE"
|
||||
assert_contains "name: logseq-repl" "$SKILL_FILE"
|
||||
assert_contains 'Desktop app `:app` REPL' "$SKILL_FILE"
|
||||
assert_contains "start-desktop-app-repl.sh" "$SKILL_FILE"
|
||||
assert_contains "start-db-worker-node-repl.sh" "$SKILL_FILE"
|
||||
assert_contains "tmp/logseq-repl" "$SKILL_FILE"
|
||||
assert_contains 'Run multiple runtimes together' "$SKILL_FILE"
|
||||
assert_contains "close browser dev app instances" "$SKILL_FILE"
|
||||
assert_contains "shared-shadow-watch.log" "$SKILL_FILE"
|
||||
assert_contains "shadow.user/electron-repl" "$SKILL_FILE"
|
||||
assert_contains "shadow.user/worker-node-repl" "$SKILL_FILE"
|
||||
assert_contains "Non-interactive verification examples" "$SKILL_FILE"
|
||||
assert_contains "printf '(prn {:runtime :app" "$SKILL_FILE"
|
||||
assert_contains "printf '(prn {:runtime :electron" "$SKILL_FILE"
|
||||
assert_contains "printf '(prn {:runtime :db-worker-node" "$SKILL_FILE"
|
||||
assert_contains 'Electron main-process `:electron` REPL' "$SKILL_FILE"
|
||||
assert_contains "Cleanup both workflows" "$SKILL_FILE"
|
||||
assert_not_contains_text "name: repl-workflows" "$SKILL_FILE"
|
||||
|
||||
rm -rf "$temp_dir"
|
||||
}
|
||||
|
||||
obsolete_skill_directories_removed_test() {
|
||||
assert_not_exists "$SKILLS_ROOT/desktop-app-repl"
|
||||
assert_not_exists "$SKILLS_ROOT/db-worker-node-repl"
|
||||
}
|
||||
|
||||
run_test "scripts exist and skill is renamed" scripts_exist_and_skill_is_renamed_test
|
||||
run_test "start --no-repl launches watch and electron" start_no_repl_launches_watch_and_electron_test
|
||||
run_test "start reuses running processes" start_reuses_running_processes_test
|
||||
run_test "start fails when multiple app runtimes exist" start_fails_when_multiple_app_runtimes_exist_test
|
||||
run_test "start attaches repl by default" start_attaches_repl_by_default_test
|
||||
run_test "electron attach works after desktop start" electron_attach_works_after_desktop_start_test
|
||||
run_test "start works without setsid" start_works_without_setsid_test
|
||||
run_test "start accepts non-Logseq window title" start_accepts_non_logseq_window_title_test
|
||||
run_test "cleanup stops tracked processes" cleanup_stops_tracked_processes_test
|
||||
run_test "cleanup does not signal its own process group" cleanup_does_not_signal_own_process_group_test
|
||||
run_test "help and docs are portable" help_and_docs_are_portable_test
|
||||
run_test "obsolete skill directories removed" obsolete_skill_directories_removed_test
|
||||
|
||||
echo
|
||||
if [[ "$FAIL_COUNT" -gt 0 ]]; then
|
||||
echo "$FAIL_COUNT test(s) failed; $PASS_COUNT passed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All $PASS_COUNT test(s) passed."
|
||||
@@ -67,7 +67,13 @@ run_test() {
|
||||
local name="$1"
|
||||
local fn="$2"
|
||||
|
||||
if (set -e; "$fn"); then
|
||||
local status
|
||||
set +e
|
||||
(set -e; "$fn")
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
if [[ "$status" -eq 0 ]]; then
|
||||
echo "PASS: $name"
|
||||
PASS_COUNT=$((PASS_COUNT + 1))
|
||||
else
|
||||
|
||||
374
.agents/skills/logseq-repl/tests/test-logseq-repl.sh
Executable file
374
.agents/skills/logseq-repl/tests/test-logseq-repl.sh
Executable file
@@ -0,0 +1,374 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_DIR="$(cd "$TEST_DIR/.." && pwd)"
|
||||
START_SCRIPT="$SKILL_DIR/scripts/start-repl.sh"
|
||||
START_PY_SCRIPT="$SKILL_DIR/scripts/start-repl.py"
|
||||
CLEANUP_SCRIPT="$SKILL_DIR/scripts/cleanup-repl.sh"
|
||||
VERIFY_SCRIPT="$SKILL_DIR/scripts/verify-repls.sh"
|
||||
SKILL_FILE="$SKILL_DIR/SKILL.md"
|
||||
ORIGINAL_PATH="$PATH"
|
||||
|
||||
# shellcheck source=./test-lib.sh
|
||||
source "$TEST_DIR/test-lib.sh"
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
|
||||
create_fake_env() {
|
||||
TEST_ROOT="$(mktemp -d)"
|
||||
REPO_ROOT="$TEST_ROOT/repo"
|
||||
BIN_DIR="$TEST_ROOT/bin"
|
||||
CMD_LOG="$TEST_ROOT/commands.log"
|
||||
|
||||
mkdir -p "$REPO_ROOT/static" "$BIN_DIR"
|
||||
: > "$CMD_LOG"
|
||||
|
||||
cat > "$BIN_DIR/pnpm" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "pnpm $*" >> "$FAKE_CMD_LOG"
|
||||
|
||||
case "${1:-}" in
|
||||
watch)
|
||||
echo "shadow-cljs - nREPL server started on port 8701"
|
||||
echo "[:electron] Build completed."
|
||||
echo "[:app] Build completed."
|
||||
echo "[:db-worker-node] Build completed."
|
||||
while true; do sleep 1; done
|
||||
;;
|
||||
dev-electron-app)
|
||||
echo "17:12:00.841 Logseq App(2.0.1) Starting..."
|
||||
echo "shadow-cljs - #6 ready!"
|
||||
while true; do sleep 1; done
|
||||
;;
|
||||
exec)
|
||||
shift
|
||||
if [[ "${1:-}" != "shadow-cljs" ]]; then
|
||||
echo "Unexpected pnpm exec command: $*" >&2
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
|
||||
input=""
|
||||
if [[ "${1:-}" == "cljs-repl" ]] && [[ -p /dev/stdin ]]; then
|
||||
input="$(cat)"
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "clj-eval" ]]; then
|
||||
echo "pnpm-exec-clj-eval shadow-cljs $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
case "$*" in
|
||||
*"repl-runtimes :app"*) echo "${FAKE_APP_RUNTIME_COUNT:-1}" ;;
|
||||
*"repl-runtimes :electron"*) echo "${FAKE_ELECTRON_RUNTIME_COUNT:-1}" ;;
|
||||
*"repl-runtimes :db-worker-node"*) echo "${FAKE_DB_RUNTIME_COUNT:-1}" ;;
|
||||
*) echo "0" ;;
|
||||
esac
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "cljs-repl" ]]; then
|
||||
echo "pnpm-exec-repl ${2:-missing}" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - connected to server"
|
||||
|
||||
if [[ "$input" == *":cljs/quit"* ]]; then
|
||||
echo "verification must not send :cljs/quit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "${2:-}" in
|
||||
app)
|
||||
if [[ "$input" != *":runtime :app"* ]]; then
|
||||
echo "unexpected app verification form" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo 'cljs.user=> {:runtime :app, :document? true}'
|
||||
;;
|
||||
electron)
|
||||
if [[ "$input" != *":runtime :electron"* ]]; then
|
||||
echo "unexpected electron verification form" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo 'cljs.user=> {:runtime :electron, :process? true}'
|
||||
;;
|
||||
db-worker-node)
|
||||
if [[ "$input" != *":runtime :db-worker-node"* ]]; then
|
||||
echo "unexpected db-worker-node verification form" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo 'cljs.user=> {:runtime :db-worker-node, :process? true}'
|
||||
;;
|
||||
*)
|
||||
echo "Unexpected cljs-repl target: ${2:-}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "cljs.user=>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Unexpected shadow-cljs command: $*" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "Unexpected pnpm command: $*" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
|
||||
cat > "$BIN_DIR/node" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "node $*" >> "$FAKE_CMD_LOG"
|
||||
echo "shadow-cljs - #6 ready!"
|
||||
while true; do sleep 1; done
|
||||
EOF
|
||||
|
||||
cat > "$BIN_DIR/lsof" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
exit 1
|
||||
EOF
|
||||
|
||||
cat > "$BIN_DIR/python3" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
exec "$(command -v python3)" "\$@"
|
||||
EOF
|
||||
|
||||
chmod +x "$BIN_DIR/pnpm" "$BIN_DIR/node" "$BIN_DIR/lsof" "$BIN_DIR/python3"
|
||||
|
||||
export PATH="$BIN_DIR:$ORIGINAL_PATH"
|
||||
export FAKE_CMD_LOG="$CMD_LOG"
|
||||
export FAKE_APP_RUNTIME_COUNT="${FAKE_APP_RUNTIME_COUNT:-1}"
|
||||
export FAKE_ELECTRON_RUNTIME_COUNT="${FAKE_ELECTRON_RUNTIME_COUNT:-1}"
|
||||
export FAKE_DB_RUNTIME_COUNT="${FAKE_DB_RUNTIME_COUNT:-1}"
|
||||
}
|
||||
|
||||
cleanup_fake_env() {
|
||||
if [[ -n "${REPO_ROOT:-}" ]]; then
|
||||
local pid_file pid
|
||||
for pid_file in "$REPO_ROOT"/tmp/logseq-repl/*.pid; do
|
||||
[[ -e "$pid_file" ]] || continue
|
||||
pid="$(tr -d '[:space:]' < "$pid_file")"
|
||||
if [[ "$pid" =~ ^[0-9]+$ ]]; then
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
PATH="$ORIGINAL_PATH"
|
||||
unset FAKE_CMD_LOG FAKE_APP_RUNTIME_COUNT FAKE_ELECTRON_RUNTIME_COUNT FAKE_DB_RUNTIME_COUNT || true
|
||||
|
||||
if [[ -n "${TEST_ROOT:-}" && -d "$TEST_ROOT" ]]; then
|
||||
rm -rf "$TEST_ROOT"
|
||||
fi
|
||||
}
|
||||
|
||||
scripts_exist_test() {
|
||||
assert_file_exists "$START_SCRIPT"
|
||||
assert_file_exists "$START_PY_SCRIPT"
|
||||
assert_file_exists "$CLEANUP_SCRIPT"
|
||||
assert_file_exists "$VERIFY_SCRIPT"
|
||||
}
|
||||
|
||||
start_launches_all_repl_processes_without_attaching_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo > "$TEST_ROOT/start.log" 2>&1
|
||||
|
||||
assert_contains "Verifying CLJS REPL targets ..." "$TEST_ROOT/start.log"
|
||||
assert_contains "REPL verification passed for :app" "$TEST_ROOT/start.log"
|
||||
assert_contains "REPL verification passed for :electron" "$TEST_ROOT/start.log"
|
||||
assert_contains "REPL verification passed for :db-worker-node" "$TEST_ROOT/start.log"
|
||||
assert_contains "Startup complete. Attach to the needed REPL manually." "$TEST_ROOT/start.log"
|
||||
assert_contains "pnpm exec shadow-cljs cljs-repl app" "$TEST_ROOT/start.log"
|
||||
assert_contains "pnpm exec shadow-cljs cljs-repl electron" "$TEST_ROOT/start.log"
|
||||
assert_contains "pnpm exec shadow-cljs cljs-repl db-worker-node" "$TEST_ROOT/start.log"
|
||||
assert_contains "pnpm watch" "$CMD_LOG"
|
||||
assert_contains "pnpm dev-electron-app" "$CMD_LOG"
|
||||
assert_contains "node ./static/db-worker-node.js --repo demo" "$CMD_LOG"
|
||||
assert_file_exists "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.pid"
|
||||
assert_file_exists "$REPO_ROOT/tmp/logseq-repl/desktop-electron.pid"
|
||||
assert_file_exists "$REPO_ROOT/tmp/logseq-repl/db-worker-node.pid"
|
||||
assert_file_exists "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.log"
|
||||
assert_file_exists "$REPO_ROOT/tmp/logseq-repl/desktop-electron.log"
|
||||
assert_file_exists "$REPO_ROOT/tmp/logseq-repl/db-worker-node.log"
|
||||
}
|
||||
|
||||
verify_script_checks_all_targets_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$VERIFY_SCRIPT" --repo-root "$REPO_ROOT" > "$TEST_ROOT/verify.log" 2>&1
|
||||
|
||||
assert_contains "Verifying CLJS REPL targets ..." "$TEST_ROOT/verify.log"
|
||||
assert_contains "REPL verification passed for :app" "$TEST_ROOT/verify.log"
|
||||
assert_contains "REPL verification passed for :electron" "$TEST_ROOT/verify.log"
|
||||
assert_contains "REPL verification passed for :db-worker-node" "$TEST_ROOT/verify.log"
|
||||
assert_contains "All CLJS REPL targets verified." "$TEST_ROOT/verify.log"
|
||||
assert_contains "pnpm-exec-repl app" "$CMD_LOG"
|
||||
assert_contains "pnpm-exec-repl electron" "$CMD_LOG"
|
||||
assert_contains "pnpm-exec-repl db-worker-node" "$CMD_LOG"
|
||||
}
|
||||
|
||||
verify_script_fails_when_target_repl_fails_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
cat > "$BIN_DIR/pnpm" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "pnpm $*" >> "$FAKE_CMD_LOG"
|
||||
|
||||
if [[ "${1:-}" == "exec" && "${2:-}" == "shadow-cljs" && "${3:-}" == "cljs-repl" && "${4:-}" == "electron" ]]; then
|
||||
echo "No available JS runtime" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "shadow-cljs - connected to server"
|
||||
echo "cljs.user=> {:ok true}"
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/pnpm"
|
||||
|
||||
if bash "$VERIFY_SCRIPT" --repo-root "$REPO_ROOT" > "$TEST_ROOT/verify.log" 2>&1; then
|
||||
fail "expected verify script to fail when one target REPL fails"
|
||||
fi
|
||||
|
||||
assert_contains "Error: REPL verification failed for :electron." "$TEST_ROOT/verify.log"
|
||||
assert_contains "No available JS runtime" "$TEST_ROOT/verify.log"
|
||||
}
|
||||
|
||||
start_reuses_all_running_processes_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo > "$TEST_ROOT/first.log" 2>&1
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo > "$TEST_ROOT/second.log" 2>&1
|
||||
|
||||
assert_equals "1" "$(grep -c '^pnpm watch$' "$CMD_LOG")"
|
||||
assert_equals "1" "$(grep -c '^pnpm dev-electron-app$' "$CMD_LOG")"
|
||||
assert_equals "1" "$(grep -c '^node ./static/db-worker-node.js --repo demo$' "$CMD_LOG")"
|
||||
assert_contains "Reusing shared shadow-cljs watch" "$TEST_ROOT/second.log"
|
||||
assert_contains "Reusing Desktop dev app" "$TEST_ROOT/second.log"
|
||||
assert_contains "Reusing db-worker-node runtime" "$TEST_ROOT/second.log"
|
||||
}
|
||||
|
||||
start_fails_when_app_runtime_is_ambiguous_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
export FAKE_APP_RUNTIME_COUNT=2
|
||||
|
||||
if bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo > "$TEST_ROOT/start.log" 2>&1; then
|
||||
fail "expected start script to fail when more than one :app runtime exists"
|
||||
fi
|
||||
|
||||
assert_contains "Expected exactly one live :app runtime" "$TEST_ROOT/start.log"
|
||||
}
|
||||
|
||||
cleanup_stops_all_repl_processes_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
bash "$START_SCRIPT" --repo-root "$REPO_ROOT" --repo demo > "$TEST_ROOT/start.log" 2>&1
|
||||
|
||||
local watch_pid desktop_pid db_pid
|
||||
watch_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.pid")"
|
||||
desktop_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/logseq-repl/desktop-electron.pid")"
|
||||
db_pid="$(tr -d '[:space:]' < "$REPO_ROOT/tmp/logseq-repl/db-worker-node.pid")"
|
||||
|
||||
bash "$CLEANUP_SCRIPT" --repo-root "$REPO_ROOT" > "$TEST_ROOT/cleanup.log" 2>&1
|
||||
|
||||
if kill -0 "$watch_pid" 2>/dev/null; then
|
||||
fail "expected shared watch to stop"
|
||||
fi
|
||||
|
||||
if kill -0 "$desktop_pid" 2>/dev/null; then
|
||||
fail "expected Desktop dev app to stop"
|
||||
fi
|
||||
|
||||
if kill -0 "$db_pid" 2>/dev/null; then
|
||||
fail "expected db-worker-node to stop"
|
||||
fi
|
||||
|
||||
assert_not_exists "$REPO_ROOT/tmp/logseq-repl/shared-shadow-watch.pid"
|
||||
assert_not_exists "$REPO_ROOT/tmp/logseq-repl/desktop-electron.pid"
|
||||
assert_not_exists "$REPO_ROOT/tmp/logseq-repl/db-worker-node.pid"
|
||||
assert_contains "Cleanup done." "$TEST_ROOT/cleanup.log"
|
||||
}
|
||||
|
||||
cleanup_removes_legacy_state_files_test() {
|
||||
create_fake_env
|
||||
trap cleanup_fake_env RETURN
|
||||
|
||||
mkdir -p "$REPO_ROOT/tmp/desktop-app-repl" "$REPO_ROOT/tmp/db-worker-node-repl" "$REPO_ROOT/tmp/logseq-repl"
|
||||
sleep 30 &
|
||||
local legacy_desktop_pid=$!
|
||||
sleep 30 &
|
||||
local legacy_db_pid=$!
|
||||
echo "$legacy_desktop_pid" > "$REPO_ROOT/tmp/desktop-app-repl/desktop-electron.pid"
|
||||
echo "$legacy_db_pid" > "$REPO_ROOT/tmp/db-worker-node-repl/db-worker-node.pid"
|
||||
echo "$legacy_db_pid" > "$REPO_ROOT/tmp/db-worker-node-repl/shadow-db-worker-node.pid"
|
||||
|
||||
bash "$CLEANUP_SCRIPT" --repo-root "$REPO_ROOT" > "$TEST_ROOT/cleanup.log" 2>&1
|
||||
|
||||
if kill -0 "$legacy_desktop_pid" 2>/dev/null; then
|
||||
fail "expected legacy desktop pid to stop"
|
||||
fi
|
||||
|
||||
if kill -0 "$legacy_db_pid" 2>/dev/null; then
|
||||
fail "expected legacy db-worker pid to stop"
|
||||
fi
|
||||
|
||||
assert_not_exists "$REPO_ROOT/tmp/desktop-app-repl/desktop-electron.pid"
|
||||
assert_not_exists "$REPO_ROOT/tmp/db-worker-node-repl/db-worker-node.pid"
|
||||
assert_not_exists "$REPO_ROOT/tmp/db-worker-node-repl/shadow-db-worker-node.pid"
|
||||
}
|
||||
|
||||
help_and_docs_describe_unified_scripts_test() {
|
||||
local temp_dir start_help cleanup_help
|
||||
temp_dir="$(mktemp -d)"
|
||||
start_help="$temp_dir/start-help.txt"
|
||||
cleanup_help="$temp_dir/cleanup-help.txt"
|
||||
|
||||
bash "$START_SCRIPT" --help > "$start_help"
|
||||
bash "$CLEANUP_SCRIPT" --help > "$cleanup_help"
|
||||
|
||||
assert_contains "start-repl.sh" "$start_help"
|
||||
assert_contains "start-repl.py" "$SKILL_FILE"
|
||||
assert_contains "cleanup-repl.sh" "$cleanup_help"
|
||||
assert_contains "start-repl.sh" "$SKILL_FILE"
|
||||
assert_contains "cleanup-repl.sh" "$SKILL_FILE"
|
||||
assert_contains "verify-repls.sh" "$SKILL_FILE"
|
||||
assert_not_contains_text "start-desktop-app-repl.sh" "$SKILL_FILE"
|
||||
assert_not_contains_text "start-db-worker-node-repl.sh" "$SKILL_FILE"
|
||||
assert_not_contains_text "cleanup-desktop-app-repl.sh" "$SKILL_FILE"
|
||||
assert_not_contains_text "cleanup-db-worker-node-repl.sh" "$SKILL_FILE"
|
||||
|
||||
rm -rf "$temp_dir"
|
||||
}
|
||||
|
||||
run_test "scripts exist" scripts_exist_test
|
||||
run_test "start launches all REPL processes without attaching" start_launches_all_repl_processes_without_attaching_test
|
||||
run_test "verify script checks all targets" verify_script_checks_all_targets_test
|
||||
run_test "verify script fails when target REPL fails" verify_script_fails_when_target_repl_fails_test
|
||||
run_test "start reuses all running processes" start_reuses_all_running_processes_test
|
||||
run_test "start fails when app runtime is ambiguous" start_fails_when_app_runtime_is_ambiguous_test
|
||||
run_test "cleanup stops all REPL processes" cleanup_stops_all_repl_processes_test
|
||||
run_test "cleanup removes legacy state files" cleanup_removes_legacy_state_files_test
|
||||
run_test "help and docs describe unified scripts" help_and_docs_describe_unified_scripts_test
|
||||
|
||||
echo
|
||||
if [[ "$FAIL_COUNT" -gt 0 ]]; then
|
||||
echo "$FAIL_COUNT test(s) failed; $PASS_COUNT passed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All $PASS_COUNT test(s) passed."
|
||||
Reference in New Issue
Block a user