fix: improve auto-updater for beta releases and DMG installs (#1681)
* fix: improve auto-updater for beta releases and DMG installs - Add allowPrerelease flag when beta channel selected, enabling electron-updater to find pre-releases on GitHub - Detect read-only volumes (DMG) on macOS and show user-friendly warning instead of silent failure - Load dotenv in electron.vite.config.ts for Sentry DSN embedding - Replace unsafe dangerouslySetInnerHTML with ReactMarkdown + rehype-sanitize for secure HTML release notes rendering - Use ES module imports and platform abstraction in app-updater.ts - Add i18n translations for read-only volume warning (en/fr) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review findings — deduplicate channel-setting logic, add consistent error handling Issue 1: Code duplication in channel-setting logic - setUpdateChannelWithDowngradeCheck() now calls setUpdateChannel() internally instead of duplicating the channel-setting code Issue 2: Inconsistent error handling across update components - Added AppUpdateErrorEvent type - Exposed onAppUpdateError event listener in preload API - Added error listeners to AppUpdateNotification.tsx, UpdateBanner.tsx, and AdvancedSettings.tsx for consistent error feedback Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add read-only volume warning to AppUpdateNotification and UpdateBanner Added onAppUpdateReadOnlyVolume event listeners to both components to show appropriate DMG warning when install fails due to read-only volume, matching the behavior in AdvancedSettings.tsx. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review findings for read-only volume warnings - Add missing i18n keys to en/fr dialogs.json and navigation.json - Add optional chaining guards for IPC listeners in AppUpdateNotification - Use setUpdateChannel() in downloadStableVersion() to reset allowPrerelease - Hide success message when read-only volume warning is active Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address follow-up PR review findings - Add optional chaining guards for IPC listeners in AdvancedSettings - Distinguish EROFS from EACCES in read-only volume detection - Remove unused stack field from AppUpdateErrorEvent and IPC payload - Return correct success:false from install IPC when blocked by read-only volume Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: disable install button on read-only warning and reset stale state - Disable install button when showReadOnlyWarning is active in all three components (UpdateBanner, AppUpdateNotification, AdvancedSettings) - Reset showReadOnlyWarning in onAppUpdateAvailable handlers across all three components to clear stale warnings on new update cycles - Revert AdvancedSettings IPC listeners to direct-call pattern matching the existing listeners in the same useEffect block Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: handle unhandled promise, add optional chaining, reset stale error state - Add .catch() to installAppUpdate preload to prevent unhandled rejection - Add optional chaining guards for onAppUpdateReadOnlyVolume and onAppUpdateError in AdvancedSettings useEffect block - Reset appUpdateError in onAppUpdateAvailable and onAppUpdateDownloaded handlers in AdvancedSettings - Remove dismiss button from read-only warning in AdvancedSettings to prevent warning-install-warning cycle - Fix misleading variable name and comment in isRunningFromReadOnlyVolume Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: show download errors to user, reset stale state, unify listener pattern - Show download failure errors to user in AdvancedSettings instead of only logging to console - Reset downloadError and showReadOnlyWarning in onAppUpdateDownloaded handlers in AppUpdateNotification and UpdateBanner - Add releaseNotes and releaseDate to AppUpdateDownloadedEvent type to match the actual IPC payload from main process - Remove unnecessary optional chaining guards from IPC listeners — all methods are required in ElectronAPI interface, use direct calls consistently across all three components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: remove /Volumes/ false positive and unify UpdateBanner listener guards - Remove /Volumes/ prefix early return in isRunningFromReadOnlyVolume() since writable external drives also mount under /Volumes/ on macOS; rely solely on accessSync EROFS check which handles all cases correctly - Remove pre-existing optional chaining guards from UpdateBanner IPC listeners to match the direct-call pattern in AppUpdateNotification and AdvancedSettings — all methods are required in ElectronAPI Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: simplify install IPC handler, reset stale state in poll check, unify API access pattern - Simplify APP_UPDATE_INSTALL handler to fire-and-forget since failure is communicated via APP_UPDATE_READONLY_VOLUME event, not IPC return value - Reset showReadOnlyWarning and downloadError in checkForUpdate poll callback when a new version is detected - Remove unnecessary optional chaining from UpdateBanner electronAPI calls — all methods are required in ElectronAPI interface Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use quitAndInstall() return value in IPC handler Return success:false when quitAndInstall() returns false (read-only volume) instead of unconditionally reporting success. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add missing downloadError i18n key, document fire-and-forget IPC, remove stale optional chaining - Add updates.downloadError key to en/fr settings.json so AdvancedSettings shows translated error text instead of raw key path - Document that APP_UPDATE_INSTALL handler is fire-and-forget with failure communicated via APP_UPDATE_READONLY_VOLUME event, not IPC return - Remove unnecessary optional chaining on electronAPI.openExternal in AppUpdateNotification to match direct-call pattern used elsewhere Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add safe link handler to ReleaseNotesRenderer, clamp progress, clear stale state - Add safe link components to ReleaseNotesRenderer in AdvancedSettings so external links open in default browser instead of navigating the Electron window - Clamp download progress percent to [0, 100] in UpdateBanner CSS width - Reset downloadProgress to null in onAppUpdateError handlers across all three components to match onAppUpdateDownloaded pattern Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Test User <test@example.com>
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { resolve } from 'path';
|
||||
import { config as dotenvConfig } from 'dotenv';
|
||||
|
||||
// Load .env file for build-time constants (Sentry DSN, etc.)
|
||||
dotenvConfig({ path: resolve(__dirname, '.env') });
|
||||
|
||||
/**
|
||||
* Sentry configuration embedded at build time.
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
"react-i18next": "^16.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-resizable-panels": "^4.2.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"semver": "^7.7.3",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
* - APP_UPDATE_ERROR: Error during update process
|
||||
*/
|
||||
|
||||
import { accessSync, constants as fsConstants } from 'fs';
|
||||
import path from 'path';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import type { UpdateInfo } from 'electron-updater';
|
||||
import { app, net } from 'electron';
|
||||
@@ -24,6 +26,7 @@ import type { BrowserWindow } from 'electron';
|
||||
import { IPC_CHANNELS } from '../shared/constants';
|
||||
import type { AppUpdateInfo } from '../shared/types';
|
||||
import { compareVersions } from './updater/version-manager';
|
||||
import { isMacOS } from './platform';
|
||||
|
||||
// GitHub repo info for API calls
|
||||
const GITHUB_OWNER = 'AndyMik90';
|
||||
@@ -91,10 +94,13 @@ function formatReleaseNotes(releaseNotes: UpdateInfo['releaseNotes']): string |
|
||||
*/
|
||||
export function setUpdateChannel(channel: UpdateChannel): void {
|
||||
autoUpdater.channel = channel;
|
||||
// Enable pre-release scanning when beta channel is selected
|
||||
// This allows electron-updater to find beta releases on GitHub
|
||||
autoUpdater.allowPrerelease = channel === 'beta';
|
||||
// Clear any downloaded update info when channel changes to prevent showing
|
||||
// an Install button for an update from a different channel
|
||||
downloadedUpdateInfo = null;
|
||||
console.warn(`[app-updater] Update channel set to: ${channel}`);
|
||||
console.warn(`[app-updater] Update channel set to: ${channel}, allowPrerelease: ${autoUpdater.allowPrerelease}`);
|
||||
}
|
||||
|
||||
// Enable more verbose logging in debug mode
|
||||
@@ -187,8 +193,7 @@ export function initializeAppUpdater(window: BrowserWindow, betaUpdates = false)
|
||||
console.error('[app-updater] Update error:', error);
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send(IPC_CHANNELS.APP_UPDATE_ERROR, {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -293,13 +298,57 @@ export async function downloadUpdate(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the app is running from a read-only volume (e.g., DMG on macOS)
|
||||
* Returns true if the app cannot be updated in place
|
||||
*/
|
||||
function isRunningFromReadOnlyVolume(): boolean {
|
||||
if (!isMacOS()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const appPath = app.getAppPath();
|
||||
|
||||
// Check if the filesystem is read-only by testing write access.
|
||||
// We don't use a /Volumes/ prefix check because writable external drives
|
||||
// (USB, external SSDs) are also mounted under /Volumes/ on macOS.
|
||||
try {
|
||||
// Navigate from app.asar to the Contents/ directory (app.asar -> Resources -> Contents)
|
||||
const contentsPath = path.resolve(appPath, '..', '..');
|
||||
|
||||
// Try to check if we can write to the app bundle's parent directory
|
||||
accessSync(path.dirname(contentsPath), fsConstants.W_OK);
|
||||
return false;
|
||||
} catch (error: unknown) {
|
||||
// Only treat as read-only if the filesystem itself is read-only (EROFS).
|
||||
// Permission errors (EACCES) in managed/enterprise environments should not
|
||||
// block updates — the updater may still have elevated access.
|
||||
const code = error instanceof Error ? (error as NodeJS.ErrnoException).code : undefined;
|
||||
return code === 'EROFS';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quit and install update
|
||||
* Called from IPC handler when user confirms installation
|
||||
* Returns false if running from a read-only volume (update cannot proceed)
|
||||
*/
|
||||
export function quitAndInstall(): void {
|
||||
export function quitAndInstall(): boolean {
|
||||
// Check if running from read-only volume before attempting install
|
||||
if (isRunningFromReadOnlyVolume()) {
|
||||
console.warn('[app-updater] Cannot install: running from read-only volume');
|
||||
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send(IPC_CHANNELS.APP_UPDATE_READONLY_VOLUME, {
|
||||
appPath: app.getAppPath()
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
console.warn('[app-updater] Quitting and installing update');
|
||||
autoUpdater.quitAndInstall(false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -483,11 +532,8 @@ export async function setUpdateChannelWithDowngradeCheck(
|
||||
channel: UpdateChannel,
|
||||
triggerDowngradeCheck = false
|
||||
): Promise<AppUpdateInfo | null> {
|
||||
autoUpdater.channel = channel;
|
||||
// Clear any downloaded update info when channel changes to prevent showing
|
||||
// an Install button for an update from a different channel
|
||||
downloadedUpdateInfo = null;
|
||||
console.warn(`[app-updater] Update channel set to: ${channel}`);
|
||||
// Use the shared channel-setting function to avoid code duplication
|
||||
setUpdateChannel(channel);
|
||||
|
||||
// If switching to stable and downgrade check requested, look for stable version
|
||||
if (channel === 'latest' && triggerDowngradeCheck) {
|
||||
@@ -509,8 +555,8 @@ export async function setUpdateChannelWithDowngradeCheck(
|
||||
* Uses electron-updater with allowDowngrade enabled to download older stable versions
|
||||
*/
|
||||
export async function downloadStableVersion(): Promise<void> {
|
||||
// Switch to stable channel
|
||||
autoUpdater.channel = 'latest';
|
||||
// Switch to stable channel (resets allowPrerelease and clears downloadedUpdateInfo)
|
||||
setUpdateChannel('latest');
|
||||
// Enable downgrade to allow downloading older versions (e.g., stable when on beta)
|
||||
autoUpdater.allowDowngrade = true;
|
||||
console.warn('[app-updater] Downloading stable version (allowDowngrade=true)...');
|
||||
|
||||
@@ -95,6 +95,10 @@ export function registerAppUpdateHandlers(): void {
|
||||
IPC_CHANNELS.APP_UPDATE_INSTALL,
|
||||
async (): Promise<IPCResult> => {
|
||||
try {
|
||||
// quitAndInstall() returns false if blocked by read-only volume,
|
||||
// but the user is notified via APP_UPDATE_READONLY_VOLUME event instead.
|
||||
// The preload fires this as fire-and-forget, so the return value is
|
||||
// only consumed by the .catch() handler for unexpected errors.
|
||||
quitAndInstall();
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
AppUpdateProgress,
|
||||
AppUpdateAvailableEvent,
|
||||
AppUpdateDownloadedEvent,
|
||||
AppUpdateErrorEvent,
|
||||
IPCResult
|
||||
} from '../../shared/types';
|
||||
import { createIpcListener, invokeIpc, IpcListenerCleanup } from './modules/ipc-utils';
|
||||
@@ -34,6 +35,12 @@ export interface AppUpdateAPI {
|
||||
onAppUpdateStableDowngrade: (
|
||||
callback: (info: AppUpdateInfo) => void
|
||||
) => IpcListenerCleanup;
|
||||
onAppUpdateReadOnlyVolume: (
|
||||
callback: (info: { appPath: string }) => void
|
||||
) => IpcListenerCleanup;
|
||||
onAppUpdateError: (
|
||||
callback: (error: AppUpdateErrorEvent) => void
|
||||
) => IpcListenerCleanup;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,7 +58,9 @@ export const createAppUpdateAPI = (): AppUpdateAPI => ({
|
||||
invokeIpc(IPC_CHANNELS.APP_UPDATE_DOWNLOAD_STABLE),
|
||||
|
||||
installAppUpdate: (): void => {
|
||||
invokeIpc(IPC_CHANNELS.APP_UPDATE_INSTALL);
|
||||
invokeIpc(IPC_CHANNELS.APP_UPDATE_INSTALL).catch((err) =>
|
||||
console.error('[app-update] Install failed:', err)
|
||||
);
|
||||
},
|
||||
|
||||
getAppVersion: (): Promise<string> =>
|
||||
@@ -79,5 +88,15 @@ export const createAppUpdateAPI = (): AppUpdateAPI => ({
|
||||
onAppUpdateStableDowngrade: (
|
||||
callback: (info: AppUpdateInfo) => void
|
||||
): IpcListenerCleanup =>
|
||||
createIpcListener(IPC_CHANNELS.APP_UPDATE_STABLE_DOWNGRADE, callback)
|
||||
createIpcListener(IPC_CHANNELS.APP_UPDATE_STABLE_DOWNGRADE, callback),
|
||||
|
||||
onAppUpdateReadOnlyVolume: (
|
||||
callback: (info: { appPath: string }) => void
|
||||
): IpcListenerCleanup =>
|
||||
createIpcListener(IPC_CHANNELS.APP_UPDATE_READONLY_VOLUME, callback),
|
||||
|
||||
onAppUpdateError: (
|
||||
callback: (error: AppUpdateErrorEvent) => void
|
||||
): IpcListenerCleanup =>
|
||||
createIpcListener(IPC_CHANNELS.APP_UPDATE_ERROR, callback)
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Download, RefreshCw, CheckCircle2, AlertCircle, ExternalLink } from "lucide-react";
|
||||
import { Download, RefreshCw, CheckCircle2, AlertCircle, AlertTriangle, ExternalLink } from "lucide-react";
|
||||
import ReactMarkdown, { type Components } from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import rehypeSanitize from "rehype-sanitize";
|
||||
import { Button } from "./ui/button";
|
||||
import { Progress } from "./ui/progress";
|
||||
import {
|
||||
@@ -70,6 +72,7 @@ export function AppUpdateNotification() {
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [isDownloaded, setIsDownloaded] = useState(false);
|
||||
const [downloadError, setDownloadError] = useState<string | null>(null);
|
||||
const [showReadOnlyWarning, setShowReadOnlyWarning] = useState(false);
|
||||
|
||||
// Create markdown components with translated accessibility text
|
||||
const markdownComponents: Components = useMemo(
|
||||
@@ -88,6 +91,7 @@ export function AppUpdateNotification() {
|
||||
setIsDownloaded(false);
|
||||
setDownloadProgress(null);
|
||||
setDownloadError(null);
|
||||
setShowReadOnlyWarning(false);
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
@@ -99,6 +103,8 @@ export function AppUpdateNotification() {
|
||||
setIsDownloading(false);
|
||||
setIsDownloaded(true);
|
||||
setDownloadProgress(null);
|
||||
setDownloadError(null);
|
||||
setShowReadOnlyWarning(false);
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
@@ -113,6 +119,26 @@ export function AppUpdateNotification() {
|
||||
return cleanup;
|
||||
}, []);
|
||||
|
||||
// Listen for update errors (e.g., install failures)
|
||||
useEffect(() => {
|
||||
const cleanup = window.electronAPI.onAppUpdateError((error) => {
|
||||
setDownloadError(error.message);
|
||||
setIsDownloading(false);
|
||||
setDownloadProgress(null);
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
}, []);
|
||||
|
||||
// Listen for read-only volume warning (when trying to install from DMG on macOS)
|
||||
useEffect(() => {
|
||||
const cleanup = window.electronAPI.onAppUpdateReadOnlyVolume(() => {
|
||||
setShowReadOnlyWarning(true);
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
}, []);
|
||||
|
||||
const handleDownload = async () => {
|
||||
setIsDownloading(true);
|
||||
setDownloadError(null);
|
||||
@@ -189,7 +215,11 @@ export function AppUpdateNotification() {
|
||||
{updateInfo.releaseNotes && (
|
||||
<div className="bg-background rounded-lg p-4 max-h-64 overflow-y-auto border border-border/50">
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeRaw, rehypeSanitize]}
|
||||
components={markdownComponents}
|
||||
>
|
||||
{updateInfo.releaseNotes}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
@@ -201,7 +231,7 @@ export function AppUpdateNotification() {
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="w-full text-xs text-muted-foreground gap-1"
|
||||
onClick={() => window.electronAPI?.openExternal?.(CLAUDE_CODE_CHANGELOG_URL)}
|
||||
onClick={() => window.electronAPI.openExternal(CLAUDE_CODE_CHANGELOG_URL)}
|
||||
aria-label={t(
|
||||
"dialogs:appUpdate.claudeCodeChangelogAriaLabel",
|
||||
"View Claude Code Changelog (opens in new window)"
|
||||
@@ -238,8 +268,23 @@ export function AppUpdateNotification() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Read-Only Volume Warning (DMG install on macOS) */}
|
||||
{showReadOnlyWarning && (
|
||||
<div className="flex items-start gap-3 text-sm text-warning bg-warning/10 border border-warning/30 rounded-lg p-3">
|
||||
<AlertTriangle className="h-5 w-5 shrink-0 mt-0.5" />
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium">
|
||||
{t("dialogs:appUpdate.readOnlyVolumeTitle", "Cannot install from disk image")}
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
{t("dialogs:appUpdate.readOnlyVolumeDescription", "Please move Auto Claude to your Applications folder before updating.")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Downloaded Success */}
|
||||
{isDownloaded && (
|
||||
{isDownloaded && !showReadOnlyWarning && (
|
||||
<div className="flex items-center gap-3 text-sm text-success bg-success/10 border border-success/30 rounded-lg p-3">
|
||||
<CheckCircle2 className="h-5 w-5 shrink-0" />
|
||||
<span>
|
||||
@@ -260,7 +305,7 @@ export function AppUpdateNotification() {
|
||||
</Button>
|
||||
|
||||
{isDownloaded ? (
|
||||
<Button onClick={handleInstall}>
|
||||
<Button onClick={handleInstall} disabled={showReadOnlyWarning}>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
{t("dialogs:appUpdate.installAndRestart", "Install and Restart")}
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Download, X, RefreshCw } from "lucide-react";
|
||||
import { Download, X, RefreshCw, AlertTriangle } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import { cn } from "../lib/utils";
|
||||
import type { AppUpdateAvailableEvent, AppUpdateProgress } from "../../shared/types";
|
||||
@@ -25,6 +25,7 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
const [downloadProgress, setDownloadProgress] = useState<AppUpdateProgress | null>(null);
|
||||
const [isDownloaded, setIsDownloaded] = useState(false);
|
||||
const [downloadError, setDownloadError] = useState<string | null>(null);
|
||||
const [showReadOnlyWarning, setShowReadOnlyWarning] = useState(false);
|
||||
|
||||
// Ref to track current version for stable callbacks
|
||||
const currentVersionRef = useRef<string | null>(null);
|
||||
@@ -32,18 +33,16 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
// Check for updates
|
||||
const checkForUpdate = useCallback(async () => {
|
||||
try {
|
||||
if (!window.electronAPI?.checkAppUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.electronAPI.checkAppUpdate();
|
||||
if (result.success && result.data) {
|
||||
const newVersion = result.data.version;
|
||||
// New update available - show banner (unless same version already dismissed)
|
||||
if (currentVersionRef.current !== newVersion) {
|
||||
setIsDismissed(false);
|
||||
// Reset downloaded state when a newer version is found
|
||||
// Reset stale state when a newer version is found
|
||||
setIsDownloaded(false);
|
||||
setShowReadOnlyWarning(false);
|
||||
setDownloadError(null);
|
||||
currentVersionRef.current = newVersion;
|
||||
}
|
||||
setUpdateInfo({
|
||||
@@ -61,9 +60,6 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
useEffect(() => {
|
||||
const checkDownloaded = async () => {
|
||||
try {
|
||||
if (!window.electronAPI?.getDownloadedAppUpdate) {
|
||||
return;
|
||||
}
|
||||
const result = await window.electronAPI.getDownloadedAppUpdate();
|
||||
if (result.success && result.data) {
|
||||
currentVersionRef.current = result.data.version;
|
||||
@@ -94,10 +90,6 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
|
||||
// Listen for push notifications about updates
|
||||
useEffect(() => {
|
||||
if (!window.electronAPI?.onAppUpdateAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanup = window.electronAPI.onAppUpdateAvailable((info) => {
|
||||
// New update notification - reset dismiss state if new version
|
||||
if (currentVersionRef.current !== info.version) {
|
||||
@@ -109,6 +101,7 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
setIsDownloaded(false);
|
||||
setDownloadProgress(null);
|
||||
setDownloadError(null);
|
||||
setShowReadOnlyWarning(false);
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
@@ -116,10 +109,6 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
|
||||
// Listen for download progress
|
||||
useEffect(() => {
|
||||
if (!window.electronAPI?.onAppUpdateProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanup = window.electronAPI.onAppUpdateProgress((progress) => {
|
||||
setDownloadProgress(progress);
|
||||
});
|
||||
@@ -129,14 +118,32 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
|
||||
// Listen for download completed
|
||||
useEffect(() => {
|
||||
if (!window.electronAPI?.onAppUpdateDownloaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanup = window.electronAPI.onAppUpdateDownloaded(() => {
|
||||
setIsDownloading(false);
|
||||
setIsDownloaded(true);
|
||||
setDownloadProgress(null);
|
||||
setDownloadError(null);
|
||||
setShowReadOnlyWarning(false);
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
}, []);
|
||||
|
||||
// Listen for update errors (e.g., install failures)
|
||||
useEffect(() => {
|
||||
const cleanup = window.electronAPI.onAppUpdateError((error) => {
|
||||
setDownloadError(error.message);
|
||||
setIsDownloading(false);
|
||||
setDownloadProgress(null);
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
}, []);
|
||||
|
||||
// Listen for read-only volume warning (when trying to install from DMG on macOS)
|
||||
useEffect(() => {
|
||||
const cleanup = window.electronAPI.onAppUpdateReadOnlyVolume(() => {
|
||||
setShowReadOnlyWarning(true);
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
@@ -146,7 +153,7 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
const handleUpdate = async () => {
|
||||
if (isDownloaded) {
|
||||
// Already downloaded - just install
|
||||
window.electronAPI?.installAppUpdate?.();
|
||||
window.electronAPI.installAppUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,11 +162,6 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
setDownloadError(null);
|
||||
|
||||
try {
|
||||
if (!window.electronAPI?.downloadAppUpdate) {
|
||||
setDownloadError(t("navigation:updateBanner.downloadError"));
|
||||
setIsDownloading(false);
|
||||
return;
|
||||
}
|
||||
const result = await window.electronAPI.downloadAppUpdate();
|
||||
if (!result.success) {
|
||||
setDownloadError(result.error || t("navigation:updateBanner.downloadError"));
|
||||
@@ -221,7 +223,7 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
<div className="h-1 w-full bg-muted rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-info transition-all duration-300"
|
||||
style={{ width: `${downloadProgress.percent}%` }}
|
||||
style={{ width: `${Math.min(100, Math.max(0, downloadProgress.percent))}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -232,12 +234,20 @@ export function UpdateBanner({ className }: UpdateBannerProps) {
|
||||
<p className="text-[10px] text-destructive mb-2">{downloadError}</p>
|
||||
)}
|
||||
|
||||
{/* Read-only volume warning (DMG install on macOS) */}
|
||||
{showReadOnlyWarning && (
|
||||
<div className="flex items-start gap-2 text-[10px] text-warning bg-warning/10 border border-warning/30 rounded p-2 mb-2">
|
||||
<AlertTriangle className="h-3 w-3 shrink-0 mt-0.5" />
|
||||
<span>{t("navigation:updateBanner.readOnlyVolumeWarning", "Move to Applications folder to update")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action button */}
|
||||
<Button
|
||||
size="sm"
|
||||
className="w-full h-7 text-xs gap-1.5"
|
||||
onClick={handleUpdate}
|
||||
disabled={isDownloading}
|
||||
disabled={isDownloading || showReadOnlyWarning}
|
||||
>
|
||||
{isDownloading ? (
|
||||
<>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
RefreshCw,
|
||||
@@ -6,8 +6,14 @@ import {
|
||||
Download,
|
||||
Sparkles,
|
||||
ArrowDownToLine,
|
||||
X
|
||||
X,
|
||||
AlertTriangle,
|
||||
AlertCircle
|
||||
} from 'lucide-react';
|
||||
import ReactMarkdown, { type Components } from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import rehypeSanitize from 'rehype-sanitize';
|
||||
import { Button } from '../ui/button';
|
||||
import { Label } from '../ui/label';
|
||||
import { Switch } from '../ui/switch';
|
||||
@@ -22,39 +28,38 @@ import type {
|
||||
} from '../../../shared/types';
|
||||
|
||||
/**
|
||||
* Simple markdown renderer for release notes
|
||||
* Handles: headers, bold, lists, line breaks
|
||||
* Release notes renderer that handles both HTML and markdown input.
|
||||
* GitHub release notes come as HTML, so we detect and handle both formats.
|
||||
* Uses ReactMarkdown with rehype-sanitize to prevent XSS attacks.
|
||||
*/
|
||||
function ReleaseNotesRenderer({ markdown }: { markdown: string }) {
|
||||
const html = useMemo(() => {
|
||||
const result = markdown
|
||||
// Escape HTML
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
// Headers (### Header -> <h3>)
|
||||
.replace(/^### (.+)$/gm, '<h4 class="text-sm font-semibold text-foreground mt-3 mb-1.5 first:mt-0">$1</h4>')
|
||||
.replace(/^## (.+)$/gm, '<h3 class="text-sm font-semibold text-foreground mt-3 mb-1.5 first:mt-0">$1</h3>')
|
||||
// Bold (**text** -> <strong>)
|
||||
.replace(/\*\*([^*]+)\*\*/g, '<strong class="text-foreground font-medium">$1</strong>')
|
||||
// Inline code (`code` -> <code>)
|
||||
.replace(/`([^`]+)`/g, '<code class="px-1 py-0.5 bg-muted rounded text-xs">$1</code>')
|
||||
// List items (- item -> <li>)
|
||||
.replace(/^- (.+)$/gm, '<li class="ml-4 text-muted-foreground before:content-[\'•\'] before:mr-2 before:text-muted-foreground/60">$1</li>')
|
||||
// Wrap consecutive list items
|
||||
.replace(/(<li[^>]*>.*?<\/li>\n?)+/g, '<ul class="space-y-1 my-2">$&</ul>')
|
||||
// Line breaks for remaining lines
|
||||
.replace(/\n\n/g, '<div class="h-2"></div>')
|
||||
.replace(/\n/g, '<br/>');
|
||||
|
||||
return result;
|
||||
}, [markdown]);
|
||||
/** Safe link component that opens external URLs in the default browser */
|
||||
const safeMarkdownComponents: Components = {
|
||||
a: ({ href, children, ...props }) => {
|
||||
const isExternal = href?.startsWith('http://') || href?.startsWith('https://');
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
{...props}
|
||||
{...(isExternal && { target: '_blank', rel: 'noopener noreferrer' })}
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function ReleaseNotesRenderer({ content }: { content: string }) {
|
||||
return (
|
||||
<div
|
||||
className="text-sm text-muted-foreground leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
<div className="text-sm text-muted-foreground leading-relaxed prose prose-sm dark:prose-invert max-w-none [&_ul]:ml-4 [&_ol]:ml-4">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeRaw, rehypeSanitize]}
|
||||
components={safeMarkdownComponents}
|
||||
>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,6 +84,10 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
const [isAppUpdateDownloaded, setIsAppUpdateDownloaded] = useState(false);
|
||||
// Stable downgrade state (shown when user turns off beta while on prerelease)
|
||||
const [stableDowngradeInfo, setStableDowngradeInfo] = useState<AppUpdateInfo | null>(null);
|
||||
// Read-only volume warning (shown when trying to install from DMG)
|
||||
const [showReadOnlyWarning, setShowReadOnlyWarning] = useState(false);
|
||||
// General update error state
|
||||
const [appUpdateError, setAppUpdateError] = useState<string | null>(null);
|
||||
|
||||
// Check for updates on mount, including any already-downloaded updates
|
||||
useEffect(() => {
|
||||
@@ -141,6 +150,8 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
const cleanupAvailable = window.electronAPI.onAppUpdateAvailable((info) => {
|
||||
setAppUpdateInfo(info);
|
||||
setIsCheckingAppUpdate(false);
|
||||
setShowReadOnlyWarning(false);
|
||||
setAppUpdateError(null);
|
||||
});
|
||||
|
||||
const cleanupDownloaded = window.electronAPI.onAppUpdateDownloaded((info) => {
|
||||
@@ -150,6 +161,9 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
setAppDownloadProgress(null);
|
||||
// Clear downgrade info if any update downloaded
|
||||
setStableDowngradeInfo(null);
|
||||
// Reset read-only warning when a new update is downloaded
|
||||
setShowReadOnlyWarning(false);
|
||||
setAppUpdateError(null);
|
||||
});
|
||||
|
||||
const cleanupProgress = window.electronAPI.onAppUpdateProgress((progress) => {
|
||||
@@ -161,11 +175,25 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
setStableDowngradeInfo(info);
|
||||
});
|
||||
|
||||
// Listen for read-only volume warning (when trying to install from DMG)
|
||||
const cleanupReadOnlyVolume = window.electronAPI.onAppUpdateReadOnlyVolume(() => {
|
||||
setShowReadOnlyWarning(true);
|
||||
});
|
||||
|
||||
// Listen for update errors (e.g., install failures)
|
||||
const cleanupError = window.electronAPI.onAppUpdateError((error) => {
|
||||
setAppUpdateError(error.message);
|
||||
setIsDownloadingAppUpdate(false);
|
||||
setAppDownloadProgress(null);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cleanupAvailable();
|
||||
cleanupDownloaded();
|
||||
cleanupProgress();
|
||||
cleanupStableDowngrade();
|
||||
cleanupReadOnlyVolume();
|
||||
cleanupError();
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -188,15 +216,17 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
|
||||
const handleDownloadAppUpdate = async () => {
|
||||
setIsDownloadingAppUpdate(true);
|
||||
setAppUpdateError(null);
|
||||
try {
|
||||
const result = await window.electronAPI.downloadAppUpdate();
|
||||
if (!result.success) {
|
||||
console.error('Failed to download app update:', result.error);
|
||||
setAppUpdateError(result.error || t('updates.downloadError'));
|
||||
setIsDownloadingAppUpdate(false);
|
||||
}
|
||||
// Note: Success case is handled by the onAppUpdateDownloaded event listener
|
||||
} catch (err) {
|
||||
console.error('Failed to download app update:', err);
|
||||
setAppUpdateError(t('updates.downloadError'));
|
||||
setIsDownloadingAppUpdate(false);
|
||||
}
|
||||
};
|
||||
@@ -207,16 +237,18 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
|
||||
const handleDownloadStableVersion = async () => {
|
||||
setIsDownloadingAppUpdate(true);
|
||||
setAppUpdateError(null);
|
||||
try {
|
||||
// Use dedicated stable download API with allowDowngrade enabled
|
||||
const result = await window.electronAPI.downloadStableUpdate();
|
||||
if (!result.success) {
|
||||
console.error('Failed to download stable version:', result.error);
|
||||
setAppUpdateError(result.error || t('updates.downloadError'));
|
||||
setIsDownloadingAppUpdate(false);
|
||||
}
|
||||
// Note: Success case is handled by the onAppUpdateDownloaded event listener
|
||||
} catch (err) {
|
||||
console.error('Failed to download stable version:', err);
|
||||
setAppUpdateError(t('updates.downloadError'));
|
||||
setIsDownloadingAppUpdate(false);
|
||||
}
|
||||
};
|
||||
@@ -304,7 +336,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
{/* Release Notes */}
|
||||
{appUpdateInfo?.releaseNotes && (
|
||||
<div className="bg-background rounded-lg p-4 max-h-48 overflow-y-auto border border-border/50">
|
||||
<ReleaseNotesRenderer markdown={appUpdateInfo.releaseNotes} />
|
||||
<ReleaseNotesRenderer content={appUpdateInfo.releaseNotes} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -324,18 +356,37 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Update Error */}
|
||||
{appUpdateError && (
|
||||
<div className="flex items-center gap-3 text-sm text-destructive bg-destructive/10 border border-destructive/30 rounded-lg p-3">
|
||||
<AlertCircle className="h-5 w-5 shrink-0" />
|
||||
<span>{appUpdateError}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Downloaded Success */}
|
||||
{isAppUpdateDownloaded && (
|
||||
{isAppUpdateDownloaded && !showReadOnlyWarning && (
|
||||
<div className="flex items-center gap-3 text-sm text-success bg-success/10 border border-success/30 rounded-lg p-3">
|
||||
<CheckCircle2 className="h-5 w-5 shrink-0" />
|
||||
<span>{t('updates.updateDownloaded')}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Read-Only Volume Warning */}
|
||||
{showReadOnlyWarning && (
|
||||
<div className="flex items-start gap-3 text-sm text-warning bg-warning/10 border border-warning/30 rounded-lg p-3">
|
||||
<AlertTriangle className="h-5 w-5 shrink-0 mt-0.5" />
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium text-warning">{t('updates.readOnlyVolumeTitle')}</p>
|
||||
<p className="text-muted-foreground">{t('updates.readOnlyVolumeDescription')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-3">
|
||||
{isAppUpdateDownloaded ? (
|
||||
<Button onClick={handleInstallAppUpdate}>
|
||||
<Button onClick={handleInstallAppUpdate} disabled={showReadOnlyWarning}>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
{t('updates.installAndRestart')}
|
||||
</Button>
|
||||
@@ -445,7 +496,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
|
||||
{/* Release Notes */}
|
||||
{stableDowngradeInfo.releaseNotes && (
|
||||
<div className="bg-background rounded-lg p-4 max-h-48 overflow-y-auto border border-border/50">
|
||||
<ReleaseNotesRenderer markdown={stableDowngradeInfo.releaseNotes} />
|
||||
<ReleaseNotesRenderer content={stableDowngradeInfo.releaseNotes} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -54,5 +54,7 @@ export const settingsMock = {
|
||||
onAppUpdateAvailable: () => () => {},
|
||||
onAppUpdateDownloaded: () => () => {},
|
||||
onAppUpdateProgress: () => () => {},
|
||||
onAppUpdateStableDowngrade: () => () => {}
|
||||
onAppUpdateStableDowngrade: () => () => {},
|
||||
onAppUpdateReadOnlyVolume: () => () => {},
|
||||
onAppUpdateError: () => () => {}
|
||||
};
|
||||
|
||||
@@ -524,6 +524,7 @@ export const IPC_CHANNELS = {
|
||||
APP_UPDATE_PROGRESS: 'app-update:progress',
|
||||
APP_UPDATE_ERROR: 'app-update:error',
|
||||
APP_UPDATE_STABLE_DOWNGRADE: 'app-update:stable-downgrade', // Stable version available for downgrade from beta
|
||||
APP_UPDATE_READONLY_VOLUME: 'app-update:readonly-volume', // App running from read-only volume (DMG), needs to be moved
|
||||
|
||||
// Release operations
|
||||
RELEASE_SUGGEST_VERSION: 'release:suggestVersion',
|
||||
|
||||
@@ -162,7 +162,9 @@
|
||||
"updateDownloaded": "Update downloaded successfully! Click Install to restart and apply the update.",
|
||||
"downloadError": "Failed to download update",
|
||||
"claudeCodeChangelog": "View Claude Code Changelog",
|
||||
"claudeCodeChangelogAriaLabel": "View Claude Code Changelog (opens in new window)"
|
||||
"claudeCodeChangelogAriaLabel": "View Claude Code Changelog (opens in new window)",
|
||||
"readOnlyVolumeTitle": "Cannot install from disk image",
|
||||
"readOnlyVolumeDescription": "Please move Auto Claude to your Applications folder before updating."
|
||||
},
|
||||
"versionWarning": {
|
||||
"title": "Action Required",
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
"installAndRestart": "Install and Restart",
|
||||
"downloading": "Downloading...",
|
||||
"dismiss": "Dismiss",
|
||||
"downloadError": "Failed to download update"
|
||||
"downloadError": "Failed to download update",
|
||||
"readOnlyVolumeWarning": "Move to Applications folder to update"
|
||||
},
|
||||
"claudeCode": {
|
||||
"checking": "Checking Claude Code...",
|
||||
|
||||
@@ -302,7 +302,10 @@
|
||||
"stableDowngradeAvailable": "Stable Version Available",
|
||||
"stableDowngradeDescription": "You're currently on a beta version. Since you've disabled beta updates, you can switch to the latest stable release.",
|
||||
"stableVersion": "Stable Version",
|
||||
"downloadStableVersion": "Download Stable Version"
|
||||
"downloadStableVersion": "Download Stable Version",
|
||||
"readOnlyVolumeTitle": "Cannot Install from Disk Image",
|
||||
"readOnlyVolumeDescription": "Auto Claude is running from a read-only disk image (DMG). Please drag the app to your Applications folder and relaunch it from there to install updates.",
|
||||
"downloadError": "Failed to download update"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
|
||||
@@ -162,7 +162,9 @@
|
||||
"updateDownloaded": "Mise à jour téléchargée avec succès ! Cliquez sur Installer pour redémarrer et appliquer la mise à jour.",
|
||||
"downloadError": "Échec du téléchargement de la mise à jour",
|
||||
"claudeCodeChangelog": "Voir le journal des modifications Claude Code",
|
||||
"claudeCodeChangelogAriaLabel": "Voir le journal des modifications Claude Code (s'ouvre dans une nouvelle fenêtre)"
|
||||
"claudeCodeChangelogAriaLabel": "Voir le journal des modifications Claude Code (s'ouvre dans une nouvelle fenêtre)",
|
||||
"readOnlyVolumeTitle": "Impossible d'installer depuis une image disque",
|
||||
"readOnlyVolumeDescription": "Veuillez déplacer Auto Claude dans votre dossier Applications avant de mettre à jour."
|
||||
},
|
||||
"versionWarning": {
|
||||
"title": "Action requise",
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
"installAndRestart": "Installer et redémarrer",
|
||||
"downloading": "Téléchargement...",
|
||||
"dismiss": "Ignorer",
|
||||
"downloadError": "Échec du téléchargement de la mise à jour"
|
||||
"downloadError": "Échec du téléchargement de la mise à jour",
|
||||
"readOnlyVolumeWarning": "Déplacez l'app dans le dossier Applications pour mettre à jour"
|
||||
},
|
||||
"claudeCode": {
|
||||
"checking": "Vérification de Claude Code...",
|
||||
|
||||
@@ -302,7 +302,10 @@
|
||||
"stableDowngradeAvailable": "Version stable disponible",
|
||||
"stableDowngradeDescription": "Vous êtes actuellement sur une version bêta. Comme vous avez désactivé les mises à jour bêta, vous pouvez passer à la dernière version stable.",
|
||||
"stableVersion": "Version stable",
|
||||
"downloadStableVersion": "Télécharger la version stable"
|
||||
"downloadStableVersion": "Télécharger la version stable",
|
||||
"readOnlyVolumeTitle": "Impossible d'installer depuis l'image disque",
|
||||
"readOnlyVolumeDescription": "Auto Claude s'exécute depuis une image disque en lecture seule (DMG). Veuillez glisser l'application dans votre dossier Applications et la relancer depuis cet emplacement pour installer les mises à jour.",
|
||||
"downloadError": "Échec du téléchargement de la mise à jour"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
|
||||
@@ -22,4 +22,10 @@ export interface AppUpdateAvailableEvent {
|
||||
|
||||
export interface AppUpdateDownloadedEvent {
|
||||
version: string;
|
||||
releaseNotes?: string;
|
||||
releaseDate?: string;
|
||||
}
|
||||
|
||||
export interface AppUpdateErrorEvent {
|
||||
message: string;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ import type {
|
||||
TerminalProfileChangedEvent
|
||||
} from './agent';
|
||||
import type { AppSettings, SourceEnvConfig, SourceEnvCheckResult } from './settings';
|
||||
import type { AppUpdateInfo, AppUpdateProgress, AppUpdateAvailableEvent, AppUpdateDownloadedEvent } from './app-update';
|
||||
import type { AppUpdateInfo, AppUpdateProgress, AppUpdateAvailableEvent, AppUpdateDownloadedEvent, AppUpdateErrorEvent } from './app-update';
|
||||
import type {
|
||||
ChangelogTask,
|
||||
TaskSpecContent,
|
||||
@@ -710,6 +710,12 @@ export interface ElectronAPI {
|
||||
onAppUpdateStableDowngrade: (
|
||||
callback: (info: AppUpdateInfo) => void
|
||||
) => () => void;
|
||||
onAppUpdateReadOnlyVolume: (
|
||||
callback: (info: { appPath: string }) => void
|
||||
) => () => void;
|
||||
onAppUpdateError: (
|
||||
callback: (error: AppUpdateErrorEvent) => void
|
||||
) => () => void;
|
||||
|
||||
// Shell operations
|
||||
openExternal: (url: string) => Promise<void>;
|
||||
|
||||
Generated
+190
-35
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "auto-claude",
|
||||
"version": "2.7.6-beta.1",
|
||||
"version": "2.7.6-beta.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "auto-claude",
|
||||
"version": "2.7.6-beta.1",
|
||||
"version": "2.7.6-beta.2",
|
||||
"license": "AGPL-3.0",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"apps/frontend": {
|
||||
"name": "auto-claude-ui",
|
||||
"version": "2.7.6-beta.1",
|
||||
"version": "2.7.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
@@ -74,6 +74,8 @@
|
||||
"react-i18next": "^16.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-resizable-panels": "^4.2.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"semver": "^7.7.3",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
@@ -260,7 +262,6 @@
|
||||
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/generator": "^7.28.6",
|
||||
@@ -825,7 +826,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -869,7 +869,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -909,7 +908,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -2196,7 +2194,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -2218,7 +2215,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.4.0.tgz",
|
||||
"integrity": "sha512-jn0phJ+hU7ZuvaoZE/8/Euw3gvHJrn2yi+kXrymwObEPVPjtwCmkvXDRQCWli+fCTTF/aSOtXaLr7CLIvv3LQg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
},
|
||||
@@ -2231,7 +2227,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz",
|
||||
"integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
@@ -2247,7 +2242,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz",
|
||||
"integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.208.0",
|
||||
"import-in-the-middle": "^2.0.0",
|
||||
@@ -2650,7 +2644,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz",
|
||||
"integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.4.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
@@ -2667,7 +2660,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz",
|
||||
"integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.4.0",
|
||||
"@opentelemetry/resources": "2.4.0",
|
||||
@@ -2685,7 +2677,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz",
|
||||
"integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
@@ -4880,7 +4871,6 @@
|
||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
@@ -5183,7 +5173,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
|
||||
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -5194,7 +5183,6 @@
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -5464,7 +5452,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5497,7 +5484,6 @@
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -5968,7 +5954,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -6939,7 +6924,6 @@
|
||||
"integrity": "sha512-ce4Ogns4VMeisIuCSK0C62umG0lFy012jd8LMZ6w/veHUeX4fqfDrGe+HTWALAEwK6JwKP+dhPvizhArSOsFbg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "26.4.0",
|
||||
"builder-util": "26.3.4",
|
||||
@@ -7097,7 +7081,6 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^24.9.0",
|
||||
@@ -7382,7 +7365,6 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
@@ -8137,6 +8119,91 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-parse5": {
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz",
|
||||
"integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/unist": "^3.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"hastscript": "^9.0.0",
|
||||
"property-information": "^7.0.0",
|
||||
"vfile": "^6.0.0",
|
||||
"vfile-location": "^5.0.0",
|
||||
"web-namespaces": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-parse-selector": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
|
||||
"integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-raw": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz",
|
||||
"integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/unist": "^3.0.0",
|
||||
"@ungap/structured-clone": "^1.0.0",
|
||||
"hast-util-from-parse5": "^8.0.0",
|
||||
"hast-util-to-parse5": "^8.0.0",
|
||||
"html-void-elements": "^3.0.0",
|
||||
"mdast-util-to-hast": "^13.0.0",
|
||||
"parse5": "^7.0.0",
|
||||
"unist-util-position": "^5.0.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"vfile": "^6.0.0",
|
||||
"web-namespaces": "^2.0.0",
|
||||
"zwitch": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-raw/node_modules/parse5": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-sanitize": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz",
|
||||
"integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@ungap/structured-clone": "^1.0.0",
|
||||
"unist-util-position": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-jsx-runtime": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
||||
@@ -8164,6 +8231,25 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-parse5": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz",
|
||||
"integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"comma-separated-tokens": "^2.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"property-information": "^7.0.0",
|
||||
"space-separated-tokens": "^2.0.0",
|
||||
"web-namespaces": "^2.0.0",
|
||||
"zwitch": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-whitespace": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
||||
@@ -8177,6 +8263,23 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hastscript": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
|
||||
"integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"comma-separated-tokens": "^2.0.0",
|
||||
"hast-util-parse-selector": "^4.0.0",
|
||||
"property-information": "^7.0.0",
|
||||
"space-separated-tokens": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
|
||||
@@ -8242,6 +8345,16 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/html-void-elements": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
|
||||
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||
@@ -8326,7 +8439,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4"
|
||||
},
|
||||
@@ -8653,7 +8765,6 @@
|
||||
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@acemir/cssom": "^0.9.28",
|
||||
"@asamuzakjp/dom-selector": "^6.7.6",
|
||||
@@ -11140,7 +11251,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -11331,7 +11441,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -11341,7 +11450,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -11554,6 +11662,35 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/rehype-raw": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz",
|
||||
"integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"hast-util-raw": "^9.0.0",
|
||||
"vfile": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/rehype-sanitize": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz",
|
||||
"integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"hast-util-sanitize": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-gfm": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
||||
@@ -12308,8 +12445,7 @@
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
@@ -12488,7 +12624,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -12647,7 +12782,6 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -12934,6 +13068,20 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile-location": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
|
||||
"integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"vfile": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile-message": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
|
||||
@@ -12954,7 +13102,6 @@
|
||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -13547,7 +13694,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -13678,6 +13824,16 @@
|
||||
"defaults": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/web-namespaces": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
||||
"integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
|
||||
@@ -14002,7 +14158,6 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
|
||||
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user