Skip to content

Commit

Permalink
Looser zod schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
toebeann committed Sep 2, 2024
1 parent 4694824 commit 932219d
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 166 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "gib",
"version": "0.7.2",
"version": "0.7.3",
"description": "A TUI application for automating the installation of BepInEx",
"license": "ISC",
"author": "Tobey Blaber",
Expand Down
21 changes: 17 additions & 4 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,9 @@ export const run = async () => {
buildPlist(
{
CFBundleIconFile,
CFBundleName: `${CFBundleName ?? name} (Vanilla)`,
CFBundleName: `${
typeof CFBundleName === "string" ? CFBundleName : name
} (Vanilla)`,
CFBundleInfoDictionaryVersion: "1.0",
CFBundlePackageType: "APPL",
CFBundleVersion: "1.0",
Expand Down Expand Up @@ -913,14 +915,21 @@ export const run = async () => {
* - get current shortcuts, add new shortcut
*/
const { CFBundleName, CFBundleIconFile } = plist;
const gameName = CFBundleName ?? basename(gameAppPath);
const gameName = typeof CFBundleName === "string"
? CFBundleName
: basename(gameAppPath);
const game = code(gameName);

const steamInstalled = await isInstalled();
const [userId, user] = steamInstalled
? await getMostRecentUser()
: [undefined, undefined];
const username = user && code(user.PersonaName ?? user.AccountName);
const username = user &&
code(
typeof user.PersonaName === "string"
? user.PersonaName
: user.AccountName,
);

shouldAddShortcut = await isInstalled() && confirmShim(wrap([
[
Expand Down Expand Up @@ -1229,7 +1238,11 @@ export const run = async () => {
"We also added a shortcut to Steam to launch the game with",
"BepInEx. You can find it in your Steam library named",
code(
`${plist.CFBundleName ?? basename(gameAppPath)} (BepInEx)`,
`${
typeof plist.CFBundleName === "string"
? plist.CFBundleName
: basename(gameAppPath)
} (BepInEx)`,
),
].join(" "),
null,
Expand Down
114 changes: 72 additions & 42 deletions src/launchers/epic/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,42 @@ export type App = AppBase<AppManifest> & {
launchId: string;
};

export const isFullyInstalled = (app: App) =>
app.manifest.bIsIncompleteInstall !== true;
export const isFullyInstalled = ({ manifest: { bIsIncompleteInstall } }: App) =>
bIsIncompleteInstall !== true;

/** Gets information about installed Epic Games Launcher apps. */
export async function* getApps() {
const launcherInstalled = getLauncherInstalled();

for await (const manifest of getManifests()) {
for await (const _manifest of getManifests()) {
const info = (await launcherInstalled)
.find((x) => [x.artifactId, x.appName].includes(manifest.appName));
.find((x) => [x.artifactId, x.appName].includes(_manifest.appName));

if (info) {
const merged = { ...info, ...manifest };
const manifest = { ...info, ..._manifest };
const {
displayName: name,
installLocation: path,
namespaceId,
catalogNamespace,
itemId,
catalogItemId,
artifactId,
appName,
} = manifest;
const id = typeof artifactId === "string" ? artifactId : appName;
const launchId = [
typeof namespaceId === "string" ? namespaceId : catalogNamespace,
typeof itemId === "string" ? itemId : catalogItemId,
id,
].filter(Boolean).join(":");
yield {
launcher,
manifest: merged,
id: merged.artifactId ?? merged.appName,
name: merged.displayName,
path: merged.installLocation,
launchId: [
merged.namespaceId ?? merged.catalogNamespace,
merged.itemId ?? merged.catalogItemId,
merged.artifactId ?? merged.appName,
]
.filter(Boolean)
.join(":"),
manifest,
id,
name,
path,
launchId,
} satisfies App;
}
}
Expand All @@ -67,21 +77,31 @@ export const getAppById = async (id: string) =>
]),
)
.returnType<App | undefined>()
.with([P.not(P.nullish), P.not(P.nullish)], ([info, manifest]) => {
const merged = { ...info, ...manifest };
.with([P.not(P.nullish), P.not(P.nullish)], ([info, _manifest]) => {
const manifest = { ...info, ..._manifest };
const {
displayName: name,
installLocation: path,
namespaceId,
catalogNamespace,
itemId,
catalogItemId,
artifactId,
appName,
} = manifest;
const id = typeof artifactId === "string" ? artifactId : appName;
const launchId = [
typeof namespaceId === "string" ? namespaceId : catalogNamespace,
typeof itemId === "string" ? itemId : catalogItemId,
id,
].filter(Boolean).join(":");
return {
launcher,
manifest: merged,
id: merged.artifactId ?? merged.appName,
name: merged.displayName,
path: merged.installLocation,
launchId: [
merged.namespaceId ?? merged.catalogNamespace,
merged.itemId ?? merged.catalogItemId,
merged.artifactId ?? merged.appName,
]
.filter(Boolean)
.join(":"),
manifest,
id,
name,
path,
launchId,
} satisfies App;
})
.otherwise(() => undefined);
Expand All @@ -102,21 +122,31 @@ export const getAppByPath = async (path: string) =>
]),
)
.returnType<App | undefined>()
.with([P.not(P.nullish), P.not(P.nullish)], ([info, manifest]) => {
const merged = { ...info, ...manifest };
.with([P.not(P.nullish), P.not(P.nullish)], ([info, _manifest]) => {
const manifest = { ...info, ..._manifest };
const {
displayName: name,
installLocation: path,
namespaceId,
catalogNamespace,
itemId,
catalogItemId,
artifactId,
appName,
} = manifest;
const id = typeof artifactId === "string" ? artifactId : appName;
const launchId = [
typeof namespaceId === "string" ? namespaceId : catalogNamespace,
typeof itemId === "string" ? itemId : catalogItemId,
id,
].filter(Boolean).join(":");
return {
launcher,
manifest: merged,
id: merged.artifactId ?? merged.appName,
name: merged.displayName,
path: merged.installLocation,
launchId: [
merged.namespaceId ?? merged.catalogNamespace,
merged.itemId ?? merged.catalogItemId,
merged.artifactId ?? merged.appName,
]
.filter(Boolean)
.join(":"),
manifest,
id,
name,
path,
launchId,
} satisfies App;
})
.otherwise(() => undefined);
Expand Down
12 changes: 8 additions & 4 deletions src/launchers/epic/launcherInstalled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ export const launcherInstalledSchema = toCamelCaseKeys(
installationList: toCamelCaseKeys(
z.object({
installLocation: z.string(),
namespaceId: z.string().optional(),
itemId: z.string().optional(),
artifactId: z.string().optional(),
appVersion: z.string().optional(),
/** @type {string | undefined} */
namespaceId: z.unknown().optional(),
/** @type {string | undefined} */
itemId: z.unknown().optional(),
/** @type {string | undefined} */
artifactId: z.unknown().optional(),
/** @type {string | undefined} */
appVersion: z.unknown().optional(),
appName: z.string(),
}).passthrough(),
).array(),
Expand Down
132 changes: 88 additions & 44 deletions src/launchers/epic/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,55 +10,99 @@ import type { launcherInstalledSchema } from "./launcherInstalled.ts";
export const appManifestSchema = toCamelCaseKeys(
z.object({
formatVersion: z.number(),
bIsIncompleteInstall: z.boolean().optional(),
launchCommand: z.string().optional(),
launchExecutable: z.string().optional(),
manifestLocation: z.string().optional(),
manifestHash: z.string().optional(),
bIsApplication: z.boolean().optional(),
bIsExecutable: z.boolean().optional(),
bIsManaged: z.boolean().optional(),
bNeedsValidation: z.boolean().optional(),
bRequiresAuth: z.boolean().optional(),
bAllowMultipleInstances: z.boolean().optional(),
bCanRunOffline: z.boolean().optional(),
bAllowUriCmdArgs: z.boolean().optional(),
bLaunchElevated: z.boolean().optional(),
baseUrLs: z.string().array().optional(),
buildLabel: z.string().optional(),
appCategories: z.string().array().optional(),
chunkDbs: z.unknown().array().optional(),
compatibleApps: z.unknown().array().optional(),
/** @type {boolean | undefined} */
bIsIncompleteInstall: z.unknown().optional(),
/** @type {string | undefined} */
launchCommand: z.unknown().optional(),
/** @type {string | undefined} */
launchExecutable: z.unknown().optional(),
/** @type {string | undefined} */
manifestLocation: z.unknown().optional(),
/** @type {string | undefined} */
manifestHash: z.unknown().optional(),
/** @type {boolean | undefined} */
bIsApplication: z.unknown().optional(),
/** @type {boolean | undefined} */
bIsExecutable: z.unknown().optional(),
/** @type {boolean | undefined} */
bIsManaged: z.unknown().optional(),
/** @type {boolean | undefined} */
bNeedsValidation: z.unknown().optional(),
/** @type {boolean | undefined} */
bRequiresAuth: z.unknown().optional(),
/** @type {boolean | undefined} */
bAllowMultipleInstances: z.unknown().optional(),
/** @type {boolean | undefined} */
bCanRunOffline: z.unknown().optional(),
/** @type {boolean | undefined} */
bAllowUriCmdArgs: z.unknown().optional(),
/** @type {boolean | undefined} */
bLaunchElevated: z.unknown().optional(),
/** @type {string[] | undefined} */
baseUrLs: z.unknown().optional(),
/** @type {string | undefined} */
buildLabel: z.unknown().optional(),
/** @type {string[] | undefined} */
appCategories: z.unknown().optional(),
/** @type {unknown[] | undefined} */
chunkDbs: z.unknown().optional(),
/** @type {unknown[] | undefined} */
compatibleApps: z.unknown().optional(),
displayName: z.string(),
installationGuid: z.string().optional(),
/** @type {string | undefined} */
installationGuid: z.unknown().optional(),
installLocation: z.string(),
installSessionId: z.string().optional(),
installTags: z.unknown().array().optional(),
installComponents: z.unknown().array().optional(),
hostInstallationGuid: z.string().optional(),
prereqIds: z.unknown().array().optional(),
prereqSha1Hash: z.string().optional(),
lastPrereqSucceededSha1Hash: z.string().optional(),
stagingLocation: z.string().optional(),
technicalType: z.string().optional(),
vaultThumbnailUrl: z.string().optional(),
vaultTitleText: z.string().optional(),
installSize: z.number().optional(),
mainWindowProcessName: z.string().optional(),
processNames: z.unknown().array().optional(),
backgroundProcessNames: z.unknown().array().optional(),
ignoredProcessNames: z.unknown().array().optional(),
dlcProcessNames: z.unknown().array().optional(),
mandatoryAppFolderName: z.string().optional(),
ownershipToken: z.string().optional(),
/** @type {string | undefined} */
installSessionId: z.unknown().optional(),
/** @type {unknown[] | undefined} */
installTags: z.unknown().optional(),
/** @type {unknown[] | undefined} */
installComponents: z.unknown().optional(),
/** @type {string | undefined} */
hostInstallationGuid: z.unknown().optional(),
/** @type {unknown[] | undefined} */
prereqIds: z.unknown().optional(),
/** @type {string | undefined} */
prereqSha1Hash: z.unknown().optional(),
/** @type {string | undefined} */
lastPrereqSucceededSha1Hash: z.unknown().optional(),
/** @type {string | undefined} */
stagingLocation: z.unknown().optional(),
/** @type {string | undefined} */
technicalType: z.unknown().optional(),
/** @type {string | undefined} */
vaultThumbnailUrl: z.unknown().optional(),
/** @type {string | undefined} */
vaultTitleText: z.unknown().optional(),
/** @type {number | undefined} */
installSize: z.unknown().optional(),
/** @type {string | undefined} */
mainWindowProcessName: z.unknown().optional(),
/** @type {unknown[] | undefined} */
processNames: z.unknown().optional(),
/** @type {unknown[] | undefined} */
backgroundProcessNames: z.unknown().optional(),
/** @type {unknown[] | undefined} */
ignoredProcessNames: z.unknown().optional(),
/** @type {unknown[] | undefined} */
dlcProcessNames: z.unknown().optional(),
/** @type {string | undefined} */
mandatoryAppFolderName: z.unknown().optional(),
/** @type {string | undefined} */
ownershipToken: z.unknown().optional(),
catalogNamespace: z.string(),
catalogItemId: z.string(),
appName: z.string(),
appVersionString: z.string().optional(),
mainGameCatalogNamespace: z.string().optional(),
mainGameCatalogItemId: z.string().optional(),
mainGameAppName: z.string().optional(),
allowedUriEnvVars: z.unknown().array().optional(),
/** @type {string | undefined} */
appVersionString: z.unknown().optional(),
/** @type {string | undefined} */
mainGameCatalogNamespace: z.unknown().optional(),
/** @type {string | undefined} */
mainGameCatalogItemId: z.unknown().optional(),
/** @type {string | undefined} */
mainGameAppName: z.unknown().optional(),
/** @type {unknown[] | undefined} */
allowedUriEnvVars: z.unknown().optional(),
}).passthrough(),
);

Expand Down
3 changes: 2 additions & 1 deletion src/launchers/steam/launchOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ export async function getLaunchOptions(
)
.returnType<string | undefined>()
.with(P.number, (n) => n.toString())
.otherwise((x) => x);
.with(P.string, (s) => s)
.otherwise(() => undefined);

return launchOptions && removeSlashes(launchOptions) || undefined;
}
Expand Down
Loading

0 comments on commit 932219d

Please sign in to comment.