{"version":3,"file":"vite-plugin.mjs","names":[],"sources":["../src/vite-plugin.ts"],"sourcesContent":["import type { Result } from 'tinyexec'\nimport type { Plugin } from 'vite'\n\nimport type { CapacitorPlatform } from './native'\n\nimport process from 'node:process'\n\nimport { resolve } from 'node:path'\n\nimport * as readline from 'node:readline'\n\nimport { x } from 'tinyexec'\n\nimport { parseCapacitorPlatform, pickServerUrl, resolveCapRunArgs, shouldRestartForNativeChange } from './native'\nimport { errorMessageFromValue } from './utils/error-message'\n\nexport interface CapVitePluginOptions {\n  capArgs: string[]\n}\n\nexport function capVitePlugin(options: CapVitePluginOptions): Plugin {\n  const platform = parseCapacitorPlatform(options.capArgs[0])\n  if (!platform) {\n    throw new Error('The first `cap run` argument must be `ios` or `android`.')\n  }\n  const resolvedPlatform: CapacitorPlatform = platform\n\n  return {\n    apply: 'serve',\n    async configureServer(server) {\n      const resolvedCapArgs = await resolveCapRunArgs(options.capArgs)\n      const cwd = resolve(server.config.root)\n      const platformRoot = resolve(cwd, resolvedPlatform)\n      const debounceMs = 300\n      const logger = server.config.logger\n\n      let currentCapProcess: Result | undefined\n      let restartTask: Promise<void> | undefined\n      let queuedRestartReason: string | undefined\n      let disposeShortcut: (() => void) | undefined\n      let shuttingDown = false\n      let restartTimer: NodeJS.Timeout | undefined\n\n      function launchCapProcess() {\n        const url = pickServerUrl(server)\n        currentCapProcess = startCapProcess(cwd, resolvedCapArgs, url)\n        currentCapProcess.then(() => {\n          logger.info(`[cap-vite] Ran \"cap run ${resolvedCapArgs.join(' ')}\". Press R to re-run. Press Ctrl+C to exit.`)\n        })\n      }\n\n      function requestRestart(reason: string) {\n        if (shuttingDown) {\n          return\n        }\n\n        queuedRestartReason = reason\n        if (!restartTask) {\n          restartTask = flushPendingRestarts()\n        }\n      }\n\n      async function flushPendingRestarts() {\n        try {\n          while (queuedRestartReason) {\n            const activeReason = queuedRestartReason\n            queuedRestartReason = undefined\n\n            if (shuttingDown) {\n              return\n            }\n\n            logger.info(`[cap-vite] ${activeReason}. Re-running \"cap run ${resolvedCapArgs.join(' ')}\".`)\n            const previous = currentCapProcess\n            currentCapProcess = undefined\n            await stopCapProcess(previous)\n\n            if (shuttingDown) {\n              return\n            }\n\n            launchCapProcess()\n          }\n        }\n        catch (error) {\n          logger.error(`[cap-vite] ${errorMessageFromValue(error)}`)\n          await shutdown()\n        }\n        finally {\n          restartTask = undefined\n        }\n      }\n\n      /**\n       * Requests a Capacitor restart after a native project file changes.\n       *\n       * Triggering workflow:\n       *\n       * `server.watcher`\n       *   -> `all`\n       *     -> `onWatcherEvent`\n       *       -> `requestRestart`\n       *\n       * Upstream:\n       * - `server.watcher`\n       *\n       * Downstream:\n       * - `requestRestart`\n       */\n      function onWatcherEvent(_event: string, file: string) {\n        if (!shouldRestartForNativeChange(file, resolvedPlatform, cwd)) {\n          return\n        }\n\n        clearTimeout(restartTimer)\n        restartTimer = setTimeout(() => {\n          requestRestart(`native file changed: ${resolve(cwd, file)}`)\n        }, debounceMs)\n      }\n\n      function handleShutdownRequest() {\n        void shutdown()\n      }\n\n      async function shutdown() {\n        if (shuttingDown) {\n          return\n        }\n\n        shuttingDown = true\n        clearTimeout(restartTimer)\n        queuedRestartReason = undefined\n        const disposeBoundShortcut = disposeShortcut\n        disposeShortcut = undefined\n        disposeBoundShortcut?.()\n        server.watcher.off('all', onWatcherEvent)\n        process.off('SIGINT', handleShutdownRequest)\n        process.off('SIGTERM', handleShutdownRequest)\n        await server.watcher.unwatch(platformRoot)\n        await stopCapProcess(currentCapProcess)\n      }\n\n      server.watcher.add(platformRoot)\n      server.watcher.on('all', onWatcherEvent)\n\n      server.httpServer?.once('listening', () => {\n        launchCapProcess()\n        disposeShortcut = bindCapViteShortcuts(() => requestRestart('manual restart requested'), shutdown)\n      })\n      server.httpServer?.once('close', handleShutdownRequest)\n      process.once('SIGINT', handleShutdownRequest)\n      process.once('SIGTERM', handleShutdownRequest)\n    },\n    name: 'cap-vite:run-capacitor',\n  }\n}\n\nfunction bindCapViteShortcuts(\n  onRestart: () => void,\n  onShutdown: () => Promise<void>,\n) {\n  if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== 'function') {\n    return () => {}\n  }\n\n  process.stdin.resume()\n  process.stdin.setEncoding('utf8')\n  readline.emitKeypressEvents(process.stdin)\n\n  const shouldRestoreRawMode = !process.stdin.isRaw\n  if (shouldRestoreRawMode) {\n    process.stdin.setRawMode(true)\n  }\n\n  async function shutdownFromShortcut() {\n    try {\n      await onShutdown()\n    }\n    finally {\n      if (shouldRestoreRawMode) {\n        process.stdin.setRawMode(false)\n      }\n\n      process.kill(process.pid, 'SIGINT')\n    }\n  }\n\n  const onKeyPress = (input: string, key: readline.Key) => {\n    if (key.ctrl && key.name === 'c') {\n      void shutdownFromShortcut()\n      return\n    }\n\n    const keyName = key.name?.toLowerCase() ?? input.toLowerCase()\n    if (!key.ctrl && !key.meta && keyName === 'r') {\n      onRestart()\n    }\n  }\n\n  process.stdin.on('keypress', onKeyPress)\n\n  return () => {\n    process.stdin.off('keypress', onKeyPress)\n\n    if (shouldRestoreRawMode) {\n      process.stdin.setRawMode(false)\n    }\n  }\n}\n\nfunction startCapProcess(cwd: string, capArgs: string[], url: URL) {\n  return x('cap', ['run', ...capArgs], {\n    nodeOptions: {\n      cwd,\n      env: {\n        CAPACITOR_DEV_SERVER_URL: url.toString(),\n      },\n      // NOTICE: cap-vite owns the terminal shortcuts, so cap run should not\n      // consume stdin while still mirroring its stdout/stderr to the console.\n      stdio: ['ignore', 'inherit', 'inherit'],\n    },\n    throwOnError: false,\n  })\n}\n\nasync function stopCapProcess(current: Result | undefined) {\n  if (!current) {\n    return\n  }\n\n  current.kill('SIGINT')\n\n  try {\n    await current\n  }\n  catch {\n    // tinyexec rejects when a process is stopped during a restart.\n  }\n}\n"],"mappings":"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"}