Skip to content

feat: unified cross-platform dev/prod launcher with automatic venv setup - #1401

Open
kaihere14 wants to merge 3 commits into
AOSSIE-Org:mainfrom
kaihere14:feat/unified-run-script-cross-platform
Open

feat: unified cross-platform dev/prod launcher with automatic venv setup#1401
kaihere14 wants to merge 3 commits into
AOSSIE-Org:mainfrom
kaihere14:feat/unified-run-script-cross-platform

Conversation

@kaihere14

@kaihere14 kaihere14 commented Jul 21, 2026

Copy link
Copy Markdown

Addressed Issues:

Fixes #1395

Summary:

Adds scripts/run.js and scripts/run.sh — a single command (node scripts/run.js or bash scripts/run.sh, --prod for production mode) that starts the backend, sync-microservice, and Tauri frontend together. Replaces juggling three terminals and manually creating/activating two separate Python virtual environments.

On a completely fresh clone, running the launcher now takes you from zero to all three services running with no manual setup steps at all — venvs are created automatically if missing, dependencies are installed automatically, and everything starts together.

What it does:

One-command orchestration

  • Starts backend, sync-microservice, and frontend from a single entrypoint, in both dev and --prod modes.
  • Node and bash versions are functionally equivalent so it works the same way cross-platform.

Automatic virtual environment setup

  • Checks whether backend/.venv and sync-microservice/.sync-venv exist before starting each service.
  • If missing, creates them automatically (python -m venv <path>) with a clear console message, instead of failing or requiring manual setup first.
  • Each service's python.exe/python binary is resolved directly from its venv's current location every run — no source <venv>/activate, since that script bakes in an absolute path at creation time and silently falls through to system Python with no error if the venv folder is ever renamed or moved.
  • Each service runs python -m pip install -r requirements.txt before starting, so it never comes up against a stale or incomplete venv.

Windows-specific bugs found and fixed during testing:

  1. Buffered/missing output — Node pipes child stdio, so Python defaulted to full buffering instead of line-buffering, making it look like backend/sync had silently failed to start when they just hadn't flushed output yet. Fixed with PYTHONUNBUFFERED=1.
  2. Corrupted pip-generated launcher .exe stubsuvicorn.exe/fastapi.exe/pip.exe console-script launchers can fail instantly with no output (Fatal error in launcher: Unable to find an appended archive), and the error is invisible with no console attached (spawned via Node, or under Git Bash). Fixed by invoking everything as python -m <module> instead of relying on generated launcher stubs.
  3. fastapi dev's startup banner crashing on Windowsrich/rich_toolkit falls back to a legacy Windows console writer (hardcoded cp1252 encoding) with no real console attached, and the 🚀 emoji in its own banner can't be encoded, crashing the process before it binds to a port. Fixed with PYTHONUTF8=1.
  4. Fragile venv activation in run.sh — previously used source <venv>/activate; replaced with fresh bin-dir resolution each run (see above).

Backend and sync-microservice intentionally diverge in dev mode: backend uses python -m uvicorn --reload (its pinned fastapi-cli==0.0.3 predates python -m fastapi support), while sync-microservice uses python -m fastapi dev (its fastapi-cli==0.0.8 supports it).

Screenshots/Recordings:

N/A — this PR only touches developer tooling (scripts/run.js, scripts/run.sh), no UI changes. Verified end-to-end locally on Windows instead:

  • Deleted both .venv and .sync-venv and ran node scripts/run.js from a clean state — both venvs were created automatically, dependencies installed, and all three services (backend :52123, sync-microservice :52124, Tauri frontend) came up together and exchanged live requests successfully (/health, /models/status, /folders/status all returning 200).
  • Re-ran with existing venvs present to confirm no unnecessary recreation/reinstall overhead.

AI Usage Disclosure:

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact. AI slop is strongly discouraged and may lead to banning and blocking. Do not spam our repos with AI slop.

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have read the AI Usage Policy and this PR complies with this policy. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: Claude Code (Claude Sonnet 5) — used to implement and debug run.js/run.sh, including the venv auto-creation logic, and to verify the full fresh-clone-to-running flow locally on Windows.

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions
  • If applicable, I have made corresponding changes or additions to the documentation
  • If applicable, I have made corresponding changes or additions to tests
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.
  • I have filled this PR template completely and carefully, and I understand that my PR may be closed without review otherwise.

Summary by CodeRabbit

  • New Features
    • Added cross-platform launchers for starting the backend, synchronization service, and frontend together.
    • Added development and production launch modes, including configurable backend workers.
    • Added automatic virtual environment setup, dependency installation, and required-tool validation.
    • Added clear, prefixed service output and coordinated shutdown handling across platforms.

@github-actions github-actions Bot added the scripts Changes related to scripts label Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds cross-platform JavaScript and shell launchers that validate prerequisites, prepare Python virtual environments, install service dependencies, and run PictoPy’s backend, sync-microservice, and Tauri frontend in development or production mode.

Changes

Unified service launchers

Layer / File(s) Summary
Mode selection and preflight validation
scripts/run.js, scripts/run.sh
Selects development or production mode, detects the operating system, resolves service directories, and validates required tools and directories.
Virtual environment and dependency setup
scripts/run.js, scripts/run.sh
Resolves or creates service-specific virtual environments, configures Python execution, and installs each service’s requirements before startup.
Process lifecycle management
scripts/run.js, scripts/run.sh
Starts tracked child processes with prefixed output and terminates them through signal handlers using platform-specific cleanup behavior.
Backend, sync, and frontend startup
scripts/run.js, scripts/run.sh
Runs the backend on port 52123, sync-microservice on port 52124, and Tauri frontend with mode-specific commands and worker settings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Launcher
  participant Backend
  participant SyncMicroservice
  participant Frontend
  Launcher->>Backend: prepare venv and install requirements
  Launcher->>SyncMicroservice: prepare venv and install requirements
  Launcher->>Backend: start uvicorn on port 52123
  Launcher->>SyncMicroservice: start uvicorn or fastapi dev on port 52124
  Launcher->>Frontend: run npm run tauri dev
  Launcher->>Backend: terminate on shutdown
  Launcher->>SyncMicroservice: terminate on shutdown
  Launcher->>Frontend: terminate on shutdown
Loading

Suggested labels: TypeScript/JavaScript

Poem

I’m a bunny with launchers, three services in flight,
Backend and sync hop, frontend shines bright.
Venvs bloom softly, dependencies align,
Signals say “stop,” and the cleanup is fine.
One little command makes the whole garden run.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive Most requirements are covered, but the summary doesn't confirm the required backend and sync ports from #1395. Confirm the launcher binds backend to 52123 and sync-microservice to 52124, or update the summary/diff with that evidence.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the new unified launcher and automatic venv setup.
Out of Scope Changes check ✅ Passed The changes stay focused on launcher scripts and process orchestration, with no obvious unrelated additions.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/run.js`:
- Around line 244-331: Refactor the service startup flow so BACKEND, SYNC, and
FRONTEND setup and launch begin concurrently, matching the backgrounded subshell
behavior in run.sh. Replace blocking setup calls such as createVenv and
pipInstall with asynchronous equivalents, update startBackend, startSync, and
startFrontend to await their work, and start all three together from the
top-level entrypoint while preserving each service’s existing setup,
environment, and failure behavior.
- Around line 201-213: Update cleanup() to wait for all child processes to emit
their exit events after killChild(child) before terminating the launcher.
Preserve the shuttingDown guard and existing shutdown messaging, and only call
process.exit(0) once every child has finished or the established child-wait
mechanism completes.

In `@scripts/run.sh`:
- Around line 155-217: Update cleanup() to tolerate already-exited PIDs by
guarding each kill and ensure wait still runs for all tracked processes. In
start_backend(), start_sync(), and start_frontend(), replace the background
pipeline with process substitution so $! records the service subshell PID while
preserving prefixed sed output, allowing cleanup to terminate the actual
services.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 57b6ce10-2b9a-433a-a1f6-1640b0f9d3e9

📥 Commits

Reviewing files that changed from the base of the PR and between f465155 and f6a0057.

📒 Files selected for processing (3)
  • backend/scripts/reset_database.py
  • scripts/run.js
  • scripts/run.sh

Comment thread scripts/run.js
Comment on lines +201 to +213
function cleanup() {
if (shuttingDown) return;
shuttingDown = true;
console.log('');
console.log('Shutting down all services...');
for (const child of children) {
killChild(child);
}
process.exit(0);
}
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

cleanup() exits before children actually terminate.

Unlike scripts/run.sh's cleanup(), which calls wait after kill so backgrounded services finish shutting down before the script exits, this cleanup() calls process.exit(0) immediately after issuing SIGTERM/taskkill to each child, without waiting for their exit events. Signals are delivered asynchronously, so the launcher can return control to the terminal while backend/sync/frontend are still mid-shutdown (e.g., uvicorn's reload subprocess), producing interleaved or orphaned output after the prompt returns.

♻️ Suggested fix: wait for children before exiting
 function cleanup() {
   if (shuttingDown) return;
   shuttingDown = true;
   console.log('');
   console.log('Shutting down all services...');
-  for (const child of children) {
-    killChild(child);
-  }
-  process.exit(0);
+  const exits = children.map(
+    (child) => new Promise((resolve) => child.once('exit', resolve)),
+  );
+  for (const child of children) {
+    killChild(child);
+  }
+  Promise.all(exits).then(() => process.exit(0));
+  setTimeout(() => process.exit(0), 5000); // safety timeout
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function cleanup() {
if (shuttingDown) return;
shuttingDown = true;
console.log('');
console.log('Shutting down all services...');
for (const child of children) {
killChild(child);
}
process.exit(0);
}
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
function cleanup() {
if (shuttingDown) return;
shuttingDown = true;
console.log('');
console.log('Shutting down all services...');
const exits = children.map(
(child) => new Promise((resolve) => child.once('exit', resolve)),
);
for (const child of children) {
killChild(child);
}
Promise.all(exits).then(() => process.exit(0));
setTimeout(() => process.exit(0), 5000); // safety timeout
}
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/run.js` around lines 201 - 213, Update cleanup() to wait for all
child processes to emit their exit events after killChild(child) before
terminating the launcher. Preserve the shuttingDown guard and existing shutdown
messaging, and only call process.exit(0) once every child has finished or the
established child-wait mechanism completes.

Comment thread scripts/run.js
Comment thread scripts/run.sh
Comment on lines +155 to +217
PIDS=()

cleanup() {
echo ""
echo "Shutting down all services..."
for pid in "${PIDS[@]}"; do
kill "$pid" 2>/dev/null
done
wait 2>/dev/null
exit 0
}
trap cleanup INT TERM

start_backend() {
(
echo "[BACKEND] Starting... ${BACKEND_DIR}"
cd "$BACKEND_DIR"
bin_dir=$(resolve_venv "BACKEND" "$BACKEND_DIR" ".env" "venv") || exit 1
export PATH="$bin_dir:$PATH"
pip_install "BACKEND" || exit 1
if [[ "$MODE" == "prod" ]]; then
echo "[BACKEND] Starting in production mode on port 52123..."
python -m uvicorn main:app --host 0.0.0.0 --port 52123 --workers "${WORKERS:-1}"
else
# Backend pins fastapi-cli==0.0.3, which predates
# `fastapi.__main__` (no `python -m fastapi` support), unlike
# sync-microservice's newer fastapi-cli. Use uvicorn directly.
echo "[BACKEND] Starting in dev mode on port 52123..."
python -m uvicorn main:app --host 0.0.0.0 --port 52123 --reload
fi
) 2>&1 | sed -u 's/^/[BACKEND] /' &
PIDS+=($!)
}

start_sync() {
(
cd "$SYNC_DIR"
bin_dir=$(resolve_venv "SYNC" "$SYNC_DIR" ".sync-env" "venv") || exit 1
export PATH="$bin_dir:$PATH"
pip_install "SYNC" || exit 1
echo "[SYNC] Starting sync-microservice on port 52124..."
if [[ "$MODE" == "prod" ]]; then
python -m uvicorn main:app --host 0.0.0.0 --port 52124
else
python -m fastapi dev --port 52124
fi
) 2>&1 | sed -u 's/^/[SYNC] /' &
PIDS+=($!)
}

start_frontend() {
(
cd "$FRONTEND_DIR"
if [[ ! -d "node_modules" ]]; then
echo -e "${RED}[FRONTEND] node_modules not found.${NC}"
echo -e "${YELLOW}[FRONTEND] ${SETUP_HINT}${NC}"
exit 1
fi
echo "[FRONTEND] Starting Tauri dev..."
npm run tauri dev
) 2>&1 | sed -u 's/^/[FRONTEND] /' &
PIDS+=($!)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

cleanup() never actually terminates the tracked services — $! captures sed's PID, not the service subshell's, and set -e can abort the kill loop early.

Two compounding issues here:

  1. In each start_* function, ( ... ) 2>&1 | sed -u '...' & backgrounds a pipeline. Bash's $! after a background pipeline refers to the PID of the last command in the pipe (sed), not the subshell that actually runs resolve_venv/pip_install/python -m uvicorn/npm run tauri dev. So PIDS tracks the formatter process, not the service.
  2. cleanup() runs kill "$pid" 2>/dev/null in a loop under set -e (Line 13). If any PID already exited, kill returns non-zero, and since 2>/dev/null only suppresses stderr (not the exit status), errexit aborts the function mid-loop — skipping remaining kills and the trailing wait.

Net effect: on Ctrl+C/SIGTERM, the actual backend/sync/frontend processes may survive as orphans instead of being terminated, undermining the "terminate tracked services when interrupt or termination signals are received" behavior this PR is meant to deliver.

🐛 Suggested fix: track the real service PID via process substitution, and don't let `kill` trip errexit
 start_backend() {
     (
         echo "[BACKEND] Starting... ${BACKEND_DIR}"
         cd "$BACKEND_DIR"
         bin_dir=$(resolve_venv "BACKEND" "$BACKEND_DIR" ".env" "venv") || exit 1
         export PATH="$bin_dir:$PATH"
         pip_install "BACKEND" || exit 1
         if [[ "$MODE" == "prod" ]]; then
             echo "[BACKEND] Starting in production mode on port 52123..."
             python -m uvicorn main:app --host 0.0.0.0 --port 52123 --workers "${WORKERS:-1}"
         else
             echo "[BACKEND] Starting in dev mode on port 52123..."
             python -m uvicorn main:app --host 0.0.0.0 --port 52123 --reload
         fi
-    ) 2>&1 | sed -u 's/^/[BACKEND] /' &
+    ) > >(sed -u 's/^/[BACKEND] /') 2>&1 &
     PIDS+=($!)
 }

Apply the same > >(sed ...) 2>&1 & swap to start_sync and start_frontend.

 cleanup() {
     echo ""
     echo "Shutting down all services..."
     for pid in "${PIDS[@]}"; do
-        kill "$pid" 2>/dev/null
+        kill "$pid" 2>/dev/null || true
     done
     wait 2>/dev/null
     exit 0
 }
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 173-173: Modification of PATH is local (to subshell caused by (..) group).

(SC2030)


[info] 193-193: PATH was modified in a subshell. That change might be lost.

(SC2031)


[info] 193-193: PATH was modified in a subshell. That change might be lost.

(SC2031)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/run.sh` around lines 155 - 217, Update cleanup() to tolerate
already-exited PIDs by guarding each kill and ensure wait still runs for all
tracked processes. In start_backend(), start_sync(), and start_frontend(),
replace the background pipeline with process substitution so $! records the
service subshell PID while preserving prefixed sed output, allowing cleanup to
terminate the actual services.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undo this change and pull latest changes from 'main'. It'll automatically fix the lint error.

- Introduced  for Node.js-based launching, supporting both development and production modes, with Windows compatibility.
- Added  for bash-based launching, maintaining existing functionality while enhancing user guidance for command requirements and directory checks.
- Updated  to improve error message formatting for better readability.
- Removed legacy command verification and activation logic for virtual environments in both JavaScript and shell scripts.
- Introduced a more reliable method for resolving and using Python executables directly, enhancing compatibility across platforms.
- Updated dependency installation process to ensure services start only with complete environments, improving error handling and user guidance.
- Added automatic creation of Python virtual environments in both JavaScript and shell scripts if none exist, improving setup experience.
- Updated the venv resolution logic to include a prefix for better context in error messages.
- Enhanced error handling for Python command checks and virtual environment creation, ensuring clearer feedback for users.
@kaihere14
kaihere14 force-pushed the feat/unified-run-script-cross-platform branch from f6a0057 to 05d717e Compare July 26, 2026 19:08
@kaihere14

Copy link
Copy Markdown
Author

Hey @rohan-pandeyy done rebased onto latest main and reverted the reset_database.py change, so that file no longer shows any diff in this PR. Force-pushed the update. Let me know if there's anything else needed!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/run.js (1)

289-310: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Sync-microservice dev mode binds to localhost only, unlike every other mode/service.

fastapi dev defaults to "listen on the IP address 127.0.0.1, which is the IP for your machine to communicate with itself alone (localhost)", unlike fastapi run/uvicorn which "listen on the IP address 0.0.0.0, which means all the available IP addresses". Backend's dev/prod commands and sync's prod command all explicitly pass --host 0.0.0.0, but sync's dev command in both launchers omits --host, so it silently falls back to 127.0.0.1. This asymmetry can cause confusing "connection refused" behavior for anyone testing from another device/container while backend works fine.

  • scripts/run.js#L289-L310: add --host 0.0.0.0 to the fastapi dev args at Line 304, matching the prod branch.
  • scripts/run.sh#L189-L203: add --host 0.0.0.0 to the fastapi dev invocation at Line 199, matching the prod branch.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/run.js` around lines 289 - 310, Update the sync-microservice
development launch commands to explicitly bind to all interfaces by adding the
host argument with value 0.0.0.0 to the fastapi dev invocation in scripts/run.js
lines 289-310 and scripts/run.sh lines 189-203; leave the production commands
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/run.js`:
- Around line 159-182: Update spawnService so child startup failures decrement
aliveCount even when no exit event is emitted. Add a shared guarded settled
handler used by both the child error and exit listeners, ensuring aliveCount is
decremented only once and the existing zero-count shutdown behavior remains
intact.

---

Nitpick comments:
In `@scripts/run.js`:
- Around line 289-310: Update the sync-microservice development launch commands
to explicitly bind to all interfaces by adding the host argument with value
0.0.0.0 to the fastapi dev invocation in scripts/run.js lines 289-310 and
scripts/run.sh lines 189-203; leave the production commands unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8440705f-4679-474e-a4e0-5ff5317646f3

📥 Commits

Reviewing files that changed from the base of the PR and between f6a0057 and 05d717e.

📒 Files selected for processing (2)
  • scripts/run.js
  • scripts/run.sh

Comment thread scripts/run.js
Comment on lines +159 to +182
function spawnService(prefix, command, args, options) {
const child = spawn(command, args, {
stdio: ['ignore', 'pipe', 'pipe'],
...options,
});

prefixStream(child.stdout, prefix, process.stdout);
prefixStream(child.stderr, prefix, process.stdout); // merged like run.sh's 2>&1

child.on('error', (err) => {
process.stdout.write(`[${prefix}] Failed to start: ${err.message}\n`);
});

aliveCount++;
child.on('exit', () => {
aliveCount--;
if (aliveCount === 0 && !shuttingDown) {
process.exit(0);
}
});

children.push(child);
return child;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Repository files matching run.js/npm/sh scripts:\n'
git ls-files | rg '(^|/)(run\.js|package\.json|run\.sh|.*\.js$)' || true

printf '\nscripts/run.js line count and relevant section:\n'
wc -l scripts/run.js
sed -n '1,240p' scripts/run.js

printf '\nSearch for spawnService/exit handlers/shutdown:\n'
rg -n "spawnService|aliveCount|children\.push|shuttingDown|process\.exit|on\\('error'|on\\(\"error\"|on\\('exit'|on\\(\"exit\"" scripts/run.js

Repository: AOSSIE-Org/PictoPy

Length of output: 9838


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Behavioral probe for Node's child_process spawn error/exit semantics using only core Node modules.
# This does not execute repository code; it runs a small standalone Node probe in the sandbox.
node - <<'JS'
const { spawn } = require('node:child_process');

async function runProbe(command) {
  const child = spawn(command, [], { stdio: ['ignore', 'pipe', 'pipe'] });
  const events = [];
  let aliveCount = 1;

  child.on('error', (err) => {
    events.push(`error:${err.code}`);
  });
  child.on('exit', (code, signal) => {
    events.push(`exit:${String(code)}:${String(signal)}`);
    aliveCount--;
  });

  await new Promise((resolve) => {
    child.on('close', (code, signal) => {
      events.push(`close:${String(code)}:${String(signal)}`);
      resolve(events);
    });
    setTimeout(resolve, 200, `[timeout] events=${JSON.stringify(events)} aliveCount=${aliveCount}`);
  });
}

runProbe('totally-missing-missing-command').then((events) => {
  console.log(events.join(' | '));
  // The main test harness is the presence of Node's documented behavior:
  // if error happens before launch, exit may not fire and aliveCount can stay positive.
});

runProbe('node').then((events) => {
  console.log(events.join(' | '));
});
JS

Repository: AOSSIE-Org/PictoPy

Length of output: 329


🌐 Web query:

Node.js child_process spawn error event exit event may or may not fire after error occurred documentation

💡 Result:

According to official Node.js documentation, the 'exit' event in a child_process may or may not fire after an error has occurred [1][2]. Because of this behavior, it is explicitly recommended that developers guard against accidentally invoking handler functions multiple times when listening to both the 'exit' and 'error' events [1][2]. The 'error' event is emitted for various reasons, including when a process cannot be spawned, killed, or when sending a message fails [1][2]. In contrast, the 'exit' event is emitted only after a successfully spawned process ends [1][2]. If a process fails to spawn, the 'spawn' event is not emitted, and an 'error' event is emitted instead [1][2]. Conversely, if a process is successfully spawned but later encounters an error (such as failing to execute a command within a shell), the 'spawn' event will have fired, and subsequent events like 'exit' may still occur depending on the nature of the error [1][3].

Citations:


Decrement aliveCount on failed spawns as well.

When child_process.spawn fails before the process launches, it emits error but the exit event may not fire. A misspelled/stale venv command or other failed command can therefore leave spawnService with aliveCount > 0; once the other children terminate normally, the launcher will stay alive instead of auto-exiting. Track error and exit from a shared, guarded settled handler.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/run.js` around lines 159 - 182, Update spawnService so child startup
failures decrement aliveCount even when no exit event is emitted. Add a shared
guarded settled handler used by both the child error and exit listeners,
ensuring aliveCount is decremented only once and the existing zero-count
shutdown behavior remains intact.

@rohan-pandeyy

Copy link
Copy Markdown
Member

@kaihere14 I tested the scripts out but the application did not start with the command specified node scripts/run.js

image

Is there something wrong I did on my end?

@kaihere14

Copy link
Copy Markdown
Author

@kaihere14 I tested the scripts out but the application did not start with the command specified node scripts/run.js

image Is there something wrong I did on my end?

Hey @rohan-pandeyy , nothing wrong on your end this is expected behavior, not a bug. The script only handles the two Python venvs (backend, sync-microservice) automatically since those are self-contained. frontend/node_modules isn't something the launcher creates, since npm installs aren't as cheap/safe to trigger automatically the way python -m venv is.

Your log actually shows the script working correctly: [FRONTEND] node_modules not found → Run 'npm run setup' from the repo root. That's the preflight check catching a missing dependency and telling you exactly what to run, instead of letting npm run tauri dev fail with a confusing error.

Could you install the dependencies first , then try node scripts/run.js again? That should get all three services up.

The main target of run.sh/run.js was to run all three services together once dependencies are in place happy to add auto-setup for the frontend too if that's something you'd want, similar to how it already handles the Python venvs automatically.

@rohan-pandeyy

Copy link
Copy Markdown
Member

If that is the case, then the pull request should also update the documentation, which clearly lets the developers know what these steps are to use the "single command".

@kaihere14

Copy link
Copy Markdown
Author

If that is the case, then the pull request should also update the documentation, which clearly lets the developers know what these steps are to use the "single command".

Alright I will update the documentation and than ping you

@rohan-pandeyy

Copy link
Copy Markdown
Member

@kaihere14, the backend, sync-microservices, both always run pip install commands with node scripts/run.js... why is that the intended flow?

@rohan-pandeyy

Copy link
Copy Markdown
Member

Can you also check whether files are being indexed/ai-tagged? It's not happening on my end

@rohan-pandeyy

Copy link
Copy Markdown
Member

Can you also check whether files are being indexed/ai-tagged? It's not happening on my end

Stuck on "Indexing Folder"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scripts Changes related to scripts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat:Rewrite run.sh into a unified dev/prod launcher for backend, sync-microservice, and frontend

2 participants