import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Ref from "effect/Ref";import * as Schema from "effect/Schema"; import * as ElectronApp from "../electron/ElectronApp.ts";import * as DesktopAssets from "./DesktopAssets.ts";import * as DesktopEnvironment from "./DesktopEnvironment.ts"; const COMMIT_HASH_PATTERN = /^[0-9a-f]{7,40}$/i;const COMMIT_HASH_DISPLAY_LENGTH = 12; const AppPackageMetadata = Schema.Struct({ t3codeCommitHash: Schema.optional(Schema.String),});const decodeAppPackageMetadata = Schema.decodeEffect(Schema.fromJsonString(AppPackageMetadata)); export class DesktopAppIdentity extends Context.Service< DesktopAppIdentity, { readonly resolveUserDataPath: Effect.Effect<string>; readonly configure: Effect.Effect<void>; }>()("@t3tools/desktop/app/DesktopAppIdentity") {} const normalizeCommitHash = (value: string): Option.Option<string> => { const trimmed = value.trim(); return COMMIT_HASH_PATTERN.test(trimmed) ? Option.some(trimmed.slice(0, COMMIT_HASH_DISPLAY_LENGTH).toLowerCase()) : Option.none();}; const make = Effect.gen(function* () { const assets = yield* DesktopAssets.DesktopAssets; const electronApp = yield* ElectronApp.ElectronApp; const environment = yield* DesktopEnvironment.DesktopEnvironment; const fileSystem = yield* FileSystem.FileSystem; const commitHashCache = yield* Ref.make<Option.Option<Option.Option<string>>>(Option.none()); const resolveEmbeddedCommitHash = Effect.gen(function* () { const packageJsonPath = environment.path.join(environment.appRoot, "package.json"); const raw = yield* fileSystem.readFileString(packageJsonPath).pipe(Effect.option); return yield* Option.match(raw, { onNone: () => Effect.succeed(Option.none<string>()), onSome: (value) => decodeAppPackageMetadata(value).pipe( Effect.map((parsed) => Option.fromNullishOr(parsed.t3codeCommitHash).pipe(Option.flatMap(normalizeCommitHash)), ), Effect.orElseSucceed(() => Option.none<string>()), ), }); }); const resolveAboutCommitHash = Effect.gen(function* () { const cached = yield* Ref.get(commitHashCache); if (Option.isSome(cached)) { return cached.value; } const override = Option.flatMap(environment.commitHashOverride, normalizeCommitHash); if (Option.isSome(override)) { yield* Ref.set(commitHashCache, Option.some(override)); return override; } if (!environment.isPackaged) { const empty = Option.none<string>(); yield* Ref.set(commitHashCache, Option.some(empty)); return empty; } const commitHash = yield* resolveEmbeddedCommitHash; yield* Ref.set(commitHashCache, Option.some(commitHash)); return commitHash; }); const resolveUserDataPath = Effect.gen(function* () { const legacyPath = environment.path.join( environment.appDataDirectory, environment.legacyUserDataDirName, ); const legacyPathExists = yield* fileSystem .exists(legacyPath) .pipe(Effect.orElseSucceed(() => false)); return legacyPathExists ? legacyPath : environment.path.join(environment.appDataDirectory, environment.userDataDirName); }).pipe(Effect.withSpan("desktop.appIdentity.resolveUserDataPath")); const configure = Effect.gen(function* () { const commitHash = yield* resolveAboutCommitHash; yield* electronApp.setName(environment.displayName); yield* electronApp.setAboutPanelOptions({ applicationName: environment.displayName, applicationVersion: environment.appVersion, version: Option.getOrElse(commitHash, () => "unknown"), }); if (environment.platform === "win32") { yield* electronApp.setAppUserModelId(environment.appUserModelId); } if (environment.platform === "linux") { yield* electronApp.setDesktopName(environment.linuxDesktopEntryName); } if (environment.platform === "darwin") { const iconPaths = yield* assets.iconPaths; yield* Option.match(iconPaths.png, { onNone: () => Effect.void, onSome: electronApp.setDockIcon, }); } }).pipe(Effect.withSpan("desktop.appIdentity.configure")); return DesktopAppIdentity.of({ resolveUserDataPath, configure, });}); export const layer = Layer.effect(DesktopAppIdentity, make);
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Ref from "effect/Ref";
import * as Schema from "effect/Schema";
import * as ElectronApp from "../electron/ElectronApp.ts";
import * as DesktopAssets from "./DesktopAssets.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";
const COMMIT_HASH_PATTERN = /^[0-9a-f]{7,40}$/i;
const COMMIT_HASH_DISPLAY_LENGTH = 12;
const AppPackageMetadata = Schema.Struct({
t3codeCommitHash: Schema.optional(Schema.String),
});
const decodeAppPackageMetadata = Schema.decodeEffect(Schema.fromJsonString(AppPackageMetadata));
export class DesktopAppIdentity extends Context.Service<
DesktopAppIdentity,
{
readonly resolveUserDataPath: Effect.Effect<string>;
readonly configure: Effect.Effect<void>;
}
>()("@t3tools/desktop/app/DesktopAppIdentity") {}
const normalizeCommitHash = (value: string): Option.Option<string> => {
const trimmed = value.trim();
return COMMIT_HASH_PATTERN.test(trimmed)
? Option.some(trimmed.slice(0, COMMIT_HASH_DISPLAY_LENGTH).toLowerCase())
: Option.none();
};
const make = Effect.gen(function* () {
const assets = yield* DesktopAssets.DesktopAssets;
const electronApp = yield* ElectronApp.ElectronApp;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fileSystem = yield* FileSystem.FileSystem;
const commitHashCache = yield* Ref.make<Option.Option<Option.Option<string>>>(Option.none());
const resolveEmbeddedCommitHash = Effect.gen(function* () {
const packageJsonPath = environment.path.join(environment.appRoot, "package.json");
const raw = yield* fileSystem.readFileString(packageJsonPath).pipe(Effect.option);
return yield* Option.match(raw, {
onNone: () => Effect.succeed(Option.none<string>()),
onSome: (value) =>
decodeAppPackageMetadata(value).pipe(
Effect.map((parsed) =>
Option.fromNullishOr(parsed.t3codeCommitHash).pipe(Option.flatMap(normalizeCommitHash)),
),
Effect.orElseSucceed(() => Option.none<string>()),
),
});
});
const resolveAboutCommitHash = Effect.gen(function* () {
const cached = yield* Ref.get(commitHashCache);
if (Option.isSome(cached)) {
return cached.value;
}
const override = Option.flatMap(environment.commitHashOverride, normalizeCommitHash);
if (Option.isSome(override)) {
yield* Ref.set(commitHashCache, Option.some(override));
return override;
}
if (!environment.isPackaged) {
const empty = Option.none<string>();
yield* Ref.set(commitHashCache, Option.some(empty));
return empty;
}
const commitHash = yield* resolveEmbeddedCommitHash;
yield* Ref.set(commitHashCache, Option.some(commitHash));
return commitHash;
});
const resolveUserDataPath = Effect.gen(function* () {
const legacyPath = environment.path.join(
environment.appDataDirectory,
environment.legacyUserDataDirName,
);
const legacyPathExists = yield* fileSystem
.exists(legacyPath)
.pipe(Effect.orElseSucceed(() => false));
return legacyPathExists
? legacyPath
: environment.path.join(environment.appDataDirectory, environment.userDataDirName);
}).pipe(Effect.withSpan("desktop.appIdentity.resolveUserDataPath"));
const configure = Effect.gen(function* () {
const commitHash = yield* resolveAboutCommitHash;
yield* electronApp.setName(environment.displayName);
yield* electronApp.setAboutPanelOptions({
applicationName: environment.displayName,
applicationVersion: environment.appVersion,
version: Option.getOrElse(commitHash, () => "unknown"),
});
if (environment.platform === "win32") {
yield* electronApp.setAppUserModelId(environment.appUserModelId);
}
if (environment.platform === "linux") {
yield* electronApp.setDesktopName(environment.linuxDesktopEntryName);
}
if (environment.platform === "darwin") {
const iconPaths = yield* assets.iconPaths;
yield* Option.match(iconPaths.png, {
onNone: () => Effect.void,
onSome: electronApp.setDockIcon,
});
}
}).pipe(Effect.withSpan("desktop.appIdentity.configure"));
return DesktopAppIdentity.of({
resolveUserDataPath,
configure,
});
});
export const layer = Layer.effect(DesktopAppIdentity, make);