Update files
This commit is contained in:
@@ -22,21 +22,18 @@ import { confirmDialog } from '@/utils/confirm-dialog'
|
||||
import { openExternalUrl } from '@/utils/open-external-url'
|
||||
import { formatVersionWithPrefix, isVersionMismatch } from '@/desktop/updates/desktop-updates'
|
||||
import {
|
||||
getCliShimStatus,
|
||||
getCliSymlinkInstructions,
|
||||
getManagedDaemonLogs,
|
||||
getManagedDaemonPairing,
|
||||
getManagedDaemonStatus,
|
||||
installManagedCliShim,
|
||||
restartManagedDaemon,
|
||||
shouldUseManagedDesktopDaemon,
|
||||
startManagedDaemon,
|
||||
stopManagedDaemon,
|
||||
uninstallManagedCliShim,
|
||||
type CliSymlinkInstructions,
|
||||
type ManagedDaemonLogs,
|
||||
type ManagedPairingOffer,
|
||||
type ManagedDaemonStatus,
|
||||
type CliManualInstructions,
|
||||
type CliShimStatus,
|
||||
} from '@/desktop/managed-runtime/managed-runtime'
|
||||
|
||||
export interface LocalDaemonSectionProps {
|
||||
@@ -51,29 +48,27 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
const [statusError, setStatusError] = useState<string | null>(null)
|
||||
const [isRestartingDaemon, setIsRestartingDaemon] = useState(false)
|
||||
const [isUpdatingDaemonManagement, setIsUpdatingDaemonManagement] = useState(false)
|
||||
const [isInstallingCli, setIsInstallingCli] = useState(false)
|
||||
const [isLoadingCliSymlinkInstructions, setIsLoadingCliSymlinkInstructions] = useState(false)
|
||||
const [statusMessage, setStatusMessage] = useState<string | null>(null)
|
||||
const [cliStatusMessage, setCliStatusMessage] = useState<string | null>(null)
|
||||
const [managedLogs, setManagedLogs] = useState<ManagedDaemonLogs | null>(null)
|
||||
const [cliShimStatus, setCliShimStatus] = useState<CliShimStatus | null>(null)
|
||||
const [isLogsModalOpen, setIsLogsModalOpen] = useState(false)
|
||||
const [isPairingModalOpen, setIsPairingModalOpen] = useState(false)
|
||||
const [isCliInstallModalOpen, setIsCliInstallModalOpen] = useState(false)
|
||||
const [isCliSymlinkModalOpen, setIsCliSymlinkModalOpen] = useState(false)
|
||||
const [isLoadingPairing, setIsLoadingPairing] = useState(false)
|
||||
const [pairingOffer, setPairingOffer] = useState<ManagedPairingOffer | null>(null)
|
||||
const [cliInstallInstructions, setCliInstallInstructions] =
|
||||
useState<CliManualInstructions | null>(null)
|
||||
const [cliSymlinkInstructions, setCliSymlinkInstructions] =
|
||||
useState<CliSymlinkInstructions | null>(null)
|
||||
const [pairingStatusMessage, setPairingStatusMessage] = useState<string | null>(null)
|
||||
|
||||
const loadManagedStatus = useCallback(() => {
|
||||
if (!showSection) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.all([getManagedDaemonStatus(), getManagedDaemonLogs(), getCliShimStatus()])
|
||||
.then(([status, logs, shimStatus]) => {
|
||||
return Promise.all([getManagedDaemonStatus(), getManagedDaemonLogs()])
|
||||
.then(([status, logs]) => {
|
||||
setManagedStatus(status)
|
||||
setManagedLogs(logs)
|
||||
setCliShimStatus(shimStatus)
|
||||
setStatusError(null)
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -224,50 +219,39 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
updateSettings,
|
||||
])
|
||||
|
||||
const handleToggleCliShim = useCallback(() => {
|
||||
if (!showSection || isInstallingCli) {
|
||||
const handleOpenCliSymlinkInstructions = useCallback(() => {
|
||||
if (!showSection || isLoadingCliSymlinkInstructions) {
|
||||
return
|
||||
}
|
||||
setIsInstallingCli(true)
|
||||
const isInstalling = !cliShimStatus?.path
|
||||
setCliStatusMessage(
|
||||
isInstalling ? 'A permissions popup may appear while Paseo installs the CLI globally.' : null
|
||||
)
|
||||
const action = cliShimStatus?.path ? uninstallManagedCliShim : installManagedCliShim
|
||||
void action()
|
||||
.then((result) => {
|
||||
setCliStatusMessage(result.message)
|
||||
if (result.manualInstructions) {
|
||||
setCliInstallInstructions(result.manualInstructions)
|
||||
setIsCliInstallModalOpen(true)
|
||||
} else {
|
||||
setCliInstallInstructions(null)
|
||||
setIsCliInstallModalOpen(false)
|
||||
}
|
||||
return loadManagedStatus()
|
||||
setIsLoadingCliSymlinkInstructions(true)
|
||||
setCliStatusMessage(null)
|
||||
void getCliSymlinkInstructions()
|
||||
.then((instructions) => {
|
||||
setCliSymlinkInstructions(instructions)
|
||||
setIsCliSymlinkModalOpen(true)
|
||||
})
|
||||
.catch((error) => {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
setCliStatusMessage(`CLI install failed: ${message}`)
|
||||
setCliStatusMessage(`Unable to load CLI symlink instructions: ${message}`)
|
||||
})
|
||||
.finally(() => {
|
||||
setIsInstallingCli(false)
|
||||
setIsLoadingCliSymlinkInstructions(false)
|
||||
})
|
||||
}, [cliShimStatus?.path, isInstallingCli, loadManagedStatus, showSection])
|
||||
}, [isLoadingCliSymlinkInstructions, showSection])
|
||||
|
||||
const handleCopyCliInstallCommands = useCallback(() => {
|
||||
if (!cliInstallInstructions?.commands) {
|
||||
const handleCopyCliSymlinkCommands = useCallback(() => {
|
||||
if (!cliSymlinkInstructions?.commands) {
|
||||
return
|
||||
}
|
||||
void Clipboard.setStringAsync(cliInstallInstructions.commands)
|
||||
void Clipboard.setStringAsync(cliSymlinkInstructions.commands)
|
||||
.then(() => {
|
||||
Alert.alert('Copied', 'CLI install commands copied.')
|
||||
Alert.alert('Copied', 'CLI symlink commands copied.')
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[Settings] Failed to copy CLI install commands', error)
|
||||
Alert.alert('Error', 'Unable to copy CLI install commands.')
|
||||
console.error('[Settings] Failed to copy CLI symlink commands', error)
|
||||
Alert.alert('Error', 'Unable to copy CLI symlink commands.')
|
||||
})
|
||||
}, [cliInstallInstructions?.commands])
|
||||
}, [cliSymlinkInstructions?.commands])
|
||||
|
||||
const handleCopyLogPath = useCallback(() => {
|
||||
const logPath = managedLogs?.logPath
|
||||
@@ -417,17 +401,17 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
<View style={[styles.row, styles.rowBorder]}>
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowTitle}>Command line (CLI)</Text>
|
||||
<Text style={styles.hintText}>Adds the `paseo` command to your terminal.</Text>
|
||||
<Text style={styles.hintText}>Shows the command to add `paseo` to your terminal.</Text>
|
||||
{cliStatusMessage ? <Text style={styles.statusText}>{cliStatusMessage}</Text> : null}
|
||||
</View>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
leftIcon={<Terminal size={theme.iconSize.sm} color={theme.colors.foreground} />}
|
||||
onPress={handleToggleCliShim}
|
||||
disabled={isInstallingCli}
|
||||
onPress={handleOpenCliSymlinkInstructions}
|
||||
disabled={isLoadingCliSymlinkInstructions}
|
||||
>
|
||||
{isInstallingCli ? 'Working...' : cliShimStatus?.path ? 'Uninstall CLI' : 'Install CLI'}
|
||||
{isLoadingCliSymlinkInstructions ? 'Loading...' : 'Show instructions'}
|
||||
</Button>
|
||||
</View>
|
||||
<View style={[styles.row, styles.rowBorder]}>
|
||||
@@ -483,27 +467,26 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
) : null}
|
||||
|
||||
<AdaptiveModalSheet
|
||||
visible={isCliInstallModalOpen}
|
||||
onClose={() => setIsCliInstallModalOpen(false)}
|
||||
title="Install CLI manually"
|
||||
testID="managed-daemon-cli-install-dialog"
|
||||
visible={isCliSymlinkModalOpen}
|
||||
onClose={() => setIsCliSymlinkModalOpen(false)}
|
||||
title="Add paseo to your shell"
|
||||
testID="managed-daemon-cli-symlink-dialog"
|
||||
>
|
||||
<View style={styles.modalBody}>
|
||||
<Text style={styles.hintText}>
|
||||
A permissions popup should appear when Paseo installs the CLI globally. If it does not
|
||||
complete, open a terminal and run the commands below.
|
||||
Paseo does not add the command for you. Run the command below in your terminal.
|
||||
</Text>
|
||||
{cliInstallInstructions?.detail ? (
|
||||
<Text style={styles.hintText}>{cliInstallInstructions.detail}</Text>
|
||||
{cliSymlinkInstructions?.detail ? (
|
||||
<Text style={styles.hintText}>{cliSymlinkInstructions.detail}</Text>
|
||||
) : null}
|
||||
<Text style={styles.codeBlock} selectable>
|
||||
{cliInstallInstructions?.commands ?? ''}
|
||||
{cliSymlinkInstructions?.commands ?? ''}
|
||||
</Text>
|
||||
<View style={styles.modalActions}>
|
||||
<Button variant="outline" size="sm" onPress={() => setIsCliInstallModalOpen(false)}>
|
||||
<Button variant="outline" size="sm" onPress={() => setIsCliSymlinkModalOpen(false)}>
|
||||
Close
|
||||
</Button>
|
||||
<Button size="sm" onPress={handleCopyCliInstallCommands}>
|
||||
<Button size="sm" onPress={handleCopyCliSymlinkCommands}>
|
||||
Copy commands
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
@@ -1,46 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseCliShimResult } from "./managed-runtime";
|
||||
import { parseCliSymlinkInstructions } from "./managed-runtime";
|
||||
|
||||
describe("parseCliShimResult", () => {
|
||||
it("parses manual install payloads from the desktop backend", () => {
|
||||
describe("parseCliSymlinkInstructions", () => {
|
||||
it("parses CLI symlink instructions from the desktop backend", () => {
|
||||
expect(
|
||||
parseCliShimResult({
|
||||
status: "manualInstallRequired",
|
||||
installed: false,
|
||||
path: "/usr/local/bin/paseo",
|
||||
message: "Install it manually.",
|
||||
manualInstructions: {
|
||||
title: "Install from Terminal",
|
||||
detail: "Run these commands.",
|
||||
commands: "sudo tee /usr/local/bin/paseo",
|
||||
},
|
||||
parseCliSymlinkInstructions({
|
||||
title: "Add paseo to your shell",
|
||||
detail: "Create a symlink to the Paseo desktop executable.",
|
||||
commands: "sudo ln -sf /Applications/Paseo.app/Contents/MacOS/Paseo /usr/local/bin/paseo",
|
||||
})
|
||||
).toEqual({
|
||||
status: "manualInstallRequired",
|
||||
installed: false,
|
||||
path: "/usr/local/bin/paseo",
|
||||
message: "Install it manually.",
|
||||
manualInstructions: {
|
||||
title: "Install from Terminal",
|
||||
detail: "Run these commands.",
|
||||
commands: "sudo tee /usr/local/bin/paseo",
|
||||
},
|
||||
title: "Add paseo to your shell",
|
||||
detail: "Create a symlink to the Paseo desktop executable.",
|
||||
commands: "sudo ln -sf /Applications/Paseo.app/Contents/MacOS/Paseo /usr/local/bin/paseo",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to installed or removed when older payloads omit status", () => {
|
||||
expect(
|
||||
parseCliShimResult({
|
||||
installed: true,
|
||||
path: "/usr/local/bin/paseo",
|
||||
message: "Installed.",
|
||||
})
|
||||
).toEqual({
|
||||
status: "installed",
|
||||
installed: true,
|
||||
path: "/usr/local/bin/paseo",
|
||||
message: "Installed.",
|
||||
manualInstructions: null,
|
||||
});
|
||||
it("rejects non-object payloads", () => {
|
||||
expect(() => parseCliSymlinkInstructions(null)).toThrow(
|
||||
"Unexpected CLI symlink instructions response."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,30 +23,13 @@ export type ManagedDaemonLogs = {
|
||||
contents: string
|
||||
}
|
||||
|
||||
export type CliShimStatus = {
|
||||
path: string | null
|
||||
}
|
||||
|
||||
export type ManagedPairingOffer = {
|
||||
relayEnabled: boolean
|
||||
url: string | null
|
||||
qr: string | null
|
||||
}
|
||||
|
||||
export type CliShimResult = {
|
||||
status:
|
||||
| 'installed'
|
||||
| 'removed'
|
||||
| 'elevationDenied'
|
||||
| 'automaticInstallUnavailable'
|
||||
| 'manualInstallRequired'
|
||||
installed: boolean
|
||||
path: string | null
|
||||
message: string
|
||||
manualInstructions: CliManualInstructions | null
|
||||
}
|
||||
|
||||
export type CliManualInstructions = {
|
||||
export type CliSymlinkInstructions = {
|
||||
title: string
|
||||
detail: string
|
||||
commands: string
|
||||
@@ -122,15 +105,6 @@ function parseManagedDaemonLogs(raw: unknown): ManagedDaemonLogs {
|
||||
}
|
||||
}
|
||||
|
||||
function parseCliShimStatus(raw: unknown): CliShimStatus {
|
||||
if (!isRecord(raw)) {
|
||||
throw new Error('Unexpected CLI shim status response.')
|
||||
}
|
||||
return {
|
||||
path: toStringOrNull(raw.path),
|
||||
}
|
||||
}
|
||||
|
||||
function parseManagedPairingOffer(raw: unknown): ManagedPairingOffer {
|
||||
if (!isRecord(raw)) {
|
||||
throw new Error('Unexpected managed daemon pairing response.')
|
||||
@@ -142,7 +116,7 @@ function parseManagedPairingOffer(raw: unknown): ManagedPairingOffer {
|
||||
}
|
||||
}
|
||||
|
||||
function parseCliManualInstructions(raw: unknown): CliManualInstructions | null {
|
||||
function parseCliSymlinkInstructionsInternal(raw: unknown): CliSymlinkInstructions | null {
|
||||
if (!isRecord(raw)) {
|
||||
return null
|
||||
}
|
||||
@@ -153,21 +127,6 @@ function parseCliManualInstructions(raw: unknown): CliManualInstructions | null
|
||||
}
|
||||
}
|
||||
|
||||
export function parseCliShimResult(raw: unknown): CliShimResult {
|
||||
if (!isRecord(raw)) {
|
||||
throw new Error('Unexpected CLI shim response.')
|
||||
}
|
||||
return {
|
||||
status:
|
||||
(toStringOrNull(raw.status) as CliShimResult['status'] | null) ??
|
||||
(raw.installed === true ? 'installed' : 'removed'),
|
||||
installed: raw.installed === true,
|
||||
path: toStringOrNull(raw.path),
|
||||
message: toStringOrNull(raw.message) ?? '',
|
||||
manualInstructions: parseCliManualInstructions(raw.manualInstructions),
|
||||
}
|
||||
}
|
||||
|
||||
export function shouldUseManagedDesktopDaemon(): boolean {
|
||||
return isTauriEnvironment() && getTauri() !== null
|
||||
}
|
||||
@@ -196,20 +155,20 @@ export async function getManagedDaemonLogs(): Promise<ManagedDaemonLogs> {
|
||||
return parseManagedDaemonLogs(await invokeDesktopCommand('managed_daemon_logs'))
|
||||
}
|
||||
|
||||
export async function getCliShimStatus(): Promise<CliShimStatus> {
|
||||
return parseCliShimStatus(await invokeDesktopCommand('cli_shim_status'))
|
||||
}
|
||||
|
||||
export async function getManagedDaemonPairing(): Promise<ManagedPairingOffer> {
|
||||
return parseManagedPairingOffer(await invokeDesktopCommand('managed_daemon_pairing'))
|
||||
}
|
||||
|
||||
export async function installManagedCliShim(): Promise<CliShimResult> {
|
||||
return parseCliShimResult(await invokeDesktopCommand('install_cli_shim'))
|
||||
export function parseCliSymlinkInstructions(raw: unknown): CliSymlinkInstructions {
|
||||
const instructions = parseCliSymlinkInstructionsInternal(raw)
|
||||
if (!instructions) {
|
||||
throw new Error('Unexpected CLI symlink instructions response.')
|
||||
}
|
||||
return instructions
|
||||
}
|
||||
|
||||
export async function uninstallManagedCliShim(): Promise<CliShimResult> {
|
||||
return parseCliShimResult(await invokeDesktopCommand('uninstall_cli_shim'))
|
||||
export async function getCliSymlinkInstructions(): Promise<CliSymlinkInstructions> {
|
||||
return parseCliSymlinkInstructions(await invokeDesktopCommand('cli_symlink_instructions'))
|
||||
}
|
||||
|
||||
export async function updateManagedDaemonTcpSettings(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Image, Text, View } from "react-native";
|
||||
import { Text, View } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { PaseoLogo } from "@/components/icons/paseo-logo";
|
||||
import { useTauriDragHandlers } from "@/utils/tauri-window";
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
container: {
|
||||
@@ -8,11 +10,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.colors.surface0,
|
||||
},
|
||||
logo: {
|
||||
width: 96,
|
||||
height: 96,
|
||||
marginBottom: theme.spacing[6],
|
||||
},
|
||||
status: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.base,
|
||||
@@ -20,13 +17,11 @@ const styles = StyleSheet.create((theme) => ({
|
||||
}));
|
||||
|
||||
export function StartupSplashScreen() {
|
||||
const dragHandlers = useTauriDragHandlers();
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Image
|
||||
source={require("../../assets/images/icon.png")}
|
||||
style={styles.logo}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<View style={styles.container} {...dragHandlers}>
|
||||
<PaseoLogo size={96} />
|
||||
<Text style={styles.status}>Starting up…</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user