@antelopejs/dms 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/implementations/dms/page.js +69 -22
  3. package/dist/implementations/dms/page.js.map +1 -1
  4. package/dist/index.js +2 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/pages/settings/users/member-invite-form.d.ts +5 -0
  7. package/dist/pages/settings/users/member-invite-form.js +11 -2
  8. package/dist/pages/settings/users/member-invite-form.js.map +1 -1
  9. package/dist/pages/settings/users/members.d.ts +2 -0
  10. package/dist/pages/settings/users/members.js +11 -0
  11. package/dist/pages/settings/users/members.js.map +1 -1
  12. package/dist/realtime/index.js +2 -0
  13. package/dist/realtime/index.js.map +1 -1
  14. package/dist/realtime/sessions.d.ts +3 -0
  15. package/dist/realtime/sessions.js +5 -0
  16. package/dist/realtime/sessions.js.map +1 -1
  17. package/dist/realtime/sse.d.ts +1 -1
  18. package/dist/realtime/sse.js +9 -3
  19. package/dist/realtime/sse.js.map +1 -1
  20. package/dist/routes/realtime.js +8 -3
  21. package/dist/routes/realtime.js.map +1 -1
  22. package/dist/test/helpers/reload-host.d.ts +7 -2
  23. package/dist/test/helpers/reload-host.js +23 -4
  24. package/dist/test/helpers/reload-host.js.map +1 -1
  25. package/dist/test/reload-host/index.js +27 -1
  26. package/dist/test/reload-host/index.js.map +1 -1
  27. package/dist/test/reload-host/package.json +1 -1
  28. package/dist/test/unit/implementations/dms/missing-category.test.d.ts +1 -0
  29. package/dist/test/unit/implementations/dms/missing-category.test.js +174 -0
  30. package/dist/test/unit/implementations/dms/missing-category.test.js.map +1 -0
  31. package/dist/test/unit/interfaces/dms/database-initialized-hook.test.d.ts +1 -0
  32. package/dist/test/unit/interfaces/dms/database-initialized-hook.test.js +92 -0
  33. package/dist/test/unit/interfaces/dms/database-initialized-hook.test.js.map +1 -0
  34. package/dist/test/unit/interfaces/dms/invite-extension.test.js +59 -0
  35. package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
  36. package/dist/test/unit/interfaces/dms/module-reload-registrations.test.js +2 -2
  37. package/dist/test/unit/interfaces/dms/module-reload-registrations.test.js.map +1 -1
  38. package/dist/test/unit/interfaces/dms/page-extension-ownership.test.d.ts +1 -0
  39. package/dist/test/unit/interfaces/dms/page-extension-ownership.test.js +153 -0
  40. package/dist/test/unit/interfaces/dms/page-extension-ownership.test.js.map +1 -0
  41. package/dist/test/unit/pages/settings/invite-availability.test.d.ts +1 -0
  42. package/dist/test/unit/pages/settings/invite-availability.test.js +112 -0
  43. package/dist/test/unit/pages/settings/invite-availability.test.js.map +1 -0
  44. package/dist/test/unit/realtime/stop-closes-streams.test.d.ts +1 -0
  45. package/dist/test/unit/realtime/stop-closes-streams.test.js +99 -0
  46. package/dist/test/unit/realtime/stop-closes-streams.test.js.map +1 -0
  47. package/dist/test/unit/skills/dms-dev-script.test.d.ts +1 -0
  48. package/dist/test/unit/skills/dms-dev-script.test.js +137 -0
  49. package/dist/test/unit/skills/dms-dev-script.test.js.map +1 -0
  50. package/dist/validation/auth.schema.d.ts +2 -2
  51. package/frontend-vue/dms.frontend.ts +4 -3
  52. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +116 -44
  53. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +2 -0
  54. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +18 -8
  55. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +12 -0
  56. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/custom-button.ts +4 -0
  57. package/frontend-vue/layers/dms-ui/app/plugins/shortcuts.ts +1 -1
  58. package/frontend-vue/layers/dms-ui/app/types/shortcuts-aggregated.d.ts +8 -0
  59. package/frontend-vue/tests/realtime-reconnect.test.ts +215 -0
  60. package/frontend-vue/tests/shortcuts-aggregate.test.ts +41 -0
  61. package/frontend-vue/vitest.config.ts +4 -0
  62. package/package.json +4 -4
  63. package/skills/dms-dev/SKILL.md +28 -16
  64. package/skills/dms-dev/scripts/dms-dev.sh +275 -83
@@ -1,13 +1,16 @@
1
1
  #!/usr/bin/env bash
2
- # dms-dev — start / stop / restart the AntelopeJS DMS dev servers.
2
+ # dms-dev — start / stop / restart the AntelopeJS DMS dev servers of ONE project.
3
3
  # Run from the DMS backend package dir (DMS_BACK_DIR). By default it invokes that project's
4
4
  # `pnpm dev` / `pnpm frontend:dev` scripts — a convention our playground projects adopt, which
5
5
  # expand to `ajs project dev …` (the AntelopeJS backend; `ajs project run` is the legacy
6
6
  # alias) and `ajs dms dev` (the Inertia frontend loader — it auto-discovers the backend, -b optional).
7
7
  # Those script names aren't shipped by the DMS, so override the actual commands with
8
8
  # DMS_BACK_CMD / DMS_FRONT_CMD when a project starts its servers differently.
9
- # Dev watchers cover many edits, but the deterministic recipe when verifying a change is:
10
- # stop both → start backend → wait for "Server started, listening" → start frontend.
9
+ #
10
+ # Scope: the script only signals the processes of DMS_BACK_DIR. The pids it starts are
11
+ # recorded in DMS_BACK_DIR/.antelope/dms-dev.pid; without that file it falls back to the dev
12
+ # processes whose working directory or command line lies under DMS_BACK_DIR. Other projects'
13
+ # dev servers on the same machine are never touched.
11
14
 
12
15
  set -uo pipefail
13
16
 
@@ -17,14 +20,24 @@ FRONT_CMD="${DMS_FRONT_CMD:-pnpm frontend:dev}"
17
20
  BACK_LOG="${DMS_BACK_LOG:-/tmp/dms-back.log}"
18
21
  FRONT_LOG="${DMS_FRONT_LOG:-/tmp/dms-front.log}"
19
22
  BACK_READY_PATTERN='Server started, listening'
20
- FRONT_READY_PATTERN='Local:.*localhost:'
23
+ FRONT_READY_PATTERN='Server ready on|Local:.*localhost:'
21
24
  # Patterns that indicate a fatal startup error — detecting any of these aborts
22
25
  # the wait immediately instead of burning the full timeout.
23
26
  BACK_ERROR_PATTERN='Failed to load module|Module load failed|Error loading module|UnhandledPromiseRejection|Cannot find module|ENOENT.*package\.json|TypeError:.*at .*ajs|FATAL'
24
27
  FRONT_ERROR_PATTERN='Cannot find module|Failed to compile|Vite error|ENOENT.*package\.json|EADDRINUSE'
25
28
  WAIT_TIMEOUT="${DMS_DEV_TIMEOUT:-180}"
29
+ STOP_GRACE="${DMS_DEV_STOP_GRACE:-10}"
30
+ DEV_JSON_WAIT=5
31
+ DEFAULT_BACK_URL='http://localhost:5010'
32
+ DEFAULT_FRONT_URL='http://localhost:3001'
26
33
 
27
- PROC_PATTERN='antelope-runner|ajs project (run|dev)|(^|[[:space:]/])(ajs dms|ajs-dms) dev|dms-frontend/dist/index\.js dev'
34
+ # Only picks candidates when no pid file exists, and is always combined with the
35
+ # DMS_BACK_DIR scope check: on its own it would match every project on the machine.
36
+ PROC_PATTERN='antelope-runner|ajs project (run|dev)|(^|[[:space:]/])(ajs dms|ajs-dms) dev|dms-frontend/dist/index\.js dev|pnpm( run)? (dev|frontend:dev)'
37
+
38
+ PROJECT_ROOT=""
39
+ PID_FILE=""
40
+ LAUNCHED_PID=""
28
41
 
29
42
  log() { printf '[dms-dev] %s\n' "$*"; }
30
43
 
@@ -42,84 +55,274 @@ require_dms_back_dir() {
42
55
  log "error: DMS_BACK_DIR=$DMS_BACK_DIR has no package.json — not a DMS backend project."
43
56
  exit 2
44
57
  fi
58
+ PROJECT_ROOT="$(cd "$DMS_BACK_DIR" && pwd -P)"
59
+ PID_FILE="$PROJECT_ROOT/.antelope/dms-dev.pid"
45
60
  }
46
61
 
47
- stop_servers() {
48
- log "stopping dev servers…"
49
- pkill -9 -f "antelope-runner" 2>/dev/null || true
50
- pkill -9 -f "ajs project (run|dev)" 2>/dev/null || true
51
- pkill -9 -f "(^|[[:space:]/])(ajs dms|ajs-dms) dev" 2>/dev/null || true
52
- pkill -9 -f "dms-frontend/dist/index\.js dev" 2>/dev/null || true
53
- sleep 2
54
- if pgrep -af "$PROC_PATTERN" >/dev/null 2>&1; then
55
- log "warning: some processes are still running, killing again:"
56
- pgrep -af "$PROC_PATTERN" | sed 's/^/ /'
57
- pgrep -af "$PROC_PATTERN" | awk '{print $1}' | xargs -r kill -9 2>/dev/null || true
58
- sleep 2
62
+ is_alive() { [ -n "$1" ] && kill -0 "$1" 2>/dev/null; }
63
+
64
+ process_cwd() {
65
+ if [ -e "/proc/$1/cwd" ]; then
66
+ readlink "/proc/$1/cwd" 2>/dev/null
67
+ return
68
+ fi
69
+ if command -v lsof >/dev/null 2>&1; then
70
+ lsof -a -p "$1" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -1
59
71
  fi
60
- if pgrep -af "$PROC_PATTERN" >/dev/null 2>&1; then
61
- log "error: could not stop all processes:"
62
- pgrep -af "$PROC_PATTERN" | sed 's/^/ /'
63
- return 1
72
+ }
73
+
74
+ process_args() { ps -o args= -p "$1" 2>/dev/null; }
75
+
76
+ is_in_project() {
77
+ case "$(process_cwd "$1")/" in "$PROJECT_ROOT"/*) return 0 ;; esac
78
+ case "$(process_args "$1") " in *"$PROJECT_ROOT"/* | *"$PROJECT_ROOT "*) return 0 ;; esac
79
+ return 1
80
+ }
81
+
82
+ # The given roots plus every process whose parent chain or process group leads to one.
83
+ expand_tree() {
84
+ ps -A -o pid=,ppid=,pgid= | awk -v roots="$*" '
85
+ BEGIN { n = split(roots, r, " "); for (i = 1; i <= n; i++) keep[r[i]] = 1 }
86
+ { parent[$1] = $2; group[$1] = $3 }
87
+ END {
88
+ do {
89
+ grew = 0
90
+ for (p in parent)
91
+ if (!(p in keep) && ((parent[p] in keep) || (group[p] in keep))) { keep[p] = 1; grew = 1 }
92
+ } while (grew)
93
+ for (p in keep) if (p in parent) print p
94
+ }'
95
+ }
96
+
97
+ pid_file_roots() {
98
+ [ -f "$PID_FILE" ] || return 0
99
+ local name pid
100
+ while read -r name pid; do
101
+ [ -n "${pid:-}" ] || continue
102
+ if is_alive "$pid" && is_in_project "$pid"; then
103
+ echo "$pid"
104
+ else
105
+ log "ignoring stale $name pid $pid from $PID_FILE" >&2
106
+ fi
107
+ done < "$PID_FILE"
108
+ }
109
+
110
+ scanned_roots() {
111
+ local listing pid
112
+ # Captured first so the grep below never sees its own command line.
113
+ listing="$(ps -A -o pid=,args=)"
114
+ printf '%s\n' "$listing" | grep -E "$PROC_PATTERN" | awk '{print $1}' | while read -r pid; do
115
+ is_in_project "$pid" && echo "$pid"
116
+ done
117
+ }
118
+
119
+ project_pids() {
120
+ local roots
121
+ roots="$(pid_file_roots)"
122
+ [ -n "$roots" ] || roots="$(scanned_roots)"
123
+ [ -n "$roots" ] || return 0
124
+ # shellcheck disable=SC2086 # one pid per word
125
+ expand_tree $roots | grep -vx "$$"
126
+ }
127
+
128
+ self_ancestors() {
129
+ local pid="$$"
130
+ while [ -n "$pid" ] && [ "$pid" -gt 1 ]; do
131
+ echo "$pid"
132
+ pid="$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ')"
133
+ done
134
+ }
135
+
136
+ # An agent spawned by the backend (e.g. the dms-ai sidecar) would take its own host down.
137
+ refuse_if_hosting_self() {
138
+ local hosting
139
+ hosting="$(printf '%s\n' "$@" | grep -Fx -f <(self_ancestors) | head -1)"
140
+ [ -z "$hosting" ] && return 0
141
+ log "error: this shell runs inside the dev servers it was asked to stop (pid $hosting)."
142
+ log " An agent running inside the DMS must not stop or restart them: rely on hot reload."
143
+ return 1
144
+ }
145
+
146
+ alive_among() {
147
+ local pid
148
+ for pid in "$@"; do is_alive "$pid" && echo "$pid"; done
149
+ }
150
+
151
+ signal_and_wait() {
152
+ local deadline survivors
153
+ kill -TERM "$@" 2>/dev/null
154
+ deadline=$(($(date +%s) + STOP_GRACE))
155
+ survivors="$(alive_among "$@")"
156
+ while [ -n "$survivors" ] && [ "$(date +%s)" -lt "$deadline" ]; do
157
+ sleep 1
158
+ survivors="$(alive_among "$@")"
159
+ done
160
+ [ -n "$survivors" ] || return 0
161
+ log "still running after ${STOP_GRACE}s, sending SIGKILL to: $(echo "$survivors" | tr '\n' ' ')"
162
+ # shellcheck disable=SC2086 # one pid per word
163
+ kill -KILL $survivors 2>/dev/null
164
+ sleep 1
165
+ # shellcheck disable=SC2086
166
+ survivors="$(alive_among $survivors)"
167
+ [ -z "$survivors" ] && return 0
168
+ log "error: could not stop: $(echo "$survivors" | tr '\n' ' ')"
169
+ return 1
170
+ }
171
+
172
+ stop_servers() {
173
+ require_dms_back_dir stop
174
+ log "stopping dev servers of $PROJECT_ROOT…"
175
+ local targets
176
+ targets="$(project_pids)"
177
+ if [ -z "$targets" ]; then
178
+ rm -f "$PID_FILE"
179
+ log "nothing running."
180
+ return 0
64
181
  fi
182
+ # shellcheck disable=SC2086 # one pid per word
183
+ refuse_if_hosting_self $targets || exit 3
184
+ # shellcheck disable=SC2086
185
+ signal_and_wait $targets || return 1
186
+ rm -f "$PID_FILE"
65
187
  log "stopped."
66
188
  }
67
189
 
190
+ # Detaches the command into its own session so it survives this shell, and records its
191
+ # pid. `exec` keeps that pid stable through setsid/nohup down to the command itself.
192
+ launch_detached() {
193
+ local name="$1" cmd="$2" log_file="$3"
194
+ if command -v setsid >/dev/null 2>&1; then
195
+ # shellcheck disable=SC2086 # word-splitting of the command is intentional
196
+ (cd "$PROJECT_ROOT" && exec setsid nohup $cmd >> "$log_file" 2>&1 < /dev/null) &
197
+ elif command -v perl >/dev/null 2>&1; then
198
+ # macOS ships no setsid(1); perl's POSIX::setsid does the same before exec.
199
+ # shellcheck disable=SC2086
200
+ (cd "$PROJECT_ROOT" && exec perl -MPOSIX -e 'POSIX::setsid(); exec { $ARGV[0] } @ARGV or die "exec $ARGV[0]: $!\n"' \
201
+ nohup $cmd >> "$log_file" 2>&1 < /dev/null) &
202
+ else
203
+ # shellcheck disable=SC2086
204
+ (cd "$PROJECT_ROOT" && exec nohup $cmd >> "$log_file" 2>&1 < /dev/null) &
205
+ fi
206
+ LAUNCHED_PID=$!
207
+ mkdir -p "$(dirname "$PID_FILE")"
208
+ echo "$name $LAUNCHED_PID" >> "$PID_FILE"
209
+ }
210
+
211
+ fail_start() {
212
+ local label="$1" log_file="$2" reason="$3"
213
+ log "FAILED TO START: $label — $reason. Tail of $log_file:"
214
+ tail -40 "$log_file" | sed 's/^/ /'
215
+ }
216
+
68
217
  wait_for() {
69
- local log_file="$1" pattern="$2" label="$3" error_pattern="$4" proc_pattern="$5" started
218
+ local log_file="$1" pattern="$2" label="$3" error_pattern="$4" pid="$5" started
70
219
  started=$(date +%s)
71
- # Grace period: pnpm forks/execs into the actual dev process, so pgrep can
72
- # legitimately return nothing for the first second or two. Don't treat that
73
- # as a failure.
74
- local grace_until=$((started + 8))
75
220
  while ! grep -qE "$pattern" "$log_file" 2>/dev/null; do
76
221
  if grep -qE "$error_pattern" "$log_file" 2>/dev/null; then
77
- log "FAILED TO START: $label — fatal error detected in $log_file:"
222
+ log "fatal error detected in $log_file:"
78
223
  grep -nE "$error_pattern" "$log_file" | head -5 | sed 's/^/ /'
79
- log "tail of $log_file:"
80
- tail -40 "$log_file" | sed 's/^/ /'
224
+ fail_start "$label" "$log_file" "fatal error"
81
225
  return 1
82
226
  fi
83
- if [ "$(date +%s)" -gt "$grace_until" ] && ! pgrep -f "$proc_pattern" >/dev/null 2>&1; then
84
- log "FAILED TO START: $label — process exited before becoming ready. Tail of $log_file:"
85
- tail -40 "$log_file" | sed 's/^/ /'
227
+ if ! is_alive "$pid"; then
228
+ fail_start "$label" "$log_file" "process $pid exited before becoming ready"
86
229
  return 1
87
230
  fi
88
231
  if [ $(($(date +%s) - started)) -gt "$WAIT_TIMEOUT" ]; then
89
- log "FAILED TO START: $label — timed out after ${WAIT_TIMEOUT}s waiting for readiness pattern. Tail of $log_file:"
90
- tail -40 "$log_file" | sed 's/^/ /'
232
+ fail_start "$label" "$log_file" "timed out after ${WAIT_TIMEOUT}s waiting for readiness pattern"
91
233
  return 1
92
234
  fi
93
- sleep 2
235
+ sleep 1
236
+ done
237
+ }
238
+
239
+ # Prints "<pid> <url>" for the api endpoint a dev.json records.
240
+ read_dev_json() {
241
+ command -v node >/dev/null 2>&1 || return 0
242
+ # shellcheck disable=SC2016 # a JavaScript template literal, not a shell expansion
243
+ node -e '
244
+ const dev = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
245
+ const servers = dev.servers ?? {};
246
+ const endpoint = (servers.api ?? Object.values(servers)[0])?.endpoints?.[0];
247
+ if (!endpoint) process.exit(0);
248
+ const wildcard = ["0.0.0.0", "::", ""].includes(endpoint.host ?? "");
249
+ const host = wildcard ? "localhost" : endpoint.host;
250
+ console.log(`${dev.pid} ${endpoint.protocol ?? "http"}://${host}:${endpoint.port}`);
251
+ ' "$1" 2>/dev/null
252
+ }
253
+
254
+ # The project may live in DMS_BACK_DIR or one level below (`ajs project dev -p playground`).
255
+ # A dev.json outlives its backend, so only one whose writer is alive and ours is trusted.
256
+ dev_json_url() {
257
+ local file pid url
258
+ for file in "$PROJECT_ROOT/.antelope/dev.json" "$PROJECT_ROOT"/*/.antelope/dev.json; do
259
+ [ -f "$file" ] || continue
260
+ read -r pid url <<< "$(read_dev_json "$file")"
261
+ if [ -n "${url:-}" ] && is_alive "$pid" && is_in_project "$pid"; then
262
+ echo "$url"
263
+ return
264
+ fi
265
+ done
266
+ }
267
+
268
+ backend_url() {
269
+ local url deadline
270
+ deadline=$(($(date +%s) + DEV_JSON_WAIT))
271
+ url="$(dev_json_url)"
272
+ while [ -z "$url" ] && [ "$(date +%s)" -lt "$deadline" ]; do
273
+ sleep 1
274
+ url="$(dev_json_url)"
94
275
  done
95
- log "$label ready."
276
+ [ -n "$url" ] || url="$(grep -oE "$BACK_READY_PATTERN on https?://[^ /]+" "$BACK_LOG" | tail -1 | awk '{print $NF}')"
277
+ echo "${url:-$DEFAULT_BACK_URL (assumed: no .antelope/dev.json found)}"
278
+ }
279
+
280
+ frontend_url() {
281
+ local url
282
+ url="$(grep -oE '(Server ready on|Local:)[[:space:]]+https?://[^ /]+' "$FRONT_LOG" | tail -1 | awk '{print $NF}')"
283
+ echo "${url:-$DEFAULT_FRONT_URL (assumed: no ready line with a URL)}"
96
284
  }
97
285
 
98
286
  start_backend() {
99
- require_dms_back_dir start
100
- log "starting backend in $DMS_BACK_DIR (logs → $BACK_LOG)…"
287
+ log "starting backend in $PROJECT_ROOT (logs → $BACK_LOG)…"
101
288
  : > "$BACK_LOG"
102
- # shellcheck disable=SC2086 # word-splitting of the command is intentional
103
- ( cd "$DMS_BACK_DIR" && setsid nohup $BACK_CMD >> "$BACK_LOG" 2>&1 < /dev/null & )
104
- wait_for "$BACK_LOG" "$BACK_READY_PATTERN" "backend (http://localhost:5010)" \
105
- "$BACK_ERROR_PATTERN" 'pnpm.*\bdev\b|antelope-runner|ajs project (run|dev)'
289
+ launch_detached backend "$BACK_CMD" "$BACK_LOG"
290
+ wait_for "$BACK_LOG" "$BACK_READY_PATTERN" "backend" "$BACK_ERROR_PATTERN" "$LAUNCHED_PID" || return 1
291
+ log "backend ready on $(backend_url)."
106
292
  }
107
293
 
108
294
  start_frontend() {
109
- require_dms_back_dir start
110
- log "starting frontend in $DMS_BACK_DIR (logs → $FRONT_LOG)…"
295
+ log "starting frontend in $PROJECT_ROOT (logs → $FRONT_LOG)…"
111
296
  : > "$FRONT_LOG"
112
- # shellcheck disable=SC2086 # word-splitting of the command is intentional
113
- ( cd "$DMS_BACK_DIR" && setsid nohup $FRONT_CMD >> "$FRONT_LOG" 2>&1 < /dev/null & )
114
- wait_for "$FRONT_LOG" "$FRONT_READY_PATTERN" "frontend (http://localhost:3001)" \
115
- "$FRONT_ERROR_PATTERN" 'pnpm.*frontend:dev|(^|[[:space:]/])(ajs dms|ajs-dms) dev|dms-frontend/dist/index\.js dev'
297
+ launch_detached frontend "$FRONT_CMD" "$FRONT_LOG"
298
+ wait_for "$FRONT_LOG" "$FRONT_READY_PATTERN" "frontend" "$FRONT_ERROR_PATTERN" "$LAUNCHED_PID" || return 1
299
+ log "frontend ready on $(frontend_url)."
300
+ }
301
+
302
+ start_servers() {
303
+ local verb="$1"
304
+ rm -f "$PID_FILE"
305
+ if ! start_backend; then
306
+ log "aborting $verb: backend failed to start."
307
+ exit 1
308
+ fi
309
+ if ! start_frontend; then
310
+ log "aborting $verb: frontend failed to start."
311
+ exit 1
312
+ fi
116
313
  }
117
314
 
118
315
  status() {
119
- log "running processes:"
120
- if ! pgrep -af "$PROC_PATTERN"; then
316
+ require_dms_back_dir status
317
+ log "dev processes of $PROJECT_ROOT:"
318
+ local targets
319
+ targets="$(project_pids)"
320
+ if [ -z "$targets" ]; then
121
321
  echo " (none)"
322
+ return
122
323
  fi
324
+ # shellcheck disable=SC2086 # one pid per word
325
+ ps -o pid=,args= -p "$(echo $targets | tr ' ' ',')" | sed 's/^/ /'
123
326
  }
124
327
 
125
328
  usage() {
@@ -127,24 +330,26 @@ usage() {
127
330
  Usage: $(basename "$0") <command>
128
331
 
129
332
  Commands:
130
- restart Stop both servers, start backend, wait, start frontend, wait.
131
- Use this after ANY edit to the DMS backend or any DMS module's sources.
333
+ restart Stop this project's servers, start backend, wait, start frontend, wait.
334
+ Only when hot reload cannot apply a change, and never from an agent
335
+ running inside the DMS: it would stop its own host.
132
336
  start Same as restart but does not stop first (errors if already running).
133
- stop Kill both servers and any zombie processes.
134
- status Show running DMS dev processes.
337
+ stop Stop this project's servers: SIGTERM, then SIGKILL after the grace period.
338
+ status Show this project's dev processes.
135
339
  back-log [N] Tail the last N (default 60) lines of the backend log.
136
340
  front-log [N] Tail the last N (default 60) lines of the frontend log.
137
341
 
138
342
  Environment:
139
- DMS_BACK_DIR REQUIRED for start/restart. Absolute path to the DMS backend package
140
- directory of the AntelopeJS DMS project to run.
141
- DMS_BACK_CMD Command to start the backend, run in DMS_BACK_DIR (default: "pnpm dev",
142
- the playground convention for "ajs project dev -w" / legacy "run -w").
143
- DMS_FRONT_CMD Command to start the frontend, run in DMS_BACK_DIR (default:
144
- "pnpm frontend:dev", the playground convention for "ajs dms dev").
145
- DMS_BACK_LOG (default: /tmp/dms-back.log)
146
- DMS_FRONT_LOG (default: /tmp/dms-front.log)
147
- DMS_DEV_TIMEOUT (default: 180 seconds per server)
343
+ DMS_BACK_DIR REQUIRED for start/restart/stop/status. Absolute path to the DMS backend
344
+ package directory of the AntelopeJS DMS project to run.
345
+ DMS_BACK_CMD Command to start the backend, run in DMS_BACK_DIR (default: "pnpm dev",
346
+ the playground convention for "ajs project dev -w" / legacy "run -w").
347
+ DMS_FRONT_CMD Command to start the frontend, run in DMS_BACK_DIR (default:
348
+ "pnpm frontend:dev", the playground convention for "ajs dms dev").
349
+ DMS_BACK_LOG (default: /tmp/dms-back.log)
350
+ DMS_FRONT_LOG (default: /tmp/dms-front.log)
351
+ DMS_DEV_TIMEOUT (default: 180 seconds per server)
352
+ DMS_DEV_STOP_GRACE (default: 10 seconds between SIGTERM and SIGKILL)
148
353
  EOF
149
354
  }
150
355
 
@@ -152,29 +357,16 @@ cmd="${1:-}"
152
357
  case "$cmd" in
153
358
  restart)
154
359
  stop_servers || true
155
- if ! start_backend; then
156
- log "aborting restart: backend failed to start."
157
- exit 1
158
- fi
159
- if ! start_frontend; then
160
- log "aborting restart: frontend failed to start."
161
- exit 1
162
- fi
360
+ start_servers restart
163
361
  ;;
164
362
  start)
165
- if pgrep -af "$PROC_PATTERN" >/dev/null 2>&1; then
363
+ require_dms_back_dir start
364
+ if [ -n "$(project_pids)" ]; then
166
365
  log "error: servers already running. Use restart, or stop first."
167
366
  status
168
367
  exit 1
169
368
  fi
170
- if ! start_backend; then
171
- log "aborting start: backend failed to start."
172
- exit 1
173
- fi
174
- if ! start_frontend; then
175
- log "aborting start: frontend failed to start."
176
- exit 1
177
- fi
369
+ start_servers start
178
370
  ;;
179
371
  stop)
180
372
  stop_servers
@@ -185,12 +377,12 @@ case "$cmd" in
185
377
  back-log)
186
378
  tail -n "${2:-60}" "$BACK_LOG"
187
379
  ;;
188
- front-log)
189
- tail -n "${2:-60}" "$FRONT_LOG"
190
- ;;
191
380
  ""|-h|--help|help)
192
381
  usage
193
382
  ;;
383
+ front-log)
384
+ tail -n "${2:-60}" "$FRONT_LOG"
385
+ ;;
194
386
  *)
195
387
  usage
196
388
  exit 2