// eslint-disable-next-line import-x/no-extraneous-dependencies import type { Configuration, PackagerOptions, PublishOptions, } from 'app-builder-lib'; import type { IApp2 } from './desktopify2'; import { BreakpointPauseLease, BreakpointQueueEntry, CurrentBreakpointState, } from './introspection/breakpoints'; import type { IApp } from './toDesktop'; type appBuilderLib = PackagerOptions & PublishOptions; export interface IAppBuilderLib extends appBuilderLib { config: Configuration; } export interface ExtraFileReference { from: FilePath; to?: FilePath; } export type ISODate = string; // TODO: define more export type Platform = `${PlatformName}`; export enum PlatformName { linux = 'linux', mac = 'mac', windows = 'windows', } export type FilePath = string; export type SemanticVersion = string; // TODO: define more export type URL = string; // TODO: define more export enum PackageManager { bun = 'bun', npm = 'npm', pnpm = 'pnpm', yarn = 'yarn', } export enum BuildStatus { building = 'building', cancelled = 'cancelled', failed = 'failed', preparation = 'preparation', queued = 'queued', succeeded = 'succeeded', } export enum AnalysisStatus { analyzing = 'analyzing', done = 'done', downloading = 'downloading', error = 'error', extracting = 'extracting', initializing = 'initializing', notStarted = 'notStarted', } export type Arch = 'arm64' | 'ia32' | 'universal' | 'x64'; export type MacArch = 'arm64' | 'universal' | 'x64'; export type LinuxArch = 'arm64' | 'x64'; export type WindowsArch = 'arm64' | 'ia32' | 'universal' | 'x64'; export type LinuxArtifactName = 'appImage' | 'deb' | 'rpm' | 'snap'; export type MacArtifactName = 'dmg' | 'installer' | 'mas' | 'pkg' | 'zip'; export type WindowsArtifactName = | 'appx' | 'msi' | 'nsis-web-7z' | 'nsis-web' | 'nsis'; type ArtifactObject = { size: number; standardUrl: URL; url: URL }; type ArtifactDownload = null | Record; // The `installer` artifact is special because it has an additional property // `isPinnedToVersion` for apps that have `appData.macUniversalInstallerConfig?.shouldPinToVersion` set to true type InstallerArtifact = { [K in Arch]: ({ isPinnedToVersion?: boolean } & ArtifactObject) | null; }; export type LinuxArtifactDownloads = Record< LinuxArtifactName, ArtifactDownload >; export type MacArtifactDownloads = { [K in MacArtifactName]: K extends 'installer' ? InstallerArtifact : ArtifactDownload; }; export type WindowsArtifactDownloads = Record< WindowsArtifactName, ArtifactDownload >; export type CodeSignSkipReason = 'no-cert' | 'user-disabled'; export interface PlatformBuild { appBuilderLibConfig?: IAppBuilderLib; // appBuilderLibVersion: SemanticVersion; artifactDownloads?: | LinuxArtifactDownloads | MacArtifactDownloads | WindowsArtifactDownloads; // desktopifyCommitId: string; codeSignSkipReason?: CodeSignSkipReason; desktopifyVersion?: SemanticVersion; didCodeSign: boolean; didUseCachedDependencies?: boolean; downloadUrl?: URL; electronVersionUsed?: SemanticVersion; endedAt: ISODate; errorMessage?: string; isBeingCancelled?: boolean; // nodeVersion: number; // npmVersion: number; logs?: { content: string; label: string }[]; numberOfAttemptedBuilds: number; platform: PlatformName; progressActivityName: string; progressActivityType: string; progressPercentage: number; screenshotUrl?: URL; shouldSkip: boolean; standardDownloadUrl?: URL; startedAt: ISODate; status: BuildStatus; } export type CIRunner = 'azure' | 'circle'; export type IntrospectBreakpointStatus = | 'error' | 'finished' | 'initializing' | 'paused' | 'ready' | 'resuming'; export type IntrospectShellStatus = | 'connected' | 'connecting' | 'disconnected' | 'error' | 'initializing' | 'ready'; export interface IntrospectPlatformData { breakpointQueue?: BreakpointQueueEntry[]; breakpointStatus: IntrospectBreakpointStatus; connectedAt?: Date; connectedUserId?: string; createdAt: ISODate; currentBreakpoint?: CurrentBreakpointState; disconnectedAt?: Date; enabled: boolean; error?: string; jtiExpiresAt?: Date; pauseLease?: BreakpointPauseLease; sessionJti?: string; shellStatus: IntrospectShellStatus; tunnelUrl?: string; usedJti?: string; } export interface IntrospectData { linux: IntrospectPlatformData; mac: IntrospectPlatformData; windows: IntrospectPlatformData; } export interface Build { appCustomDomain?: string; appName: string; appNotarizaionBundleId: string; appVersion?: SemanticVersion; buildServerExecutionId?: number; bundlePhobiaData?: Record; ciRunner?: CIRunner; cliConfigSchemaVersion: number; cloned?: { appId: string; buildId?: string; clonedAt: string; clonedBy: string; stage: 'dev' | 'local' | 'prod'; }; commitId?: string; commitMessage?: string; continuousIntegrationServiceName?: string; createdAt: ISODate; createdByUserDisplayName?: string; createdByUserId?: string; dependencyAnalysis?: { // `result` is a stringified JSON object result?: string; status: AnalysisStatus; }; // createdByOsArch: string; // createdByOsName: string; // createdByOsVersion: string; desktopifyVersion?: SemanticVersion; electronVersionSpecified?: SemanticVersion; electronVersionUsed?: SemanticVersion; endedAt?: ISODate; environmentVariables?: IApp['environmentVariables']; errorMessage?: string; hash?: string; icon?: string; id: string; introspect?: IntrospectData; isArtifactsPruned?: boolean; isBeingCancelled?: boolean; linux?: PlatformBuild; mac?: PlatformBuild; onBuildFinishedWebhook?: string; partiallyReleasedAt?: ISODate; projectConfig?: string; // Stringified version of todesktop.json releasable?: boolean; releasedAt?: ISODate; shouldCodeSign: boolean; shouldRelease: boolean; shouldRetainForever?: boolean; // Artifacts can't be pruned smokeTest?: { buildServerExecutionId?: string; isCanceled?: boolean; linux?: SmokeTestProgress; mac?: SmokeTestProgress; windows?: SmokeTestProgress; }; // sourceArchiveUrl: URL; sourcePackageManager?: PackageManager; standardUniversalDownloadUrl?: URL; startedAt: ISODate; staticAnalysis?: { error?: string; // `result` is a stringified JSON object result?: string; status: AnalysisStatus; }; status: BuildStatus; todesktopRuntimeVersionSpecified?: SemanticVersion; todesktopRuntimeVersionUsed?: SemanticVersion; // TODO: total progress percentage? universalDownloadUrl?: URL; url?: URL; useCachedDependencies?: boolean; versionControlInfo?: { branchName: string; commitDate: string; commitId: string; commitMessage: string; hasUncommittedChanges: boolean; repositoryRemoteUrl: string; versionControlSystemName: string; }; // wasCreatedByContinuousIntegrationService: boolean; windows?: PlatformBuild; } export type BundlePhobiaItem = { error: null | string; result: { description: string; gzip: string; name: string; size: number; } | null; }; export type ComputedBuildProperties = Pick< Build, | 'desktopifyVersion' | 'electronVersionUsed' | 'endedAt' | 'errorMessage' | 'releasedAt' | 'standardUniversalDownloadUrl' | 'startedAt' | 'status' | 'universalDownloadUrl' >; export enum ManifestCategory { buildStamped = 'build-stamped', primary = 'primary', versioned = 'versioned', } export interface CustomManifestArtifactDetails { path: FilePath; url: URL; } export interface CustomManifest { artifacts: { [propertyName: string]: null | Record< string, CustomManifestArtifactDetails | null >; }; createdAt: ISODate; version: SemanticVersion; } export interface LinuxCustomManifest extends CustomManifest { artifacts: Record< LinuxArtifactName, null | Record >; } export interface MacCustomManifest extends CustomManifest { artifacts: Record< MacArtifactName, null | Record >; } export interface WindowsCustomManifest extends CustomManifest { artifacts: Record< WindowsArtifactName, null | Record >; } export interface SmokeTestProgress { abSkipReason?: string; abState?: SmokeTestState; appLaunched?: boolean; appUpdated?: boolean; bcState?: SmokeTestState; buildAId?: string; code?: string; // string currently, since it's still a subject to change message?: string; performance?: { ab?: SmokeTestPerformance; bc: SmokeTestPerformance; }; progress: number; screenshot?: string; state: SmokeTestState; updatedAppHasNoMainErrors?: boolean; vm: { agentVersion: string; cpu?: string; // win only image: string; imageVersion: string; }; } export interface SmokeTestPerformance { /** * Time between `process.getCreationTime()` and `App#redy event` */ appReadyMs: number; /** * Time between `process.getCreationTime()` and app is closed for update */ appReadyToRestartForUpdateMs: number; /** * Time between `process.getCreationTime()` and connect event received from * the restarted app. It can be up to 4 minutes on Windows. */ appRestartedAfterUpdateMs: number; /** * From `process.cpuUsage()` user + system. * This value measures time spent in user and system code, and may end up * being greater than actual elapsed time if multiple CPU cores are * performing work for this process. * Under the hood, low-level calls like `getrusage` are used, so this value * is calculated from the process start. * It measures for the first 10-12 seconds of the app execution. */ cpuUsageMainProcessMs: number; /** * Maximum memory consumption by the main + renders + any other helper * processes */ memoryUsageAllProcessesMb: number; /** * Maximum memory consumption by the main process */ memoryUsageMainProcessMb: number; /** * Maximum memory consumption by a renderer process (if started) */ memoryUsageRendererProcessMb?: number; /** * Time between `process.getCreationTime()` and `initSmokeTest()` of * runtime execution */ runtimeLoadedMs: number; /** * Time between `process.getCreationTime()` and the update file downloaded * from the server. */ updateDownloadedMs: number; /** * Time between `process.getCreationTime()` and the first firing of * WebContents#dom-ready if any window is created */ webContentsDomReadyMs?: number; /** * Time between `process.getCreationTime()` and the first firing of * WebContents#did-finish-load if any window is created */ webContentsFinishLoadMs?: number; } export type SmokeTestState = 'done' | 'error' | 'progress' | 'skipped'; export const hasBuildKickedOff = (build: Build): boolean => { if (!build) { return false; } return build.status && build.status !== BuildStatus.preparation; }; export const isPlatformBuildRunning = ( platformBuild: PlatformBuild, ): boolean => { if (!platformBuild) { return false; } return ( !platformBuild.shouldSkip && // <-- Noteworthy !['cancelled', 'succeeded'].includes(platformBuild.status) && (platformBuild.status !== 'failed' || platformBuild.numberOfAttemptedBuilds < 2) ); }; // NOTE: this relies on Firestore solely export const isCiBuildRunning = (build: Build): boolean => { if (!build) { return false; } return ( build.status === 'building' || (build.status === 'failed' && ['linux', 'mac', 'windows'].some( (platform) => build.status === 'building' || (build.status === 'failed' && isPlatformBuildRunning(build[platform as 'mac']!)), )) ); }; export const isBuildRunning = (build: Build): boolean => { if (!build) { return false; } return ( !['cancelled', 'succeeded'].includes(build.status) && ['linux', 'mac', 'windows'].some((platform) => isPlatformBuildRunning(build[platform as 'mac']!), ) ); }; export const isBuildCancellable = (build: Build): boolean => hasBuildKickedOff(build) && isBuildRunning(build); export interface DesktopifyApp { alwaysOnTop: boolean; // remove appId: string; appProtocol?: string; appType?: string; autoHideMenuBar?: boolean; // remove companyName?: string; crashReporter?: string; disableContextMenu: boolean; disableDevTools: boolean; enablePushNotifications?: boolean; extraBrowserWindowOptions?: any; fullScreen: boolean; // remove googleOAuthIsExternal?: boolean; height: number; // remove insecure?: boolean; internalUrls?: string; isFindInPageEnabled?: boolean; isNativeWindowOpenDisabled?: boolean; isResizable: boolean; // remove isTitleStatic?: boolean; maxHeight?: number; // remove maxWidth?: number; // remove menubarIcon?: string; minHeight?: number; // remove minWidth?: number; // remove name: string; pollForAppUpdatesEveryXMinutes?: number; preventBackgroundThrottling?: boolean; runtimeEnvs?: string; shouldLaunchAtStartupByDefault?: boolean; shouldMakeSameDomainAnExternalLink?: boolean; shouldOnlySendAbsolutelyNecessaryRequests?: boolean; shouldReuseRendererProcess?: boolean; shouldUseRealUserAgent?: boolean; singleInstance: boolean; targetUrl: string; themeSource?: 'dark' | 'light' | 'system'; themeSourceMac?: 'dark' | 'light' | 'system'; titleBarStyle?: 'customButtonsOnHover' | 'hidden' | 'hiddenInset'; // remove toggleVisibilityKeyboardShortcut?: string; trayIcon?: string; userAgent?: string; // https://linear.app/todesktop/issue/TD-1428/html-injection-due-to-regular-expression-bypass useSafeInternalUrlMatcher?: boolean; width: number; // remove } export interface Release { appReleaseSnapshot?: IApp2; id: string; releasedAt?: string; startedAt?: string; }