mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-22 09:01:45 +03:00
Tauri v1 -> v2
Refactor appSettings to accommodate new Tauri v2 API - creates AppSettings class and injects it where needed - avoids `window` undeclared variable during vite build process
This commit is contained in:
parent
7be3e7a6e7
commit
2ef866baa6
6
.github/workflows/publish.yaml
vendored
6
.github/workflows/publish.yaml
vendored
@ -157,6 +157,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt update;
|
||||
sudo apt install -y \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
build-essential \
|
||||
curl \
|
||||
wget \
|
||||
@ -186,11 +187,10 @@ jobs:
|
||||
--dist "./release" \
|
||||
--version "${{ env.version }}"
|
||||
env:
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_PROVIDER_SHORT_NAME }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
|
2909
Cargo.lock
generated
2909
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,7 @@
|
||||
"prepare": "svelte-kit sync"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "^0.27.3",
|
||||
"@codemirror/lang-cpp": "^6.0.2",
|
||||
"@codemirror/lang-css": "^6.2.1",
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
@ -42,7 +43,16 @@
|
||||
"@sveltejs/adapter-static": "catalog:svelte",
|
||||
"@sveltejs/kit": "catalog:svelte",
|
||||
"@sveltejs/vite-plugin-svelte": "catalog:svelte",
|
||||
"@tauri-apps/api": "^1.6.0",
|
||||
"@tauri-apps/api": "^2.0.3",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.1",
|
||||
"@tauri-apps/plugin-http": "^2.0.1",
|
||||
"@tauri-apps/plugin-fs": "^2.0.1",
|
||||
"@tauri-apps/plugin-log": "^2.0.0",
|
||||
"@tauri-apps/plugin-os": "^2.0.0",
|
||||
"@tauri-apps/plugin-process": "^2.0.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.1",
|
||||
"@tauri-apps/plugin-store": "^2.1.0",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||
"@testing-library/jest-dom": "^6.4.8",
|
||||
"@testing-library/svelte": "^5.2.1",
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
@ -77,15 +87,12 @@
|
||||
"svelte": "catalog:svelte",
|
||||
"svelte-check": "catalog:svelte",
|
||||
"svelte-french-toast": "^1.2.0",
|
||||
"tauri-plugin-log-api": "https://github.com/tauri-apps/tauri-plugin-log#v1",
|
||||
"tauri-plugin-store-api": "https://github.com/tauri-apps/tauri-plugin-store#v1",
|
||||
"tinykeys": "^2.1.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.27.3",
|
||||
"openai": "^4.47.3"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { showError } from '$lib/notifications/toasts';
|
||||
import { captureException } from '@sentry/sveltekit';
|
||||
import { error as logErrorToFile } from 'tauri-plugin-log-api';
|
||||
import { error as logErrorToFile } from '@tauri-apps/plugin-log';
|
||||
import type { HandleClientError } from '@sveltejs/kit';
|
||||
|
||||
// SvelteKit error handler.
|
||||
@ -25,20 +25,23 @@ window.onunhandledrejection = (e: PromiseRejectionEvent) => {
|
||||
};
|
||||
|
||||
function logError(error: unknown) {
|
||||
let message = error instanceof Error ? error.message : String(error);
|
||||
const stack = error instanceof Error ? error.stack : undefined;
|
||||
try {
|
||||
let message = error instanceof Error ? error.message : String(error);
|
||||
const stack = error instanceof Error ? error.stack : undefined;
|
||||
|
||||
const id = captureException(message, {
|
||||
mechanism: {
|
||||
type: 'sveltekit',
|
||||
handled: false
|
||||
}
|
||||
});
|
||||
message = `${id}: ${message}\n`;
|
||||
if (stack) message = `${message}\n${stack}\n`;
|
||||
const id = captureException(message, {
|
||||
mechanism: {
|
||||
type: 'sveltekit',
|
||||
handled: false
|
||||
}
|
||||
});
|
||||
message = `${id}: ${message}\n`;
|
||||
if (stack) message = `${message}\n${stack}\n`;
|
||||
|
||||
logErrorToFile(message);
|
||||
console.error(message);
|
||||
showError('Something went wrong', message);
|
||||
return id;
|
||||
logErrorToFile(message);
|
||||
showError('Something went wrong', message);
|
||||
return id;
|
||||
} catch (err: unknown) {
|
||||
console.error('Error while trying to log error.', err);
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,10 @@ import {
|
||||
SHORT_DEFAULT_BRANCH_TEMPLATE,
|
||||
SHORT_DEFAULT_PR_TEMPLATE
|
||||
} from '$lib/ai/prompts';
|
||||
import {
|
||||
type AIClient,
|
||||
type AIEvalOptions,
|
||||
type AnthropicModelName,
|
||||
type Prompt
|
||||
} from '$lib/ai/types';
|
||||
import { andThenAsync, ok, wrapAsync, type Result } from '$lib/result';
|
||||
import { type AIEvalOptions } from '$lib/ai/types';
|
||||
import { type AIClient, type AnthropicModelName, type Prompt } from '$lib/ai/types';
|
||||
import { andThenAsync, wrapAsync } from '$lib/result';
|
||||
import { ok, type Result } from '$lib/result';
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
import type { RawMessageStreamEvent } from '@anthropic-ai/sdk/resources/messages.mjs';
|
||||
import type { Stream } from '@anthropic-ai/sdk/streaming.mjs';
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
import { MessageRole, type PromptMessage, type AIClient, type Prompt } from '$lib/ai/types';
|
||||
import { andThen, buildFailureFromAny, ok, wrap, wrapAsync, type Result } from '$lib/result';
|
||||
import { isNonEmptyObject } from '@gitbutler/ui/utils/typeguards';
|
||||
import { fetch, Body, Response } from '@tauri-apps/api/http';
|
||||
import { fetch } from '@tauri-apps/plugin-http';
|
||||
|
||||
export const DEFAULT_OLLAMA_ENDPOINT = 'http://127.0.0.1:11434';
|
||||
export const DEFAULT_OLLAMA_MODEL_NAME = 'llama3';
|
||||
@ -137,9 +137,9 @@ ${JSON.stringify(OLLAMA_CHAT_MESSAGE_FORMAT_SCHEMA, null, 2)}`
|
||||
* @param request - The OllamaChatRequest object containing the request details.
|
||||
* @returns A Promise that resolves to the Response object.
|
||||
*/
|
||||
private async fetchChat(request: OllamaChatRequest): Promise<Result<Response<any>, Error>> {
|
||||
private async fetchChat(request: OllamaChatRequest): Promise<Result<any, Error>> {
|
||||
const url = new URL(OllamaAPEndpoint.Chat, this.endpoint);
|
||||
const body = Body.json(request);
|
||||
const body = JSON.stringify(request);
|
||||
return await wrapAsync(
|
||||
async () =>
|
||||
await fetch(url.toString(), {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { type Prompt, MessageRole } from '$lib/ai/types';
|
||||
import { type Prompt, MessageRole } from './types';
|
||||
|
||||
export const SHORT_DEFAULT_COMMIT_TEMPLATE: Prompt = [
|
||||
{
|
||||
|
@ -1,4 +1,12 @@
|
||||
import { DEFAULT_PR_SUMMARY_MAIN_DIRECTIVE, getPrTemplateDirective } from './prompts';
|
||||
import {
|
||||
OpenAIModelName,
|
||||
type AIClient,
|
||||
AnthropicModelName,
|
||||
ModelKind,
|
||||
MessageRole,
|
||||
type Prompt
|
||||
} from './types';
|
||||
import { AnthropicAIClient } from '$lib/ai/anthropicClient';
|
||||
import { ButlerAIClient } from '$lib/ai/butlerClient';
|
||||
import {
|
||||
@ -7,14 +15,6 @@ import {
|
||||
OllamaClient
|
||||
} from '$lib/ai/ollamaClient';
|
||||
import { OpenAIClient } from '$lib/ai/openAIClient';
|
||||
import {
|
||||
OpenAIModelName,
|
||||
type AIClient,
|
||||
AnthropicModelName,
|
||||
ModelKind,
|
||||
MessageRole,
|
||||
type Prompt
|
||||
} from '$lib/ai/types';
|
||||
import { buildFailureFromAny, isFailure, ok, type Result } from '$lib/result';
|
||||
import { splitMessage } from '$lib/utils/commitMessage';
|
||||
import { get } from 'svelte/store';
|
||||
|
@ -1,36 +1,24 @@
|
||||
import { initPostHog } from '$lib/analytics/posthog';
|
||||
import { initSentry } from '$lib/analytics/sentry';
|
||||
import { appAnalyticsConfirmed } from '$lib/config/appSettings';
|
||||
import {
|
||||
appMetricsEnabled,
|
||||
appErrorReportingEnabled,
|
||||
appNonAnonMetricsEnabled
|
||||
} from '$lib/config/appSettings';
|
||||
import { AppSettings } from '$lib/config/appSettings';
|
||||
import posthog from 'posthog-js';
|
||||
|
||||
export function initAnalyticsIfEnabled() {
|
||||
const analyticsConfirmed = appAnalyticsConfirmed();
|
||||
analyticsConfirmed.onDisk().then((confirmed) => {
|
||||
export function initAnalyticsIfEnabled(appSettings: AppSettings) {
|
||||
appSettings.appAnalyticsConfirmed.onDisk().then((confirmed) => {
|
||||
if (confirmed) {
|
||||
appErrorReportingEnabled()
|
||||
.onDisk()
|
||||
.then((enabled) => {
|
||||
if (enabled) initSentry();
|
||||
});
|
||||
appMetricsEnabled()
|
||||
.onDisk()
|
||||
.then((enabled) => {
|
||||
if (enabled) initPostHog();
|
||||
});
|
||||
appNonAnonMetricsEnabled()
|
||||
.onDisk()
|
||||
.then((enabled) => {
|
||||
if (enabled) {
|
||||
posthog.capture('nonAnonMetricsEnabled');
|
||||
} else {
|
||||
posthog.capture('nonAnonMetricsDisabled');
|
||||
}
|
||||
});
|
||||
appSettings.appErrorReportingEnabled.onDisk().then((enabled) => {
|
||||
if (enabled) initSentry();
|
||||
});
|
||||
appSettings.appMetricsEnabled.onDisk().then((enabled) => {
|
||||
if (enabled) initPostHog();
|
||||
});
|
||||
appSettings.appNonAnonMetricsEnabled.onDisk().then((enabled) => {
|
||||
if (enabled) {
|
||||
posthog.capture('nonAnonMetricsEnabled');
|
||||
} else {
|
||||
posthog.capture('nonAnonMetricsDisabled');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { invoke as invokeTauri } from '@tauri-apps/api/core';
|
||||
import { listen as listenTauri } from '@tauri-apps/api/event';
|
||||
import { invoke as invokeTauri } from '@tauri-apps/api/tauri';
|
||||
import type { EventCallback, EventName } from '@tauri-apps/api/event';
|
||||
|
||||
export enum Code {
|
||||
|
@ -2,7 +2,7 @@ import { invoke } from '$lib/backend/ipc';
|
||||
import { showError } from '$lib/notifications/toasts';
|
||||
import * as toasts from '$lib/utils/toasts';
|
||||
import { persisted } from '@gitbutler/shared/persisted';
|
||||
import { open } from '@tauri-apps/api/dialog';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { derived, get, writable, type Readable } from 'svelte/store';
|
||||
import type { ForgeType } from './forge';
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { invoke as invokeIpc, listen as listenIpc } from './ipc';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { checkUpdate, onUpdaterEvent } from '@tauri-apps/api/updater';
|
||||
import { check } from '@tauri-apps/plugin-updater';
|
||||
|
||||
export class Tauri {
|
||||
invoke = invokeIpc;
|
||||
listen = listenIpc;
|
||||
checkUpdate = checkUpdate;
|
||||
onUpdaterEvent = onUpdaterEvent;
|
||||
checkUpdate = check;
|
||||
currentVersion = getVersion;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { Tauri } from './tauri';
|
||||
import { UPDATE_INTERVAL_MS, UpdaterService } from './updater';
|
||||
import { get } from 'svelte/store';
|
||||
import { expect, test, describe, vi, beforeEach, afterEach } from 'vitest';
|
||||
import type { Update } from '@tauri-apps/plugin-updater';
|
||||
|
||||
/**
|
||||
* It is important to understand the sync `get` method performs a store subscription
|
||||
@ -16,7 +17,6 @@ describe('Updater', () => {
|
||||
tauri = new Tauri();
|
||||
updater = new UpdaterService(tauri);
|
||||
vi.spyOn(tauri, 'listen').mockReturnValue(async () => {});
|
||||
vi.spyOn(tauri, 'onUpdaterEvent').mockReturnValue(Promise.resolve(() => {}));
|
||||
vi.spyOn(tauri, 'currentVersion').mockReturnValue(Promise.resolve('0.1'));
|
||||
});
|
||||
|
||||
@ -28,38 +28,33 @@ describe('Updater', () => {
|
||||
test('should not show up-to-date on interval check', async () => {
|
||||
vi.spyOn(tauri, 'checkUpdate').mockReturnValue(
|
||||
Promise.resolve({
|
||||
shouldUpdate: false
|
||||
})
|
||||
available: false
|
||||
} as Update)
|
||||
);
|
||||
|
||||
vi.spyOn(tauri, 'onUpdaterEvent').mockImplementation(async (handler) => {
|
||||
handler({ status: 'UPTODATE' });
|
||||
return await Promise.resolve(() => {});
|
||||
});
|
||||
|
||||
await updater.checkForUpdate();
|
||||
expect(get(updater.update)).toHaveProperty('status', undefined);
|
||||
expect(get(updater.update)).toMatchObject({});
|
||||
});
|
||||
|
||||
test('should show up-to-date on manual check', async () => {
|
||||
vi.spyOn(tauri, 'checkUpdate').mockReturnValue(
|
||||
Promise.resolve({
|
||||
shouldUpdate: false
|
||||
})
|
||||
available: false,
|
||||
version: '1'
|
||||
} as Update)
|
||||
);
|
||||
await updater.checkForUpdate(true); // manual = true;
|
||||
expect(get(updater.update)).toHaveProperty('status', 'UPTODATE');
|
||||
expect(get(updater.update)).toHaveProperty('status', 'Up-to-date');
|
||||
});
|
||||
|
||||
test('should prompt again on new version', async () => {
|
||||
const body = 'release notes';
|
||||
const date = '2024-01-01';
|
||||
|
||||
vi.spyOn(tauri, 'checkUpdate').mockReturnValue(
|
||||
Promise.resolve({
|
||||
shouldUpdate: true,
|
||||
manifest: { version: '1', body, date }
|
||||
})
|
||||
available: true,
|
||||
version: '1',
|
||||
body
|
||||
} as Update)
|
||||
);
|
||||
|
||||
await updater.checkForUpdate();
|
||||
@ -70,9 +65,10 @@ describe('Updater', () => {
|
||||
|
||||
vi.spyOn(tauri, 'checkUpdate').mockReturnValue(
|
||||
Promise.resolve({
|
||||
shouldUpdate: true,
|
||||
manifest: { version: '2', body, date }
|
||||
})
|
||||
available: true,
|
||||
version: '2',
|
||||
body
|
||||
} as Update)
|
||||
);
|
||||
await updater.checkForUpdate();
|
||||
const update2 = get(updater.update);
|
||||
@ -83,13 +79,13 @@ describe('Updater', () => {
|
||||
test('should not prompt download for seen version', async () => {
|
||||
const version = '1';
|
||||
const body = 'release notes';
|
||||
const date = '2024-01-01';
|
||||
|
||||
vi.spyOn(tauri, 'checkUpdate').mockReturnValue(
|
||||
Promise.resolve({
|
||||
shouldUpdate: true,
|
||||
manifest: { version, body, date }
|
||||
})
|
||||
available: true,
|
||||
version,
|
||||
body
|
||||
} as Update)
|
||||
);
|
||||
const updater = new UpdaterService(tauri);
|
||||
await updater.checkForUpdate();
|
||||
@ -101,22 +97,20 @@ describe('Updater', () => {
|
||||
updater.dismiss();
|
||||
await updater.checkForUpdate();
|
||||
const update2 = get(updater.update);
|
||||
expect(update2).toHaveProperty('version', undefined);
|
||||
expect(update2).toHaveProperty('releaseNotes', undefined);
|
||||
expect(update2).toMatchObject({});
|
||||
});
|
||||
|
||||
test('should check for updates continously', async () => {
|
||||
const mock = vi.spyOn(tauri, 'checkUpdate').mockReturnValue(
|
||||
Promise.resolve({
|
||||
shouldUpdate: false
|
||||
})
|
||||
available: false
|
||||
} as Update)
|
||||
);
|
||||
|
||||
const unsubscribe = updater.update.subscribe(() => {});
|
||||
await vi.advanceTimersToNextTimerAsync();
|
||||
expect(mock).toHaveBeenCalledOnce();
|
||||
|
||||
for (let i = 2; i < 24; i++) {
|
||||
for (let i = 2; i < 12; i++) {
|
||||
await vi.advanceTimersByTimeAsync(UPDATE_INTERVAL_MS);
|
||||
expect(mock).toHaveBeenCalledTimes(i);
|
||||
}
|
||||
|
@ -1,17 +1,30 @@
|
||||
import { Tauri } from './tauri';
|
||||
import { showToast } from '$lib/notifications/toasts';
|
||||
import { relaunch } from '@tauri-apps/api/process';
|
||||
import {
|
||||
installUpdate,
|
||||
type UpdateResult,
|
||||
type UpdateManifest,
|
||||
type UpdateStatus
|
||||
} from '@tauri-apps/api/updater';
|
||||
import { relaunch } from '@tauri-apps/plugin-process';
|
||||
import { type DownloadEvent, Update } from '@tauri-apps/plugin-updater';
|
||||
import posthog from 'posthog-js';
|
||||
import { derived, readable, writable } from 'svelte/store';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
type Status = UpdateStatus | 'DOWNLOADED';
|
||||
const TIMEOUT_SECONDS = 30;
|
||||
type UpdateStatus = {
|
||||
version?: string;
|
||||
releaseNotes?: string;
|
||||
status?: InstallStatus | undefined;
|
||||
};
|
||||
|
||||
export type InstallStatus =
|
||||
| 'Checking'
|
||||
| 'Downloading'
|
||||
| 'Downloaded'
|
||||
| 'Installing'
|
||||
| 'Done'
|
||||
| 'Up-to-date'
|
||||
| 'Error';
|
||||
|
||||
const downloadStatusMap: { [K in DownloadEvent['event']]: InstallStatus } = {
|
||||
Started: 'Downloading',
|
||||
Progress: 'Downloading',
|
||||
Finished: 'Downloaded'
|
||||
};
|
||||
|
||||
export const UPDATE_INTERVAL_MS = 3600000; // Hourly
|
||||
|
||||
@ -19,39 +32,23 @@ export const UPDATE_INTERVAL_MS = 3600000; // Hourly
|
||||
* Note that the Tauri API `checkUpdate` hangs indefinitely in dev mode, build
|
||||
* a nightly if you want to test the updater manually.
|
||||
*
|
||||
* export TAURI_PRIVATE_KEY=doesnot
|
||||
* export TAURI_KEY_PASSWORD=matter
|
||||
* export TAURI_SIGNING_PRIVATE_KEY=doesnot
|
||||
* export TAURI_SIGNING_PRIVATE_KEY_PASSWORD=matter
|
||||
* ./scripts/release.sh --channel nightly --version "0.5.678"
|
||||
*/
|
||||
export class UpdaterService {
|
||||
readonly loading = writable(false);
|
||||
readonly status = writable<Status | undefined>();
|
||||
private manifest = writable<UpdateManifest | undefined>(undefined, () => {
|
||||
readonly update = writable<UpdateStatus>({}, () => {
|
||||
this.start();
|
||||
return () => {
|
||||
this.stop();
|
||||
};
|
||||
});
|
||||
|
||||
private currentVersion = readable<string | undefined>(undefined, (set) => {
|
||||
this.tauri.currentVersion().then((version) => set(version));
|
||||
});
|
||||
|
||||
readonly update = derived(
|
||||
[this.manifest, this.status, this.currentVersion],
|
||||
([manifest, status, currentVersion]) => {
|
||||
return {
|
||||
version: manifest?.version,
|
||||
releaseNotes: manifest?.body,
|
||||
status,
|
||||
currentVersion
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
private intervalId: any;
|
||||
private seenVersion: string | undefined;
|
||||
private lastCheckWasManual = false;
|
||||
private tauriDownload: Update['download'] | undefined;
|
||||
private tauriInstall: Update['install'] | undefined;
|
||||
|
||||
unlistenStatus?: () => void;
|
||||
unlistenMenu?: () => void;
|
||||
@ -62,18 +59,6 @@ export class UpdaterService {
|
||||
this.unlistenMenu = this.tauri.listen<string>('menu://global/update/clicked', () => {
|
||||
this.checkForUpdate(true);
|
||||
});
|
||||
|
||||
this.unlistenStatus = await this.tauri.onUpdaterEvent((event) => {
|
||||
const { error, status } = event;
|
||||
if (status !== 'UPTODATE' || this.lastCheckWasManual) {
|
||||
this.status.set(status);
|
||||
}
|
||||
if (error) {
|
||||
handleError(error, false);
|
||||
posthog.capture('App Update Status Error', { error });
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(async () => await this.checkForUpdate(), UPDATE_INTERVAL_MS);
|
||||
this.checkForUpdate();
|
||||
}
|
||||
@ -81,7 +66,6 @@ export class UpdaterService {
|
||||
private async stop() {
|
||||
this.unlistenStatus?.();
|
||||
this.unlistenMenu?.();
|
||||
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = undefined;
|
||||
@ -90,55 +74,81 @@ export class UpdaterService {
|
||||
|
||||
async checkForUpdate(manual = false) {
|
||||
this.loading.set(true);
|
||||
this.lastCheckWasManual = manual;
|
||||
try {
|
||||
const update = await Promise.race([
|
||||
this.tauri.checkUpdate(), // In DEV mode this never returns.
|
||||
new Promise<UpdateResult>((_resolve, reject) =>
|
||||
// For manual testing use resolve instead of reject here.
|
||||
setTimeout(
|
||||
() => reject(`Timed out after ${TIMEOUT_SECONDS} seconds.`),
|
||||
TIMEOUT_SECONDS * 1000
|
||||
)
|
||||
)
|
||||
]);
|
||||
await this.processUpdate(update, manual);
|
||||
this.handleUpdate(await this.tauri.checkUpdate(), manual); // In DEV mode this never returns.
|
||||
} catch (err: unknown) {
|
||||
// No toast unless manually invoked.
|
||||
if (manual) {
|
||||
handleError(err, true);
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
handleError(err, manual);
|
||||
} finally {
|
||||
this.loading.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async processUpdate(update: UpdateResult, manual: boolean) {
|
||||
const { shouldUpdate, manifest } = update;
|
||||
if (shouldUpdate === false && manual) {
|
||||
this.status.set('UPTODATE');
|
||||
private handleUpdate(update: Update | null, manual: boolean) {
|
||||
if (update === null) {
|
||||
this.update.set({});
|
||||
return;
|
||||
}
|
||||
if (manifest && manifest.version !== this.seenVersion) {
|
||||
this.manifest.set(manifest);
|
||||
this.seenVersion = manifest.version;
|
||||
if (!update.available && manual) {
|
||||
this.setStatus('Up-to-date');
|
||||
} else if (
|
||||
update.available &&
|
||||
update.version !== this.seenVersion &&
|
||||
update.currentVersion !== '0.0.0' // DEV mode.
|
||||
) {
|
||||
const { version, body, download, install } = update;
|
||||
this.tauriDownload = download.bind(update);
|
||||
this.tauriInstall = install.bind(update);
|
||||
this.seenVersion = version;
|
||||
this.update.set({
|
||||
version,
|
||||
releaseNotes: body,
|
||||
status: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async installUpdate() {
|
||||
async downloadAndInstall() {
|
||||
this.loading.set(true);
|
||||
try {
|
||||
await installUpdate();
|
||||
await this.download();
|
||||
await this.install();
|
||||
posthog.capture('App Update Successful');
|
||||
} catch (err: any) {
|
||||
} catch (error: any) {
|
||||
// We expect toast to be shown by error handling in `onUpdaterEvent`
|
||||
posthog.capture('App Update Install Error', { error: err });
|
||||
handleError(error, true);
|
||||
this.update.set({ status: 'Error' });
|
||||
posthog.capture('App Update Install Error', { error });
|
||||
} finally {
|
||||
this.loading.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async download() {
|
||||
if (!this.tauriDownload) {
|
||||
throw new Error('Download function not available.');
|
||||
}
|
||||
this.setStatus('Downloading');
|
||||
await this.tauriDownload((progress: DownloadEvent) => {
|
||||
this.setStatus(downloadStatusMap[progress.event]);
|
||||
});
|
||||
this.setStatus('Downloaded');
|
||||
}
|
||||
|
||||
private async install() {
|
||||
if (!this.tauriInstall) {
|
||||
throw new Error('Install function not available.');
|
||||
}
|
||||
this.setStatus('Installing');
|
||||
await this.tauriInstall();
|
||||
this.setStatus('Done');
|
||||
}
|
||||
|
||||
private setStatus(status: InstallStatus) {
|
||||
this.update.update((update) => {
|
||||
return { ...update, status };
|
||||
});
|
||||
}
|
||||
|
||||
async relaunchApp() {
|
||||
try {
|
||||
await relaunch();
|
||||
@ -148,8 +158,7 @@ export class UpdaterService {
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
this.manifest.set(undefined);
|
||||
this.status.set(undefined);
|
||||
this.update.set({});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { UpdaterService } from '$lib/backend/updater';
|
||||
import { UpdaterService, type InstallStatus } from '$lib/backend/updater';
|
||||
import { showToast } from '$lib/notifications/toasts';
|
||||
import { getContext } from '@gitbutler/shared/context';
|
||||
import Button from '@gitbutler/ui/Button.svelte';
|
||||
@ -11,10 +11,10 @@
|
||||
|
||||
let version = $state<string | undefined>();
|
||||
let releaseNotes = $state<string | undefined>();
|
||||
let status = $state<string | undefined>();
|
||||
let status = $state<InstallStatus | undefined>();
|
||||
|
||||
$effect(() => {
|
||||
({ version, releaseNotes, status } = $update || {});
|
||||
({ version, releaseNotes, status } = $update);
|
||||
});
|
||||
|
||||
function handleDismiss() {
|
||||
@ -22,14 +22,14 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if version || status === 'UPTODATE'}
|
||||
{#if version || status === 'Up-to-date'}
|
||||
<div class="update-banner" data-testid="update-banner" class:busy={$loading}>
|
||||
<div class="floating-button">
|
||||
<Button icon="cross-small" style="ghost" onclick={handleDismiss} />
|
||||
</div>
|
||||
<div class="img">
|
||||
<div class="circle-img">
|
||||
{#if status !== 'DONE' && status !== 'UPTODATE'}
|
||||
{#if status !== 'Done' && status !== 'Up-to-date'}
|
||||
<svg
|
||||
class="arrow-img"
|
||||
width="12"
|
||||
@ -96,17 +96,19 @@
|
||||
</div>
|
||||
|
||||
<h4 class="text-13 label">
|
||||
{#if status === 'UPTODATE'}
|
||||
{#if status === 'Up-to-date'}
|
||||
You are up-to-date!
|
||||
{:else if status === 'PENDING'}
|
||||
Downloading update...
|
||||
{:else if status === 'DOWNLOADED'}
|
||||
Installing update...
|
||||
{:else if status === 'DONE'}
|
||||
{:else if status === 'Downloading'}
|
||||
Downloading update…
|
||||
{:else if status === 'Downloaded'}
|
||||
Update downloaded
|
||||
{:else if status === 'Installing'}
|
||||
Installing update…
|
||||
{:else if status === 'Done'}
|
||||
Install complete
|
||||
{:else if status === 'CHECKING'}
|
||||
{:else if status === 'Checking'}
|
||||
Checking for update…
|
||||
{:else if status === 'ERROR'}
|
||||
{:else if status === 'Error'}
|
||||
Error occurred
|
||||
{:else if version}
|
||||
New version available
|
||||
@ -130,7 +132,7 @@
|
||||
</Button>
|
||||
{/if}
|
||||
<div class="status-section">
|
||||
{#if status !== 'ERROR' && status !== 'UPTODATE'}
|
||||
{#if status !== 'Error' && status !== 'Up-to-date'}
|
||||
<div class="sliding-gradient"></div>
|
||||
{/if}
|
||||
<div class="cta-btn" transition:fade={{ duration: 100 }}>
|
||||
@ -141,12 +143,12 @@
|
||||
kind="solid"
|
||||
testId="download-update"
|
||||
onmousedown={async () => {
|
||||
await updaterService.installUpdate();
|
||||
await updaterService.downloadAndInstall();
|
||||
}}
|
||||
>
|
||||
Update to {version}
|
||||
</Button>
|
||||
{:else if status === 'UPTODATE'}
|
||||
{:else if status === 'Up-to-date'}
|
||||
<Button
|
||||
wide
|
||||
style="pop"
|
||||
@ -158,7 +160,7 @@
|
||||
>
|
||||
Got it!
|
||||
</Button>
|
||||
{:else if status === 'DONE'}
|
||||
{:else if status === 'Done'}
|
||||
<Button
|
||||
style="pop"
|
||||
kind="solid"
|
||||
|
@ -2,7 +2,9 @@ import AppUpdater from './AppUpdater.svelte';
|
||||
import { Tauri } from '$lib/backend/tauri';
|
||||
import { UpdaterService } from '$lib/backend/updater';
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { expect, test, describe, vi, beforeEach, afterEach } from 'vitest';
|
||||
import type { Update } from '@tauri-apps/plugin-updater';
|
||||
|
||||
describe('AppUpdater', () => {
|
||||
let tauri: Tauri;
|
||||
@ -15,7 +17,6 @@ describe('AppUpdater', () => {
|
||||
updater = new UpdaterService(tauri);
|
||||
context = new Map([[UpdaterService, updater]]);
|
||||
vi.spyOn(tauri, 'listen').mockReturnValue(async () => {});
|
||||
vi.spyOn(tauri, 'onUpdaterEvent').mockReturnValue(Promise.resolve(() => {}));
|
||||
vi.spyOn(tauri, 'currentVersion').mockReturnValue(Promise.resolve('0.1'));
|
||||
});
|
||||
|
||||
@ -27,8 +28,8 @@ describe('AppUpdater', () => {
|
||||
test('should be hidden if no update', async () => {
|
||||
vi.spyOn(tauri, 'checkUpdate').mockReturnValue(
|
||||
Promise.resolve({
|
||||
shouldUpdate: false
|
||||
})
|
||||
version: '1'
|
||||
} as Update)
|
||||
);
|
||||
|
||||
render(AppUpdater, { context });
|
||||
@ -41,13 +42,10 @@ describe('AppUpdater', () => {
|
||||
test('should display download button', async () => {
|
||||
vi.spyOn(tauri, 'checkUpdate').mockReturnValue(
|
||||
Promise.resolve({
|
||||
shouldUpdate: true,
|
||||
manifest: {
|
||||
version: '1',
|
||||
body: 'release notes',
|
||||
date: '2024-01-01'
|
||||
}
|
||||
})
|
||||
available: true,
|
||||
version: '1',
|
||||
body: 'release notes'
|
||||
} as Update)
|
||||
);
|
||||
|
||||
render(AppUpdater, { context });
|
||||
@ -60,8 +58,8 @@ describe('AppUpdater', () => {
|
||||
test('should display up-to-date on manaul check', async () => {
|
||||
vi.spyOn(tauri, 'checkUpdate').mockReturnValue(
|
||||
Promise.resolve({
|
||||
shouldUpdate: false
|
||||
})
|
||||
available: false
|
||||
} as Update)
|
||||
);
|
||||
render(AppUpdater, { context });
|
||||
updater.checkForUpdate(true);
|
||||
@ -74,18 +72,31 @@ describe('AppUpdater', () => {
|
||||
test('should display restart button on install complete', async () => {
|
||||
vi.spyOn(tauri, 'checkUpdate').mockReturnValue(
|
||||
Promise.resolve({
|
||||
shouldUpdate: true,
|
||||
manifest: { version: '1', body: 'release notes', date: '2024-01-01' }
|
||||
})
|
||||
available: true,
|
||||
currentVersion: '1',
|
||||
version: '2',
|
||||
body: 'release notes',
|
||||
download: () => {
|
||||
console.log('HELLO');
|
||||
},
|
||||
install: () => {
|
||||
console.log('WORLD');
|
||||
}
|
||||
} as Update)
|
||||
);
|
||||
vi.spyOn(tauri, 'onUpdaterEvent').mockImplementation(async (handler) => {
|
||||
handler({ status: 'DONE' });
|
||||
return () => {};
|
||||
});
|
||||
|
||||
render(AppUpdater, { context });
|
||||
updater.checkForUpdate(true);
|
||||
await updater.checkForUpdate(true);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
console.log('download and install');
|
||||
await updater.downloadAndInstall();
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
await vi.advanceTimersToNextTimerAsync();
|
||||
await vi.advanceTimersToNextTimerAsync();
|
||||
await vi.advanceTimersToNextTimerAsync();
|
||||
await vi.advanceTimersToNextTimerAsync();
|
||||
await vi.advanceTimersToNextTimerAsync();
|
||||
console.log(get(updater.update));
|
||||
|
||||
const button = screen.getByTestId('restart-app');
|
||||
expect(button).toBeVisible();
|
||||
|
@ -27,7 +27,7 @@
|
||||
loading = true;
|
||||
try {
|
||||
// TODO: Refactor temporary solution to forcing Windows to use system executable
|
||||
if ($platformName === 'win32') {
|
||||
if (platformName === 'windows') {
|
||||
project.preferred_key = 'systemExecutable';
|
||||
await projectsService.updateProject(project);
|
||||
await baseBranchService.refresh();
|
||||
@ -41,7 +41,7 @@
|
||||
</script>
|
||||
|
||||
<DecorativeSplitView img={newProjectSvg}>
|
||||
{#if selectedBranch[0] && selectedBranch[0] !== '' && $platformName !== 'win32'}
|
||||
{#if selectedBranch[0] && selectedBranch[0] !== '' && platformName !== 'windows'}
|
||||
{@const [remoteName, branchName] = selectedBranch[0].split(/\/(.*)/s)}
|
||||
<KeysForm {remoteName} {branchName} disabled={loading} />
|
||||
<div class="actions">
|
||||
@ -59,7 +59,7 @@
|
||||
on:branchSelected={async (e) => {
|
||||
selectedBranch = e.detail;
|
||||
// TODO: Temporary solution to forcing Windows to use system executable
|
||||
if ($platformName === 'win32') {
|
||||
if (platformName === 'windows') {
|
||||
setTarget();
|
||||
}
|
||||
}}
|
||||
|
@ -254,7 +254,7 @@
|
||||
testId="set-base-branch"
|
||||
id="set-base-branch"
|
||||
>
|
||||
{#if $platformName === 'win32'}
|
||||
{#if platformName === 'windows'}
|
||||
Let's go
|
||||
{:else}
|
||||
Continue
|
||||
|
@ -54,8 +54,8 @@
|
||||
}
|
||||
|
||||
async function readZipFile(path: string, filename?: string): Promise<File | Blob> {
|
||||
const { readBinaryFile } = await import('@tauri-apps/api/fs');
|
||||
const file = await readBinaryFile(path);
|
||||
const { readFile } = await import('@tauri-apps/plugin-fs');
|
||||
const file = await readFile(path);
|
||||
const fileName = filename ?? path.split('/').pop();
|
||||
return fileName
|
||||
? new File([file], fileName, { type: 'application/zip' })
|
||||
|
@ -2,99 +2,83 @@
|
||||
* This file contains functions for managing application settings.
|
||||
* Settings are persisted in <Application Data>/settings.json and are used by both the UI and the backend.
|
||||
*
|
||||
* @module appSettings
|
||||
* TODO: Rewrite this to be an injectable object so we don't need `storeInstance`.
|
||||
*/
|
||||
|
||||
import { Store } from '@tauri-apps/plugin-store';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { Store } from 'tauri-plugin-store-api';
|
||||
|
||||
const store = new Store('settings.json');
|
||||
|
||||
/**
|
||||
* Persisted confirmation that user has confirmed their analytics settings.
|
||||
*/
|
||||
export function appAnalyticsConfirmed() {
|
||||
return persisted(false, 'appAnalyticsConfirmed');
|
||||
export async function loadAppSettings() {
|
||||
const diskStore = await Store.load('settings.json', { autoSave: true });
|
||||
return new AppSettings(diskStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a writable store for obtaining or setting the current state of application metrics.
|
||||
* The application metrics can be enabled or disabled by setting the value of the store to true or false.
|
||||
* @returns A writable store with the appMetricsEnabled config.
|
||||
*/
|
||||
export function appMetricsEnabled() {
|
||||
return persisted(true, 'appMetricsEnabled');
|
||||
}
|
||||
export class AppSettings {
|
||||
constructor(private diskStore: Store) {}
|
||||
|
||||
/**
|
||||
* Provides a writable store for obtaining or setting the current state of application error reporting.
|
||||
* The application error reporting can be enabled or disabled by setting the value of the store to true or false.
|
||||
* @returns A writable store with the appErrorReportingEnabled config.
|
||||
*/
|
||||
export function appErrorReportingEnabled() {
|
||||
return persisted(true, 'appErrorReportingEnabled');
|
||||
}
|
||||
/**
|
||||
* Persisted confirmation that user has confirmed their analytics settings.
|
||||
*/
|
||||
readonly appAnalyticsConfirmed = this.persisted(false, 'appAnalyticsConfirmed');
|
||||
|
||||
/**
|
||||
* Provides a writable store for obtaining or setting the current state of non-anonemous application metrics.
|
||||
* The setting can be enabled or disabled by setting the value of the store to true or false.
|
||||
* @returns A writable store with the appNonAnonMetricsEnabled config.
|
||||
*/
|
||||
export function appNonAnonMetricsEnabled() {
|
||||
return persisted(false, 'appNonAnonMetricsEnabled');
|
||||
}
|
||||
/**
|
||||
* Provides a writable store for obtaining or setting the current state of application metrics.
|
||||
* The application metrics can be enabled or disabled by setting the value of the store to true or false.
|
||||
* @returns A writable store with the appMetricsEnabled config.
|
||||
*/
|
||||
readonly appMetricsEnabled = this.persisted(true, 'appMetricsEnabled');
|
||||
|
||||
function persisted<T>(initial: T, key: string): Writable<T> & { onDisk: () => Promise<T> } {
|
||||
async function setAndPersist(value: T, set: (value: T) => void) {
|
||||
await store.set(key, value);
|
||||
await store.save();
|
||||
/**
|
||||
* Provides a writable store for obtaining or setting the current state of application error reporting.
|
||||
* The application error reporting can be enabled or disabled by setting the value of the store to true or false.
|
||||
* @returns A writable store with the appErrorReportingEnabled config.
|
||||
*/
|
||||
readonly appErrorReportingEnabled = this.persisted(true, 'appErrorReportingEnabled');
|
||||
|
||||
set(value);
|
||||
/**
|
||||
* Provides a writable store for obtaining or setting the current state of non-anonemous application metrics.
|
||||
* The setting can be enabled or disabled by setting the value of the store to true or false.
|
||||
* @returns A writable store with the appNonAnonMetricsEnabled config.
|
||||
*/
|
||||
readonly appNonAnonMetricsEnabled = this.persisted(false, 'appNonAnonMetricsEnabled');
|
||||
|
||||
private persisted<T>(initial: T, key: string): Writable<T> & { onDisk: () => Promise<T> } {
|
||||
const diskStore = this.diskStore;
|
||||
const storeValueWithDefault = this.storeValueWithDefault.bind(this);
|
||||
|
||||
const keySpecificStore = writable<T>(initial, (set) => {
|
||||
synchronize(set);
|
||||
});
|
||||
|
||||
const subscribe = keySpecificStore.subscribe;
|
||||
|
||||
async function setAndPersist(value: T, set: (value: T) => void) {
|
||||
diskStore?.set(key, value);
|
||||
set(value);
|
||||
}
|
||||
|
||||
async function synchronize(set: (value: T) => void): Promise<void> {
|
||||
const value = await storeValueWithDefault(initial, key);
|
||||
set(value);
|
||||
}
|
||||
|
||||
async function set(value: T) {
|
||||
setAndPersist(value, keySpecificStore.set);
|
||||
}
|
||||
|
||||
async function onDisk() {
|
||||
return await storeValueWithDefault(initial, key);
|
||||
}
|
||||
|
||||
function update() {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
|
||||
return { subscribe, set, update, onDisk };
|
||||
}
|
||||
|
||||
async function synchronize(set: (value: T) => void): Promise<void> {
|
||||
const value = await storeValueWithDefault(initial, key);
|
||||
set(value);
|
||||
}
|
||||
|
||||
function update() {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
|
||||
const thisStore = writable<T>(initial, (set) => {
|
||||
synchronize(set);
|
||||
});
|
||||
|
||||
async function set(value: T) {
|
||||
setAndPersist(value, thisStore.set);
|
||||
}
|
||||
|
||||
async function onDisk() {
|
||||
return await storeValueWithDefault(initial, key);
|
||||
}
|
||||
|
||||
const subscribe = thisStore.subscribe;
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
update,
|
||||
onDisk
|
||||
};
|
||||
}
|
||||
|
||||
async function storeValueWithDefault<T>(initial: T, key: string): Promise<T> {
|
||||
try {
|
||||
await store.load();
|
||||
} catch {
|
||||
// If file does not exist, reset it
|
||||
store.reset();
|
||||
}
|
||||
const stored = (await store.get(key)) as T;
|
||||
|
||||
if (stored === null) {
|
||||
return initial;
|
||||
} else {
|
||||
return stored;
|
||||
async storeValueWithDefault<T>(initial: T, key: string): Promise<T> {
|
||||
const stored = this.diskStore?.get(key) as T;
|
||||
return stored === null ? initial : stored;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { readable } from 'svelte/store';
|
||||
|
||||
export const editor = readable<string>('vscode', (set) => {
|
||||
|
@ -113,9 +113,9 @@
|
||||
role="menu"
|
||||
>
|
||||
<!-- condition prevents split second UI shift -->
|
||||
{#if $platformName || env.PUBLIC_TESTING}
|
||||
{#if platformName || env.PUBLIC_TESTING}
|
||||
<div class="navigation-top">
|
||||
{#if $platformName === 'darwin'}
|
||||
{#if platformName === 'macos'}
|
||||
<div class="drag-region" data-tauri-drag-region></div>
|
||||
{/if}
|
||||
<ProjectSelector isNavCollapsed={$isNavCollapsed} />
|
||||
|
@ -10,9 +10,9 @@
|
||||
import Spacer from '@gitbutler/ui/Spacer.svelte';
|
||||
import Textbox from '@gitbutler/ui/Textbox.svelte';
|
||||
import * as Sentry from '@sentry/sveltekit';
|
||||
import { open } from '@tauri-apps/api/dialog';
|
||||
import { documentDir } from '@tauri-apps/api/path';
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { posthog } from 'posthog-js';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
|
@ -1,6 +1,3 @@
|
||||
import { platform } from '@tauri-apps/api/os';
|
||||
import { readable } from 'svelte/store';
|
||||
import { platform } from '@tauri-apps/plugin-os';
|
||||
|
||||
export const platformName = readable<string | undefined>(undefined, (set) => {
|
||||
platform().then((platform) => set(platform));
|
||||
});
|
||||
export const platformName = platform();
|
||||
|
@ -1,13 +1,7 @@
|
||||
import { AISecretHandle } from '$lib/ai/service';
|
||||
import { invoke } from '$lib/backend/ipc';
|
||||
import { buildContext } from '@gitbutler/shared/context';
|
||||
import type { GitConfigService } from '$lib/backend/gitConfigService';
|
||||
|
||||
const MIGRATION_HANDLES = [
|
||||
AISecretHandle.AnthropicKey.toString(),
|
||||
AISecretHandle.OpenAIKey.toString()
|
||||
];
|
||||
|
||||
export type SecretsService = {
|
||||
get(handle: string): Promise<string | undefined>;
|
||||
set(handle: string, secret: string): Promise<void>;
|
||||
@ -22,12 +16,6 @@ export class RustSecretService implements SecretsService {
|
||||
async get(handle: string) {
|
||||
const secret = await invoke<string>('secret_get_global', { handle });
|
||||
if (secret) return secret;
|
||||
|
||||
if (MIGRATION_HANDLES.includes(handle)) {
|
||||
const key = 'gitbutler.' + handle;
|
||||
const migratedSecret = await this.migrate(key, handle);
|
||||
if (migratedSecret !== undefined) return migratedSecret;
|
||||
}
|
||||
}
|
||||
|
||||
async set(handle: string, secret: string) {
|
||||
|
@ -1,10 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { initAnalyticsIfEnabled } from '$lib/analytics/analytics';
|
||||
import { AppSettings } from '$lib/config/appSettings';
|
||||
import AnalyticsSettings from '$lib/settings/AnalyticsSettings.svelte';
|
||||
import { getContext } from '@gitbutler/shared/context';
|
||||
import Button from '@gitbutler/ui/Button.svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let analyticsConfirmed: Writable<boolean>;
|
||||
const appSettings = getContext(AppSettings);
|
||||
const analyticsConfirmed = appSettings.appAnalyticsConfirmed;
|
||||
</script>
|
||||
|
||||
<div class="analytics-confirmation">
|
||||
@ -19,7 +21,7 @@
|
||||
icon="chevron-right-small"
|
||||
onclick={() => {
|
||||
$analyticsConfirmed = true;
|
||||
initAnalyticsIfEnabled();
|
||||
initAnalyticsIfEnabled(appSettings);
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
|
@ -1,16 +1,14 @@
|
||||
<script lang="ts">
|
||||
import SectionCard from '$lib/components/SectionCard.svelte';
|
||||
import {
|
||||
appErrorReportingEnabled,
|
||||
appMetricsEnabled,
|
||||
appNonAnonMetricsEnabled
|
||||
} from '$lib/config/appSettings';
|
||||
import { AppSettings } from '$lib/config/appSettings';
|
||||
import Link from '$lib/shared/Link.svelte';
|
||||
import { getContext } from '@gitbutler/shared/context';
|
||||
import Toggle from '@gitbutler/ui/Toggle.svelte';
|
||||
|
||||
const errorReportingEnabled = appErrorReportingEnabled();
|
||||
const metricsEnabled = appMetricsEnabled();
|
||||
const nonAnonMetricsEnabled = appNonAnonMetricsEnabled();
|
||||
const appSettings = getContext(AppSettings);
|
||||
const errorReportingEnabled = appSettings.appErrorReportingEnabled;
|
||||
const metricsEnabled = appSettings.appMetricsEnabled;
|
||||
const nonAnonMetricsEnabled = appSettings.appNonAnonMetricsEnabled;
|
||||
</script>
|
||||
|
||||
<div class="analytics-settings__content">
|
||||
|
@ -12,7 +12,7 @@
|
||||
import Button from '@gitbutler/ui/Button.svelte';
|
||||
import Textbox from '@gitbutler/ui/Textbox.svelte';
|
||||
import Toggle from '@gitbutler/ui/Toggle.svelte';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const projectsService = getContext(ProjectsService);
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
<Section>
|
||||
<CommitSigningForm />
|
||||
{#if $platformName !== 'win32'}
|
||||
{#if platformName !== 'windows'}
|
||||
<Spacer />
|
||||
<KeysForm showProjectName={false} />
|
||||
{/if}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { persisted } from '@gitbutler/shared/persisted';
|
||||
import { get, type Readable } from 'svelte/store';
|
||||
import { get, type Readable, type Writable } from 'svelte/store';
|
||||
import type { Project } from '$lib/backend/projects';
|
||||
import type { GitHostIssueService } from '$lib/gitHost/interface/gitHostIssueService';
|
||||
|
||||
@ -12,15 +12,13 @@ export type Topic = {
|
||||
};
|
||||
|
||||
export class TopicService {
|
||||
topics = persisted<Topic[]>([], this.localStorageKey);
|
||||
topics: Writable<Topic[]>;
|
||||
|
||||
constructor(
|
||||
private project: Project,
|
||||
private issueService: Readable<GitHostIssueService | undefined>
|
||||
) {}
|
||||
|
||||
private get localStorageKey(): string {
|
||||
return `TopicService--${this.project.id}`;
|
||||
) {
|
||||
this.topics = persisted<Topic[]>([], `TopicService--${this.project.id}`);
|
||||
}
|
||||
|
||||
create(title: string, body: string, hasIssue: boolean = false): Topic {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { listen } from '$lib/backend/ipc';
|
||||
import { parseRemoteFiles } from '$lib/vbranches/remoteCommits';
|
||||
import { RemoteFile } from '$lib/vbranches/types';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { readable, type Readable } from 'svelte/store';
|
||||
import type { Project } from '$lib/backend/projects';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { appWindow, type Theme } from '@tauri-apps/api/window';
|
||||
import { getCurrentWindow, type Theme } from '@tauri-apps/api/window';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import type { Settings } from '$lib/settings/userSettings';
|
||||
const appWindow = getCurrentWindow();
|
||||
|
||||
export const theme = writable('dark');
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
import AppUpdater from '$lib/components/AppUpdater.svelte';
|
||||
import PromptModal from '$lib/components/PromptModal.svelte';
|
||||
import ShareIssueModal from '$lib/components/ShareIssueModal.svelte';
|
||||
import { AppSettings } from '$lib/config/appSettings';
|
||||
import {
|
||||
createGitHubUserServiceStore as createGitHubUserServiceStore,
|
||||
GitHubUserService
|
||||
@ -70,6 +71,7 @@
|
||||
setContext(AIPromptService, data.aiPromptService);
|
||||
setContext(LineManagerFactory, data.lineManagerFactory);
|
||||
setContext(StackingLineManagerFactory, data.stackingLineManagerFactory);
|
||||
setContext(AppSettings, data.appSettings);
|
||||
|
||||
const webRoutesService = new WebRoutesService(true, env.PUBLIC_CLOUD_BASE_URL);
|
||||
const desktopRoutesService = new DesktopRoutesService(webRoutesService);
|
||||
|
@ -8,6 +8,7 @@ import { ProjectsService } from '$lib/backend/projects';
|
||||
import { PromptService } from '$lib/backend/prompt';
|
||||
import { Tauri } from '$lib/backend/tauri';
|
||||
import { UpdaterService } from '$lib/backend/updater';
|
||||
import { loadAppSettings } from '$lib/config/appSettings';
|
||||
import { RemotesService } from '$lib/remotes/service';
|
||||
import { RustSecretService } from '$lib/secrets/secretsService';
|
||||
import { TokenMemoryService } from '$lib/stores/tokenMemoryService';
|
||||
@ -28,7 +29,10 @@ export const csr = true;
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const load: LayoutLoad = async () => {
|
||||
initAnalyticsIfEnabled();
|
||||
// Awaited and will block initial render, but it is necessary in order to respect the user
|
||||
// settings on telemetry.
|
||||
const appSettings = await loadAppSettings();
|
||||
initAnalyticsIfEnabled(appSettings);
|
||||
|
||||
// TODO: Find a workaround to avoid this dynamic import
|
||||
// https://github.com/sveltejs/kit/issues/905
|
||||
@ -57,6 +61,7 @@ export const load: LayoutLoad = async () => {
|
||||
return {
|
||||
commandService,
|
||||
tokenMemoryService,
|
||||
appSettings,
|
||||
authService,
|
||||
cloud: httpClient,
|
||||
projectsService,
|
||||
|
@ -3,16 +3,18 @@
|
||||
import newProjectSvg from '$lib/assets/illustrations/new-project.svg?raw';
|
||||
import DecorativeSplitView from '$lib/components/DecorativeSplitView.svelte';
|
||||
import Welcome from '$lib/components/Welcome.svelte';
|
||||
import { appAnalyticsConfirmed } from '$lib/config/appSettings';
|
||||
import { AppSettings } from '$lib/config/appSettings';
|
||||
import AnalyticsConfirmation from '$lib/settings/AnalyticsConfirmation.svelte';
|
||||
import { getContext } from '@gitbutler/shared/context';
|
||||
|
||||
const analyticsConfirmed = appAnalyticsConfirmed();
|
||||
const appSettings = getContext(AppSettings);
|
||||
const analyticsConfirmed = appSettings.appAnalyticsConfirmed;
|
||||
</script>
|
||||
|
||||
<DecorativeSplitView img={$analyticsConfirmed ? newProjectSvg : analyticsSvg}>
|
||||
{#if $analyticsConfirmed}
|
||||
<Welcome />
|
||||
{:else}
|
||||
<AnalyticsConfirmation {analyticsConfirmed} />
|
||||
<AnalyticsConfirmation />
|
||||
{/if}
|
||||
</DecorativeSplitView>
|
||||
|
@ -45,7 +45,7 @@ export default defineConfig({
|
||||
strict: false
|
||||
}
|
||||
},
|
||||
// to make use of `TAURI_DEBUG` and other env variables
|
||||
// to make use of `TAURI_ENV_DEBUG` and other env variables
|
||||
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
|
||||
envPrefix: ['VITE_', 'TAURI_'],
|
||||
resolve: {
|
||||
@ -53,9 +53,9 @@ export default defineConfig({
|
||||
},
|
||||
build: {
|
||||
// Tauri supports es2021
|
||||
target: process.env.TAURI_PLATFORM === 'windows' ? 'chrome105' : 'safari13',
|
||||
target: process.env.TAURI_ENV_PLATFORM === 'windows' ? 'chrome105' : 'safari13',
|
||||
// minify production builds
|
||||
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
|
||||
minify: !process.env.TAURI_ENV_DEBUG ? 'esbuild' : false,
|
||||
// ship sourcemaps for better sentry error reports
|
||||
sourcemap: true
|
||||
},
|
||||
|
6
crates/gitbutler-tauri/.gitignore
vendored
6
crates/gitbutler-tauri/.gitignore
vendored
@ -1 +1,5 @@
|
||||
/gitbutler-git-*
|
||||
# Tauri generated output.
|
||||
gen/
|
||||
|
||||
# Extra binaries, figure out why they end up in this directory on build.
|
||||
gitbutler-git-*
|
||||
|
@ -7,6 +7,7 @@ publish = false
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
crate-type = ["lib", "staticlib", "cdylib"]
|
||||
|
||||
[[bin]]
|
||||
name = "gitbutler-tauri"
|
||||
@ -14,7 +15,7 @@ path = "src/main.rs"
|
||||
test = false
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.5.5", features = [] }
|
||||
tauri-build = { version = "2.0.2", features = [] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4"
|
||||
@ -37,11 +38,18 @@ once_cell = "1.20"
|
||||
reqwest = { version = "0.12.8", features = ["json"] }
|
||||
serde.workspace = true
|
||||
serde_json = { version = "1.0", features = ["std", "arbitrary_precision"] }
|
||||
tauri-plugin-context-menu = { git = "https://github.com/c2r0b/tauri-plugin-context-menu", branch = "main" }
|
||||
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tauri = { version = "^2.0.6", features = ["unstable"] }
|
||||
tauri-plugin-dialog = "2.0.3"
|
||||
tauri-plugin-fs = "2.0.3"
|
||||
tauri-plugin-http = "2.0.3"
|
||||
tauri-plugin-log = "2.0.1"
|
||||
tauri-plugin-os = "2.0.1"
|
||||
tauri-plugin-process = "2.0.1"
|
||||
tauri-plugin-shell = "2.0.2"
|
||||
tauri-plugin-single-instance = "2.0.1"
|
||||
tauri-plugin-store = "2.1.0"
|
||||
tauri-plugin-updater = "2.0.2"
|
||||
tauri-plugin-window-state = "2.0.1"
|
||||
parking_lot.workspace = true
|
||||
log = "^0.4"
|
||||
thiserror.workspace = true
|
||||
@ -78,22 +86,6 @@ gitbutler-forge.workspace = true
|
||||
open = "5"
|
||||
url = "2.5.2"
|
||||
|
||||
[dependencies.tauri]
|
||||
version = "1.8.0"
|
||||
features = [
|
||||
"http-all",
|
||||
"os-all",
|
||||
"dialog-open",
|
||||
"fs-read-file",
|
||||
"path-all",
|
||||
"process-relaunch",
|
||||
"protocol-asset",
|
||||
"window-maximize",
|
||||
"window-start-dragging",
|
||||
"window-unmaximize",
|
||||
"shell-open",
|
||||
]
|
||||
|
||||
[lints.clippy]
|
||||
all = "deny"
|
||||
perf = "deny"
|
||||
|
18
crates/gitbutler-tauri/capabilities/main.json
Normal file
18
crates/gitbutler-tauri/capabilities/main.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "main",
|
||||
"description": "permissions for gitbutler tauri",
|
||||
"windows": ["main"],
|
||||
"local": true,
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:default",
|
||||
"dialog:allow-open",
|
||||
"log:default",
|
||||
"process:default",
|
||||
"shell:allow-open",
|
||||
"store:default",
|
||||
"updater:default"
|
||||
]
|
||||
}
|
7
crates/gitbutler-tauri/capabilities/migrated.json
Normal file
7
crates/gitbutler-tauri/capabilities/migrated.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"identifier": "migrated",
|
||||
"description": "permissions that were migrated from v1",
|
||||
"local": true,
|
||||
"windows": ["main"],
|
||||
"permissions": ["core:default"]
|
||||
}
|
@ -7,7 +7,7 @@ use tracing_subscriber::{fmt::format::FmtSpan, layer::SubscriberExt, Layer};
|
||||
|
||||
pub fn init(app_handle: &AppHandle, performance_logging: bool) {
|
||||
let logs_dir = app_handle
|
||||
.path_resolver()
|
||||
.path()
|
||||
.app_log_dir()
|
||||
.expect("failed to get logs dir");
|
||||
fs::create_dir_all(&logs_dir).expect("failed to create logs dir");
|
||||
@ -88,11 +88,7 @@ pub fn init(app_handle: &AppHandle, performance_logging: bool) {
|
||||
|
||||
fn get_server_addr(app_handle: &AppHandle) -> (Ipv4Addr, u16) {
|
||||
let config = app_handle.config();
|
||||
let product_name = config
|
||||
.package
|
||||
.product_name
|
||||
.as_ref()
|
||||
.expect("product name not set");
|
||||
let product_name = config.product_name.as_ref().expect("product name not set");
|
||||
let port = if product_name.eq("GitButler") {
|
||||
6667
|
||||
} else if product_name.eq("GitButler Nightly") {
|
||||
|
@ -15,16 +15,15 @@ use gitbutler_tauri::{
|
||||
askpass, commands, config, forge, github, logs, menu, modes, open, projects, remotes, repo,
|
||||
secret, stack, undo, users, virtual_branches, zip, App, WindowState,
|
||||
};
|
||||
use tauri::Emitter;
|
||||
use tauri::{generate_context, Manager};
|
||||
use tauri_plugin_log::LogTarget;
|
||||
use tauri_plugin_log::{Target, TargetKind};
|
||||
|
||||
fn main() {
|
||||
let performance_logging = std::env::var_os("GITBUTLER_PERFORMANCE_LOG").is_some();
|
||||
gitbutler_project::configure_git2();
|
||||
let tauri_context = generate_context!();
|
||||
gitbutler_secret::secret::set_application_namespace(
|
||||
&tauri_context.config().tauri.bundle.identifier,
|
||||
);
|
||||
gitbutler_secret::secret::set_application_namespace(&tauri_context.config().identifier);
|
||||
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
@ -34,33 +33,30 @@ fn main() {
|
||||
tauri::async_runtime::set(tokio::runtime::Handle::current());
|
||||
|
||||
let log = tauri_plugin_log::Builder::default()
|
||||
.log_name("ui-logs")
|
||||
.target(LogTarget::LogDir)
|
||||
.target(Target::new(TargetKind::LogDir {
|
||||
file_name: Some("ui-logs".to_string()),
|
||||
}))
|
||||
.level(log::LevelFilter::Error);
|
||||
|
||||
let builder = tauri::Builder::default()
|
||||
.setup(move |tauri_app| {
|
||||
let window = gitbutler_tauri::window::create(
|
||||
&tauri_app.handle(),
|
||||
tauri_app.handle(),
|
||||
"main",
|
||||
"index.html".into(),
|
||||
)
|
||||
.expect("Failed to create window");
|
||||
#[cfg(debug_assertions)]
|
||||
window.open_devtools();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
let mut six_hours =
|
||||
tokio::time::interval(tokio::time::Duration::new(6 * 60 * 60, 0));
|
||||
loop {
|
||||
six_hours.tick().await;
|
||||
_ = window.emit_and_trigger("tauri://update", ());
|
||||
}
|
||||
});
|
||||
// TODO(mtsgrd): Is there a better way to disable devtools in E2E tests?
|
||||
#[cfg(debug_assertions)]
|
||||
if tauri_app.config().product_name != Some("GitButler Test".to_string()) {
|
||||
window.open_devtools();
|
||||
}
|
||||
|
||||
let app_handle = tauri_app.handle();
|
||||
|
||||
logs::init(&app_handle, performance_logging);
|
||||
logs::init(app_handle, performance_logging);
|
||||
|
||||
tracing::info!(
|
||||
"system git executable for fetch/push: {git:?}",
|
||||
git = gix::path::env::exe_invocation(),
|
||||
@ -81,14 +77,14 @@ fn main() {
|
||||
let handle = app_handle.clone();
|
||||
move |event| {
|
||||
handle
|
||||
.emit_all("git_prompt", event)
|
||||
.emit("git_prompt", event)
|
||||
.expect("tauri event emission doesn't fail in practice")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let (app_data_dir, app_cache_dir, app_log_dir) = {
|
||||
let paths = app_handle.path_resolver();
|
||||
let paths = app_handle.path();
|
||||
(
|
||||
paths.app_data_dir().expect("missing app data dir"),
|
||||
paths.app_cache_dir().expect("missing app cache dir"),
|
||||
@ -116,10 +112,20 @@ fn main() {
|
||||
});
|
||||
app_handle.manage(app);
|
||||
|
||||
tauri_app.on_menu_event(move |_handle, event| {
|
||||
menu::handle_event(&window.clone(), &event)
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_single_instance::init(|_, _, _| {}))
|
||||
.plugin(tauri_plugin_context_menu::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
// .plugin(tauri_plugin_context_menu::init())
|
||||
.plugin(tauri_plugin_store::Builder::default().build())
|
||||
.plugin(log.build())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
@ -221,36 +227,30 @@ fn main() {
|
||||
forge::commands::get_available_review_templates,
|
||||
forge::commands::get_review_template_contents,
|
||||
])
|
||||
.menu(menu::build(tauri_context.package_info()))
|
||||
.on_menu_event(|event| menu::handle_event(&event))
|
||||
.on_window_event(|event| {
|
||||
let window = event.window();
|
||||
match event.event() {
|
||||
#[cfg(target_os = "macos")]
|
||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
||||
if window.app_handle().windows().len() == 1 {
|
||||
tracing::debug!(
|
||||
"Hiding all application windows and preventing exit"
|
||||
);
|
||||
window.app_handle().hide().ok();
|
||||
api.prevent_close();
|
||||
}
|
||||
.menu(menu::build)
|
||||
.on_window_event(|window, event| match event {
|
||||
#[cfg(target_os = "macos")]
|
||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
||||
if window.app_handle().windows().len() == 1 {
|
||||
tracing::debug!("Hiding all application windows and preventing exit");
|
||||
window.app_handle().hide().ok();
|
||||
api.prevent_close();
|
||||
}
|
||||
tauri::WindowEvent::Destroyed => {
|
||||
window
|
||||
.app_handle()
|
||||
.state::<WindowState>()
|
||||
.remove(window.label());
|
||||
}
|
||||
tauri::WindowEvent::Focused(focused) if *focused => {
|
||||
window
|
||||
.app_handle()
|
||||
.state::<WindowState>()
|
||||
.flush(window.label())
|
||||
.ok();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
tauri::WindowEvent::Destroyed => {
|
||||
window
|
||||
.app_handle()
|
||||
.state::<WindowState>()
|
||||
.remove(window.label());
|
||||
}
|
||||
tauri::WindowEvent::Focused(focused) if *focused => {
|
||||
window
|
||||
.app_handle()
|
||||
.state::<WindowState>()
|
||||
.flush(window.label())
|
||||
.ok();
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
|
@ -1,14 +1,14 @@
|
||||
use std::{env, fs};
|
||||
|
||||
use crate::open::open_that as open_url;
|
||||
use anyhow::Context;
|
||||
use gitbutler_error::{error, error::Code};
|
||||
use gitbutler_error::error::{self, Code};
|
||||
use serde_json::json;
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::AboutMetadata;
|
||||
use tauri::menu::AboutMetadata;
|
||||
use tauri::Emitter;
|
||||
use tauri::{
|
||||
AppHandle, CustomMenuItem, Manager, Menu, MenuItem, PackageInfo, Runtime, Submenu,
|
||||
WindowMenuEvent,
|
||||
menu::{Menu, MenuEvent, MenuItemBuilder, PredefinedMenuItem, Submenu, SubmenuBuilder},
|
||||
AppHandle, Manager, Runtime, WebviewWindow,
|
||||
};
|
||||
use tracing::instrument;
|
||||
|
||||
@ -16,21 +16,22 @@ use crate::error::Error;
|
||||
|
||||
#[tauri::command(async)]
|
||||
#[instrument(skip(handle), err(Debug))]
|
||||
pub fn menu_item_set_enabled(
|
||||
handle: AppHandle,
|
||||
menu_item_id: &str,
|
||||
enabled: bool,
|
||||
) -> Result<(), Error> {
|
||||
pub fn menu_item_set_enabled(handle: AppHandle, id: &str, enabled: bool) -> Result<(), Error> {
|
||||
let window = handle
|
||||
.get_window("main")
|
||||
.expect("main window always present");
|
||||
|
||||
let menu_item = window
|
||||
.menu_handle()
|
||||
.try_get_item(menu_item_id)
|
||||
.with_context(|| error::Context::new(format!("menu item not found: {}", menu_item_id)))?;
|
||||
.menu()
|
||||
.context("menu not found")?
|
||||
.get(id)
|
||||
.with_context(|| error::Context::new(format!("menu item not found: {}", id)))?;
|
||||
|
||||
menu_item.set_enabled(enabled).context(Code::Unknown)?;
|
||||
menu_item
|
||||
.as_menuitem()
|
||||
.context(Code::Unknown)?
|
||||
.set_enabled(enabled)
|
||||
.context(Code::Unknown)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -56,275 +57,264 @@ fn check_if_installed(executable_name: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(_package_info: &PackageInfo) -> Menu {
|
||||
let mut menu = Menu::new();
|
||||
|
||||
// Used in different menus depending on target os.
|
||||
let check_for_updates = CustomMenuItem::new("global/update", "Check for updates…");
|
||||
pub fn build<R: Runtime>(handle: &AppHandle<R>) -> tauri::Result<tauri::menu::Menu<R>> {
|
||||
let check_for_updates =
|
||||
MenuItemBuilder::with_id("global/update", "Check for updates…").build(handle)?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let app_name = &_package_info.name;
|
||||
|
||||
menu = menu.add_submenu(Submenu::new(
|
||||
app_name,
|
||||
Menu::new()
|
||||
.add_native_item(MenuItem::About(
|
||||
app_name.to_string(),
|
||||
AboutMetadata::default(),
|
||||
))
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_item(CustomMenuItem::new("global/settings", "Settings").accelerator("Cmd+,"))
|
||||
.add_item(check_for_updates)
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_native_item(MenuItem::Services)
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_native_item(MenuItem::Hide)
|
||||
.add_native_item(MenuItem::HideOthers)
|
||||
.add_native_item(MenuItem::ShowAll)
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_native_item(MenuItem::Quit),
|
||||
));
|
||||
}
|
||||
|
||||
let mut file_menu = Menu::new();
|
||||
|
||||
file_menu = file_menu.add_item(
|
||||
CustomMenuItem::new("file/add-local-repo", "Add Local Repository…")
|
||||
.accelerator("CmdOrCtrl+O"),
|
||||
);
|
||||
file_menu = file_menu.add_item(
|
||||
CustomMenuItem::new("file/clone-repo", "Clone Repository…")
|
||||
.accelerator("CmdOrCtrl+Shift+O"),
|
||||
);
|
||||
let app_name = handle
|
||||
.config()
|
||||
.product_name
|
||||
.clone()
|
||||
.context("App name not defined.")?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// NB: macOS has the concept of having an app running but its
|
||||
// window closed, but other platforms do not
|
||||
file_menu = file_menu.add_native_item(MenuItem::Separator);
|
||||
file_menu = file_menu.add_native_item(MenuItem::CloseWindow);
|
||||
}
|
||||
let mac_menu = &SubmenuBuilder::new(handle, app_name)
|
||||
.about(Some(AboutMetadata::default()))
|
||||
.separator()
|
||||
.text("global/settings", "Settings")
|
||||
.item(&check_for_updates)
|
||||
.separator()
|
||||
.services()
|
||||
.separator()
|
||||
.hide()
|
||||
.hide_others()
|
||||
.show_all()
|
||||
.separator()
|
||||
.quit()
|
||||
.build()?;
|
||||
|
||||
let file_menu = &SubmenuBuilder::new(handle, "File")
|
||||
.items(&[
|
||||
&MenuItemBuilder::with_id("file/add-local-repo", "Add Local Repository…")
|
||||
.accelerator("CmdOrCtrl+O")
|
||||
.build(handle)?,
|
||||
&MenuItemBuilder::with_id("file/clone-repo", "Clone Repository…")
|
||||
.accelerator("CmdOrCtrl+Shift+O")
|
||||
.build(handle)?,
|
||||
&PredefinedMenuItem::separator(handle)?,
|
||||
])
|
||||
.build()?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
file_menu.append(&PredefinedMenuItem::close_window(handle, None)?)?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
file_menu = file_menu.add_native_item(MenuItem::Separator);
|
||||
file_menu = file_menu.add_native_item(MenuItem::Quit);
|
||||
file_menu = file_menu.add_item(check_for_updates)
|
||||
}
|
||||
|
||||
menu = menu.add_submenu(Submenu::new("File", file_menu));
|
||||
file_menu.append_items(&[&PredefinedMenuItem::quit(handle, None)?, &check_for_updates])?;
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let mut edit_menu = Menu::new();
|
||||
let edit_menu = &Submenu::new(handle, "Edit", true)?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Undo);
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Redo);
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Separator);
|
||||
edit_menu.append_items(&[
|
||||
&PredefinedMenuItem::undo(handle, None)?,
|
||||
&PredefinedMenuItem::redo(handle, None)?,
|
||||
&PredefinedMenuItem::separator(handle)?,
|
||||
])?;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Cut);
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Copy);
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::Paste);
|
||||
edit_menu.append_items(&[
|
||||
&PredefinedMenuItem::cut(handle, None)?,
|
||||
&PredefinedMenuItem::copy(handle, None)?,
|
||||
&PredefinedMenuItem::paste(handle, None)?,
|
||||
])?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
edit_menu = edit_menu.add_native_item(MenuItem::SelectAll);
|
||||
}
|
||||
edit_menu.append(&PredefinedMenuItem::select_all(handle, None)?)?;
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
menu = menu.add_submenu(Submenu::new("Edit", edit_menu));
|
||||
}
|
||||
|
||||
let mut view_menu = Menu::new();
|
||||
let view_menu = &Submenu::new(handle, "View", true)?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
view_menu = view_menu.add_native_item(MenuItem::EnterFullScreen);
|
||||
}
|
||||
|
||||
view_menu = view_menu.add_item(
|
||||
CustomMenuItem::new("view/switch-theme", "Switch Theme").accelerator("CmdOrCtrl+T"),
|
||||
);
|
||||
|
||||
view_menu = view_menu.add_native_item(MenuItem::Separator);
|
||||
view_menu = view_menu
|
||||
.add_item(CustomMenuItem::new("view/zoom-in", "Zoom In").accelerator("CmdOrCtrl+="));
|
||||
view_menu = view_menu
|
||||
.add_item(CustomMenuItem::new("view/zoom-out", "Zoom Out").accelerator("CmdOrCtrl+-"));
|
||||
view_menu = view_menu
|
||||
.add_item(CustomMenuItem::new("view/zoom-reset", "Reset Zoom").accelerator("CmdOrCtrl+0"));
|
||||
|
||||
view_menu = view_menu.add_native_item(MenuItem::Separator);
|
||||
view_menu.append(&PredefinedMenuItem::fullscreen(handle, None)?)?;
|
||||
view_menu.append_items(&[
|
||||
&MenuItemBuilder::with_id("view/switch-theme", "Switch Theme")
|
||||
.accelerator("CmdOrCtrl+T")
|
||||
.build(handle)?,
|
||||
&PredefinedMenuItem::separator(handle)?,
|
||||
&MenuItemBuilder::with_id("view/zoom-in", "Zoom In")
|
||||
.accelerator("CmdOrCtrl+=")
|
||||
.build(handle)?,
|
||||
&MenuItemBuilder::with_id("view/zoom-out", "Zoom Out")
|
||||
.accelerator("CmdOrCtrl+-")
|
||||
.build(handle)?,
|
||||
&MenuItemBuilder::with_id("view/zoom-reset", "Reset Zoom")
|
||||
.accelerator("CmdOrCtrl+0")
|
||||
.build(handle)?,
|
||||
&PredefinedMenuItem::separator(handle)?,
|
||||
])?;
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
{
|
||||
view_menu = view_menu.add_item(CustomMenuItem::new("view/devtools", "Developer Tools"));
|
||||
}
|
||||
view_menu.append_items(&[
|
||||
&MenuItemBuilder::with_id("view/devtools", "Developer Tools").build(handle)?,
|
||||
&MenuItemBuilder::with_id("view/reload", "Reload View")
|
||||
.accelerator("CmdOrCtrl+R")
|
||||
.build(handle)?,
|
||||
])?;
|
||||
|
||||
view_menu = view_menu
|
||||
.add_item(CustomMenuItem::new("view/reload", "Reload View").accelerator("CmdOrCtrl+R"));
|
||||
|
||||
menu = menu.add_submenu(Submenu::new("View", view_menu));
|
||||
|
||||
let mut project_menu = Menu::new();
|
||||
project_menu = project_menu.add_item(
|
||||
CustomMenuItem::new("project/history", "Project History").accelerator("CmdOrCtrl+Shift+H"),
|
||||
);
|
||||
project_menu = project_menu.add_item(CustomMenuItem::new(
|
||||
"project/open-in-vscode",
|
||||
"Open in VS Code",
|
||||
));
|
||||
|
||||
project_menu = project_menu.add_native_item(MenuItem::Separator);
|
||||
project_menu =
|
||||
project_menu.add_item(CustomMenuItem::new("project/settings", "Project Settings"));
|
||||
menu = menu.add_submenu(Submenu::new("Project", project_menu));
|
||||
let project_menu = &SubmenuBuilder::new(handle, "Project")
|
||||
.item(
|
||||
&MenuItemBuilder::with_id("project/history", "Project History")
|
||||
.accelerator("CmdOrCtrl+Shift+H")
|
||||
.build(handle)?,
|
||||
)
|
||||
.text("project/open-in-vscode", "Open in VS Code")
|
||||
.separator()
|
||||
.text("project/settings", "Project Settings")
|
||||
.build()?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let mut window_menu = Menu::new();
|
||||
window_menu = window_menu.add_native_item(MenuItem::Minimize);
|
||||
let window_menu = &SubmenuBuilder::new(handle, "Window")
|
||||
.items(&[
|
||||
&PredefinedMenuItem::minimize(handle, None)?,
|
||||
&PredefinedMenuItem::maximize(handle, None)?,
|
||||
&PredefinedMenuItem::separator(handle)?,
|
||||
&PredefinedMenuItem::close_window(handle, None)?,
|
||||
])
|
||||
.build()?;
|
||||
|
||||
window_menu = window_menu.add_native_item(MenuItem::Zoom);
|
||||
window_menu = window_menu.add_native_item(MenuItem::Separator);
|
||||
let help_menu = &SubmenuBuilder::new(handle, "Help")
|
||||
.text("help/documentation", "Documentation")
|
||||
.text("help/github", "Source Code")
|
||||
.text("help/release-notes", "Release Notes")
|
||||
.separator()
|
||||
.text("help/share-debug-info", "Share Debug Info…")
|
||||
.text("help/report-issue", "Report an Issue…")
|
||||
.separator()
|
||||
.text("help/discord", "Discord")
|
||||
.text("help/youtube", "YouTube")
|
||||
.text("help/x", "X")
|
||||
.separator()
|
||||
.item(
|
||||
&MenuItemBuilder::with_id(
|
||||
"help/version",
|
||||
format!("Version {}", handle.package_info().version),
|
||||
)
|
||||
.enabled(false)
|
||||
.build(handle)?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
window_menu = window_menu.add_native_item(MenuItem::CloseWindow);
|
||||
menu = menu.add_submenu(Submenu::new("Window", window_menu));
|
||||
}
|
||||
|
||||
let mut help_menu = Menu::new();
|
||||
help_menu = help_menu.add_item(CustomMenuItem::new("help/documentation", "Documentation"));
|
||||
help_menu = help_menu.add_item(CustomMenuItem::new("help/github", "Source Code"));
|
||||
help_menu = help_menu.add_item(CustomMenuItem::new("help/release-notes", "Release Notes"));
|
||||
help_menu = help_menu.add_native_item(MenuItem::Separator);
|
||||
help_menu = help_menu.add_item(CustomMenuItem::new(
|
||||
"help/share-debug-info",
|
||||
"Share Debug Info…",
|
||||
));
|
||||
help_menu = help_menu.add_item(CustomMenuItem::new("help/report-issue", "Report an Issue…"));
|
||||
help_menu = help_menu.add_native_item(MenuItem::Separator);
|
||||
help_menu = help_menu.add_item(CustomMenuItem::new("help/discord", "Discord"));
|
||||
help_menu = help_menu.add_item(CustomMenuItem::new("help/youtube", "YouTube"));
|
||||
help_menu = help_menu.add_item(CustomMenuItem::new("help/x", "X"));
|
||||
help_menu = help_menu.add_native_item(MenuItem::Separator);
|
||||
help_menu = help_menu.add_item(disabled_menu_item(
|
||||
"help/version",
|
||||
&format!("Version {}", _package_info.version),
|
||||
));
|
||||
menu = menu.add_submenu(Submenu::new("Help", help_menu));
|
||||
|
||||
menu
|
||||
Menu::with_items(
|
||||
handle,
|
||||
&[
|
||||
#[cfg(target_os = "macos")]
|
||||
mac_menu,
|
||||
file_menu,
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
edit_menu,
|
||||
view_menu,
|
||||
project_menu,
|
||||
#[cfg(target_os = "macos")]
|
||||
window_menu,
|
||||
help_menu,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn disabled_menu_item(id: &str, title: &str) -> CustomMenuItem {
|
||||
let mut item = CustomMenuItem::new(id, title);
|
||||
item.enabled = false;
|
||||
item
|
||||
}
|
||||
|
||||
pub fn handle_event<R: Runtime>(event: &WindowMenuEvent<R>) {
|
||||
if event.menu_item_id() == "file/add-local-repo" {
|
||||
emit(event.window(), "menu://file/add-local-repo/clicked");
|
||||
pub fn handle_event(webview: &WebviewWindow, event: &MenuEvent) {
|
||||
if event.id() == "file/add-local-repo" {
|
||||
emit(webview, "menu://file/add-local-repo/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "file/clone-repo" {
|
||||
emit(event.window(), "menu://file/clone-repo/clicked");
|
||||
if event.id() == "file/clone-repo" {
|
||||
emit(webview, "menu://file/clone-repo/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
{
|
||||
if event.menu_item_id() == "view/devtools" {
|
||||
event.window().open_devtools();
|
||||
if event.id() == "view/devtools" {
|
||||
webview.open_devtools();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "view/switch-theme" {
|
||||
emit(event.window(), "menu://view/switch-theme/clicked");
|
||||
if event.id() == "view/switch-theme" {
|
||||
emit(webview, "menu://view/switch-theme/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "view/reload" {
|
||||
emit(event.window(), "menu://view/reload/clicked");
|
||||
if event.id() == "view/reload" {
|
||||
emit(webview, "menu://view/reload/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "view/zoom-in" {
|
||||
emit(event.window(), "menu://view/zoom-in/clicked");
|
||||
if event.id() == "view/zoom-in" {
|
||||
emit(webview, "menu://view/zoom-in/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "view/zoom-out" {
|
||||
emit(event.window(), "menu://view/zoom-out/clicked");
|
||||
if event.id() == "view/zoom-out" {
|
||||
emit(webview, "menu://view/zoom-out/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "view/zoom-reset" {
|
||||
emit(event.window(), "menu://view/zoom-reset/clicked");
|
||||
if event.id() == "view/zoom-reset" {
|
||||
emit(webview, "menu://view/zoom-reset/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "help/share-debug-info" {
|
||||
emit(event.window(), "menu://help/share-debug-info/clicked");
|
||||
if event.id() == "help/share-debug-info" {
|
||||
emit(webview, "menu://help/share-debug-info/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "project/history" {
|
||||
emit(event.window(), "menu://project/history/clicked");
|
||||
if event.id() == "project/history" {
|
||||
emit(webview, "menu://project/history/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "project/open-in-vscode" {
|
||||
emit(event.window(), "menu://project/open-in-vscode/clicked");
|
||||
if event.id() == "project/open-in-vscode" {
|
||||
emit(webview, "menu://project/open-in-vscode/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "project/settings" {
|
||||
emit(event.window(), "menu://project/settings/clicked");
|
||||
if event.id() == "project/settings" {
|
||||
emit(webview, "menu://project/settings/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "global/settings" {
|
||||
emit(event.window(), "menu://global/settings/clicked");
|
||||
if event.id() == "global/settings" {
|
||||
emit(webview, "menu://global/settings/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
if event.menu_item_id() == "global/update" {
|
||||
emit(event.window(), "menu://global/update/clicked");
|
||||
if event.id() == "global/update" {
|
||||
emit(webview, "menu://global/update/clicked");
|
||||
return;
|
||||
}
|
||||
|
||||
'open_link: {
|
||||
let result = match event.menu_item_id() {
|
||||
"help/documentation" => open_url("https://docs.gitbutler.com"),
|
||||
"help/github" => open_url("https://github.com/gitbutlerapp/gitbutler"),
|
||||
"help/release-notes" => open_url("https://github.com/gitbutlerapp/gitbutler/releases"),
|
||||
"help/report-issue" => open_url("https://github.com/gitbutlerapp/gitbutler/issues/new"),
|
||||
"help/discord" => open_url("https://discord.com/invite/MmFkmaJ42D"),
|
||||
"help/youtube" => open_url("https://www.youtube.com/@gitbutlerapp"),
|
||||
"help/x" => open_url("https://x.com/gitbutler"),
|
||||
let result = match event.id().0.as_str() {
|
||||
"help/documentation" => open::that("https://docs.gitbutler.com"),
|
||||
"help/github" => open::that("https://github.com/gitbutlerapp/gitbutler"),
|
||||
"help/release-notes" => {
|
||||
open::that("https://discord.com/channels/1060193121130000425/1183737922785116161")
|
||||
}
|
||||
"help/report-issue" => {
|
||||
open::that("https://github.com/gitbutlerapp/gitbutler/issues/new")
|
||||
}
|
||||
"help/discord" => open::that("https://discord.com/invite/MmFkmaJ42D"),
|
||||
"help/youtube" => open::that("https://www.youtube.com/@gitbutlerapp"),
|
||||
"help/x" => open::that("https://x.com/gitbutler"),
|
||||
_ => break 'open_link,
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
tracing::error!(error = ?err, "failed to open url for {}", event.menu_item_id());
|
||||
tracing::error!(error = ?err, "failed to open url for {}", event.id().0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
tracing::error!("unhandled 'help' menu event: {}", event.menu_item_id());
|
||||
tracing::error!("unhandled 'help' menu event: {}", event.id().0);
|
||||
}
|
||||
|
||||
fn emit<R: Runtime>(window: &tauri::Window<R>, event: &str) {
|
||||
fn emit<R: Runtime>(window: &tauri::WebviewWindow<R>, event: &str) {
|
||||
if let Err(err) = window.emit(event, json!({})) {
|
||||
tracing::error!(error = ?err, "failed to emit event");
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ pub(super) mod state {
|
||||
use anyhow::{Context, Result};
|
||||
use gitbutler_project::ProjectId;
|
||||
use gitbutler_watcher::Change;
|
||||
use tauri::Manager;
|
||||
use tauri::Emitter;
|
||||
|
||||
/// A change we want to inform the frontend about.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -64,7 +64,7 @@ pub(super) mod state {
|
||||
impl ChangeForFrontend {
|
||||
pub(super) fn send(&self, app_handle: &tauri::AppHandle) -> Result<()> {
|
||||
app_handle
|
||||
.emit_all(&self.name, Some(&self.payload))
|
||||
.emit(&self.name, Some(&self.payload))
|
||||
.context("emit event")?;
|
||||
tracing::trace!(event_name = self.name);
|
||||
Ok(())
|
||||
@ -204,16 +204,16 @@ pub fn create(
|
||||
handle: &tauri::AppHandle,
|
||||
label: &state::WindowLabelRef,
|
||||
window_relative_url: String,
|
||||
) -> tauri::Result<tauri::Window> {
|
||||
) -> tauri::Result<tauri::WebviewWindow> {
|
||||
tracing::info!("creating window '{label}' created at '{window_relative_url}'");
|
||||
let window = tauri::WindowBuilder::new(
|
||||
let window = tauri::WebviewWindowBuilder::new(
|
||||
handle,
|
||||
label,
|
||||
tauri::WindowUrl::App(window_relative_url.into()),
|
||||
tauri::WebviewUrl::App(window_relative_url.into()),
|
||||
)
|
||||
.resizable(true)
|
||||
.title(handle.package_info().name.clone())
|
||||
.disable_file_drop_handler()
|
||||
.disable_drag_drop_handler()
|
||||
.min_inner_size(800.0, 600.0)
|
||||
.inner_size(1160.0, 720.0)
|
||||
.build()?;
|
||||
@ -225,19 +225,19 @@ pub fn create(
|
||||
handle: &tauri::AppHandle,
|
||||
label: &state::WindowLabelRef,
|
||||
window_relative_url: String,
|
||||
) -> tauri::Result<tauri::Window> {
|
||||
) -> tauri::Result<tauri::WebviewWindow> {
|
||||
tracing::info!("creating window '{label}' created at '{window_relative_url}'");
|
||||
let window = tauri::WindowBuilder::new(
|
||||
let window = tauri::WebviewWindowBuilder::new(
|
||||
handle,
|
||||
label,
|
||||
tauri::WindowUrl::App(window_relative_url.into()),
|
||||
tauri::WebviewUrl::App(window_relative_url.into()),
|
||||
)
|
||||
.resizable(true)
|
||||
.title(handle.package_info().name.clone())
|
||||
.min_inner_size(800.0, 600.0)
|
||||
.inner_size(1160.0, 720.0)
|
||||
.hidden_title(true)
|
||||
.disable_file_drop_handler()
|
||||
.disable_drag_drop_handler()
|
||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||
.build()?;
|
||||
Ok(window)
|
||||
|
@ -1,74 +1,53 @@
|
||||
{
|
||||
"productName": "GitButler Dev",
|
||||
"identifier": "com.gitbutler.app.dev",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev:internal-tauri",
|
||||
"beforeBuildCommand": "[ $CI = true ] || pnpm build:desktop -- --mode development",
|
||||
"devPath": "http://localhost:1420",
|
||||
"distDir": "../../apps/desktop/build",
|
||||
"withGlobalTauri": false
|
||||
"frontendDist": "../../apps/desktop/build",
|
||||
"devUrl": "http://localhost:1420"
|
||||
},
|
||||
"package": {
|
||||
"productName": "GitButler Dev"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"fs": {
|
||||
"readFile": true,
|
||||
"scope": ["$APPCACHE/archives/*", "$RESOURCE/_up_/scripts/*"]
|
||||
"bundle": {
|
||||
"active": false,
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "Copyright © 2023-2024 GitButler. All rights reserved.",
|
||||
"createUpdaterArtifacts": "v1Compatible",
|
||||
"targets": ["app", "dmg", "appimage", "deb", "rpm", "msi"],
|
||||
"icon": [
|
||||
"icons/dev/32x32.png",
|
||||
"icons/dev/128x128.png",
|
||||
"icons/dev/128x128@2x.png",
|
||||
"icons/dev/icon.icns",
|
||||
"icons/dev/icon.ico"
|
||||
],
|
||||
"windows": {
|
||||
"certificateThumbprint": null
|
||||
},
|
||||
"linux": {
|
||||
"rpm": {
|
||||
"depends": ["webkit2gtk4.1-devel"]
|
||||
},
|
||||
"dialog": {
|
||||
"open": true
|
||||
},
|
||||
"os": {
|
||||
"all": true
|
||||
},
|
||||
"protocol": {
|
||||
"asset": true,
|
||||
"assetScope": ["$APPCACHE/images/*"]
|
||||
},
|
||||
"process": {
|
||||
"relaunch": true
|
||||
},
|
||||
"window": {
|
||||
"startDragging": true,
|
||||
"maximize": true,
|
||||
"unmaximize": true
|
||||
},
|
||||
"path": {
|
||||
"all": true
|
||||
},
|
||||
"http": {
|
||||
"all": true,
|
||||
"request": true,
|
||||
"scope": [
|
||||
"https://api.anthropic.com/v1/messages",
|
||||
"http://127.0.0.1:11434/api/chat",
|
||||
"http://127.0.0.1:11434/api/generate",
|
||||
"http://127.0.0.1:11434/api/embeddings"
|
||||
]
|
||||
},
|
||||
"shell": {
|
||||
"open": true
|
||||
"deb": {
|
||||
"depends": ["libwebkit2gtk-4.1-dev", "libgtk-3-dev"]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"identifier": "com.gitbutler.app.dev",
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "Copyright © 2023-2024 GitButler. All rights reserved.",
|
||||
"icon": [
|
||||
"icons/dev/32x32.png",
|
||||
"icons/dev/128x128.png",
|
||||
"icons/dev/128x128@2x.png",
|
||||
"icons/dev/icon.icns",
|
||||
"icons/dev/icon.ico"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"endpoints": [
|
||||
"https://app.gitbutler.com/releases/nightly/{{target}}-{{arch}}/{{current_version}}"
|
||||
],
|
||||
"targets": ["app", "dmg", "appimage", "deb", "rpm", "updater", "msi"]
|
||||
},
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDYwNTc2RDhBM0U0MjM4RUIKUldUck9FSStpbTFYWUE5UkJ3eXhuekZOL2V2RnpKaFUxbGJRNzBMVmF5V0gzV1JvN3hRblJMRDIK"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": false,
|
||||
"enableGTKAppId": true,
|
||||
"security": {
|
||||
"csp": {
|
||||
"default-src": "'self'",
|
||||
"img-src": "'self' asset: https://asset.localhost data: tauri://localhost https://avatars.githubusercontent.com https://*.gitbutler.com https://gitbutler-public.s3.amazonaws.com https://*.gravatar.com https://lh3.googleusercontent.com",
|
||||
"connect-src": "'self' https://eu.posthog.com https://eu.i.posthog.com https://app.gitbutler.com https://o4504644069687296.ingest.sentry.io ws://localhost:7703 https://github.com https://api.github.com",
|
||||
"img-src": "'self' asset: https://asset.localhost data: tauri://localhost https://avatars.githubusercontent.com https://*.gitbutler.com https://gitbutler-public.s3.amazonaws.com https://*.gravatar.com https://io.wp.com https://i0.wp.com https://i1.wp.com https://i2.wp.com https://i3.wp.com https://github.com https://*.googleusercontent.com",
|
||||
"connect-src": "'self' ipc: https://eu.posthog.com https://eu.i.posthog.com https://app.gitbutler.com https://o4504644069687296.ingest.sentry.io ws://localhost:7703 https://github.com https://api.github.com https://api.openai.com",
|
||||
"script-src": "'self' https://eu.posthog.com https://eu.i.posthog.com",
|
||||
"style-src": "'self' 'unsafe-inline'"
|
||||
}
|
||||
|
@ -1,43 +1,21 @@
|
||||
{
|
||||
"productName": "GitButler Nightly",
|
||||
"identifier": "com.gitbutler.app.nightly",
|
||||
"build": {
|
||||
"beforeBuildCommand": "[ $CI = true ] || pnpm build:desktop -- --mode nightly && cargo build --release -p gitbutler-git && bash ./gitbutler-tauri/inject-git-binaries.sh"
|
||||
"beforeBuildCommand": "[ $CI = true ] || pnpm build:desktop -- --mode nightly && cargo build --release -p gitbutler-git && bash ./crates/gitbutler-tauri/inject-git-binaries.sh"
|
||||
},
|
||||
"package": {
|
||||
"productName": "GitButler Nightly"
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"externalBin": ["gitbutler-git-setsid", "gitbutler-git-askpass"]
|
||||
},
|
||||
"tauri": {
|
||||
"bundle": {
|
||||
"identifier": "com.gitbutler.app.nightly",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"externalBin": ["gitbutler-git-setsid", "gitbutler-git-askpass"],
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"wix": {
|
||||
"template": "templates/installer.wxs"
|
||||
}
|
||||
},
|
||||
"rpm": {
|
||||
"depends": ["webkit2gtk4.0-devel"]
|
||||
},
|
||||
"deb": {
|
||||
"depends": ["libwebkit2gtk-4.0-dev", "libgtk-3-dev"]
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"csp": {
|
||||
"default-src": "'self'",
|
||||
"img-src": "'self' asset: https://asset.localhost data: tauri://localhost https://avatars.githubusercontent.com https://*.gitbutler.com https://gitbutler-public.s3.amazonaws.com https://*.gravatar.com https://io.wp.com https://i0.wp.com https://i1.wp.com https://i2.wp.com https://i3.wp.com https://github.com https://*.googleusercontent.com",
|
||||
"connect-src": "'self' https://eu.posthog.com https://eu.i.posthog.com https://app.gitbutler.com https://o4504644069687296.ingest.sentry.io ws://localhost:7703 https://github.com https://api.github.com https://api.openai.com https://api.anthropic.com/v1/messages",
|
||||
"script-src": "'self' https://eu.posthog.com https://eu.i.posthog.com",
|
||||
"style-src": "'self' 'unsafe-inline'"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"active": true,
|
||||
"dialog": false,
|
||||
|
@ -1,43 +1,21 @@
|
||||
{
|
||||
"productName": "GitButler",
|
||||
"identifier": "com.gitbutler.app",
|
||||
"build": {
|
||||
"beforeBuildCommand": "[ $CI = true ] || pnpm build:desktop -- --mode production && cargo build --release -p gitbutler-git && bash ./gitbutler-tauri/inject-git-binaries.sh"
|
||||
"beforeBuildCommand": "[ $CI = true ] || pnpm build:desktop -- --mode production && cargo build --release -p gitbutler-git && bash ./crates/gitbutler-tauri/inject-git-binaries.sh"
|
||||
},
|
||||
"package": {
|
||||
"productName": "GitButler"
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"externalBin": ["gitbutler-git-setsid", "gitbutler-git-askpass"]
|
||||
},
|
||||
"tauri": {
|
||||
"bundle": {
|
||||
"identifier": "com.gitbutler.app",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"externalBin": ["gitbutler-git-setsid", "gitbutler-git-askpass"],
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"wix": {
|
||||
"template": "templates/installer.wxs"
|
||||
}
|
||||
},
|
||||
"rpm": {
|
||||
"depends": ["webkit2gtk4.0-devel"]
|
||||
},
|
||||
"deb": {
|
||||
"depends": ["libwebkit2gtk-4.0-dev", "libgtk-3-dev"]
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"csp": {
|
||||
"default-src": "'self'",
|
||||
"img-src": "'self' asset: https://asset.localhost data: tauri://localhost https://avatars.githubusercontent.com https://*.gitbutler.com https://gitbutler-public.s3.amazonaws.com https://*.gravatar.com https://io.wp.com https://i0.wp.com https://i1.wp.com https://i2.wp.com https://i3.wp.com https://github.com https://*.googleusercontent.com",
|
||||
"connect-src": "'self' https://eu.posthog.com https://eu.i.posthog.com https://app.gitbutler.com https://o4504644069687296.ingest.sentry.io ws://localhost:7703 https://github.com https://api.github.com https://api.openai.com https://api.anthropic.com/v1/messages",
|
||||
"script-src": "'self' https://eu.posthog.com https://eu.i.posthog.com",
|
||||
"style-src": "'self' 'unsafe-inline'"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"active": true,
|
||||
"dialog": false,
|
||||
|
@ -1,51 +1,7 @@
|
||||
{
|
||||
"productName": "GitButler Test",
|
||||
"identifier": "com.gitbutler.app.test",
|
||||
"build": {
|
||||
"beforeBuildCommand": "[ $CI = true ] || pnpm build:desktop -- --mode development",
|
||||
"distDir": "../../apps/desktop/build"
|
||||
},
|
||||
"package": {
|
||||
"productName": "GitButler Dev"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"fs": {
|
||||
"readFile": true,
|
||||
"scope": ["$APPCACHE/archives/*", "$RESOURCE/_up_/scripts/*"]
|
||||
},
|
||||
"dialog": {
|
||||
"open": true
|
||||
},
|
||||
"os": {
|
||||
"all": true
|
||||
},
|
||||
"protocol": {
|
||||
"asset": true,
|
||||
"assetScope": ["$APPCACHE/images/*"]
|
||||
},
|
||||
"process": {
|
||||
"relaunch": true
|
||||
},
|
||||
"window": {
|
||||
"startDragging": true,
|
||||
"maximize": true,
|
||||
"unmaximize": true
|
||||
},
|
||||
"path": {
|
||||
"all": true
|
||||
},
|
||||
"http": {
|
||||
"all": true,
|
||||
"request": true,
|
||||
"scope": [
|
||||
"https://api.anthropic.com/v1/messages",
|
||||
"http://127.0.0.1:11434/api/chat",
|
||||
"http://127.0.0.1:11434/api/generate",
|
||||
"http://127.0.0.1:11434/api/embeddings"
|
||||
]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": false
|
||||
}
|
||||
"beforeBuildCommand": "[ $CI = true ] || pnpm build:desktop -- --mode development && cargo build -p gitbutler-git"
|
||||
}
|
||||
}
|
||||
|
@ -1,350 +0,0 @@
|
||||
<?if $(sys.BUILDARCH)="x86"?>
|
||||
<?define Win64 = "no" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?elseif $(sys.BUILDARCH)="x64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?elseif $(sys.BUILDARCH)="arm64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else?>
|
||||
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
|
||||
<?endif?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product
|
||||
Id="*"
|
||||
Name="{{product_name}}"
|
||||
UpgradeCode="{{upgrade_code}}"
|
||||
Language="!(loc.TauriLanguage)"
|
||||
Manufacturer="{{manufacturer}}"
|
||||
Version="{{version}}">
|
||||
|
||||
<Package Id="*"
|
||||
Keywords="Installer"
|
||||
InstallerVersion="450"
|
||||
Languages="0"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
SummaryCodepage="!(loc.TauriCodepage)"/>
|
||||
|
||||
<!-- https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode -->
|
||||
<!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->
|
||||
<Property Id="REINSTALLMODE" Value="amus" />
|
||||
|
||||
<!-- Auto launch app after installation, useful for passive mode which usually used in updates -->
|
||||
<Property Id="AUTOLAUNCHAPP" Secure="yes" />
|
||||
<!-- Property to forward cli args to the launched app to not lose those of the pre-update instance -->
|
||||
<Property Id="LAUNCHAPPARGS" Secure="yes" />
|
||||
|
||||
{{#if allow_downgrades}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes" />
|
||||
{{else}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" AllowSameVersionUpgrades="yes" />
|
||||
{{/if}}
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<Media Id="1" Cabinet="app.cab" EmbedCab="yes" />
|
||||
|
||||
{{#if banner_path}}
|
||||
<WixVariable Id="WixUIBannerBmp" Value="{{banner_path}}" />
|
||||
{{/if}}
|
||||
{{#if dialog_image_path}}
|
||||
<WixVariable Id="WixUIDialogBmp" Value="{{dialog_image_path}}" />
|
||||
{{/if}}
|
||||
{{#if license}}
|
||||
<WixVariable Id="WixUILicenseRtf" Value="{{license}}" />
|
||||
{{/if}}
|
||||
|
||||
<Icon Id="ProductIcon" SourceFile="{{icon_path}}"/>
|
||||
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
|
||||
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" /> <!-- Remove repair -->
|
||||
<SetProperty Id="ARPNOMODIFY" Value="1" After="InstallValidate" Sequence="execute"/>
|
||||
|
||||
{{#if homepage}}
|
||||
<Property Id="ARPURLINFOABOUT" Value="{{homepage}}"/>
|
||||
<Property Id="ARPHELPLINK" Value="{{homepage}}"/>
|
||||
<Property Id="ARPURLUPDATEINFO" Value="{{homepage}}"/>
|
||||
{{/if}}
|
||||
|
||||
<!-- initialize with previous InstallDir -->
|
||||
<Property Id="INSTALLDIR">
|
||||
<RegistrySearch Id="PrevInstallDirReg" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
<!-- launch app checkbox -->
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="!(loc.LaunchApp)" />
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
|
||||
<CustomAction Id="LaunchApplication" Impersonate="yes" FileKey="Path" ExeCommand="[LAUNCHAPPARGS]" Return="asyncNoWait" />
|
||||
|
||||
<UI>
|
||||
<!-- launch app checkbox -->
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
|
||||
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
|
||||
|
||||
{{#unless license}}
|
||||
<!-- Skip license dialog -->
|
||||
<Publish Dialog="WelcomeDlg"
|
||||
Control="Next"
|
||||
Event="NewDialog"
|
||||
Value="InstallDirDlg"
|
||||
Order="2">1</Publish>
|
||||
<Publish Dialog="InstallDirDlg"
|
||||
Control="Back"
|
||||
Event="NewDialog"
|
||||
Value="WelcomeDlg"
|
||||
Order="2">1</Publish>
|
||||
{{/unless}}
|
||||
</UI>
|
||||
|
||||
<UIRef Id="WixUI_InstallDir" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="DesktopFolder" Name="Desktop">
|
||||
<Component Id="ApplicationShortcutDesktop" Guid="*">
|
||||
<Shortcut Id="ApplicationDesktopShortcut" Name="{{product_name}}" Description="Runs {{product_name}}" Target="[!Path]" WorkingDirectory="INSTALLDIR" />
|
||||
<RemoveFolder Id="DesktopFolder" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Desktop Shortcut" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</Directory>
|
||||
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="PFiles">
|
||||
<Directory Id="INSTALLDIR" Name="{{product_name}}"/>
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="ApplicationProgramsFolder" Name="{{product_name}}"/>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="INSTALLDIR">
|
||||
<Component Id="RegistryEntries" Guid="*">
|
||||
<RegistryKey Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}">
|
||||
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<!-- Change the Root to HKCU for perUser installations -->
|
||||
{{#each deep_link_protocols as |protocol| ~}}
|
||||
<RegistryKey Root="HKLM" Key="Software\Classes\\{{protocol}}">
|
||||
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
|
||||
<RegistryValue Type="string" Value="URL:{{bundle_id}} protocol"/>
|
||||
<RegistryKey Key="DefaultIcon">
|
||||
<RegistryValue Type="string" Value=""[!Path]",0" />
|
||||
</RegistryKey>
|
||||
<RegistryKey Key="shell\open\command">
|
||||
<RegistryValue Type="string" Value=""[!Path]" "%1"" />
|
||||
</RegistryKey>
|
||||
</RegistryKey>
|
||||
{{/each~}}
|
||||
</Component>
|
||||
<Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
|
||||
{{#each file_associations as |association| ~}}
|
||||
{{#each association.ext as |ext| ~}}
|
||||
<ProgId Id="{{../../product_name}}.{{ext}}" Advertise="yes" Description="{{association.description}}">
|
||||
<Extension Id="{{ext}}" Advertise="yes">
|
||||
<Verb Id="open" Command="Open with {{../../product_name}}" Argument=""%1"" />
|
||||
</Extension>
|
||||
</ProgId>
|
||||
{{/each~}}
|
||||
{{/each~}}
|
||||
</Component>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<Component Id="{{ bin.id }}" Guid="{{bin.guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Bin_{{ bin.id }}" Source="{{bin.path}}" KeyPath="yes"/>
|
||||
</Component>
|
||||
{{/each~}}
|
||||
{{#if enable_elevated_update_task}}
|
||||
<Component Id="UpdateTask" Guid="C492327D-9720-4CD5-8DB8-F09082AF44BE" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTask" Source="update.xml" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="UpdateTaskInstaller" Guid="011F25ED-9BE3-50A7-9E9B-3519ED2B9932" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTaskInstaller" Source="install-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
<Component Id="UpdateTaskUninstaller" Guid="D4F6CC3F-32DC-5FD0-95E8-782FFD7BBCE1" Win64="$(var.Win64)">
|
||||
<File Id="UpdateTaskUninstaller" Source="uninstall-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
{{/if}}
|
||||
{{resources}}
|
||||
<Component Id="CMP_UninstallShortcut" Guid="*">
|
||||
|
||||
<Shortcut Id="UninstallShortcut"
|
||||
Name="Uninstall {{product_name}}"
|
||||
Description="Uninstalls {{product_name}}"
|
||||
Target="[System64Folder]msiexec.exe"
|
||||
Arguments="/x [ProductCode]" />
|
||||
|
||||
<RemoveFolder Id="INSTALLDIR"
|
||||
On="uninstall" />
|
||||
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\\{{manufacturer}}\\{{product_name}}"
|
||||
Name="Uninstaller Shortcut"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||
<Component Id="ApplicationShortcut" Guid="*">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||
Name="{{product_name}}"
|
||||
Description="Runs {{product_name}}"
|
||||
Target="[!Path]"
|
||||
Icon="ProductIcon"
|
||||
WorkingDirectory="INSTALLDIR">
|
||||
<ShortcutProperty Key="System.AppUserModel.ID" Value="{{bundle_id}}"/>
|
||||
</Shortcut>
|
||||
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
|
||||
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Start Menu Shortcut" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
{{#each merge_modules as |msm| ~}}
|
||||
<DirectoryRef Id="TARGETDIR">
|
||||
<Merge Id="{{ msm.name }}" SourceFile="{{ msm.path }}" DiskId="1" Language="!(loc.TauriLanguage)" />
|
||||
</DirectoryRef>
|
||||
|
||||
<Feature Id="{{ msm.name }}" Title="{{ msm.name }}" AllowAdvertise="no" Display="hidden" Level="1">
|
||||
<MergeRef Id="{{ msm.name }}"/>
|
||||
</Feature>
|
||||
{{/each~}}
|
||||
|
||||
<Feature
|
||||
Id="MainProgram"
|
||||
Title="Application"
|
||||
Description="!(loc.InstallAppFeature)"
|
||||
Level="1"
|
||||
ConfigurableDirectory="INSTALLDIR"
|
||||
AllowAdvertise="no"
|
||||
Display="expand"
|
||||
Absent="disallow">
|
||||
|
||||
<ComponentRef Id="RegistryEntries"/>
|
||||
|
||||
{{#each resource_file_ids as |resource_file_id| ~}}
|
||||
<ComponentRef Id="{{ resource_file_id }}"/>
|
||||
{{/each~}}
|
||||
|
||||
{{#if enable_elevated_update_task}}
|
||||
<ComponentRef Id="UpdateTask" />
|
||||
<ComponentRef Id="UpdateTaskInstaller" />
|
||||
<ComponentRef Id="UpdateTaskUninstaller" />
|
||||
{{/if}}
|
||||
|
||||
<Feature Id="ShortcutsFeature"
|
||||
Title="Shortcuts"
|
||||
Level="1">
|
||||
<ComponentRef Id="Path"/>
|
||||
<ComponentRef Id="CMP_UninstallShortcut" />
|
||||
<ComponentRef Id="ApplicationShortcut" />
|
||||
<ComponentRef Id="ApplicationShortcutDesktop" />
|
||||
</Feature>
|
||||
|
||||
<Feature
|
||||
Id="Environment"
|
||||
Title="PATH Environment Variable"
|
||||
Description="!(loc.PathEnvVarFeature)"
|
||||
Level="1"
|
||||
Absent="allow">
|
||||
<ComponentRef Id="Path"/>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<ComponentRef Id="{{ bin.id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
</Feature>
|
||||
|
||||
<Feature Id="External" AllowAdvertise="no" Absent="disallow">
|
||||
{{#each component_group_refs as |id| ~}}
|
||||
<ComponentGroupRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each component_refs as |id| ~}}
|
||||
<ComponentRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each feature_group_refs as |id| ~}}
|
||||
<FeatureGroupRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each feature_refs as |id| ~}}
|
||||
<FeatureRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
{{#each merge_refs as |id| ~}}
|
||||
<MergeRef Id="{{ id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
|
||||
{{#if install_webview}}
|
||||
<!-- WebView2 -->
|
||||
<Property Id="WVRTINSTALLED">
|
||||
<RegistrySearch Id="WVRTInstalledSystem" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no" />
|
||||
<RegistrySearch Id="WVRTInstalledUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
|
||||
</Property>
|
||||
|
||||
{{#if download_bootstrapper}}
|
||||
<CustomAction Id='DownloadAndInvokeBootstrapper' Directory="INSTALLDIR" Execute="deferred" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\{] [\[]Net.ServicePointManager[\]]::SecurityProtocol = [\[]Net.SecurityProtocolType[\]]::Tls12 [\}] catch [\{][\}]; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ({{webview_installer_args}} '/install') -Wait' Return='check'/>
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded webview bootstrapper mode -->
|
||||
{{#if webview2_bootstrapper_path}}
|
||||
<Binary Id="MicrosoftEdgeWebview2Setup.exe" SourceFile="{{webview2_bootstrapper_path}}"/>
|
||||
<CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeBootstrapper' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<!-- Embedded offline installer -->
|
||||
{{#if webview2_installer_path}}
|
||||
<Binary Id="MicrosoftEdgeWebView2RuntimeInstaller.exe" SourceFile="{{webview2_installer_path}}"/>
|
||||
<CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='InvokeStandalone' Before='InstallFinalize'>
|
||||
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if enable_elevated_update_task}}
|
||||
<!-- Install an elevated update task within Windows Task Scheduler -->
|
||||
<CustomAction
|
||||
Id="CreateUpdateTask"
|
||||
Return="check"
|
||||
Directory="INSTALLDIR"
|
||||
Execute="commit"
|
||||
Impersonate="yes"
|
||||
ExeCommand="powershell.exe -WindowStyle hidden .\install-task.ps1" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='CreateUpdateTask' Before='InstallFinalize'>
|
||||
NOT(REMOVE)
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
<!-- Remove elevated update task during uninstall -->
|
||||
<CustomAction
|
||||
Id="DeleteUpdateTask"
|
||||
Return="check"
|
||||
Directory="INSTALLDIR"
|
||||
ExeCommand="powershell.exe -WindowStyle hidden .\uninstall-task.ps1" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="DeleteUpdateTask" Before='InstallFinalize'>
|
||||
(REMOVE = "ALL") AND NOT UPGRADINGPRODUCTCODE
|
||||
</Custom>
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="LaunchApplication" After="InstallFinalize">AUTOLAUNCHAPP AND NOT Installed</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
|
||||
</Product>
|
||||
</Wix>
|
@ -32,8 +32,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.5.0",
|
||||
"@tauri-apps/cli": "^1.6.2",
|
||||
"@types/eslint": "9.6.0",
|
||||
"@tauri-apps/cli": "^2.0.1",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/node": "^22.3.0",
|
||||
"@typescript-eslint/parser": "^7.13.1",
|
||||
|
219
pnpm-lock.yaml
219
pnpm-lock.yaml
@ -34,8 +34,8 @@ importers:
|
||||
specifier: ^9.5.0
|
||||
version: 9.5.0
|
||||
'@tauri-apps/cli':
|
||||
specifier: ^1.6.2
|
||||
version: 1.6.2
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
'@types/eslint':
|
||||
specifier: 9.6.0
|
||||
version: 9.6.0
|
||||
@ -90,13 +90,13 @@ importers:
|
||||
|
||||
apps/desktop:
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk':
|
||||
specifier: ^0.27.3
|
||||
version: 0.27.3
|
||||
openai:
|
||||
specifier: ^4.47.3
|
||||
version: 4.47.3
|
||||
devDependencies:
|
||||
'@anthropic-ai/sdk':
|
||||
specifier: ^0.27.3
|
||||
version: 0.27.3
|
||||
'@codemirror/lang-cpp':
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2
|
||||
@ -173,8 +173,35 @@ importers:
|
||||
specifier: catalog:svelte
|
||||
version: 4.0.0-next.6(svelte@5.0.0-next.243)(vite@5.2.13(@types/node@22.3.0))
|
||||
'@tauri-apps/api':
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3
|
||||
'@tauri-apps/plugin-dialog':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
'@tauri-apps/plugin-fs':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
'@tauri-apps/plugin-http':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
'@tauri-apps/plugin-log':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
'@tauri-apps/plugin-os':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
'@tauri-apps/plugin-process':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
'@tauri-apps/plugin-shell':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
'@tauri-apps/plugin-store':
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
'@tauri-apps/plugin-updater':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
'@testing-library/jest-dom':
|
||||
specifier: ^6.4.8
|
||||
version: 6.4.8
|
||||
@ -277,12 +304,6 @@ importers:
|
||||
svelte-french-toast:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(svelte@5.0.0-next.243)
|
||||
tauri-plugin-log-api:
|
||||
specifier: https://github.com/tauri-apps/tauri-plugin-log#v1
|
||||
version: https://codeload.github.com/tauri-apps/tauri-plugin-log/tar.gz/cc86b2d9878d6ec02c9f25bd48292621a4bc2a6f
|
||||
tauri-plugin-store-api:
|
||||
specifier: https://github.com/tauri-apps/tauri-plugin-store#v1
|
||||
version: https://codeload.github.com/tauri-apps/tauri-plugin-store/tar.gz/0b7079b8c55bf25f6d9d9e8c57812d03b48a9788
|
||||
tinykeys:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
@ -1788,75 +1809,101 @@ packages:
|
||||
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
'@tauri-apps/api@1.6.0':
|
||||
resolution: {integrity: sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg==}
|
||||
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
|
||||
'@tauri-apps/api@2.0.3':
|
||||
resolution: {integrity: sha512-840qk6n8rbXBXMA5/aAgTYsg5JAubKO0nXw5wf7IzGnUuYKGbB4oFBIZtXOIWy+E0kNTDI3qhq5iqsoMJfwp8g==}
|
||||
|
||||
'@tauri-apps/cli-darwin-arm64@1.6.2':
|
||||
resolution: {integrity: sha512-6mdRyf9DaLqlZvj8kZB09U3rwY+dOHSGzTZ7+GDg665GJb17f4cb30e8dExj6/aghcsOie9EGpgiURcDUvLNSQ==}
|
||||
'@tauri-apps/cli-darwin-arm64@2.0.1':
|
||||
resolution: {integrity: sha512-oWjCZoFbm57V0eLEkIbc6aUmB4iW65QF7J8JVh5sNzH4xHGP9rzlQarbkg7LOn89z7mFSZpaLJAWlaaZwoV2Ug==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@tauri-apps/cli-darwin-x64@1.6.2':
|
||||
resolution: {integrity: sha512-PLxZY5dn38H3R9VRmBN/l0ZDB5JFanCwlK4rmpzDQPPg3tQmbu5vjSCP6TVj5U6aLKsj79kFyULblPr5Dn9+vw==}
|
||||
'@tauri-apps/cli-darwin-x64@2.0.1':
|
||||
resolution: {integrity: sha512-bARd5yAnDGpG/FPhSh87+tzQ6D0TPyP2mZ5bg6cioeoXDmry68nT/FBzp87ySR1/KHvuhEQYWM/4RPrDjvI1Yg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf@1.6.2':
|
||||
resolution: {integrity: sha512-xnpj4BLeeGOh5I/ewCQlYJZwHH0CBNBN+4q8BNWNQ9MKkjN9ST366RmHRzl2ANNgWwijOPxyce7GiUmvuH8Atw==}
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf@2.0.1':
|
||||
resolution: {integrity: sha512-OK3/RpxujoZAUbV7GHe4IPAUsIO6IuWEHT++jHXP+YW5Y7QezGGjQRc43IlWaQYej/yE8wfcrwrbqisc5wtiCw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-gnu@1.6.2':
|
||||
resolution: {integrity: sha512-uaiRE0vE2P+tdsCngfKt+7yKr3VZXIq/t3w01DzSdnBgHSp0zmRsRR4AhZt7ibvoEgA8GzBP+eSHJdFNZsTU9w==}
|
||||
'@tauri-apps/cli-linux-arm64-gnu@2.0.1':
|
||||
resolution: {integrity: sha512-MGSQJduiMEApspMK97mFt4kr6ig0OtxO5SUFpPDfYPw/XmY9utaRa9CEG6LcH8e0GN9xxYMhCv+FeU48spYPhA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-musl@1.6.2':
|
||||
resolution: {integrity: sha512-o9JunVrMrhqTBLrdvEbS64W0bo1dPm0lxX51Mx+6x9SmbDjlEWGgaAHC3iKLK9khd5Yu1uO1e+8TJltAcScvmw==}
|
||||
'@tauri-apps/cli-linux-arm64-musl@2.0.1':
|
||||
resolution: {integrity: sha512-R6+vgxaPpxgGi4suMkQgGuhjMbZzMJfVyWfv2DOE/xxOzSK1BAOc54/HOjfOLxlnkA6uD6V69MwCwXgxW00A2g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-x64-gnu@1.6.2':
|
||||
resolution: {integrity: sha512-jL9f+o61DdQmNYKIt2Q3BA8YJ+hyC5+GdNxqDf7j5SoQ85j//YfUWbmp9ZgsPHVBxgSGZVvgGMNvf64Ykp0buQ==}
|
||||
'@tauri-apps/cli-linux-x64-gnu@2.0.1':
|
||||
resolution: {integrity: sha512-xrasYQnUZVhKJhBxHAeu4KxZbofaQlsG9KfZ9p1Bx+hmjs5BuujzwMnXsVD2a4l6GPW6gwblf2a6d600rySmWQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-x64-musl@1.6.2':
|
||||
resolution: {integrity: sha512-xsa4Pu9YMHKAX0J8pIoXfN/uhvAAAoECZDixDhWw8zi57VZ4QX28ycqolS+NscdD9NAGSgHk45MpBZWdvRtvjQ==}
|
||||
'@tauri-apps/cli-linux-x64-musl@2.0.1':
|
||||
resolution: {integrity: sha512-SPk+EzRTlbvk46p5aURc7O4GihzxbqG80m74vstm0rolnmQ0FX3qqIh3as3cQpDiZWLod4j6EEmX0mTU3QpvXA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-win32-arm64-msvc@1.6.2':
|
||||
resolution: {integrity: sha512-eJtUOx2UFhJpCCkm5M5+4Co9JbjvgIHTdyS/hTSZfOEdT58CNEGVJXMA39FsSZXYoxYPE+9K7Km6haMozSmlxw==}
|
||||
'@tauri-apps/cli-win32-arm64-msvc@2.0.1':
|
||||
resolution: {integrity: sha512-LAELK01eOMyEt+JZLmx4EUOdRuPYr1a+mHjlxAxCnCaS3dpeg/c5/NMZfbRAJbAH4id+STRHIfPXTdCT2zUNAw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@tauri-apps/cli-win32-ia32-msvc@1.6.2':
|
||||
resolution: {integrity: sha512-9Jwx3PrhNw3VKOgPISRRXPkvoEAZP+7rFRHXIo49dvlHy2E/o9qpWi1IntE33HWeazP6KhvsCjvXB2Ai4eGooA==}
|
||||
'@tauri-apps/cli-win32-ia32-msvc@2.0.1':
|
||||
resolution: {integrity: sha512-eMUgOS4mAusk5njU2TBxBjCUO1P4cV4uzY5CHihysoXSL2TVQdWrXT42VGeoahJh+yeQWkYFka2s4Bu0iWDMXg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@tauri-apps/cli-win32-x64-msvc@1.6.2':
|
||||
resolution: {integrity: sha512-5Z+ZjRFJE8MXghJe1UXvGephY5ZcgVhiTI9yuMi9xgX3CEaAXASatyXllzsvGJ9EDaWMEpa0PHjAzi7LBAWROw==}
|
||||
'@tauri-apps/cli-win32-x64-msvc@2.0.1':
|
||||
resolution: {integrity: sha512-U9esAOcFIv80/slzlpwjkG31Wx1OqbfDgC5KjGT1Dd9iUOSuJZCwbiY7m3rYG2I6RWLfd9zhNu86CVohsKjBfA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@tauri-apps/cli@1.6.2':
|
||||
resolution: {integrity: sha512-zpfZdxhm20s7d/Uejpg/T3a9sqLVe3Ih2ztINfy8v6iLw9Ohowkb9g+agZffYKlEWfOSpmCy69NFyBLj7OZL0A==}
|
||||
'@tauri-apps/cli@2.0.1':
|
||||
resolution: {integrity: sha512-fCheW0iWYWUtFV3ui3HlMhk3ZJpAQ5KJr7B7UmfhDzBSy1h5JBdrCtvDwy+3AcPN+Fg5Ey3JciF8zEP8eBx+vQ==}
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.0.1':
|
||||
resolution: {integrity: sha512-fnUrNr6EfvTqdls/ufusU7h6UbNFzLKvHk/zTuOiBq01R3dTODqwctZlzakdbfSp/7pNwTKvgKTAgl/NAP/Z0Q==}
|
||||
|
||||
'@tauri-apps/plugin-fs@2.0.1':
|
||||
resolution: {integrity: sha512-PkeZG2WAob9Xpmr66aPvj+McDVgFjV2a7YBzYVZjiCvbGeMs6Yk09tlXhCe3EyZdT/pwWMSi8lXUace+hlsjsw==}
|
||||
|
||||
'@tauri-apps/plugin-http@2.0.1':
|
||||
resolution: {integrity: sha512-j6IA3pVBybSCwPpsihpX4z8bs6PluuGtr06ahL/xy4D8HunNBTmRmadJrFOQi0gOAbaig4MkQ15nzNLBLy8R1A==}
|
||||
|
||||
'@tauri-apps/plugin-log@2.0.0':
|
||||
resolution: {integrity: sha512-C+NII9vzswqnOQE8k7oRtnaw0z5TZsMmnirRhXkCKDEhQQH9841Us/PC1WHtGiAaJ8za1A1JB2xXndEq/47X/w==}
|
||||
|
||||
'@tauri-apps/plugin-os@2.0.0':
|
||||
resolution: {integrity: sha512-M7hG/nNyQYTJxVG/UhTKhp9mpXriwWzrs9mqDreB8mIgqA3ek5nHLdwRZJWhkKjZrnDT4v9CpA9BhYeplTlAiA==}
|
||||
|
||||
'@tauri-apps/plugin-process@2.0.0':
|
||||
resolution: {integrity: sha512-OYzi0GnkrF4NAnsHZU7U3tjSoP0PbeAlO7T1Z+vJoBUH9sFQ1NSLqWYWQyf8hcb3gVWe7P1JggjiskO+LST1ug==}
|
||||
|
||||
'@tauri-apps/plugin-shell@2.0.1':
|
||||
resolution: {integrity: sha512-akU1b77sw3qHiynrK0s930y8zKmcdrSD60htjH+mFZqv5WaakZA/XxHR3/sF1nNv9Mgmt/Shls37HwnOr00aSw==}
|
||||
|
||||
'@tauri-apps/plugin-store@2.1.0':
|
||||
resolution: {integrity: sha512-GADqrc17opUKYIAKnGHIUgEeTZ2wJGu1ZITKQ1WMuOFdv8fvXRFBAqsqPjE3opgWohbczX6e1NpwmZK1AnuWVw==}
|
||||
|
||||
'@tauri-apps/plugin-updater@2.0.0':
|
||||
resolution: {integrity: sha512-N0cl71g7RPr7zK2Fe5aoIwzw14NcdLcz7XMGFWZVjprsqgDRWoxbnUkknyCQMZthjhGkppCd/wN2MIsUz+eAhQ==}
|
||||
|
||||
'@terrazzo/cli@0.0.11':
|
||||
resolution: {integrity: sha512-VJTzZ+uw5bzFcvX73g9kvsEtMebRAP/J1oUB7uB8KZYkHtbjKt153jL7YZl5N6B5pHRFLFRE3RU6XPslSvG29g==}
|
||||
hasBin: true
|
||||
@ -5535,14 +5582,6 @@ packages:
|
||||
tar-stream@3.1.7:
|
||||
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
|
||||
|
||||
tauri-plugin-log-api@https://codeload.github.com/tauri-apps/tauri-plugin-log/tar.gz/cc86b2d9878d6ec02c9f25bd48292621a4bc2a6f:
|
||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-log/tar.gz/cc86b2d9878d6ec02c9f25bd48292621a4bc2a6f}
|
||||
version: 0.0.0
|
||||
|
||||
tauri-plugin-store-api@https://codeload.github.com/tauri-apps/tauri-plugin-store/tar.gz/0b7079b8c55bf25f6d9d9e8c57812d03b48a9788:
|
||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-store/tar.gz/0b7079b8c55bf25f6d9d9e8c57812d03b48a9788}
|
||||
version: 0.0.0
|
||||
|
||||
telejson@7.2.0:
|
||||
resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==}
|
||||
|
||||
@ -7725,50 +7764,86 @@ snapshots:
|
||||
dependencies:
|
||||
defer-to-connect: 2.0.1
|
||||
|
||||
'@tauri-apps/api@1.6.0': {}
|
||||
'@tauri-apps/api@2.0.3': {}
|
||||
|
||||
'@tauri-apps/cli-darwin-arm64@1.6.2':
|
||||
'@tauri-apps/cli-darwin-arm64@2.0.1':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-darwin-x64@1.6.2':
|
||||
'@tauri-apps/cli-darwin-x64@2.0.1':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf@1.6.2':
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf@2.0.1':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-gnu@1.6.2':
|
||||
'@tauri-apps/cli-linux-arm64-gnu@2.0.1':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-musl@1.6.2':
|
||||
'@tauri-apps/cli-linux-arm64-musl@2.0.1':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-x64-gnu@1.6.2':
|
||||
'@tauri-apps/cli-linux-x64-gnu@2.0.1':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-x64-musl@1.6.2':
|
||||
'@tauri-apps/cli-linux-x64-musl@2.0.1':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-win32-arm64-msvc@1.6.2':
|
||||
'@tauri-apps/cli-win32-arm64-msvc@2.0.1':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-win32-ia32-msvc@1.6.2':
|
||||
'@tauri-apps/cli-win32-ia32-msvc@2.0.1':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-win32-x64-msvc@1.6.2':
|
||||
'@tauri-apps/cli-win32-x64-msvc@2.0.1':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli@1.6.2':
|
||||
'@tauri-apps/cli@2.0.1':
|
||||
optionalDependencies:
|
||||
'@tauri-apps/cli-darwin-arm64': 1.6.2
|
||||
'@tauri-apps/cli-darwin-x64': 1.6.2
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf': 1.6.2
|
||||
'@tauri-apps/cli-linux-arm64-gnu': 1.6.2
|
||||
'@tauri-apps/cli-linux-arm64-musl': 1.6.2
|
||||
'@tauri-apps/cli-linux-x64-gnu': 1.6.2
|
||||
'@tauri-apps/cli-linux-x64-musl': 1.6.2
|
||||
'@tauri-apps/cli-win32-arm64-msvc': 1.6.2
|
||||
'@tauri-apps/cli-win32-ia32-msvc': 1.6.2
|
||||
'@tauri-apps/cli-win32-x64-msvc': 1.6.2
|
||||
'@tauri-apps/cli-darwin-arm64': 2.0.1
|
||||
'@tauri-apps/cli-darwin-x64': 2.0.1
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf': 2.0.1
|
||||
'@tauri-apps/cli-linux-arm64-gnu': 2.0.1
|
||||
'@tauri-apps/cli-linux-arm64-musl': 2.0.1
|
||||
'@tauri-apps/cli-linux-x64-gnu': 2.0.1
|
||||
'@tauri-apps/cli-linux-x64-musl': 2.0.1
|
||||
'@tauri-apps/cli-win32-arm64-msvc': 2.0.1
|
||||
'@tauri-apps/cli-win32-ia32-msvc': 2.0.1
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.0.1
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.0.1':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.3
|
||||
|
||||
'@tauri-apps/plugin-fs@2.0.1':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.3
|
||||
|
||||
'@tauri-apps/plugin-http@2.0.1':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.3
|
||||
|
||||
'@tauri-apps/plugin-log@2.0.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.3
|
||||
|
||||
'@tauri-apps/plugin-os@2.0.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.3
|
||||
|
||||
'@tauri-apps/plugin-process@2.0.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.3
|
||||
|
||||
'@tauri-apps/plugin-shell@2.0.1':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.3
|
||||
|
||||
'@tauri-apps/plugin-store@2.1.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.3
|
||||
|
||||
'@tauri-apps/plugin-updater@2.0.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.3
|
||||
|
||||
'@terrazzo/cli@0.0.11':
|
||||
dependencies:
|
||||
@ -12044,14 +12119,6 @@ snapshots:
|
||||
fast-fifo: 1.3.2
|
||||
streamx: 2.18.0
|
||||
|
||||
tauri-plugin-log-api@https://codeload.github.com/tauri-apps/tauri-plugin-log/tar.gz/cc86b2d9878d6ec02c9f25bd48292621a4bc2a6f:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 1.6.0
|
||||
|
||||
tauri-plugin-store-api@https://codeload.github.com/tauri-apps/tauri-plugin-store/tar.gz/0b7079b8c55bf25f6d9d9e8c57812d03b48a9788:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 1.6.0
|
||||
|
||||
telejson@7.2.0:
|
||||
dependencies:
|
||||
memoizerific: 1.11.3
|
||||
|
@ -111,27 +111,22 @@ done
|
||||
|
||||
[ -z "${VERSION-}" ] && error "--version is not set"
|
||||
|
||||
[ -z "${TAURI_PRIVATE_KEY-}" ] && error "$TAURI_PRIVATE_KEY is not set"
|
||||
[ -z "${TAURI_KEY_PASSWORD-}" ] && error "$TAURI_KEY_PASSWORD is not set"
|
||||
[ -z "${TAURI_SIGNING_PRIVATE_KEY-}" ] && error "$TAURI_SIGNING_PRIVATE_KEY is not set"
|
||||
[ -z "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD-}" ] && error "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD is not set"
|
||||
|
||||
if [ "$CHANNEL" != "release" ] && [ "$CHANNEL" != "nightly" ]; then
|
||||
error "--channel must be either 'release' or 'nightly'"
|
||||
fi
|
||||
|
||||
export TAURI_PRIVATE_KEY="$TAURI_PRIVATE_KEY"
|
||||
export TAURI_KEY_PASSWORD="$TAURI_KEY_PASSWORD"
|
||||
|
||||
if [ "$DO_SIGN" = "true" ]; then
|
||||
if [ "$OS" = "macos" ]; then
|
||||
[ -z "${APPLE_CERTIFICATE-}" ] && error "$APPLE_CERTIFICATE is not set"
|
||||
[ -z "${APPLE_CERTIFICATE_PASSWORD-}" ] && error "$APPLE_CERTIFICATE_PASSWORD is not set"
|
||||
[ -z "${APPLE_SIGNING_IDENTITY-}" ] && error "$APPLE_SIGNING_IDENTITY is not set"
|
||||
[ -z "${APPLE_ID-}" ] && error "$APPLE_ID is not set"
|
||||
[ -z "${APPLE_TEAM_ID-}" ] && error "$APPLE_TEAM_ID is not set"
|
||||
[ -z "${APPLE_PASSWORD-}" ] && error "$APPLE_PASSWORD is not set"
|
||||
export APPLE_CERTIFICATE="$APPLE_CERTIFICATE"
|
||||
export APPLE_CERTIFICATE_PASSWORD="$APPLE_CERTIFICATE_PASSWORD"
|
||||
export APPLE_SIGNING_IDENTITY="$APPLE_SIGNING_IDENTITY"
|
||||
export APPLE_ID="$APPLE_ID"
|
||||
export APPLE_TEAM_ID="$APPLE_TEAM_ID"
|
||||
export APPLE_PASSWORD="$APPLE_PASSWORD"
|
||||
@ -163,7 +158,7 @@ trap 'rm -rf "$TMP_DIR"' exit
|
||||
CONFIG_PATH=$(readlink -f "$PWD/../crates/gitbutler-tauri/tauri.conf.$CHANNEL.json")
|
||||
|
||||
# update the version in the tauri release config
|
||||
jq '.package.version="'"$VERSION"'"' "$CONFIG_PATH" >"$TMP_DIR/tauri.conf.json"
|
||||
jq '.version="'"$VERSION"'"' "$CONFIG_PATH" >"$TMP_DIR/tauri.conf.json"
|
||||
|
||||
if [ "$OS" = "windows" ]; then
|
||||
FEATURES="windows"
|
||||
|
Loading…
Reference in New Issue
Block a user