mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-19 23:05:51 +00:00
style(B1-5): 格式化 components其余 + hooks + tools (232 files)
纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,197 +1,264 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from 'src/services/analytics/index.js';
|
||||
import { useInterval } from 'usehooks-ts';
|
||||
import { useUpdateNotification } from '../hooks/useUpdateNotification.js';
|
||||
import { Box, Text } from '../ink.js';
|
||||
import { type AutoUpdaterResult, getLatestVersion, getMaxVersion, type InstallStatus, installGlobalPackage, shouldSkipVersion } from '../utils/autoUpdater.js';
|
||||
import { getGlobalConfig, isAutoUpdaterDisabled } from '../utils/config.js';
|
||||
import { logForDebugging } from '../utils/debug.js';
|
||||
import { getCurrentInstallationType } from '../utils/doctorDiagnostic.js';
|
||||
import { installOrUpdateClaudePackage, localInstallationExists } from '../utils/localInstaller.js';
|
||||
import { removeInstalledSymlink } from '../utils/nativeInstaller/index.js';
|
||||
import { gt, gte } from '../utils/semver.js';
|
||||
import { getInitialSettings } from '../utils/settings/settings.js';
|
||||
import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
logEvent,
|
||||
} from 'src/services/analytics/index.js'
|
||||
import { useInterval } from 'usehooks-ts'
|
||||
import { useUpdateNotification } from '../hooks/useUpdateNotification.js'
|
||||
import { Box, Text } from '../ink.js'
|
||||
import {
|
||||
type AutoUpdaterResult,
|
||||
getLatestVersion,
|
||||
getMaxVersion,
|
||||
type InstallStatus,
|
||||
installGlobalPackage,
|
||||
shouldSkipVersion,
|
||||
} from '../utils/autoUpdater.js'
|
||||
import { getGlobalConfig, isAutoUpdaterDisabled } from '../utils/config.js'
|
||||
import { logForDebugging } from '../utils/debug.js'
|
||||
import { getCurrentInstallationType } from '../utils/doctorDiagnostic.js'
|
||||
import {
|
||||
installOrUpdateClaudePackage,
|
||||
localInstallationExists,
|
||||
} from '../utils/localInstaller.js'
|
||||
import { removeInstalledSymlink } from '../utils/nativeInstaller/index.js'
|
||||
import { gt, gte } from '../utils/semver.js'
|
||||
import { getInitialSettings } from '../utils/settings/settings.js'
|
||||
|
||||
type Props = {
|
||||
isUpdating: boolean;
|
||||
onChangeIsUpdating: (isUpdating: boolean) => void;
|
||||
onAutoUpdaterResult: (autoUpdaterResult: AutoUpdaterResult) => void;
|
||||
autoUpdaterResult: AutoUpdaterResult | null;
|
||||
showSuccessMessage: boolean;
|
||||
verbose: boolean;
|
||||
};
|
||||
isUpdating: boolean
|
||||
onChangeIsUpdating: (isUpdating: boolean) => void
|
||||
onAutoUpdaterResult: (autoUpdaterResult: AutoUpdaterResult) => void
|
||||
autoUpdaterResult: AutoUpdaterResult | null
|
||||
showSuccessMessage: boolean
|
||||
verbose: boolean
|
||||
}
|
||||
|
||||
export function AutoUpdater({
|
||||
isUpdating,
|
||||
onChangeIsUpdating,
|
||||
onAutoUpdaterResult,
|
||||
autoUpdaterResult,
|
||||
showSuccessMessage,
|
||||
verbose
|
||||
verbose,
|
||||
}: Props): React.ReactNode {
|
||||
const [versions, setVersions] = useState<{
|
||||
global?: string | null;
|
||||
latest?: string | null;
|
||||
}>({});
|
||||
const [hasLocalInstall, setHasLocalInstall] = useState(false);
|
||||
const updateSemver = useUpdateNotification(autoUpdaterResult?.version);
|
||||
global?: string | null
|
||||
latest?: string | null
|
||||
}>({})
|
||||
const [hasLocalInstall, setHasLocalInstall] = useState(false)
|
||||
const updateSemver = useUpdateNotification(autoUpdaterResult?.version)
|
||||
|
||||
useEffect(() => {
|
||||
void localInstallationExists().then(setHasLocalInstall);
|
||||
}, []);
|
||||
void localInstallationExists().then(setHasLocalInstall)
|
||||
}, [])
|
||||
|
||||
// Track latest isUpdating value in a ref so the memoized checkForUpdates
|
||||
// callback always sees the current value. Without this, the 30-minute
|
||||
// interval fires with a stale closure where isUpdating is false, allowing
|
||||
// a concurrent installGlobalPackage() to run while one is already in
|
||||
// progress.
|
||||
const isUpdatingRef = useRef(isUpdating);
|
||||
isUpdatingRef.current = isUpdating;
|
||||
const isUpdatingRef = useRef(isUpdating)
|
||||
isUpdatingRef.current = isUpdating
|
||||
|
||||
const checkForUpdates = React.useCallback(async () => {
|
||||
if (isUpdatingRef.current) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (("production" as string) === 'test' || ("production" as string) === 'development') {
|
||||
logForDebugging('AutoUpdater: Skipping update check in test/dev environment');
|
||||
return;
|
||||
|
||||
if (
|
||||
"production" === 'test' ||
|
||||
"production" === 'development'
|
||||
) {
|
||||
logForDebugging(
|
||||
'AutoUpdater: Skipping update check in test/dev environment',
|
||||
)
|
||||
return
|
||||
}
|
||||
const currentVersion = MACRO.VERSION;
|
||||
const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest';
|
||||
let latestVersion = await getLatestVersion(channel);
|
||||
const isDisabled = isAutoUpdaterDisabled();
|
||||
|
||||
const currentVersion = MACRO.VERSION
|
||||
const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest'
|
||||
let latestVersion = await getLatestVersion(channel)
|
||||
const isDisabled = isAutoUpdaterDisabled()
|
||||
|
||||
// Check if max version is set (server-side kill switch for auto-updates)
|
||||
const maxVersion = await getMaxVersion();
|
||||
const maxVersion = await getMaxVersion()
|
||||
if (maxVersion && latestVersion && gt(latestVersion, maxVersion)) {
|
||||
logForDebugging(`AutoUpdater: maxVersion ${maxVersion} is set, capping update from ${latestVersion} to ${maxVersion}`);
|
||||
logForDebugging(
|
||||
`AutoUpdater: maxVersion ${maxVersion} is set, capping update from ${latestVersion} to ${maxVersion}`,
|
||||
)
|
||||
if (gte(currentVersion, maxVersion)) {
|
||||
logForDebugging(`AutoUpdater: current version ${currentVersion} is already at or above maxVersion ${maxVersion}, skipping update`);
|
||||
setVersions({
|
||||
global: currentVersion,
|
||||
latest: latestVersion
|
||||
});
|
||||
return;
|
||||
logForDebugging(
|
||||
`AutoUpdater: current version ${currentVersion} is already at or above maxVersion ${maxVersion}, skipping update`,
|
||||
)
|
||||
setVersions({ global: currentVersion, latest: latestVersion })
|
||||
return
|
||||
}
|
||||
latestVersion = maxVersion;
|
||||
latestVersion = maxVersion
|
||||
}
|
||||
setVersions({
|
||||
global: currentVersion,
|
||||
latest: latestVersion
|
||||
});
|
||||
|
||||
setVersions({ global: currentVersion, latest: latestVersion })
|
||||
|
||||
// Check if update needed and perform update
|
||||
if (!isDisabled && currentVersion && latestVersion && !gte(currentVersion, latestVersion) && !shouldSkipVersion(latestVersion)) {
|
||||
const startTime = Date.now();
|
||||
onChangeIsUpdating(true);
|
||||
if (
|
||||
!isDisabled &&
|
||||
currentVersion &&
|
||||
latestVersion &&
|
||||
!gte(currentVersion, latestVersion) &&
|
||||
!shouldSkipVersion(latestVersion)
|
||||
) {
|
||||
const startTime = Date.now()
|
||||
onChangeIsUpdating(true)
|
||||
|
||||
// Remove native installer symlink since we're using JS-based updates
|
||||
// But only if user hasn't migrated to native installation
|
||||
const config = getGlobalConfig();
|
||||
const config = getGlobalConfig()
|
||||
if (config.installMethod !== 'native') {
|
||||
await removeInstalledSymlink();
|
||||
await removeInstalledSymlink()
|
||||
}
|
||||
|
||||
// Detect actual running installation type
|
||||
const installationType = await getCurrentInstallationType();
|
||||
logForDebugging(`AutoUpdater: Detected installation type: ${installationType}`);
|
||||
const installationType = await getCurrentInstallationType()
|
||||
logForDebugging(
|
||||
`AutoUpdater: Detected installation type: ${installationType}`,
|
||||
)
|
||||
|
||||
// Skip update for development builds
|
||||
if (installationType === 'development') {
|
||||
logForDebugging('AutoUpdater: Cannot auto-update development build');
|
||||
onChangeIsUpdating(false);
|
||||
return;
|
||||
logForDebugging('AutoUpdater: Cannot auto-update development build')
|
||||
onChangeIsUpdating(false)
|
||||
return
|
||||
}
|
||||
|
||||
// Choose the appropriate update method based on what's actually running
|
||||
let installStatus: InstallStatus;
|
||||
let updateMethod: 'local' | 'global';
|
||||
let installStatus: InstallStatus
|
||||
let updateMethod: 'local' | 'global'
|
||||
|
||||
if (installationType === 'npm-local') {
|
||||
// Use local update for local installations
|
||||
logForDebugging('AutoUpdater: Using local update method');
|
||||
updateMethod = 'local';
|
||||
installStatus = await installOrUpdateClaudePackage(channel);
|
||||
logForDebugging('AutoUpdater: Using local update method')
|
||||
updateMethod = 'local'
|
||||
installStatus = await installOrUpdateClaudePackage(channel)
|
||||
} else if (installationType === 'npm-global') {
|
||||
// Use global update for global installations
|
||||
logForDebugging('AutoUpdater: Using global update method');
|
||||
updateMethod = 'global';
|
||||
installStatus = await installGlobalPackage();
|
||||
logForDebugging('AutoUpdater: Using global update method')
|
||||
updateMethod = 'global'
|
||||
installStatus = await installGlobalPackage()
|
||||
} else if (installationType === 'native') {
|
||||
// This shouldn't happen - native should use NativeAutoUpdater
|
||||
logForDebugging('AutoUpdater: Unexpected native installation in non-native updater');
|
||||
onChangeIsUpdating(false);
|
||||
return;
|
||||
logForDebugging(
|
||||
'AutoUpdater: Unexpected native installation in non-native updater',
|
||||
)
|
||||
onChangeIsUpdating(false)
|
||||
return
|
||||
} else {
|
||||
// Fallback to config-based detection for unknown types
|
||||
logForDebugging(`AutoUpdater: Unknown installation type, falling back to config`);
|
||||
const isMigrated = config.installMethod === 'local';
|
||||
updateMethod = isMigrated ? 'local' : 'global';
|
||||
logForDebugging(
|
||||
`AutoUpdater: Unknown installation type, falling back to config`,
|
||||
)
|
||||
const isMigrated = config.installMethod === 'local'
|
||||
updateMethod = isMigrated ? 'local' : 'global'
|
||||
|
||||
if (isMigrated) {
|
||||
installStatus = await installOrUpdateClaudePackage(channel);
|
||||
installStatus = await installOrUpdateClaudePackage(channel)
|
||||
} else {
|
||||
installStatus = await installGlobalPackage();
|
||||
installStatus = await installGlobalPackage()
|
||||
}
|
||||
}
|
||||
onChangeIsUpdating(false);
|
||||
|
||||
onChangeIsUpdating(false)
|
||||
|
||||
if (installStatus === 'success') {
|
||||
logEvent('tengu_auto_updater_success', {
|
||||
fromVersion: currentVersion as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
toVersion: latestVersion as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
fromVersion:
|
||||
currentVersion as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
toVersion:
|
||||
latestVersion as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
durationMs: Date.now() - startTime,
|
||||
wasMigrated: updateMethod === 'local',
|
||||
installationType: installationType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
||||
});
|
||||
installationType:
|
||||
installationType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
})
|
||||
} else {
|
||||
logEvent('tengu_auto_updater_fail', {
|
||||
fromVersion: currentVersion as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
attemptedVersion: latestVersion as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
status: installStatus as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
fromVersion:
|
||||
currentVersion as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
attemptedVersion:
|
||||
latestVersion as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
status:
|
||||
installStatus as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
durationMs: Date.now() - startTime,
|
||||
wasMigrated: updateMethod === 'local',
|
||||
installationType: installationType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
||||
});
|
||||
installationType:
|
||||
installationType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
})
|
||||
}
|
||||
|
||||
onAutoUpdaterResult({
|
||||
version: latestVersion,
|
||||
status: installStatus
|
||||
});
|
||||
status: installStatus,
|
||||
})
|
||||
}
|
||||
// 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]);
|
||||
}, [onAutoUpdaterResult])
|
||||
|
||||
// Initial check
|
||||
useEffect(() => {
|
||||
void checkForUpdates();
|
||||
}, [checkForUpdates]);
|
||||
void checkForUpdates()
|
||||
}, [checkForUpdates])
|
||||
|
||||
// Check every 30 minutes
|
||||
useInterval(checkForUpdates, 30 * 60 * 1000);
|
||||
useInterval(checkForUpdates, 30 * 60 * 1000)
|
||||
|
||||
if (!autoUpdaterResult?.version && (!versions.global || !versions.latest)) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
if (!autoUpdaterResult?.version && !isUpdating) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
return <Box flexDirection="row" gap={1}>
|
||||
{verbose && <Text dimColor wrap="truncate">
|
||||
|
||||
return (
|
||||
<Box flexDirection="row" gap={1}>
|
||||
{verbose && (
|
||||
<Text dimColor wrap="truncate">
|
||||
globalVersion: {versions.global} · latestVersion:{' '}
|
||||
{versions.latest}
|
||||
</Text>}
|
||||
{isUpdating ? <>
|
||||
</Text>
|
||||
)}
|
||||
{isUpdating ? (
|
||||
<>
|
||||
<Box>
|
||||
<Text color="text" dimColor wrap="truncate">
|
||||
Auto-updating…
|
||||
</Text>
|
||||
</Box>
|
||||
</> : autoUpdaterResult?.status === 'success' && showSuccessMessage && updateSemver && <Text color="success" wrap="truncate">
|
||||
</>
|
||||
) : (
|
||||
autoUpdaterResult?.status === 'success' &&
|
||||
showSuccessMessage &&
|
||||
updateSemver && (
|
||||
<Text color="success" wrap="truncate">
|
||||
✓ Update installed · Restart to apply
|
||||
</Text>}
|
||||
{(autoUpdaterResult?.status === 'install_failed' || autoUpdaterResult?.status === 'no_permissions') && <Text color="error" wrap="truncate">
|
||||
</Text>
|
||||
)
|
||||
)}
|
||||
{(autoUpdaterResult?.status === 'install_failed' ||
|
||||
autoUpdaterResult?.status === 'no_permissions') && (
|
||||
<Text color="error" wrap="truncate">
|
||||
✗ Auto-update failed · Try <Text bold>claude doctor</Text> or{' '}
|
||||
<Text bold>
|
||||
{hasLocalInstall ? `cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}` : `npm i -g ${MACRO.PACKAGE_URL}`}
|
||||
{hasLocalInstall
|
||||
? `cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}`
|
||||
: `npm i -g ${MACRO.PACKAGE_URL}`}
|
||||
</Text>
|
||||
</Text>}
|
||||
</Box>;
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user