feat: windows install guidance (#63)
* build: add dmg/appimage and artifact metadata * feat: enhance packaging outputs and add Windows install-path notice
This commit is contained in:
7 files changed
+2280
-11
No files matched your search
+233
-7
@@ -1,19 +1,115 @@
|
|||||||
import type { ForgeConfig } from '@electron-forge/shared-types';
|
import crypto from 'crypto';
|
||||||
|
import { FuseV1Options, FuseVersion } from '@electron/fuses';
|
||||||
|
import type {
|
||||||
|
HookFunction,
|
||||||
|
HookFunctionErrorCallback,
|
||||||
|
TargetArch,
|
||||||
|
TargetPlatform,
|
||||||
|
} from '@electron/packager';
|
||||||
|
import { MakerDeb } from '@electron-forge/maker-deb';
|
||||||
|
import { MakerDMG } from '@electron-forge/maker-dmg';
|
||||||
|
import { MakerRpm } from '@electron-forge/maker-rpm';
|
||||||
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
|
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
|
||||||
import { MakerZIP } from '@electron-forge/maker-zip';
|
import { MakerZIP } from '@electron-forge/maker-zip';
|
||||||
import { MakerDeb } from '@electron-forge/maker-deb';
|
|
||||||
import { MakerRpm } from '@electron-forge/maker-rpm';
|
|
||||||
import { VitePlugin } from '@electron-forge/plugin-vite';
|
|
||||||
import { FusesPlugin } from '@electron-forge/plugin-fuses';
|
|
||||||
import { FuseV1Options, FuseVersion } from '@electron/fuses';
|
|
||||||
import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives';
|
import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives';
|
||||||
|
import { FusesPlugin } from '@electron-forge/plugin-fuses';
|
||||||
|
import { VitePlugin } from '@electron-forge/plugin-vite';
|
||||||
|
import type { ForgeConfig } from '@electron-forge/shared-types';
|
||||||
|
import MakerAppImage from '@pengx17/electron-forge-maker-appimage';
|
||||||
|
import setLanguages from 'electron-packager-languages';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { stringify as yamlStringify } from 'yaml';
|
||||||
|
|
||||||
const nativeModules = ['better-sqlite3', 'keytar', 'bindings', 'file-uri-to-path'];
|
const nativeModules = ['better-sqlite3', 'keytar', 'bindings', 'file-uri-to-path'];
|
||||||
|
const ResolvedMakerAppImage = MakerAppImage;
|
||||||
|
const keepLanguages = new Set(['en', 'en-US', 'zh-CN', 'ru']);
|
||||||
|
|
||||||
const isStartCommand = process.argv.some((arg) => arg.includes('start'));
|
const isStartCommand = process.argv.some((arg) => arg.includes('start'));
|
||||||
|
|
||||||
|
const artifactRegex = /.*\.(?:exe|dmg|AppImage|zip|deb|rpm)$/;
|
||||||
|
const platformNamesMap: Record<string, string> = {
|
||||||
|
darwin: 'macos',
|
||||||
|
linux: 'linux',
|
||||||
|
win32: 'windows',
|
||||||
|
};
|
||||||
|
const ymlMapsMap: Record<string, string> = {
|
||||||
|
darwin: 'latest-mac.yml',
|
||||||
|
linux: 'latest-linux.yml',
|
||||||
|
win32: 'latest.yml',
|
||||||
|
};
|
||||||
|
const ignorePatterns = [
|
||||||
|
/^\/\.git/,
|
||||||
|
/^\/\.github/,
|
||||||
|
/^\/\.vscode/,
|
||||||
|
/^\/\.idea/,
|
||||||
|
/^\/openspec/,
|
||||||
|
/^\/docs?/,
|
||||||
|
/^\/scripts?/,
|
||||||
|
/^\/tests?/,
|
||||||
|
/^\/mocks?/,
|
||||||
|
/^\/src/,
|
||||||
|
/^\/node_modules\/\.cache/,
|
||||||
|
];
|
||||||
|
const setLanguagesHook = setLanguages([...keepLanguages.values()]);
|
||||||
|
const packagerAfterCopy: HookFunction[] = [
|
||||||
|
(
|
||||||
|
buildPath: string,
|
||||||
|
electronVersion: string,
|
||||||
|
platform: TargetPlatform,
|
||||||
|
arch: TargetArch,
|
||||||
|
callback: HookFunctionErrorCallback,
|
||||||
|
) => {
|
||||||
|
if (platform !== 'win32') {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLanguagesHook(buildPath, electronVersion, platform, arch, callback);
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function normalizeArtifactName(value?: string) {
|
||||||
|
if (!value) {
|
||||||
|
return 'app';
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.trim().replace(/\s+/g, '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSquirrelArtifact(artifactPath: string) {
|
||||||
|
const fileName = path.basename(artifactPath);
|
||||||
|
if (fileName === 'RELEASES') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return artifactPath.endsWith('.nupkg');
|
||||||
|
}
|
||||||
|
|
||||||
|
const appImageMaker = new ResolvedMakerAppImage({
|
||||||
|
config: {
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
file: 'images/32x32.png',
|
||||||
|
size: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'images/64x64.png',
|
||||||
|
size: 64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'images/128x128.png',
|
||||||
|
size: 128,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'images/[email protected]',
|
||||||
|
size: 256,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
appImageMaker.name = '@pengx17/electron-forge-maker-appimage';
|
||||||
|
|
||||||
const config: ForgeConfig = {
|
const config: ForgeConfig = {
|
||||||
packagerConfig: {
|
packagerConfig: {
|
||||||
asar: {
|
asar: {
|
||||||
@@ -23,6 +119,9 @@ const config: ForgeConfig = {
|
|||||||
executableName: 'antigravity-manager',
|
executableName: 'antigravity-manager',
|
||||||
icon: 'images/icon', // Electron Forge automatically adds .icns/.ico
|
icon: 'images/icon', // Electron Forge automatically adds .icns/.ico
|
||||||
extraResource: ['src/assets'], // Copy assets folder to resources/assets
|
extraResource: ['src/assets'], // Copy assets folder to resources/assets
|
||||||
|
afterCopy: packagerAfterCopy,
|
||||||
|
ignore: ignorePatterns,
|
||||||
|
prune: true,
|
||||||
},
|
},
|
||||||
rebuildConfig: {},
|
rebuildConfig: {},
|
||||||
hooks: {
|
hooks: {
|
||||||
@@ -63,8 +162,135 @@ const config: ForgeConfig = {
|
|||||||
console.warn(`Assets directory not found: ${assetsSrc}`);
|
console.warn(`Assets directory not found: ${assetsSrc}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
postMake: async (_config, makeResults) => {
|
||||||
|
if (!makeResults?.length) {
|
||||||
|
return makeResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ymlByPlatform = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
basePath: string;
|
||||||
|
yml: {
|
||||||
|
version?: string;
|
||||||
|
files: {
|
||||||
|
url: string;
|
||||||
|
sha512: string;
|
||||||
|
size: number;
|
||||||
|
}[];
|
||||||
|
releaseDate?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
makeResults = makeResults.map((result) => {
|
||||||
|
const productName = normalizeArtifactName(result.packageJSON.productName);
|
||||||
|
const platformName = platformNamesMap[result.platform] || result.platform;
|
||||||
|
const version = result.packageJSON.version;
|
||||||
|
const platformKey = result.platform;
|
||||||
|
|
||||||
|
if (!ymlByPlatform.has(platformKey)) {
|
||||||
|
ymlByPlatform.set(platformKey, {
|
||||||
|
basePath: '',
|
||||||
|
yml: {
|
||||||
|
version,
|
||||||
|
files: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const platformState = ymlByPlatform.get(platformKey)!;
|
||||||
|
|
||||||
|
result.artifacts = result.artifacts
|
||||||
|
.map((artifact) => {
|
||||||
|
if (!artifact) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSquirrelArtifact(artifact)) {
|
||||||
|
return artifact;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!artifactRegex.test(artifact)) {
|
||||||
|
return artifact;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!platformState.basePath) {
|
||||||
|
platformState.basePath = path.dirname(artifact);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newArtifact = `${path.dirname(artifact)}/${productName}-${version}-${platformName}-${result.arch}${path.extname(artifact)}`;
|
||||||
|
if (newArtifact !== artifact) {
|
||||||
|
fs.renameSync(artifact, newArtifact);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileData = fs.readFileSync(newArtifact);
|
||||||
|
const hash = crypto.createHash('sha512').update(fileData).digest('base64');
|
||||||
|
const { size } = fs.statSync(newArtifact);
|
||||||
|
|
||||||
|
platformState.yml.files.push({
|
||||||
|
url: path.basename(newArtifact),
|
||||||
|
sha512: hash,
|
||||||
|
size,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
console.error(`Failed to hash ${newArtifact}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newArtifact;
|
||||||
|
})
|
||||||
|
.filter((artifact) => artifact !== null);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const releaseDate = new Date().toISOString();
|
||||||
|
for (const [platform, platformState] of ymlByPlatform.entries()) {
|
||||||
|
const ymlFileName = ymlMapsMap[platform];
|
||||||
|
if (!ymlFileName || !platformState.basePath) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
platformState.yml.releaseDate = releaseDate;
|
||||||
|
const ymlPath = path.join(platformState.basePath, ymlFileName);
|
||||||
|
fs.writeFileSync(ymlPath, yamlStringify(platformState.yml));
|
||||||
|
|
||||||
|
const sampleResult = makeResults.find((result) => result.platform === platform);
|
||||||
|
if (!sampleResult) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeResults.push({
|
||||||
|
artifacts: [ymlPath],
|
||||||
|
platform: sampleResult.platform,
|
||||||
|
arch: sampleResult.arch,
|
||||||
|
packageJSON: sampleResult.packageJSON,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeResults;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})],
|
makers: [
|
||||||
|
new MakerSquirrel({
|
||||||
|
setupIcon: 'images/icon.ico',
|
||||||
|
iconUrl:
|
||||||
|
'https://raw.githubusercontent.com/Draculabo/AntigravityManager/main/images/icon.ico',
|
||||||
|
}),
|
||||||
|
new MakerDMG(
|
||||||
|
{
|
||||||
|
overwrite: true,
|
||||||
|
icon: 'images/icon.icns',
|
||||||
|
iconSize: 160,
|
||||||
|
},
|
||||||
|
['darwin'],
|
||||||
|
),
|
||||||
|
new MakerZIP({}, ['darwin']),
|
||||||
|
appImageMaker,
|
||||||
|
new MakerRpm({}),
|
||||||
|
new MakerDeb({}),
|
||||||
|
],
|
||||||
publishers: [
|
publishers: [
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|||||||
Generated
+1814
-3
File diff suppressed because it is too large.
Load diff
@@ -25,6 +25,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron-forge/cli": "^7.10.2",
|
"@electron-forge/cli": "^7.10.2",
|
||||||
"@electron-forge/maker-deb": "^7.10.2",
|
"@electron-forge/maker-deb": "^7.10.2",
|
||||||
|
"@electron-forge/maker-dmg": "^7.10.2",
|
||||||
"@electron-forge/maker-rpm": "^7.10.2",
|
"@electron-forge/maker-rpm": "^7.10.2",
|
||||||
"@electron-forge/maker-squirrel": "^7.10.2",
|
"@electron-forge/maker-squirrel": "^7.10.2",
|
||||||
"@electron-forge/maker-zip": "^7.10.2",
|
"@electron-forge/maker-zip": "^7.10.2",
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
"@electron-forge/plugin-vite": "^7.10.2",
|
"@electron-forge/plugin-vite": "^7.10.2",
|
||||||
"@electron-forge/publisher-github": "^7.10.2",
|
"@electron-forge/publisher-github": "^7.10.2",
|
||||||
"@electron-forge/shared-types": "^7.8.1",
|
"@electron-forge/shared-types": "^7.8.1",
|
||||||
|
"@pengx17/electron-forge-maker-appimage": "^1.2.1",
|
||||||
"@electron/fuses": "~1.8.0",
|
"@electron/fuses": "~1.8.0",
|
||||||
"@electron/rebuild": "^3.7.1",
|
"@electron/rebuild": "^3.7.1",
|
||||||
"@eslint/compat": "^1.4.1",
|
"@eslint/compat": "^1.4.1",
|
||||||
@@ -61,6 +63,7 @@
|
|||||||
"electron": "^37.3.1",
|
"electron": "^37.3.1",
|
||||||
"electron-devtools-installer": "^4.0.0",
|
"electron-devtools-installer": "^4.0.0",
|
||||||
"electron-playwright-helpers": "^1.8.2",
|
"electron-playwright-helpers": "^1.8.2",
|
||||||
|
"electron-packager-languages": "^0.6.0",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.4",
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
|
|||||||
+71
-1
@@ -1,13 +1,21 @@
|
|||||||
import './instrument'; // MUST be the first import to ensure Sentry initializes before app ready
|
import './instrument'; // MUST be the first import to ensure Sentry initializes before app ready
|
||||||
import { app, BrowserWindow } from 'electron';
|
import { app, BrowserWindow, dialog, shell } from 'electron';
|
||||||
|
import type { MessageBoxOptions } from 'electron';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import squirrelStartup from 'electron-squirrel-startup';
|
||||||
|
|
||||||
import { ipcMain } from 'electron/main';
|
import { ipcMain } from 'electron/main';
|
||||||
import { ipcContext } from '@/ipc/context';
|
import { ipcContext } from '@/ipc/context';
|
||||||
import { IPC_CHANNELS } from './constants';
|
import { IPC_CHANNELS } from './constants';
|
||||||
import { updateElectronApp, UpdateSourceType } from 'update-electron-app';
|
import { updateElectronApp, UpdateSourceType } from 'update-electron-app';
|
||||||
import { logger } from './utils/logger';
|
import { logger } from './utils/logger';
|
||||||
|
import {
|
||||||
|
getExpectedInstallRoot,
|
||||||
|
getInstallNoticeText,
|
||||||
|
isRunningFromExpectedInstallDir as isRunningFromExpectedInstallDirUtil,
|
||||||
|
resolveInstallNoticeLanguage,
|
||||||
|
} from './utils/installNotice';
|
||||||
import { CloudAccountRepo } from './ipc/database/cloudHandler';
|
import { CloudAccountRepo } from './ipc/database/cloudHandler';
|
||||||
import { initDatabase } from './ipc/database/handler';
|
import { initDatabase } from './ipc/database/handler';
|
||||||
import { CloudMonitorService } from './services/CloudMonitorService';
|
import { CloudMonitorService } from './services/CloudMonitorService';
|
||||||
@@ -58,6 +66,11 @@ ipcMain.on(IPC_CHANNELS.CHANGE_LANGUAGE, (event, lang) => {
|
|||||||
|
|
||||||
app.disableHardwareAcceleration();
|
app.disableHardwareAcceleration();
|
||||||
|
|
||||||
|
if (squirrelStartup) {
|
||||||
|
app.quit();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
const inDevelopment = process.env.NODE_ENV === 'development';
|
const inDevelopment = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
// Keep a global reference of the window object, if you don't, the window will
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
@@ -67,6 +80,62 @@ let globalMainWindow: BrowserWindow | null = null;
|
|||||||
let isQuitting = false;
|
let isQuitting = false;
|
||||||
let startupConfig: AppConfig | null = null;
|
let startupConfig: AppConfig | null = null;
|
||||||
let shouldStartHidden = false;
|
let shouldStartHidden = false;
|
||||||
|
let hasShownInstallNotice = false;
|
||||||
|
|
||||||
|
function isRunningFromExpectedInstallDir() {
|
||||||
|
return isRunningFromExpectedInstallDirUtil({
|
||||||
|
platform: process.platform,
|
||||||
|
isPackaged: app.isPackaged,
|
||||||
|
localAppData: process.env.LOCALAPPDATA,
|
||||||
|
appName: app.getName(),
|
||||||
|
execPath: process.execPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWindowsInstallNoticeIfNeeded() {
|
||||||
|
if (hasShownInstallNotice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRunningFromExpectedInstallDir()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedRoot = getExpectedInstallRoot({
|
||||||
|
platform: process.platform,
|
||||||
|
localAppData: process.env.LOCALAPPDATA,
|
||||||
|
appName: app.getName(),
|
||||||
|
});
|
||||||
|
if (!expectedRoot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasShownInstallNotice = true;
|
||||||
|
const language = resolveInstallNoticeLanguage({
|
||||||
|
configLanguage: startupConfig?.language,
|
||||||
|
locale: app.getLocale(),
|
||||||
|
});
|
||||||
|
const text = getInstallNoticeText(language);
|
||||||
|
|
||||||
|
const options: MessageBoxOptions = {
|
||||||
|
type: 'info',
|
||||||
|
title: text.title,
|
||||||
|
message: text.message,
|
||||||
|
detail: `${text.detailPrefix}${expectedRoot}`,
|
||||||
|
buttons: [...text.buttons],
|
||||||
|
defaultId: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const showPromise = globalMainWindow
|
||||||
|
? dialog.showMessageBox(globalMainWindow, options)
|
||||||
|
: dialog.showMessageBox(options);
|
||||||
|
|
||||||
|
showPromise.then(({ response }) => {
|
||||||
|
if (response === 0) {
|
||||||
|
shell.openPath(expectedRoot);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const gotSingleInstanceLock = app.requestSingleInstanceLock();
|
const gotSingleInstanceLock = app.requestSingleInstanceLock();
|
||||||
|
|
||||||
@@ -154,6 +223,7 @@ function createWindow({ startHidden }: { startHidden: boolean }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Window created');
|
logger.info('Window created');
|
||||||
|
showWindowsInstallNoticeIfNeeded();
|
||||||
|
|
||||||
mainWindow.on('close', (event) => {
|
mainWindow.on('close', (event) => {
|
||||||
if (!isQuitting) {
|
if (!isQuitting) {
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export type InstallNoticeLanguage = 'zh-CN' | 'en' | 'ru';
|
||||||
|
|
||||||
|
const installNoticeText: Record<
|
||||||
|
InstallNoticeLanguage,
|
||||||
|
{
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
detailPrefix: string;
|
||||||
|
buttons: [string, string];
|
||||||
|
}
|
||||||
|
> = {
|
||||||
|
'zh-CN': {
|
||||||
|
title: '建议从开始菜单启动',
|
||||||
|
message:
|
||||||
|
'检测到你正在从非安装路径启动应用。为了确保自动更新生效,请从开始菜单或桌面快捷方式启动。如果没有快捷方式,请重新运行安装包。',
|
||||||
|
detailPrefix: '安装目录:',
|
||||||
|
buttons: ['打开安装目录', '知道了'],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: 'Please launch from the Start menu',
|
||||||
|
message:
|
||||||
|
'We detected the app is running from a non-install location. To ensure auto-updates work, launch it from the Start menu or desktop shortcut. If no shortcut exists, run the installer again.',
|
||||||
|
detailPrefix: 'Install location: ',
|
||||||
|
buttons: ['Open install folder', 'OK'],
|
||||||
|
},
|
||||||
|
ru: {
|
||||||
|
title: 'Запускайте приложение из меню «Пуск»',
|
||||||
|
message:
|
||||||
|
'Обнаружен запуск из неустановленного пути. Чтобы автообновления работали, запускайте приложение из меню «Пуск» или ярлыка. Если ярлыка нет, запустите установщик ещё раз.',
|
||||||
|
detailPrefix: 'Папка установки: ',
|
||||||
|
buttons: ['Открыть папку', 'Понятно'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resolveInstallNoticeLanguage({
|
||||||
|
configLanguage,
|
||||||
|
locale,
|
||||||
|
}: {
|
||||||
|
configLanguage?: string | null;
|
||||||
|
locale?: string | null;
|
||||||
|
}): InstallNoticeLanguage {
|
||||||
|
const rawLanguage = configLanguage || locale;
|
||||||
|
if (!rawLanguage) {
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = rawLanguage.toLowerCase();
|
||||||
|
if (normalized.startsWith('zh')) {
|
||||||
|
return 'zh-CN';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized.startsWith('ru')) {
|
||||||
|
return 'ru';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInstallNoticeText(language: InstallNoticeLanguage) {
|
||||||
|
return installNoticeText[language] || installNoticeText.en;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPathApi(platform: string) {
|
||||||
|
if (platform === 'win32') {
|
||||||
|
return path.win32;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getExpectedInstallRoot({
|
||||||
|
platform,
|
||||||
|
localAppData,
|
||||||
|
appName,
|
||||||
|
}: {
|
||||||
|
platform: string;
|
||||||
|
localAppData?: string | null;
|
||||||
|
appName: string;
|
||||||
|
}) {
|
||||||
|
if (platform !== 'win32') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localAppData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathApi = getPathApi(platform);
|
||||||
|
return pathApi.resolve(pathApi.join(localAppData, appName));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRunningFromExpectedInstallDir({
|
||||||
|
platform,
|
||||||
|
isPackaged,
|
||||||
|
localAppData,
|
||||||
|
appName,
|
||||||
|
execPath,
|
||||||
|
}: {
|
||||||
|
platform: string;
|
||||||
|
isPackaged: boolean;
|
||||||
|
localAppData?: string | null;
|
||||||
|
appName: string;
|
||||||
|
execPath: string;
|
||||||
|
}) {
|
||||||
|
if (platform !== 'win32' || !isPackaged) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedRoot = getExpectedInstallRoot({ platform, localAppData, appName });
|
||||||
|
if (!expectedRoot) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathApi = getPathApi(platform);
|
||||||
|
const normalizedExecPath = pathApi.resolve(execPath);
|
||||||
|
|
||||||
|
return normalizedExecPath.toLowerCase().startsWith(expectedRoot.toLowerCase() + pathApi.sep);
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*",
|
"src/**/*",
|
||||||
|
"types/**/*.d.ts",
|
||||||
"./package.json",
|
"./package.json",
|
||||||
"./forge.config.ts",
|
"./forge.config.ts",
|
||||||
"*.mts",
|
"*.mts",
|
||||||
|
|||||||
Vendored
+38
@@ -0,0 +1,38 @@
|
|||||||
|
declare module 'electron-packager-languages' {
|
||||||
|
type AfterCopyHook = (
|
||||||
|
buildPath: string,
|
||||||
|
electronVersion: string,
|
||||||
|
platform: string,
|
||||||
|
arch: string,
|
||||||
|
callback: () => void,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
interface SetLanguagesOptions {
|
||||||
|
allowRemovingAll?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLanguages(languages: string[], options?: SetLanguagesOptions): AfterCopyHook;
|
||||||
|
|
||||||
|
export = setLanguages;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@pengx17/electron-forge-maker-appimage' {
|
||||||
|
import type MakerBase from '@electron-forge/maker-base';
|
||||||
|
import type { ForgePlatform } from '@electron-forge/shared-types';
|
||||||
|
|
||||||
|
export interface AppImageForgeConfig {
|
||||||
|
template?: string;
|
||||||
|
chmodChromeSandbox?: string;
|
||||||
|
icons?: { file: string; size: number }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MakerAppImageConfig {
|
||||||
|
config?: AppImageForgeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MakerAppImage extends MakerBase<MakerAppImageConfig> {
|
||||||
|
name: string;
|
||||||
|
defaultPlatforms: ForgePlatform[];
|
||||||
|
isSupportedOnCurrentPlatform(): boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user