import { z } from "zod"; import { assert } from "tsafe/assert"; import type { Equals } from "tsafe"; import { id } from "tsafe/id"; import { parse as urlParse } from "url"; type ParsedPackageJson = { name: string; version: string; homepage?: string; keycloakify?: { extraPages?: string[]; extraThemeProperties?: string[]; isAppAndKeycloakServerSharingSameDomain?: boolean; }; }; const zParsedPackageJson = z.object({ "name": z.string(), "version": z.string(), "homepage": z.string().optional(), "keycloakify": z .object({ "extraPages": z.array(z.string()).optional(), "extraThemeProperties": z.array(z.string()).optional(), "isAppAndKeycloakServerSharingSameDomain": z.boolean().optional() }) .optional() }); assert, ParsedPackageJson>>(); /** Consolidated build option gathered form CLI arguments and config in package.json */ export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets; export namespace BuildOptions { export type Common = { version: string; themeName: string; extraPages?: string[]; extraThemeProperties?: string[]; //NOTE: Only for the pom.xml file, questionable utility... groupId: string; }; export type Standalone = Common & { isStandalone: true; urlPathname: string | undefined; }; export type ExternalAssets = ExternalAssets.SameDomain | ExternalAssets.DifferentDomains; export namespace ExternalAssets { export type CommonExternalAssets = Common & { isStandalone: false; }; export type SameDomain = CommonExternalAssets & { isAppAndKeycloakServerSharingSameDomain: true; }; export type DifferentDomains = CommonExternalAssets & { isAppAndKeycloakServerSharingSameDomain: false; urlOrigin: string; urlPathname: string | undefined; }; } } export function readBuildOptions(params: { packageJson: string; CNAME: string | undefined; isExternalAssetsCliParamProvided: boolean; }): BuildOptions { const { packageJson, CNAME, isExternalAssetsCliParamProvided } = params; const parsedPackageJson = zParsedPackageJson.parse(JSON.parse(packageJson)); const url = (() => { const { homepage } = parsedPackageJson; let url: URL | undefined = undefined; if (homepage !== undefined) { url = new URL(homepage); } if (CNAME !== undefined) { url = new URL(`https://${CNAME.replace(/\s+$/, "")}`); } if (url === undefined) { return undefined; } return { "origin": url.origin, "pathname": (() => { const out = url.pathname.replace(/([^/])$/, "$1/"); return out === "/" ? undefined : out; })() }; })(); const common: BuildOptions.Common = (() => { const { name, keycloakify = {}, version, homepage } = parsedPackageJson; const { extraPages, extraThemeProperties } = keycloakify ?? {}; const themeName = name .replace(/^@(.*)/, "$1") .split("/") .join("-"); return { themeName, "groupId": (() => { const fallbackGroupId = `${themeName}.keycloak`; return ( (!homepage ? fallbackGroupId : urlParse(homepage) .host?.replace(/:[0-9]+$/, "") ?.split(".") .reverse() .join(".") ?? fallbackGroupId) + ".keycloak" ); })(), "version": version, extraPages, extraThemeProperties }; })(); if (isExternalAssetsCliParamProvided) { const commonExternalAssets = id({ ...common, "isStandalone": false }); if (parsedPackageJson.keycloakify?.isAppAndKeycloakServerSharingSameDomain) { return id({ ...commonExternalAssets, "isAppAndKeycloakServerSharingSameDomain": true }); } else { assert( url !== undefined, [ "Can't compile in external assets mode if we don't know where", "the app will be hosted.", "You should provide a homepage field in the package.json (or create a", "public/CNAME file.", "Alternatively, if your app and the Keycloak server are on the same domain, ", "eg https://example.com is your app and https://example.com/auth is the keycloak", 'admin UI, you can set "keycloakify": { "isAppAndKeycloakServerSharingSameDomain": true }', "in your package.json" ].join(" ") ); return id({ ...commonExternalAssets, "isAppAndKeycloakServerSharingSameDomain": false, "urlOrigin": url.origin, "urlPathname": url.pathname }); } } return id({ ...common, "isStandalone": true, "urlPathname": url?.pathname }); }