Merge current main into PR 1039

This commit is contained in:
vorotamoroz
2026-08-09 08:45:16 +00:00
1036 changed files with 87150 additions and 45505 deletions
@@ -1,6 +1,6 @@
import type { FilePath, UXFileInfoStub, UXInternalFileInfoStub } from "@lib/common/types";
import type { FileEventItem } from "@lib/common/types";
import type { IStorageEventManagerAdapter } from "@lib/managers/adapters";
import type { FilePath, UXFileInfoStub, UXInternalFileInfoStub } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type { FileEventItem } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type { IStorageEventManagerAdapter } from "@vrtmrz/livesync-commonlib/compat/managers/adapters";
import type {
IStorageEventTypeGuardAdapter,
IStorageEventPersistenceAdapter,
@@ -8,12 +8,13 @@ import type {
IStorageEventStatusAdapter,
IStorageEventConverterAdapter,
IStorageEventWatchHandlers,
} from "@lib/managers/adapters";
import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager";
} from "@vrtmrz/livesync-commonlib/compat/managers/adapters";
import type { FileEventItemSentinel } from "@vrtmrz/livesync-commonlib/compat/managers/StorageEventManager";
import type { NodeFile, NodeFolder } from "@/apps/cli/adapters/NodeTypes";
import { watch as chokidarWatch, type FSWatcher } from "chokidar";
import type { IgnoreRules } from "@/apps/cli/serviceModules/IgnoreRules";
import { fsPromises as fs, path, type Stats } from "@/apps/cli/node-compat";
import { fsPromises as fs, path, type Stats } from "@vrtmrz/livesync-commonlib/node";
import type { CliDiagnosticReporter } from "@/apps/cli/cliOutput";
/**
* CLI-specific type guard adapter
@@ -45,7 +46,10 @@ class CLITypeGuardAdapter implements IStorageEventTypeGuardAdapter<NodeFile, Nod
class CLIPersistenceAdapter implements IStorageEventPersistenceAdapter {
private snapshotPath: string;
constructor(basePath: string) {
constructor(
basePath: string,
private reportDiagnostic: CliDiagnosticReporter = () => undefined
) {
this.snapshotPath = path.join(basePath, ".livesync-snapshot.json");
}
@@ -53,14 +57,14 @@ class CLIPersistenceAdapter implements IStorageEventPersistenceAdapter {
try {
await fs.writeFile(this.snapshotPath, JSON.stringify(snapshot, null, 2), "utf-8");
} catch (error) {
console.error("Failed to save snapshot:", error);
this.reportDiagnostic("Failed to save snapshot:", error);
}
}
async loadSnapshot(): Promise<(FileEventItem | FileEventItemSentinel)[] | null> {
try {
const content = await fs.readFile(this.snapshotPath, "utf-8");
return JSON.parse(content);
return JSON.parse(content) as (FileEventItem | FileEventItemSentinel)[];
} catch {
return null;
}
@@ -109,7 +113,8 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter {
constructor(
private basePath: string,
private ignoreRules?: IgnoreRules,
private watchEnabled: boolean = false
private watchEnabled: boolean = false,
private reportDiagnostic: CliDiagnosticReporter = () => undefined
) {}
private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile {
@@ -182,8 +187,8 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter {
});
watcher.on("error", (err) => {
console.error("[CLIWatchAdapter] Fatal watcher error — file watching stopped:", err);
console.error("[CLIWatchAdapter] Exiting for systemd restart.");
this.reportDiagnostic("[CLIWatchAdapter] Fatal watcher error — file watching stopped:", err);
this.reportDiagnostic("[CLIWatchAdapter] Exiting for systemd restart.");
void watcher.close();
this._watcher = undefined;
// Use exit(1) rather than SIGTERM so systemd Restart=on-failure engages.
@@ -212,10 +217,15 @@ export class CLIStorageEventManagerAdapter implements IStorageEventManagerAdapte
readonly status: CLIStatusAdapter;
readonly converter: CLIConverterAdapter;
constructor(basePath: string, ignoreRules?: IgnoreRules, watchEnabled: boolean = false) {
constructor(
basePath: string,
ignoreRules?: IgnoreRules,
watchEnabled: boolean = false,
reportDiagnostic: CliDiagnosticReporter = () => undefined
) {
this.typeGuard = new CLITypeGuardAdapter();
this.persistence = new CLIPersistenceAdapter(basePath);
this.watch = new CLIWatchAdapter(basePath, ignoreRules, watchEnabled);
this.persistence = new CLIPersistenceAdapter(basePath, reportDiagnostic);
this.watch = new CLIWatchAdapter(basePath, ignoreRules, watchEnabled, reportDiagnostic);
this.status = new CLIStatusAdapter();
this.converter = new CLIConverterAdapter();
}
@@ -1,5 +1,5 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
import type { IStorageEventWatchHandlers } from "@lib/managers/adapters";
import type { IStorageEventWatchHandlers } from "@vrtmrz/livesync-commonlib/compat/managers/adapters";
import type { NodeFile } from "@/apps/cli/adapters/NodeTypes";
// ── chokidar mock ──────────────────────────────────────────────────────────────
@@ -124,7 +124,8 @@ describe("CLIStorageEventManagerAdapter", () => {
});
it("error event triggers process.exit(1)", async () => {
const adapter = new CLIStorageEventManagerAdapter("/base", undefined, true);
const reportDiagnostic = vi.fn();
const adapter = new CLIStorageEventManagerAdapter("/base", undefined, true, reportDiagnostic);
const handlers = makeHandlers();
await adapter.watch.beginWatch(handlers);
@@ -138,6 +139,11 @@ describe("CLIStorageEventManagerAdapter", () => {
errorCallback(new Error("disk failure"));
expect(processExitSpy).toHaveBeenCalledWith(1);
expect(reportDiagnostic).toHaveBeenCalledWith(
"[CLIWatchAdapter] Fatal watcher error — file watching stopped:",
expect.any(Error)
);
expect(reportDiagnostic).toHaveBeenCalledWith("[CLIWatchAdapter] Exiting for systemd restart.");
processExitSpy.mockRestore();
});
@@ -1,9 +1,10 @@
import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } from "@lib/managers/StorageEventManager";
import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } from "@vrtmrz/livesync-commonlib/compat/managers/StorageEventManager";
import { CLIStorageEventManagerAdapter } from "./CLIStorageEventManagerAdapter";
import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "@/LiveSyncBaseCore";
import type { ServiceContext } from "@lib/services/base/ServiceBase";
import type { ServiceContext } from "@vrtmrz/livesync-commonlib/context";
import type { IgnoreRules } from "@/apps/cli/serviceModules/IgnoreRules";
// import type { IMinimumLiveSyncCommands } from "@lib/services/base/IService";
import { LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
// import type { IMinimumLiveSyncCommands } from "@vrtmrz/livesync-commonlib/compat/services/base/IService";
export class StorageEventManagerCLI extends StorageEventManagerBase<CLIStorageEventManagerAdapter> {
core: LiveSyncBaseCore<ServiceContext, IMinimumLiveSyncCommands>;
@@ -15,7 +16,12 @@ export class StorageEventManagerCLI extends StorageEventManagerBase<CLIStorageEv
ignoreRules?: IgnoreRules,
watchEnabled?: boolean
) {
const adapter = new CLIStorageEventManagerAdapter(basePath, ignoreRules, watchEnabled);
const adapter = new CLIStorageEventManagerAdapter(basePath, ignoreRules, watchEnabled, (message, detail) => {
dependencies.APIService.addLog(message, LOG_LEVEL_NOTICE);
if (detail !== undefined) {
dependencies.APIService.addLog(detail, LOG_LEVEL_NOTICE);
}
});
super(adapter, dependencies);
this.core = core;
}