fix(sources): hide unavailable update and management entries

This commit is contained in:
2026-04-16 13:04:54 +08:00
parent e1ec526cb9
commit 0b784af3d7
16 changed files with 667 additions and 58 deletions
+55
View File
@@ -10,6 +10,24 @@ import axios from "axios";
const logger = pino({ name: "install-manager" });
const getStoreFilterFromArgv = (): "spark" | "apm" | "both" => {
const argv = process.argv;
const noApm = argv.includes("--no-apm");
const noSpark = argv.includes("--no-spark");
if (noApm && noSpark) return "both";
if (noApm) return "spark";
if (noSpark) return "apm";
return "both";
};
const isOriginEnabled = (
storeFilter: "spark" | "apm" | "both",
origin: "spark" | "apm",
): boolean => {
return storeFilter === "both" || storeFilter === origin;
};
type InstallTask = {
id: number;
pkgname: string;
@@ -88,6 +106,14 @@ const checkApmAvailable = async (): Promise<boolean> => {
return found;
};
/** 检测本机是否具备 Spark/aptss 管理能力 */
const checkSparkAvailable = async (): Promise<boolean> => {
const { code, stdout } = await runCommandCapture("which", ["aptss"]);
const found = code === 0 && stdout.trim().length > 0;
if (!found) logger.info("未检测到 aptss 命令");
return found;
};
/** 提权执行 shell-caller aptss install apm 安装 APM,安装后需用户重启电脑 */
const runInstallApm = async (superUserCmd: string): Promise<boolean> => {
const execCommand = superUserCmd || SHELL_CALLER_PATH;
@@ -793,8 +819,33 @@ ipcMain.handle(
payload: { origin: "apm" | "spark"; pkgnameList?: string[] },
) => {
const { origin, pkgnameList } = payload;
const storeFilter = getStoreFilterFromArgv();
const apmBasePath = "/var/lib/apm/apm/files/ace-env/var/lib/apm";
if (!isOriginEnabled(storeFilter, origin)) {
return {
success: false,
message: `${origin} origin disabled by startup filter`,
apps: [],
};
}
if (origin === "spark" && !(await checkSparkAvailable())) {
return {
success: false,
message: "spark origin unavailable on this system",
apps: [],
};
}
if (origin === "apm" && !(await checkApmAvailable())) {
return {
success: false,
message: "apm origin unavailable on this system",
apps: [],
};
}
try {
const installedApps: Array<{
pkgname: string;
@@ -1033,6 +1084,10 @@ ipcMain.handle("check-apm-available", async () => {
return await checkApmAvailable();
});
ipcMain.handle("check-spark-available", async () => {
return await checkSparkAvailable();
});
// 显示 APM 安装对话框(在点击安装按钮时提前检查)
ipcMain.handle("show-apm-install-dialog", async (event) => {
const webContents = event.sender;
+64 -18
View File
@@ -12,6 +12,7 @@ import {
import { resolveUpdateItemIcons } from "./icons";
import {
createUpdateCenterService,
type StoreFilter,
type UpdateCenterIgnorePayload,
type UpdateCenterService,
type UpdateCenterStartTask,
@@ -349,35 +350,70 @@ const enrichItemIcons = (items: UpdateCenterItem[]): UpdateCenterItem[] => {
});
};
const isSourceEnabled = (
storeFilter: StoreFilter,
source: "spark" | "apm",
): boolean => {
return storeFilter === "both" || storeFilter === source;
};
const isCommandAvailable = async (
runCommand: UpdateCenterCommandRunner,
command: "aptss" | "apm",
): Promise<boolean> => {
const result = await runCommand("which", [command]);
return result.code === 0 && result.stdout.trim().length > 0;
};
export const loadUpdateCenterItems = async (
runCommand: UpdateCenterCommandRunner = runCommandCapture,
storeFilter: StoreFilter = "both",
): Promise<UpdateCenterLoadItemsResult> => {
const [sparkEnabled, apmEnabled] = await Promise.all([
isSourceEnabled(storeFilter, "spark")
? isCommandAvailable(runCommand, "aptss")
: Promise.resolve(false),
isSourceEnabled(storeFilter, "apm")
? isCommandAvailable(runCommand, "apm")
: Promise.resolve(false),
]);
const [aptssResult, apmResult, aptssInstalledResult, apmInstalledResult] =
await Promise.all([
runCommand(
APTSS_LIST_UPGRADABLE_COMMAND.command,
APTSS_LIST_UPGRADABLE_COMMAND.args,
),
runCommand("apm", ["list", "--upgradable"]),
runCommand(
DPKG_QUERY_INSTALLED_COMMAND.command,
DPKG_QUERY_INSTALLED_COMMAND.args,
),
runCommand("apm", ["list", "--installed"]),
sparkEnabled
? runCommand(
APTSS_LIST_UPGRADABLE_COMMAND.command,
APTSS_LIST_UPGRADABLE_COMMAND.args,
)
: Promise.resolve({ code: 0, stdout: "", stderr: "" }),
apmEnabled
? runCommand("apm", ["list", "--upgradable"])
: Promise.resolve({ code: 0, stdout: "", stderr: "" }),
sparkEnabled
? runCommand(
DPKG_QUERY_INSTALLED_COMMAND.command,
DPKG_QUERY_INSTALLED_COMMAND.args,
)
: Promise.resolve({ code: 0, stdout: "", stderr: "" }),
apmEnabled
? runCommand("apm", ["list", "--installed"])
: Promise.resolve({ code: 0, stdout: "", stderr: "" }),
]);
const aptssAvailable =
aptssResult.code === 0 || aptssInstalledResult.code === 0;
sparkEnabled && (aptssResult.code === 0 || aptssInstalledResult.code === 0);
const warnings = [
aptssAvailable
? getCommandError("aptss upgradable query", aptssResult)
: null,
getCommandError("apm upgradable query", apmResult),
apmEnabled ? getCommandError("apm upgradable query", apmResult) : null,
aptssAvailable
? getCommandError("dpkg installed query", aptssInstalledResult)
: null,
getCommandError("apm installed query", apmInstalledResult),
apmEnabled
? getCommandError("apm installed query", apmInstalledResult)
: null,
].filter((message): message is string => message !== null);
const aptssItems =
@@ -385,7 +421,9 @@ export const loadUpdateCenterItems = async (
? parseAptssUpgradableOutput(aptssResult.stdout)
: [];
const apmItems =
apmResult.code === 0 ? parseApmUpgradableOutput(apmResult.stdout) : [];
apmEnabled && apmResult.code === 0
? parseApmUpgradableOutput(apmResult.stdout)
: [];
const installedSources = buildInstalledSourceMap(
aptssAvailable && aptssInstalledResult.code === 0
@@ -396,13 +434,15 @@ export const loadUpdateCenterItems = async (
const [categorizedAptssItems, categorizedApmItems] = await Promise.all([
aptssAvailable ? enrichItemCategories(aptssItems) : Promise.resolve([]),
enrichItemCategories(apmItems),
apmEnabled ? enrichItemCategories(apmItems) : Promise.resolve([]),
]);
const [enrichedAptssItems, enrichedApmItems] = await Promise.all([
aptssAvailable
? enrichAptssItems(categorizedAptssItems, runCommand)
: Promise.resolve({ items: [], warnings: [] }),
enrichApmItems(categorizedApmItems, runCommand),
apmEnabled
? enrichApmItems(categorizedApmItems, runCommand)
: Promise.resolve({ items: [], warnings: [] }),
]);
return {
@@ -433,8 +473,14 @@ export const registerUpdateCenterIpc = (
| "subscribe"
>,
): void => {
ipc.handle("update-center-open", () => service.open());
ipc.handle("update-center-refresh", () => service.refresh());
ipc.handle(
"update-center-open",
(_event, storeFilter: StoreFilter = "both") => service.open(storeFilter),
);
ipc.handle(
"update-center-refresh",
(_event, storeFilter: StoreFilter = "both") => service.refresh(storeFilter),
);
ipc.handle(
"update-center-ignore",
(_event, payload: UpdateCenterIgnorePayload) => service.ignore(payload),
+15 -5
View File
@@ -13,6 +13,8 @@ import {
} from "./queue";
import type { UpdateCenterItem, UpdateSource } from "./types";
export type StoreFilter = "spark" | "apm" | "both";
export interface UpdateCenterLoadedItems {
items: UpdateCenterItem[];
warnings: string[];
@@ -68,8 +70,8 @@ export interface UpdateCenterStartTask {
}
export interface UpdateCenterService {
open: () => Promise<UpdateCenterServiceState>;
refresh: () => Promise<UpdateCenterServiceState>;
open: (storeFilter?: StoreFilter) => Promise<UpdateCenterServiceState>;
refresh: (storeFilter?: StoreFilter) => Promise<UpdateCenterServiceState>;
ignore: (payload: UpdateCenterIgnorePayload) => Promise<void>;
unignore: (payload: UpdateCenterIgnorePayload) => Promise<void>;
start: (tasks: UpdateCenterStartTask[]) => Promise<void>;
@@ -81,7 +83,9 @@ export interface UpdateCenterService {
}
export interface CreateUpdateCenterServiceOptions {
loadItems: () => Promise<UpdateCenterItem[] | UpdateCenterLoadedItems>;
loadItems: (
storeFilter: StoreFilter,
) => Promise<UpdateCenterItem[] | UpdateCenterLoadedItems>;
loadIgnoredEntries?: () => Promise<Set<string>>;
saveIgnoredEntries?: (entries: ReadonlySet<string>) => Promise<void>;
}
@@ -135,6 +139,7 @@ export const createUpdateCenterService = (
): UpdateCenterService => {
const queue = createUpdateCenterQueue();
const listeners = new Set<(snapshot: UpdateCenterServiceState) => void>();
let currentStoreFilter: StoreFilter = "both";
const loadIgnored =
options.loadIgnoredEntries ??
(() => loadIgnoredEntries(IGNORE_CONFIG_PATH));
@@ -157,13 +162,18 @@ export const createUpdateCenterService = (
return snapshot;
};
const refresh = async (): Promise<UpdateCenterServiceState> => {
const refresh = async (
storeFilter: StoreFilter = currentStoreFilter,
): Promise<UpdateCenterServiceState> => {
currentStoreFilter = storeFilter;
queue.startRefresh();
emit();
try {
const ignoredEntries = await loadIgnored();
const loadedItems = normalizeLoadedItems(await options.loadItems());
const loadedItems = normalizeLoadedItems(
await options.loadItems(currentStoreFilter),
);
const items = sortIgnoredItems(
applyIgnoredEntries(loadedItems.items, ignoredEntries),
);
+6 -4
View File
@@ -1,5 +1,7 @@
import { ipcRenderer, contextBridge, type IpcRendererEvent } from "electron";
type StoreFilter = "spark" | "apm" | "both";
type UpdateCenterSnapshot = {
items: Array<{
taskKey: string;
@@ -90,10 +92,10 @@ contextBridge.exposeInMainWorld("apm_store", {
});
contextBridge.exposeInMainWorld("updateCenter", {
open: (): Promise<UpdateCenterSnapshot> =>
ipcRenderer.invoke("update-center-open"),
refresh: (): Promise<UpdateCenterSnapshot> =>
ipcRenderer.invoke("update-center-refresh"),
open: (storeFilter: StoreFilter = "both"): Promise<UpdateCenterSnapshot> =>
ipcRenderer.invoke("update-center-open", storeFilter),
refresh: (storeFilter: StoreFilter = "both"): Promise<UpdateCenterSnapshot> =>
ipcRenderer.invoke("update-center-refresh", storeFilter),
ignore: (payload: {
packageName: string;
newVersion: string;