blob: af435e1940e7c0ba0350e09fc9ec3e33137c681b [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// Single source for the dev single-instance launch contract between the
// Electron main process (the authority) and the dev launcher (the consumer).
// Packaged builds keep the existing behavior (second instance exits 0 and
// the first window is focused); in dev (`!app.isPackaged`) a losing process
// must NOT pretend to have started. A direct launcher reads
// DEV_LOSER_EXIT_CODE; a LaunchServices-detached launcher reads its private,
// one-shot verdict file. The verdict never transfers app-process ownership.
export const DEV_LOSER_EXIT_CODE = 42 as const;
export const DEV_CONFLICT_HANDLED_BY_LAUNCHER_FLAG =
'--maka-dev-conflict-handled-by-launcher' as const;
export const DEV_LAUNCH_RESULT_FILE_ARG_PREFIX = '--maka-dev-launch-result-file=' as const;
export type DevelopmentLaunchResult = { readonly status: 'winner' } | { readonly status: 'loser' };
export function developmentLaunchResultFile(argv: readonly string[]): string | undefined {
for (let index = argv.length - 1; index >= 0; index -= 1) {
const argument = argv[index];
if (!argument?.startsWith(DEV_LAUNCH_RESULT_FILE_ARG_PREFIX)) continue;
const file = argument.slice(DEV_LAUNCH_RESULT_FILE_ARG_PREFIX.length);
return file.length > 0 ? file : undefined;
}
return undefined;
}
export function serializeDevelopmentLaunchResult(result: DevelopmentLaunchResult): string {
return `${JSON.stringify(result)}\n`;
}
export function parseDevelopmentLaunchResult(value: string): DevelopmentLaunchResult | undefined {
try {
const result: unknown = JSON.parse(value);
if (typeof result !== 'object' || result === null || !('status' in result)) return undefined;
if (result.status === 'loser') return { status: 'loser' };
if (result.status === 'winner') return { status: 'winner' };
return undefined;
} catch {
return undefined;
}
}
/**
* Whether the dev loser should show the native dialog: default yes; the flag
* is a capability promise ("the launcher handles the conflict"), so CI/CLI
* can suppress the dialog by passing it. Pure so the decision is testable.
*/
export function shouldShowLoserDialog(argv: readonly string[]): boolean {
return !argv.includes(DEV_CONFLICT_HANDLED_BY_LAUNCHER_FLAG);
}