Files
open-science/cli/config-root.test.ts
Ewen 3853172fd5 feat(cli): install a terminal launcher for the packaged app and fix daemon follow-ups (#211)
* feat(cli): install a terminal launcher for the packaged app and fix daemon follow-ups

Terminal launch after install:
- Add a "Command line tool" action (Settings -> General) that installs an
  `open-science` launcher into the user's PATH (~/.local/bin on macOS/Linux; a
  per-user bin added to the user PATH on Windows) with no elevation.
- The shim runs the bundled CLI via the app's own Electron in Node mode
  (ELECTRON_RUN_AS_NODE), so no separate Node install is needed.
- Ship cli/*.mjs as extraResources (outside the asar) so the launcher can reach
  them; new src/main/cli-install launcher + IPC, preload `cli` bridge, and web
  API map entry. Document the flow in the README.

#206 review follow-ups:
- stop: force-kill the whole process group with SIGKILL on POSIX (the daemon is
  detached) and verify the process actually died before removing state / reporting
  "stopped", instead of re-sending the ignored SIGTERM to the lone leader.
- tray: restore single-click-to-show on Windows (was double-click only) while
  keeping the right-click menu and headless menu-visibility fix.
- Strip ELECTRON_RUN_AS_NODE from the daemon spawn so the launched app starts as
  the GUI, not another Node process.
- locate-app: add ~/Applications on macOS; guard the package.json read when run
  from packaged resources; explicit flag->key parsing map.
- Tests: locate-app, isProcessAlive, cli/app constant-parity guard, launcher.

* fix(cli): reliable Windows PATH install, gate tray workaround to headless, cover lifecycle

Address PR review:
- P1: Windows PATH edit embedded the bin dir via `powershell -Command <script> -args <dir>`,
  which is unreliable — trailing `-args` can leave $args empty and write the wrong PATH. Build
  the script with the dir as a single-quoted PowerShell literal (quotes doubled) and drop -args.
  Derive the PATH separator from the target platform (not the host) so the on-PATH check is correct.
  Inject a command runner; add tests for the command build + install success/failure hint.
- P2: extract an injectable `terminateDaemon` and thread a deps bag through the CLI commands so the
  lifecycle is testable without real processes/network. New cli/lifecycle.test.ts covers graceful
  stop, force-kill escalation, "still alive → don't delete state / don't print stopped", status
  (running/down/--json exit code), and url.
- P3: gate the Windows tray right-click workaround to the headless case (electron#48982 is
  headless-only); the normal desktop app keeps the standard setContextMenu + single-click. Update
  the tray tests to assert both paths.
- Standards: add cli-install IPC handler tests (delegation + error swallowing) and a GeneralPanel
  render test for the install/uninstall command flow.

* fix(cli): single-quote POSIX launcher paths to fully escape shell metacharacters

CodeQL js/incomplete-string-escaping: the POSIX shim double-quoted paths and escaped only `"`,
leaving `\`, `$`, and backticks interpretable. Switch to single-quoting with the standard '\''
close-escape-reopen idiom, which safely quotes an arbitrary path (nothing is special inside single
quotes). Add a metacharacter test covering space/$/backtick/backslash/quote.
2026-07-19 07:17:07 -07:00

16 lines
816 B
TypeScript

import { describe, expect, it } from 'vitest'
import { TOKEN_FILE as APP_TOKEN_FILE } from '../src/main/web-service/auth'
import { WEB_SERVICE_STATE_FILE } from '../src/main/web-service/state-file'
import { STATE_FILE, TOKEN_FILE } from './config-root.mjs'
// The CLI (a standalone .mjs, separate module system) can't import the app's TypeScript constants at
// runtime, so it re-declares the state/token filenames. This guard fails loudly if the app renames one
// side without the other, which would otherwise silently break `open-science status/stop/url`.
describe('CLI config-root constants stay in lockstep with the app', () => {
it('uses the same state and token filenames as the web service', () => {
expect(STATE_FILE).toBe(WEB_SERVICE_STATE_FILE)
expect(TOKEN_FILE).toBe(APP_TOKEN_FILE)
})
})