Filemedium importancesource

NativeAutoUpdater.tsx

components/NativeAutoUpdater.tsx

193
Lines
26514
Bytes
1
Exports
14
Imports
10
Keywords

What this is

This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.

Beginner explanation

This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.

How it is used

Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.

Expert explanation

Architecturally, this file intersects with ui-flow. It contains 193 lines, 14 detected imports, and 1 detected exports.

Important relationships

Detected exports

  • NativeAutoUpdater

Keywords

texterrormessageincludesversionautoupdaterresultisupdatingcurrentutilserrortypelogevent

Detected imports

  • react
  • react
  • src/services/analytics/index.js
  • src/utils/debug.js
  • src/utils/log.js
  • usehooks-ts
  • ../hooks/useUpdateNotification.js
  • ../ink.js
  • ../utils/autoUpdater.js
  • ../utils/autoUpdater.js
  • ../utils/config.js
  • ../utils/nativeInstaller/index.js
  • ../utils/semver.js
  • ../utils/settings/settings.js

Source notes

This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.

Open parent directory

Full source

import * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import { logEvent } from 'src/services/analytics/index.js';
import { logForDebugging } from 'src/utils/debug.js';
import { logError } from 'src/utils/log.js';
import { useInterval } from 'usehooks-ts';
import { useUpdateNotification } from '../hooks/useUpdateNotification.js';
import { Box, Text } from '../ink.js';
import type { AutoUpdaterResult } from '../utils/autoUpdater.js';
import { getMaxVersion, getMaxVersionMessage } from '../utils/autoUpdater.js';
import { isAutoUpdaterDisabled } from '../utils/config.js';
import { installLatest } from '../utils/nativeInstaller/index.js';
import { gt } from '../utils/semver.js';
import { getInitialSettings } from '../utils/settings/settings.js';

/**
 * Categorize error messages for analytics
 */
function getErrorType(errorMessage: string): string {
  if (errorMessage.includes('timeout')) {
    return 'timeout';
  }
  if (errorMessage.includes('Checksum mismatch')) {
    return 'checksum_mismatch';
  }
  if (errorMessage.includes('ENOENT') || errorMessage.includes('not found')) {
    return 'not_found';
  }
  if (errorMessage.includes('EACCES') || errorMessage.includes('permission')) {
    return 'permission_denied';
  }
  if (errorMessage.includes('ENOSPC')) {
    return 'disk_full';
  }
  if (errorMessage.includes('npm')) {
    return 'npm_error';
  }
  if (errorMessage.includes('network') || errorMessage.includes('ECONNREFUSED') || errorMessage.includes('ENOTFOUND')) {
    return 'network_error';
  }
  return 'unknown';
}
type Props = {
  isUpdating: boolean;
  onChangeIsUpdating: (isUpdating: boolean) => void;
  onAutoUpdaterResult: (autoUpdaterResult: AutoUpdaterResult) => void;
  autoUpdaterResult: AutoUpdaterResult | null;
  showSuccessMessage: boolean;
  verbose: boolean;
};
export function NativeAutoUpdater({
  isUpdating,
  onChangeIsUpdating,
  onAutoUpdaterResult,
  autoUpdaterResult,
  showSuccessMessage,
  verbose
}: Props): React.ReactNode {
  const [versions, setVersions] = useState<{
    current?: string | null;
    latest?: string | null;
  }>({});
  const [maxVersionIssue, setMaxVersionIssue] = useState<string | null>(null);
  const updateSemver = useUpdateNotification(autoUpdaterResult?.version);
  const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest';

  // Track latest isUpdating value in a ref so the memoized checkForUpdates
  // callback always sees the current value without changing callback identity
  // (which would re-trigger the initial-check useEffect below and cause
  // repeated downloads on remount — the upstream trigger for #22413).
  const isUpdatingRef = useRef(isUpdating);
  isUpdatingRef.current = isUpdating;
  const checkForUpdates = React.useCallback(async () => {
    if (isUpdatingRef.current) {
      return;
    }
    if ("production" === 'test' || "production" === 'development') {
      logForDebugging('NativeAutoUpdater: Skipping update check in test/dev environment');
      return;
    }
    if (isAutoUpdaterDisabled()) {
      return;
    }
    onChangeIsUpdating(true);
    const startTime = Date.now();

    // Log the start of an auto-update check for funnel analysis
    logEvent('tengu_native_auto_updater_start', {});
    try {
      // Check if current version is above the max allowed version
      const maxVersion = await getMaxVersion();
      if (maxVersion && gt(MACRO.VERSION, maxVersion)) {
        const msg = await getMaxVersionMessage();
        setMaxVersionIssue(msg ?? 'affects your version');
      }
      const result = await installLatest(channel);
      const currentVersion = MACRO.VERSION;
      const latencyMs = Date.now() - startTime;

      // Handle lock contention gracefully - just return without treating as error
      if (result.lockFailed) {
        logEvent('tengu_native_auto_updater_lock_contention', {
          latency_ms: latencyMs
        });
        return; // Silently skip this update check, will try again later
      }

      // Update versions for display
      setVersions({
        current: currentVersion,
        latest: result.latestVersion
      });
      if (result.wasUpdated) {
        logEvent('tengu_native_auto_updater_success', {
          latency_ms: latencyMs
        });
        onAutoUpdaterResult({
          version: result.latestVersion,
          status: 'success'
        });
      } else {
        // Already up to date
        logEvent('tengu_native_auto_updater_up_to_date', {
          latency_ms: latencyMs
        });
      }
    } catch (error) {
      const latencyMs = Date.now() - startTime;
      const errorMessage = error instanceof Error ? error.message : String(error);
      logError(error);
      const errorType = getErrorType(errorMessage);
      logEvent('tengu_native_auto_updater_fail', {
        latency_ms: latencyMs,
        error_timeout: errorType === 'timeout',
        error_checksum: errorType === 'checksum_mismatch',
        error_not_found: errorType === 'not_found',
        error_permission: errorType === 'permission_denied',
        error_disk_full: errorType === 'disk_full',
        error_npm: errorType === 'npm_error',
        error_network: errorType === 'network_error'
      });
      onAutoUpdaterResult({
        version: null,
        status: 'install_failed'
      });
    } finally {
      onChangeIsUpdating(false);
    }
    // isUpdating intentionally omitted from deps; we read isUpdatingRef
    // instead so the guard is always current without changing callback
    // identity (which would re-trigger the initial-check useEffect below).
    // eslint-disable-next-line react-hooks/exhaustive-deps
    // biome-ignore lint/correctness/useExhaustiveDependencies: isUpdating read via ref
  }, [onAutoUpdaterResult, channel]);

  // Initial check
  useEffect(() => {
    void checkForUpdates();
  }, [checkForUpdates]);

  // Check every 30 minutes
  useInterval(checkForUpdates, 30 * 60 * 1000);
  const hasUpdateResult = !!autoUpdaterResult?.version;
  const hasVersionInfo = !!versions.current && !!versions.latest;
  // Show the component when:
  // - warning banner needed (above max version), or
  // - there's an update result to display (success/error), or
  // - actively checking and we have version info to show
  const shouldRender = !!maxVersionIssue || hasUpdateResult || isUpdating && hasVersionInfo;
  if (!shouldRender) {
    return null;
  }
  return <Box flexDirection="row" gap={1}>
      {verbose && <Text dimColor wrap="truncate">
          current: {versions.current} &middot; {channel}: {versions.latest}
        </Text>}
      {isUpdating ? <Box>
          <Text dimColor wrap="truncate">
            Checking for updates
          </Text>
        </Box> : autoUpdaterResult?.status === 'success' && showSuccessMessage && updateSemver && <Text color="success" wrap="truncate">
            ✓ Update installed · Restart to update
          </Text>}
      {autoUpdaterResult?.status === 'install_failed' && <Text color="error" wrap="truncate">
          ✗ Auto-update failed &middot; Try <Text bold>/status</Text>
        </Text>}
      {maxVersionIssue && "external" === 'ant' && <Text color="warning">
          ⚠ Known issue: {maxVersionIssue} &middot; Run{' '}
          <Text bold>claude rollback --safe</Text> to downgrade
        </Text>}
    </Box>;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInVzZUVmZmVjdCIsInVzZVJlZiIsInVzZVN0YXRlIiwibG9nRXZlbnQiLCJsb2dGb3JEZWJ1Z2dpbmciLCJsb2dFcnJvciIsInVzZUludGVydmFsIiwidXNlVXBkYXRlTm90aWZpY2F0aW9uIiwiQm94IiwiVGV4dCIsIkF1dG9VcGRhdGVyUmVzdWx0IiwiZ2V0TWF4VmVyc2lvbiIsImdldE1heFZlcnNpb25NZXNzYWdlIiwiaXNBdXRvVXBkYXRlckRpc2FibGVkIiwiaW5zdGFsbExhdGVzdCIsImd0IiwiZ2V0SW5pdGlhbFNldHRpbmdzIiwiZ2V0RXJyb3JUeXBlIiwiZXJyb3JNZXNzYWdlIiwiaW5jbHVkZXMiLCJQcm9wcyIsImlzVXBkYXRpbmciLCJvbkNoYW5nZUlzVXBkYXRpbmciLCJvbkF1dG9VcGRhdGVyUmVzdWx0IiwiYXV0b1VwZGF0ZXJSZXN1bHQiLCJzaG93U3VjY2Vzc01lc3NhZ2UiLCJ2ZXJib3NlIiwiTmF0aXZlQXV0b1VwZGF0ZXIiLCJSZWFjdE5vZGUiLCJ2ZXJzaW9ucyIsInNldFZlcnNpb25zIiwiY3VycmVudCIsImxhdGVzdCIsIm1heFZlcnNpb25Jc3N1ZSIsInNldE1heFZlcnNpb25Jc3N1ZSIsInVwZGF0ZVNlbXZlciIsInZlcnNpb24iLCJjaGFubmVsIiwiYXV0b1VwZGF0ZXNDaGFubmVsIiwiaXNVcGRhdGluZ1JlZiIsImNoZWNrRm9yVXBkYXRlcyIsInVzZUNhbGxiYWNrIiwic3RhcnRUaW1lIiwiRGF0ZSIsIm5vdyIsIm1heFZlcnNpb24iLCJNQUNSTyIsIlZFUlNJT04iLCJtc2ciLCJyZXN1bHQiLCJjdXJyZW50VmVyc2lvbiIsImxhdGVuY3lNcyIsImxvY2tGYWlsZWQiLCJsYXRlbmN5X21zIiwibGF0ZXN0VmVyc2lvbiIsIndhc1VwZGF0ZWQiLCJzdGF0dXMiLCJlcnJvciIsIkVycm9yIiwibWVzc2FnZSIsIlN0cmluZyIsImVycm9yVHlwZSIsImVycm9yX3RpbWVvdXQiLCJlcnJvcl9jaGVja3N1bSIsImVycm9yX25vdF9mb3VuZCIsImVycm9yX3Blcm1pc3Npb24iLCJlcnJvcl9kaXNrX2Z1bGwiLCJlcnJvcl9ucG0iLCJlcnJvcl9uZXR3b3JrIiwiaGFzVXBkYXRlUmVzdWx0IiwiaGFzVmVyc2lvbkluZm8iLCJzaG91bGRSZW5kZXIiXSwic291cmNlcyI6WyJOYXRpdmVBdXRvVXBkYXRlci50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyB1c2VFZmZlY3QsIHVzZVJlZiwgdXNlU3RhdGUgfSBmcm9tICdyZWFjdCdcbmltcG9ydCB7IGxvZ0V2ZW50IH0gZnJvbSAnc3JjL3NlcnZpY2VzL2FuYWx5dGljcy9pbmRleC5qcydcbmltcG9ydCB7IGxvZ0ZvckRlYnVnZ2luZyB9IGZyb20gJ3NyYy91dGlscy9kZWJ1Zy5qcydcbmltcG9ydCB7IGxvZ0Vycm9yIH0gZnJvbSAnc3JjL3V0aWxzL2xvZy5qcydcbmltcG9ydCB7IHVzZUludGVydmFsIH0gZnJvbSAndXNlaG9va3MtdHMnXG5pbXBvcnQgeyB1c2VVcGRhdGVOb3RpZmljYXRpb24gfSBmcm9tICcuLi9ob29rcy91c2VVcGRhdGVOb3RpZmljYXRpb24uanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi9pbmsuanMnXG5pbXBvcnQgdHlwZSB7IEF1dG9VcGRhdGVyUmVzdWx0IH0gZnJvbSAnLi4vdXRpbHMvYXV0b1VwZGF0ZXIuanMnXG5pbXBvcnQgeyBnZXRNYXhWZXJzaW9uLCBnZXRNYXhWZXJzaW9uTWVzc2FnZSB9IGZyb20gJy4uL3V0aWxzL2F1dG9VcGRhdGVyLmpzJ1xuaW1wb3J0IHsgaXNBdXRvVXBkYXRlckRpc2FibGVkIH0gZnJvbSAnLi4vdXRpbHMvY29uZmlnLmpzJ1xuaW1wb3J0IHsgaW5zdGFsbExhdGVzdCB9IGZyb20gJy4uL3V0aWxzL25hdGl2ZUluc3RhbGxlci9pbmRleC5qcydcbmltcG9ydCB7IGd0IH0gZnJvbSAnLi4vdXRpbHMvc2VtdmVyLmpzJ1xuaW1wb3J0IHsgZ2V0SW5pdGlhbFNldHRpbmdzIH0gZnJvbSAnLi4vdXRpbHMvc2V0dGluZ3Mvc2V0dGluZ3MuanMnXG5cbi8qKlxuICogQ2F0ZWdvcml6ZSBlcnJvciBtZXNzYWdlcyBmb3IgYW5hbHl0aWNzXG4gKi9cbmZ1bmN0aW9uIGdldEVycm9yVHlwZShlcnJvck1lc3NhZ2U6IHN0cmluZyk6IHN0cmluZyB7XG4gIGlmIChlcnJvck1lc3NhZ2UuaW5jbHVkZXMoJ3RpbWVvdXQnKSkge1xuICAgIHJldHVybiAndGltZW91dCdcbiAgfVxuICBpZiAoZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdDaGVja3N1bSBtaXNtYXRjaCcpKSB7XG4gICAgcmV0dXJuICdjaGVja3N1bV9taXNtYXRjaCdcbiAgfVxuICBpZiAoZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdFTk9FTlQnKSB8fCBlcnJvck1lc3NhZ2UuaW5jbHVkZXMoJ25vdCBmb3VuZCcpKSB7XG4gICAgcmV0dXJuICdub3RfZm91bmQnXG4gIH1cbiAgaWYgKGVycm9yTWVzc2FnZS5pbmNsdWRlcygnRUFDQ0VTJykgfHwgZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdwZXJtaXNzaW9uJykpIHtcbiAgICByZXR1cm4gJ3Blcm1pc3Npb25fZGVuaWVkJ1xuICB9XG4gIGlmIChlcnJvck1lc3NhZ2UuaW5jbHVkZXMoJ0VOT1NQQycpKSB7XG4gICAgcmV0dXJuICdkaXNrX2Z1bGwnXG4gIH1cbiAgaWYgKGVycm9yTWVzc2FnZS5pbmNsdWRlcygnbnBtJykpIHtcbiAgICByZXR1cm4gJ25wbV9lcnJvcidcbiAgfVxuICBpZiAoXG4gICAgZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCduZXR3b3JrJykgfHxcbiAgICBlcnJvck1lc3NhZ2UuaW5jbHVkZXMoJ0VDT05OUkVGVVNFRCcpIHx8XG4gICAgZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdFTk9URk9VTkQnKVxuICApIHtcbiAgICByZXR1cm4gJ25ldHdvcmtfZXJyb3InXG4gIH1cbiAgcmV0dXJuICd1bmtub3duJ1xufVxuXG50eXBlIFByb3BzID0ge1xuICBpc1VwZGF0aW5nOiBib29sZWFuXG4gIG9uQ2hhbmdlSXNVcGRhdGluZzogKGlzVXBkYXRpbmc6IGJvb2xlYW4pID0+IHZvaWRcbiAgb25BdXRvVXBkYXRlclJlc3VsdDogKGF1dG9VcGRhdGVyUmVzdWx0OiBBdXRvVXBkYXRlclJlc3VsdCkgPT4gdm9pZFxuICBhdXRvVXBkYXRlclJlc3VsdDogQXV0b1VwZGF0ZXJSZXN1bHQgfCBudWxsXG4gIHNob3dTdWNjZXNzTWVzc2FnZTogYm9vbGVhblxuICB2ZXJib3NlOiBib29sZWFuXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBOYXRpdmVBdXRvVXBkYXRlcih7XG4gIGlzVXBkYXRpbmcsXG4gIG9uQ2hhbmdlSXNVcGRhdGluZyxcbiAgb25BdXRvVXBkYXRlclJlc3VsdCxcbiAgYXV0b1VwZGF0ZXJSZXN1bHQsXG4gIHNob3dTdWNjZXNzTWVzc2FnZSxcbiAgdmVyYm9zZSxcbn06IFByb3BzKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgY29uc3QgW3ZlcnNpb25zLCBzZXRWZXJzaW9uc10gPSB1c2VTdGF0ZTx7XG4gICAgY3VycmVudD86IHN0cmluZyB8IG51bGxcbiAgICBsYXRlc3Q/OiBzdHJpbmcgfCBudWxsXG4gIH0+KHt9KVxuICBjb25zdCBbbWF4VmVyc2lvbklzc3VlLCBzZXRNYXhWZXJzaW9uSXNzdWVdID0gdXNlU3RhdGU8c3RyaW5nIHwgbnVsbD4obnVsbClcbiAgY29uc3QgdXBkYXRlU2VtdmVyID0gdXNlVXBkYXRlTm90aWZpY2F0aW9uKGF1dG9VcGRhdGVyUmVzdWx0Py52ZXJzaW9uKVxuICBjb25zdCBjaGFubmVsID0gZ2V0SW5pdGlhbFNldHRpbmdzKCk/LmF1dG9VcGRhdGVzQ2hhbm5lbCA/PyAnbGF0ZXN0J1xuXG4gIC8vIFRyYWNrIGxhdGVzdCBpc1VwZGF0aW5nIHZhbHVlIGluIGEgcmVmIHNvIHRoZSBtZW1vaXplZCBjaGVja0ZvclVwZGF0ZXNcbiAgLy8gY2FsbGJhY2sgYWx3YXlzIHNlZXMgdGhlIGN1cnJlbnQgdmFsdWUgd2l0aG91dCBjaGFuZ2luZyBjYWxsYmFjayBpZGVudGl0eVxuICAvLyAod2hpY2ggd291bGQgcmUtdHJpZ2dlciB0aGUgaW5pdGlhbC1jaGVjayB1c2VFZmZlY3QgYmVsb3cgYW5kIGNhdXNlXG4gIC8vIHJlcGVhdGVkIGRvd25sb2FkcyBvbiByZW1vdW50IOKAlCB0aGUgdXBzdHJlYW0gdHJpZ2dlciBmb3IgIzIyNDEzKS5cbiAgY29uc3QgaXNVcGRhdGluZ1JlZiA9IHVzZVJlZihpc1VwZGF0aW5nKVxuICBpc1VwZGF0aW5nUmVmLmN1cnJlbnQgPSBpc1VwZGF0aW5nXG5cbiAgY29uc3QgY2hlY2tGb3JVcGRhdGVzID0gUmVhY3QudXNlQ2FsbGJhY2soYXN5bmMgKCkgPT4ge1xuICAgIGlmIChpc1VwZGF0aW5nUmVmLmN1cnJlbnQpIHtcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGlmIChcbiAgICAgIFwicHJvZHVjdGlvblwiID09PSAndGVzdCcgfHxcbiAgICAgIFwicHJvZHVjdGlvblwiID09PSAnZGV2ZWxvcG1lbnQnXG4gICAgKSB7XG4gICAgICBsb2dGb3JEZWJ1Z2dpbmcoXG4gICAgICAgICdOYXRpdmVBdXRvVXBkYXRlcjogU2tpcHBpbmcgdXBkYXRlIGNoZWNrIGluIHRlc3QvZGV2IGVudmlyb25tZW50JyxcbiAgICAgIClcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGlmIChpc0F1dG9VcGRhdGVyRGlzYWJsZWQoKSkge1xuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgb25DaGFuZ2VJc1VwZGF0aW5nKHRydWUpXG4gICAgY29uc3Qgc3RhcnRUaW1lID0gRGF0ZS5ub3coKVxuXG4gICAgLy8gTG9nIHRoZSBzdGFydCBvZiBhbiBhdXRvLXVwZGF0ZSBjaGVjayBmb3IgZnVubmVsIGFuYWx5c2lzXG4gICAgbG9nRXZlbnQoJ3Rlbmd1X25hdGl2ZV9hdXRvX3VwZGF0ZXJfc3RhcnQnLCB7fSlcblxuICAgIHRyeSB7XG4gICAgICAvLyBDaGVjayBpZiBjdXJyZW50IHZlcnNpb24gaXMgYWJvdmUgdGhlIG1heCBhbGxvd2VkIHZlcnNpb25cbiAgICAgIGNvbnN0IG1heFZlcnNpb24gPSBhd2FpdCBnZXRNYXhWZXJzaW9uKClcbiAgICAgIGlmIChtYXhWZXJzaW9uICYmIGd0KE1BQ1JPLlZFUlNJT04sIG1heFZlcnNpb24pKSB7XG4gICAgICAgIGNvbnN0IG1zZyA9IGF3YWl0IGdldE1heFZlcnNpb25NZXNzYWdlKClcbiAgICAgICAgc2V0TWF4VmVyc2lvbklzc3VlKG1zZyA/PyAnYWZmZWN0cyB5b3VyIHZlcnNpb24nKVxuICAgICAgfVxuXG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBpbnN0YWxsTGF0ZXN0KGNoYW5uZWwpXG4gICAgICBjb25zdCBjdXJyZW50VmVyc2lvbiA9IE1BQ1JPLlZFUlNJT05cbiAgICAgIGNvbnN0IGxhdGVuY3lNcyA9IERhdGUubm93KCkgLSBzdGFydFRpbWVcblxuICAgICAgLy8gSGFuZGxlIGxvY2sgY29udGVudGlvbiBncmFjZWZ1bGx5IC0ganVzdCByZXR1cm4gd2l0aG91dCB0cmVhdGluZyBhcyBlcnJvclxuICAgICAgaWYgKHJlc3VsdC5sb2NrRmFpbGVkKSB7XG4gICAgICAgIGxvZ0V2ZW50KCd0ZW5ndV9uYXRpdmVfYXV0b191cGRhdGVyX2xvY2tfY29udGVudGlvbicsIHtcbiAgICAgICAgICBsYXRlbmN5X21zOiBsYXRlbmN5TXMsXG4gICAgICAgIH0pXG4gICAgICAgIHJldHVybiAvLyBTaWxlbnRseSBza2lwIHRoaXMgdXBkYXRlIGNoZWNrLCB3aWxsIHRyeSBhZ2FpbiBsYXRlclxuICAgICAgfVxuXG4gICAgICAvLyBVcGRhdGUgdmVyc2lvbnMgZm9yIGRpc3BsYXlcbiAgICAgIHNldFZlcnNpb25zKHsgY3VycmVudDogY3VycmVudFZlcnNpb24sIGxhdGVzdDogcmVzdWx0LmxhdGVzdFZlcnNpb24gfSlcblxuICAgICAgaWYgKHJlc3VsdC53YXNVcGRhdGVkKSB7XG4gICAgICAgIGxvZ0V2ZW50KCd0ZW5ndV9uYXRpdmVfYXV0b191cGRhdGVyX3N1Y2Nlc3MnLCB7XG4gICAgICAgICAgbGF0ZW5jeV9tczogbGF0ZW5jeU1zLFxuICAgICAgICB9KVxuXG4gICAgICAgIG9uQXV0b1VwZGF0ZXJSZXN1bHQoe1xuICAgICAgICAgIHZlcnNpb246IHJlc3VsdC5sYXRlc3RWZXJzaW9uLFxuICAgICAgICAgIHN0YXR1czogJ3N1Y2Nlc3MnLFxuICAgICAgICB9KVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gQWxyZWFkeSB1cCB0byBkYXRlXG4gICAgICAgIGxvZ0V2ZW50KCd0ZW5ndV9uYXRpdmVfYXV0b191cGRhdGVyX3VwX3RvX2RhdGUnLCB7XG4gICAgICAgICAgbGF0ZW5jeV9tczogbGF0ZW5jeU1zLFxuICAgICAgICB9KVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBjb25zdCBsYXRlbmN5TXMgPSBEYXRlLm5vdygpIC0gc3RhcnRUaW1lXG4gICAgICBjb25zdCBlcnJvck1lc3NhZ2UgPVxuICAgICAgICBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcilcbiAgICAgIGxvZ0Vycm9yKGVycm9yKVxuXG4gICAgICBjb25zdCBlcnJvclR5cGUgPSBnZXRFcnJvclR5cGUoZXJyb3JNZXNzYWdlKVxuICAgICAgbG9nRXZlbnQoJ3Rlbmd1X25hdGl2ZV9hdXRvX3VwZGF0ZXJfZmFpbCcsIHtcbiAgICAgICAgbGF0ZW5jeV9tczogbGF0ZW5jeU1zLFxuICAgICAgICBlcnJvcl90aW1lb3V0OiBlcnJvclR5cGUgPT09ICd0aW1lb3V0JyxcbiAgICAgICAgZXJyb3JfY2hlY2tzdW06IGVycm9yVHlwZSA9PT0gJ2NoZWNrc3VtX21pc21hdGNoJyxcbiAgICAgICAgZXJyb3Jfbm90X2ZvdW5kOiBlcnJvclR5cGUgPT09ICdub3RfZm91bmQnLFxuICAgICAgICBlcnJvcl9wZXJtaXNzaW9uOiBlcnJvclR5cGUgPT09ICdwZXJtaXNzaW9uX2RlbmllZCcsXG4gICAgICAgIGVycm9yX2Rpc2tfZnVsbDogZXJyb3JUeXBlID09PSAnZGlza19mdWxsJyxcbiAgICAgICAgZXJyb3JfbnBtOiBlcnJvclR5cGUgPT09ICducG1fZXJyb3InLFxuICAgICAgICBlcnJvcl9uZXR3b3JrOiBlcnJvclR5cGUgPT09ICduZXR3b3JrX2Vycm9yJyxcbiAgICAgIH0pXG5cbiAgICAgIG9uQXV0b1VwZGF0ZXJSZXN1bHQoe1xuICAgICAgICB2ZXJzaW9uOiBudWxsLFxuICAgICAgICBzdGF0dXM6ICdpbnN0YWxsX2ZhaWxlZCcsXG4gICAgICB9KVxuICAgIH0gZmluYWxseSB7XG4gICAgICBvbkNoYW5nZUlzVXBkYXRpbmcoZmFsc2UpXG4gICAgfVxuICAgIC8vIGlzVXBkYXRpbmcgaW50ZW50aW9uYWxseSBvbWl0dGVkIGZyb20gZGVwczsgd2UgcmVhZCBpc1VwZGF0aW5nUmVmXG4gICAgLy8gaW5zdGVhZCBzbyB0aGUgZ3VhcmQgaXMgYWx3YXlzIGN1cnJlbnQgd2l0aG91dCBjaGFuZ2luZyBjYWxsYmFja1xuICAgIC8vIGlkZW50aXR5ICh3aGljaCB3b3VsZCByZS10cmlnZ2VyIHRoZSBpbml0aWFsLWNoZWNrIHVzZUVmZmVjdCBiZWxvdykuXG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIHJlYWN0LWhvb2tzL2V4aGF1c3RpdmUtZGVwc1xuICAgIC8vIGJpb21lLWlnbm9yZSBsaW50L2NvcnJlY3RuZXNzL3VzZUV4aGF1c3RpdmVEZXBlbmRlbmNpZXM6IGlzVXBkYXRpbmcgcmVhZCB2aWEgcmVmXG4gIH0sIFtvbkF1dG9VcGRhdGVyUmVzdWx0LCBjaGFubmVsXSlcblxuICAvLyBJbml0aWFsIGNoZWNrXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgdm9pZCBjaGVja0ZvclVwZGF0ZXMoKVxuICB9LCBbY2hlY2tGb3JVcGRhdGVzXSlcblxuICAvLyBDaGVjayBldmVyeSAzMCBtaW51dGVzXG4gIHVzZUludGVydmFsKGNoZWNrRm9yVXBkYXRlcywgMzAgKiA2MCAqIDEwMDApXG5cbiAgY29uc3QgaGFzVXBkYXRlUmVzdWx0ID0gISFhdXRvVXBkYXRlclJlc3VsdD8udmVyc2lvblxuICBjb25zdCBoYXNWZXJzaW9uSW5mbyA9ICEhdmVyc2lvbnMuY3VycmVudCAmJiAhIXZlcnNpb25zLmxhdGVzdFxuICAvLyBTaG93IHRoZSBjb21wb25lbnQgd2hlbjpcbiAgLy8gLSB3YXJuaW5nIGJhbm5lciBuZWVkZWQgKGFib3ZlIG1heCB2ZXJzaW9uKSwgb3JcbiAgLy8gLSB0aGVyZSdzIGFuIHVwZGF0ZSByZXN1bHQgdG8gZGlzcGxheSAoc3VjY2Vzcy9lcnJvciksIG9yXG4gIC8vIC0gYWN0aXZlbHkgY2hlY2tpbmcgYW5kIHdlIGhhdmUgdmVyc2lvbiBpbmZvIHRvIHNob3dcbiAgY29uc3Qgc2hvdWxkUmVuZGVyID1cbiAgICAhIW1heFZlcnNpb25Jc3N1ZSB8fCBoYXNVcGRhdGVSZXN1bHQgfHwgKGlzVXBkYXRpbmcgJiYgaGFzVmVyc2lvbkluZm8pXG5cbiAgaWYgKCFzaG91bGRSZW5kZXIpIHtcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJyb3dcIiBnYXA9ezF9PlxuICAgICAge3ZlcmJvc2UgJiYgKFxuICAgICAgICA8VGV4dCBkaW1Db2xvciB3cmFwPVwidHJ1bmNhdGVcIj5cbiAgICAgICAgICBjdXJyZW50OiB7dmVyc2lvbnMuY3VycmVudH0gJm1pZGRvdDsge2NoYW5uZWx9OiB7dmVyc2lvbnMubGF0ZXN0fVxuICAgICAgICA8L1RleHQ+XG4gICAgICApfVxuICAgICAge2lzVXBkYXRpbmcgPyAoXG4gICAgICAgIDxCb3g+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3Igd3JhcD1cInRydW5jYXRlXCI+XG4gICAgICAgICAgICBDaGVja2luZyBmb3IgdXBkYXRlc1xuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICApIDogKFxuICAgICAgICBhdXRvVXBkYXRlclJlc3VsdD8uc3RhdHVzID09PSAnc3VjY2VzcycgJiZcbiAgICAgICAgc2hvd1N1Y2Nlc3NNZXNzYWdlICYmXG4gICAgICAgIHVwZGF0ZVNlbXZlciAmJiAoXG4gICAgICAgICAgPFRleHQgY29sb3I9XCJzdWNjZXNzXCIgd3JhcD1cInRydW5jYXRlXCI+XG4gICAgICAgICAgICDinJMgVXBkYXRlIGluc3RhbGxlZCDCtyBSZXN0YXJ0IHRvIHVwZGF0ZVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgKVxuICAgICAgKX1cbiAgICAgIHthdXRvVXBkYXRlclJlc3VsdD8uc3RhdHVzID09PSAnaW5zdGFsbF9mYWlsZWQnICYmIChcbiAgICAgICAgPFRleHQgY29sb3I9XCJlcnJvclwiIHdyYXA9XCJ0cnVuY2F0ZVwiPlxuICAgICAgICAgIOKclyBBdXRvLXVwZGF0ZSBmYWlsZWQgJm1pZGRvdDsgVHJ5IDxUZXh0IGJvbGQ+L3N0YXR1czwvVGV4dD5cbiAgICAgICAgPC9UZXh0PlxuICAgICAgKX1cbiAgICAgIHttYXhWZXJzaW9uSXNzdWUgJiYgXCJleHRlcm5hbFwiID09PSAnYW50JyAmJiAoXG4gICAgICAgIDxUZXh0IGNvbG9yPVwid2FybmluZ1wiPlxuICAgICAgICAgIOKaoCBLbm93biBpc3N1ZToge21heFZlcnNpb25Jc3N1ZX0gJm1pZGRvdDsgUnVueycgJ31cbiAgICAgICAgICA8VGV4dCBib2xkPmNsYXVkZSByb2xsYmFjayAtLXNhZmU8L1RleHQ+IHRvIGRvd25ncmFkZVxuICAgICAgICA8L1RleHQ+XG4gICAgICApfVxuICAgIDwvQm94PlxuICApXG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsU0FBUyxFQUFFQyxNQUFNLEVBQUVDLFFBQVEsUUFBUSxPQUFPO0FBQ25ELFNBQVNDLFFBQVEsUUFBUSxpQ0FBaUM7QUFDMUQsU0FBU0MsZUFBZSxRQUFRLG9CQUFvQjtBQUNwRCxTQUFTQyxRQUFRLFFB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