handle method

  1. @override
Future<int> handle(
  1. ArtisanContext ctx
)
override

Execute the command. Returns the process exit code.

Implementation

@override
Future<int> handle(ArtisanContext ctx) async {
  final state = await StateFile.read();
  if (state == null) {
    ctx.output.writeln('No state file; nothing to stop.');
    return 0;
  }

  // The legacy pointer describes whichever app started last, so without
  // this a stop from a project with nothing up reaches across and kills a
  // sibling's running app, reporting a clean success while doing it.
  final String? ownership = sessionOwnershipError(
    state: state,
    workingDirectory: Directory.current.path,
    explicitStatePath: StateFile.explicitPath(),
  );
  if (ownership != null) {
    ctx.output.error(ownership);
    return 1;
  }

  // 1. flutter run process.
  final pid = state['pid'] as int?;
  if (pid != null) {
    try {
      stopKillFunction(pid, ProcessSignal.sigterm);
      ctx.output.success('Sent SIGTERM to pid=$pid.');
    } catch (e) {
      ctx.output.warning('SIGTERM failed: $e (continuing).');
    }
  }

  // 2. FIFO stdin holder (the `sleep infinity > fifo` background process
  //    that keeps the pipe's write end open across reload/hot-restart calls).
  final holderPid = state['stdinHolderPid'] as int?;
  if (holderPid != null) {
    try {
      stopKillFunction(holderPid, ProcessSignal.sigterm);
    } catch (_) {
      // Holder may already be gone; safe to ignore.
    }
  }

  // 3. Named pipe file. Safe to delete even when readers/writers are
  //    still attached — POSIX unlinks the inode, fds stay valid until
  //    closed naturally.
  final pipePath = state['stdinPipe'] as String?;
  if (pipePath != null) {
    try {
      final pipe = File(pipePath);
      if (pipe.existsSync()) await pipe.delete();
    } catch (_) {
      // Best-effort cleanup; don't block stop on FIFO removal failure.
    }
  }

  // 4. Chrome process + tmp profile dir (CDP mode only; absent in non-CDP
  //    runs). Mirrors the SIGTERM-grace-SIGKILL-rm pattern from
  //    fluttersdk_dusk/lib/src/utils/chrome_reaper.dart without importing
  //    that package (no cross-package dep on a downstream plugin).
  final chromePid = state['chromePid'] as int?;
  if (chromePid != null) {
    await _reapChrome(ctx, chromePid, state['tmpProfileDir'] as String?);
  }

  await StateFile.delete();
  ctx.output.success('state.json removed.');
  return 0;
}