handleUpdateRuns function

Future<void> handleUpdateRuns(
  1. Map<String, dynamic> args
)

Handle oracular update runs [--port NNNN] [--output-dir <path>].

Adds (or refreshes) IntelliJ / Android Studio run configurations for an Oracular project, in two flavors:

  1. Deploy All at the project root — oracular deploy all. Always emitted (template-agnostic). Lives in <output-dir>/.idea/runConfigurations/Deploy_All.run.xml.

  2. Serve / Build / Killall :PORT for every Jaspr web package found under output-dir. Lives in <package>/.idea/runConfigurations/. Skipped if no Jaspr packages are detected.

Why this command exists: a project scaffolded by Oracular < 3.4.0 won't have these configs, and the wizard's full rebuild (oracular rebuild) is too heavy-handed when the user only wants the IDE wiring back. This command is the fast, surgical update.

Detection strategy for Jaspr packages (in order):

  1. Load <output-dir>/config/setup_config.env if it exists. If the saved template is a Jaspr template, use SetupConfig.webPackageName as the canonical target — even if other Jaspr-like packages coexist in the tree.
  2. Otherwise (no setup_config.env, or non-Jaspr template), scan output-dir one level deep for any folder whose pubspec.yaml declares a jaspr: dependency. Each matching folder becomes a target.
  3. As a last resort, if output-dir itself is a Jaspr package, treat it as the single target.

All configs are idempotent on re-run.

Implementation

Future<void> handleUpdateRuns(Map<String, dynamic> args) async {
  // darted_cli has an internal bug: it strips hyphens from arg names
  // when parsing input, but then re-builds `--<name>` to match against
  // the original input — dropping any arg whose name contains a hyphen.
  // Workaround: name our flags without hyphens (`dir` instead of
  // `output-dir`) and accept multiple aliases below for robustness.
  final String? rawDir = (args['dir'] as String?) ??
      (args['d'] as String?) ??
      (args['output-dir'] as String?) ??
      (args['outputdir'] as String?);
  final String outputDir = (rawDir != null && rawDir.trim().isNotEmpty)
      ? rawDir.trim()
      : Directory.current.path;
  final int port = _parsePort(args['port'] ?? args['p']);

  print('');
  UserPrompt.printDivider(title: 'Update IntelliJ run configurations');

  // ── Step 1: project-root "Deploy All" config ──────────────────────────
  //
  // Template-agnostic — emit unconditionally for any directory the user
  // points us at. Even if the dir isn't an Oracular project, the user
  // may want a `oracular deploy all` button (e.g., they're about to run
  // `oracular create`).
  int deployWrittenCount = 0;
  try {
    final List<String> deployWritten =
        await IntellijRunConfigGenerator.generateDeploy(
      projectDir: outputDir,
    );
    deployWrittenCount = deployWritten.length;
    if (deployWritten.isNotEmpty) {
      info(
        '  ./ + ${p.basename(deployWritten.first)} '
        '(oracular deploy all)',
      );
    }
  } catch (e) {
    warn('Failed to write project-level Deploy All config: $e');
  }

  // ── Step 2: per-Jaspr-package Serve / Build / Killall configs ─────────
  final List<_JasprTarget> targets = await _resolveTargets(outputDir);
  if (targets.isEmpty) {
    if (deployWrittenCount == 0) {
      // Neither Deploy nor Jaspr configs were emitted — nothing to do.
      // This is genuinely unexpected because the Deploy generator is
      // unconditional, so log a hint rather than a hard failure.
      error(
        'No run configs emitted for $outputDir.',
      );
      return;
    }
    print('');
    success(
      'Wrote project-level "Deploy All" run config '
      '(no Jaspr packages found — Serve/Build/Killall skipped).',
    );
    print('');
    UserPrompt.printList(<String>[
      'Open the project in IntelliJ / Android Studio.',
      'You should see "Deploy All" in the run configurations dropdown',
      '(top-right corner). Click ▶ to run `oracular deploy all`.',
    ]);
    return;
  }

  info(
    'Found ${targets.length} Jaspr package'
    '${targets.length == 1 ? '' : 's'} — writing Serve/Build/Killall '
    'configs (port: $port)...',
  );

  int totalWritten = 0;
  int totalPruned = 0;
  for (final _JasprTarget target in targets) {
    info('  ${target.relativePath}/');
    final List<String> deleted =
        await IntellijRunConfigGenerator.pruneStaleKillallConfigs(
      packageDir: target.absolutePath,
      currentPort: port,
    );
    if (deleted.isNotEmpty) {
      verbose(
        '    Pruned ${deleted.length} stale Killall config(s) for '
        'previous port(s).',
      );
      totalPruned += deleted.length;
    }
    final List<String> written = await IntellijRunConfigGenerator.generate(
      packageDir: target.absolutePath,
      port: port,
    );
    for (final String f in written) {
      verbose('    + ${p.basename(f)}');
    }
    totalWritten += written.length;
  }

  print('');
  success(
    'Updated ${totalWritten + deployWrittenCount} run config'
    '${(totalWritten + deployWrittenCount) == 1 ? '' : 's'} '
    '($deployWrittenCount project-level Deploy + $totalWritten Jaspr '
    'across ${targets.length} package'
    '${targets.length == 1 ? '' : 's'})'
    '${totalPruned > 0 ? ' (pruned $totalPruned stale)' : ''}.',
  );
  print('');
  UserPrompt.printList(<String>[
    'Open the project in IntelliJ / Android Studio.',
    'You should see "Deploy All" plus "Serve", "Build", and ',
    '"Killall :$port" in the run configurations dropdown (top-right).',
    'Re-run with --port NNNN to change the Jaspr port; the killall',
    'config will auto-prune the previous port file.',
  ]);
}