mirror of
https://github.com/logseq/logseq.git
synced 2026-05-24 20:54:09 +00:00
feat(skill): add logseq-repl skill
This commit is contained in:
@@ -1,158 +0,0 @@
|
||||
---
|
||||
name: db-worker-node-repl
|
||||
description: Start `db-worker-node` in development and attach a `shadow-cljs` CLJS REPL (or editor nREPL session) to the `:db-worker-node` build for interactive debugging.
|
||||
---
|
||||
|
||||
# db-worker-node REPL
|
||||
|
||||
## When to use
|
||||
|
||||
Use this skill when the user asks how to:
|
||||
|
||||
- start `db-worker-node` locally,
|
||||
- start `shadow-cljs` REPL/nREPL,
|
||||
- connect a CLJS REPL to the `:db-worker-node` runtime.
|
||||
|
||||
## Repo context
|
||||
|
||||
Commands below assume repo root:
|
||||
|
||||
- `/Users/rcmerci/gh-repos/logseq`
|
||||
|
||||
## Automation scripts (recommended)
|
||||
|
||||
This skill now includes two scripts under `scripts/`:
|
||||
|
||||
- `scripts/start-db-worker-node-repl.sh`
|
||||
- `scripts/cleanup-db-worker-node-repl.sh`
|
||||
|
||||
Location:
|
||||
|
||||
- `/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/`
|
||||
|
||||
### Start script
|
||||
|
||||
`start-db-worker-node-repl.sh` automates the workflow:
|
||||
|
||||
1. starts/reuses `npx shadow-cljs watch db-worker-node`,
|
||||
2. starts/reuses `node ./static/db-worker-node.js --repo <name>`,
|
||||
3. attaches `npx shadow-cljs cljs-repl db-worker-node` (unless `--no-repl`).
|
||||
|
||||
Basic usage:
|
||||
|
||||
```bash
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/start-db-worker-node-repl.sh
|
||||
```
|
||||
|
||||
Common options:
|
||||
|
||||
```bash
|
||||
# Start with a specific repo and do not attach REPL
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/start-db-worker-node-repl.sh --repo demo --no-repl
|
||||
|
||||
# Pass extra args to db-worker-node
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/start-db-worker-node-repl.sh --repo demo -- --create-empty-db
|
||||
|
||||
# Override repo root
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/start-db-worker-node-repl.sh --repo-root /path/to/logseq
|
||||
```
|
||||
|
||||
Environment overrides:
|
||||
|
||||
- `REPO_ROOT` (default: `/Users/rcmerci/gh-repos/logseq`)
|
||||
- `DB_REPO` (default: `demo`)
|
||||
|
||||
State/log files created under:
|
||||
|
||||
- `<repo>/tmp/db-worker-node-repl/shadow-db-worker-node.log`
|
||||
- `<repo>/tmp/db-worker-node-repl/db-worker-node.log`
|
||||
- `<repo>/tmp/db-worker-node-repl/shadow-db-worker-node.pid`
|
||||
- `<repo>/tmp/db-worker-node-repl/db-worker-node.pid`
|
||||
|
||||
### Cleanup script
|
||||
|
||||
`cleanup-db-worker-node-repl.sh` stops the processes tracked by PID files.
|
||||
|
||||
Basic usage:
|
||||
|
||||
```bash
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/cleanup-db-worker-node-repl.sh
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
```bash
|
||||
# Force kill if graceful stop times out
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/cleanup-db-worker-node-repl.sh --force
|
||||
|
||||
# Override repo root
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/cleanup-db-worker-node-repl.sh --repo-root /path/to/logseq
|
||||
```
|
||||
|
||||
## Editor nREPL attach flow (Calva/CIDER/Cursive)
|
||||
|
||||
If your editor connects to nREPL directly:
|
||||
|
||||
- Host: `localhost`
|
||||
- Port: `8701`
|
||||
- Build: `:db-worker-node`
|
||||
|
||||
For a CLJ-first session (for example CIDER), after connecting to nREPL run:
|
||||
|
||||
```clojure
|
||||
(shadow.cljs.devtools.api/repl :db-worker-node)
|
||||
```
|
||||
|
||||
## If `yarn watch` is already running
|
||||
|
||||
Still prefer the start script as the single entry point:
|
||||
|
||||
```bash
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/start-db-worker-node-repl.sh
|
||||
```
|
||||
|
||||
If state becomes inconsistent, run cleanup first, then start again.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `shadow-cljs already running in project`
|
||||
|
||||
Cause: stale or conflicting shadow/db-worker-node processes.
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/cleanup-db-worker-node-repl.sh
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/start-db-worker-node-repl.sh
|
||||
```
|
||||
|
||||
### `repo is required`
|
||||
|
||||
Cause: runtime was started without `--repo`.
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/start-db-worker-node-repl.sh --repo <name>
|
||||
```
|
||||
|
||||
### `No available JS runtime`
|
||||
|
||||
Cause: shadow REPL is up but no live `:db-worker-node` runtime is attached.
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/cleanup-db-worker-node-repl.sh
|
||||
/Users/rcmerci/gh-repos/logseq/skills/db-worker-node-repl/scripts/start-db-worker-node-repl.sh --repo <name>
|
||||
```
|
||||
|
||||
### Runtime/module startup errors in node process
|
||||
|
||||
If startup fails with missing module/bundled runtime errors, rebuild runtime artifacts first:
|
||||
|
||||
```bash
|
||||
yarn db-worker-node:release:bundle
|
||||
```
|
||||
|
||||
Then run the start script again.
|
||||
@@ -32,7 +32,7 @@ Useful tools:
|
||||
|
||||
### `db-worker-node`
|
||||
|
||||
Before controlling or attaching to `db-worker-node`, load repo-local `db-worker-node-repl`.
|
||||
Before controlling or attaching to `db-worker-node`, load repo-local `logseq-repl`.
|
||||
|
||||
Useful tools:
|
||||
|
||||
|
||||
293
.agents/skills/logseq-repl/SKILL.md
Normal file
293
.agents/skills/logseq-repl/SKILL.md
Normal file
@@ -0,0 +1,293 @@
|
||||
---
|
||||
name: logseq-repl
|
||||
description: Start and coordinate Logseq development REPL workflows for the Desktop renderer `:app` runtime and the `:db-worker-node` runtime while sharing a single `yarn watch` process.
|
||||
---
|
||||
|
||||
# Logseq REPL workflows
|
||||
|
||||
Use this skill when the user needs a Logseq development REPL for:
|
||||
|
||||
- Desktop renderer `:app`
|
||||
- `:db-worker-node`
|
||||
- or both at once
|
||||
|
||||
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.
|
||||
|
||||
## 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? You probably need `:electron`, not this workflow.
|
||||
|
||||
Runtime reminders:
|
||||
|
||||
- `:app` = Electron renderer
|
||||
- `:electron` = Electron main process
|
||||
- `:db-worker` = browser worker
|
||||
- `:db-worker-node` = Node worker
|
||||
|
||||
### Fast paths
|
||||
|
||||
Desktop `:app`:
|
||||
|
||||
```bash
|
||||
.agents/skills/logseq-repl/scripts/start-desktop-app-repl.sh --no-repl
|
||||
npx shadow-cljs cljs-repl app
|
||||
```
|
||||
|
||||
`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
|
||||
```
|
||||
|
||||
Both 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 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/worker-node-repl)
|
||||
```
|
||||
|
||||
### Non-interactive verification examples
|
||||
|
||||
Desktop `:app`:
|
||||
|
||||
```bash
|
||||
printf '(prn {:runtime :app :document? (some? js/document) :title (.-title js/document)})\n:cljs/quit\n' | npx shadow-cljs cljs-repl app
|
||||
```
|
||||
|
||||
`db-worker-node`:
|
||||
|
||||
```bash
|
||||
printf '(prn {:runtime :db-worker-node :process? (some? js/process) :platform (.-platform js/process)})\n:cljs/quit\n' | npx shadow-cljs cljs-repl db-worker-node
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
### 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 both `:app` and `:db-worker-node` together
|
||||
|
||||
Use one terminal or editor session per runtime.
|
||||
|
||||
Shared `yarn watch` is expected here. Do not rely on one interactive REPL session to cover both 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:
|
||||
|
||||
```clojure
|
||||
(shadow.user/cljs-repl)
|
||||
```
|
||||
|
||||
#### `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
|
||||
|
||||
#### 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. re-run the script without `--no-repl`, or attach from your editor
|
||||
|
||||
#### 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`.
|
||||
|
||||
#### `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
|
||||
```
|
||||
|
||||
#### `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>
|
||||
```
|
||||
|
||||
#### `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 both REPLs:
|
||||
|
||||
1. identify whether they need `:app`, `:db-worker-node`, or both
|
||||
2. if they need Desktop `:app`, close browser dev app instances first
|
||||
3. run the matching start script under `logseq-repl/scripts/`
|
||||
4. if they need both runtimes, start both with `--no-repl`
|
||||
5. attach from the matching build or helper
|
||||
6. point troubleshooting at `tmp/logseq-repl/shared-shadow-watch.log`
|
||||
7. run the matching cleanup script when finished
|
||||
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="${REPO_ROOT:-/Users/rcmerci/gh-repos/logseq}"
|
||||
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() {
|
||||
@@ -12,7 +14,7 @@ Usage:
|
||||
cleanup-db-worker-node-repl.sh [options]
|
||||
|
||||
Options:
|
||||
--repo-root <path> Logseq repository root (default: /Users/rcmerci/gh-repos/logseq)
|
||||
--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
|
||||
@@ -41,8 +43,13 @@ while [[ $# -gt 0 ]]; do
|
||||
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"
|
||||
@@ -102,13 +109,40 @@ stop_by_pid_file() {
|
||||
rm -f "$pid_file"
|
||||
}
|
||||
|
||||
if [[ ! -d "$LOG_DIR" ]]; then
|
||||
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"
|
||||
stop_by_pid_file "$SHADOW_PID_FILE" "shadow-cljs watch"
|
||||
rm -f "$DB_REPO_FILE"
|
||||
stop_shadow_watch
|
||||
|
||||
echo "Cleanup done."
|
||||
170
.agents/skills/logseq-repl/scripts/cleanup-desktop-app-repl.sh
Executable file
170
.agents/skills/logseq-repl/scripts/cleanup-desktop-app-repl.sh
Executable file
@@ -0,0 +1,170 @@
|
||||
#!/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-desktop-app-repl.sh.
|
||||
|
||||
Usage:
|
||||
cleanup-desktop-app-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/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"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
process_group_id() {
|
||||
local pid="$1"
|
||||
ps -o pgid= -p "$pid" 2>/dev/null | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
current_process_group_id() {
|
||||
process_group_id "$$"
|
||||
}
|
||||
|
||||
signal_process_group() {
|
||||
local signal="$1"
|
||||
local pid="$2"
|
||||
|
||||
local current_pgid pgid
|
||||
pgid="$(process_group_id "$pid" || true)"
|
||||
current_pgid="$(current_process_group_id || true)"
|
||||
|
||||
if [[ -n "${pgid:-}" && "$pgid" =~ ^[0-9]+$ && "$pgid" != "${current_pgid:-}" ]]; then
|
||||
kill "-$signal" "-$pgid" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
kill "-$signal" "$pid" 2>/dev/null || true
|
||||
}
|
||||
|
||||
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"
|
||||
signal_process_group TERM "$pid"
|
||||
|
||||
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"
|
||||
signal_process_group KILL "$pid"
|
||||
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 "$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
|
||||
}
|
||||
|
||||
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 "$DESKTOP_PID_FILE" "desktop-electron"
|
||||
stop_shadow_watch
|
||||
|
||||
echo "Cleanup done."
|
||||
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="${REPO_ROOT:-/Users/rcmerci/gh-repos/logseq}"
|
||||
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=()
|
||||
@@ -15,7 +17,7 @@ Usage:
|
||||
|
||||
Options:
|
||||
--repo <name> Graph repo name passed to db-worker-node (default: demo)
|
||||
--repo-root <path> Logseq repository root (default: /Users/rcmerci/gh-repos/logseq)
|
||||
--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
|
||||
@@ -69,18 +71,28 @@ if ! command -v npx >/dev/null 2>&1; then
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
is_running_pid() {
|
||||
local pid="$1"
|
||||
@@ -131,51 +143,80 @@ wait_for_shadow_build_ready() {
|
||||
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
|
||||
local existing_pid shared_pid
|
||||
existing_pid="$(read_pid "$SHADOW_PID_FILE" || true)"
|
||||
|
||||
if [[ -n "${existing_pid:-}" ]] && is_running_pid "$existing_pid"; then
|
||||
echo "Reusing shadow-cljs watch (pid=$existing_pid)"
|
||||
echo "Reusing shared shadow-cljs watch (pid=$existing_pid)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Starting shadow-cljs watch db-worker-node ..."
|
||||
pushd "$REPO_ROOT" >/dev/null
|
||||
nohup npx shadow-cljs watch db-worker-node > "$SHADOW_LOG" 2>&1 &
|
||||
local shadow_pid=$!
|
||||
popd >/dev/null
|
||||
shared_pid="$(read_pid "$SHARED_SHADOW_PID_FILE" || true)"
|
||||
if [[ -n "${shared_pid:-}" ]] && 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 ! is_running_pid "$shadow_pid"; then
|
||||
echo "Error: shadow-cljs exited early. Check $SHADOW_LOG" >&2
|
||||
echo "Error: yarn watch exited early. Check $SHARED_SHADOW_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if wait_for_log_pattern "$SHADOW_LOG" "watching build :db-worker-node" 45; then
|
||||
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 "$SHADOW_LOG" 120; then
|
||||
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 $SHADOW_LOG" >&2
|
||||
echo "Error: shadow-cljs build is not ready. Check $SHARED_SHADOW_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_db_worker_node() {
|
||||
local existing_pid
|
||||
local existing_pid existing_repo
|
||||
existing_pid="$(read_pid "$DB_PID_FILE" || true)"
|
||||
existing_repo="$(read_pid "$DB_REPO_FILE" || true)"
|
||||
|
||||
if [[ -n "${existing_pid:-}" ]] && is_running_pid "$existing_pid"; then
|
||||
local existing_cmd
|
||||
existing_cmd="$(ps -p "$existing_pid" -o command= || true)"
|
||||
if [[ "$existing_cmd" == *"--repo $DB_REPO"* ]]; then
|
||||
if [[ "$existing_repo" == "$DB_REPO" ]]; then
|
||||
echo "Reusing db-worker-node runtime (pid=$existing_pid, repo=$DB_REPO)"
|
||||
return 0
|
||||
fi
|
||||
@@ -196,6 +237,7 @@ ensure_db_worker_node() {
|
||||
popd >/dev/null
|
||||
|
||||
echo "$db_pid" > "$DB_PID_FILE"
|
||||
echo "$DB_REPO" > "$DB_REPO_FILE"
|
||||
sleep 1
|
||||
|
||||
if ! is_running_pid "$db_pid"; then
|
||||
@@ -247,9 +289,11 @@ ensure_db_worker_node
|
||||
verify_repl_connectivity
|
||||
|
||||
echo
|
||||
|
||||
echo "Logs:"
|
||||
echo " shadow-cljs: $SHADOW_LOG"
|
||||
echo " db-worker: $DB_LOG"
|
||||
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"
|
||||
329
.agents/skills/logseq-repl/scripts/start-desktop-app-repl.sh
Executable file
329
.agents/skills/logseq-repl/scripts/start-desktop-app-repl.sh
Executable file
@@ -0,0 +1,329 @@
|
||||
#!/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
|
||||
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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="$(read_pid "$SHADOW_PID_FILE" || true)"
|
||||
|
||||
if [[ -n "${existing_pid:-}" ]] && is_running_pid "$existing_pid"; then
|
||||
echo "Reusing shared shadow-cljs watch (pid=$existing_pid)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
shared_pid="$(read_pid "$SHARED_SHADOW_PID_FILE" || true)"
|
||||
if [[ -n "${shared_pid:-}" ]] && 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 ! 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 \
|
||||
"shadow-cljs - nREPL server started on port 8701" \
|
||||
"[:electron] Build completed." \
|
||||
"[:app] Build completed."; then
|
||||
echo "shadow-cljs watch is ready (pid=$shadow_pid)"
|
||||
else
|
||||
echo "Error: yarn watch did not become ready 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
|
||||
}
|
||||
|
||||
get_app_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 :app))))" 2>&1)"; then
|
||||
popd >/dev/null
|
||||
echo "Error: failed to inspect :app runtimes." >&2
|
||||
echo "--- clj-eval output ---" >&2
|
||||
echo "$output" >&2
|
||||
echo "-----------------------" >&2
|
||||
exit 1
|
||||
fi
|
||||
popd >/dev/null
|
||||
|
||||
local runtime_count
|
||||
runtime_count="$(printf '%s\n' "$output" | awk '/^[0-9]+$/{n=$0} END{if (n != "") print n; else exit 1}')" || {
|
||||
echo "Error: could not parse :app runtime count." >&2
|
||||
echo "--- clj-eval output ---" >&2
|
||||
echo "$output" >&2
|
||||
echo "-----------------------" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
printf '%s\n' "$runtime_count"
|
||||
}
|
||||
|
||||
ensure_single_app_runtime() {
|
||||
local runtime_count second
|
||||
|
||||
for ((second=0; second<60; second++)); do
|
||||
runtime_count="$(get_app_runtime_count)"
|
||||
|
||||
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 "Make sure the Desktop dev app 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
|
||||
343
.agents/skills/logseq-repl/tests/test-db-worker-node-repl.sh
Normal file
343
.agents/skills/logseq-repl/tests/test-db-worker-node-repl.sh
Normal file
@@ -0,0 +1,343 @@
|
||||
#!/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 "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 both `:app` and `:db-worker-node` together' "$SKILL_FILE"
|
||||
assert_contains "shared-shadow-watch.log" "$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."
|
||||
363
.agents/skills/logseq-repl/tests/test-desktop-app-repl.sh
Normal file
363
.agents/skills/logseq-repl/tests/test-desktop-app-repl.sh
Normal file
@@ -0,0 +1,363 @@
|
||||
#!/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" ]] && [[ -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
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
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 both `:app` and `:db-worker-node` together' "$SKILL_FILE"
|
||||
assert_contains "close browser dev app instances" "$SKILL_FILE"
|
||||
assert_contains "shared-shadow-watch.log" "$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 :db-worker-node" "$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 "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."
|
||||
77
.agents/skills/logseq-repl/tests/test-lib.sh
Normal file
77
.agents/skills/logseq-repl/tests/test-lib.sh
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
assert_file_exists() {
|
||||
local path="$1"
|
||||
[[ -e "$path" ]] || fail "expected file to exist: $path"
|
||||
}
|
||||
|
||||
assert_not_exists() {
|
||||
local path="$1"
|
||||
[[ ! -e "$path" ]] || fail "expected path to be absent: $path"
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local needle="$1"
|
||||
local haystack_file="$2"
|
||||
if ! grep -Fq "$needle" "$haystack_file"; then
|
||||
echo "Expected to find: $needle" >&2
|
||||
echo "--- file: $haystack_file ---" >&2
|
||||
cat "$haystack_file" >&2
|
||||
echo "----------------------------" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains_text() {
|
||||
local needle="$1"
|
||||
local file="$2"
|
||||
if grep -Fq "$needle" "$file"; then
|
||||
echo "Did not expect to find: $needle" >&2
|
||||
echo "--- file: $file ---" >&2
|
||||
cat "$file" >&2
|
||||
echo "--------------------" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_matches() {
|
||||
local pattern="$1"
|
||||
local file="$2"
|
||||
if grep -Eq "$pattern" "$file"; then
|
||||
echo "Did not expect regex match: $pattern" >&2
|
||||
echo "--- file: $file ---" >&2
|
||||
cat "$file" >&2
|
||||
echo "--------------------" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
[[ "$expected" == "$actual" ]] || fail "expected '$expected' but got '$actual'"
|
||||
}
|
||||
|
||||
portable_path_pattern() {
|
||||
local slash='/'
|
||||
local windows_drive='[A-Za-z]:\\[^[:space:]]+'
|
||||
printf '%s' "${slash}Users${slash}[^[:space:]]+|${slash}home${slash}[^[:space:]]+|${windows_drive}"
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local fn="$2"
|
||||
|
||||
if (set -e; "$fn"); then
|
||||
echo "PASS: $name"
|
||||
PASS_COUNT=$((PASS_COUNT + 1))
|
||||
else
|
||||
echo "FAIL: $name" >&2
|
||||
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||
fi
|
||||
}
|
||||
@@ -12,5 +12,5 @@
|
||||
- `--profile` - performance check
|
||||
- Logseq-cli skill - explore cli self in agent
|
||||
- db-worker-node.log - logs for db-worker-node process
|
||||
- db-worker-node-repl skill - Directly validate some db worker node code in the REPL
|
||||
- logseq-repl skill - Directly validate some db worker node code in the REPL
|
||||
|
||||
|
||||
Reference in New Issue
Block a user