import cheerio from "cheerio"; import { replaceImportsFromStaticInJsCode } from "../replacers/replaceImportsFromStaticInJsCode"; import { generateCssCodeToDefineGlobals } from "../replacers/replaceImportsInCssCode"; import { replaceImportsInInlineCssCode } from "../replacers/replaceImportsInInlineCssCode"; import * as fs from "fs"; import { join as pathJoin } from "path"; import { objectKeys } from "tsafe/objectKeys"; import { ftlValuesGlobalName } from "../ftlValuesGlobalName"; import type { BuildOptions } from "../BuildOptions"; import { assert } from "tsafe/assert"; import { Reflect } from "tsafe/Reflect"; // https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java export const pageIds = [ "login.ftl", "register.ftl", "register-user-profile.ftl", "info.ftl", "error.ftl", "login-reset-password.ftl", "login-verify-email.ftl", "terms.ftl", "login-otp.ftl", "login-update-profile.ftl", "login-update-password.ftl", "login-idp-link-confirm.ftl", "login-idp-link-email.ftl", "login-page-expired.ftl", "login-config-totp.ftl", "logout-confirm.ftl" ] as const; export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets; export namespace BuildOptionsLike { export type Standalone = { isStandalone: true; urlPathname: string | undefined; }; export type ExternalAssets = ExternalAssets.SameDomain | ExternalAssets.DifferentDomains; export namespace ExternalAssets { export type CommonExternalAssets = { isStandalone: false; }; export type SameDomain = CommonExternalAssets & { isAppAndKeycloakServerSharingSameDomain: true; }; export type DifferentDomains = CommonExternalAssets & { isAppAndKeycloakServerSharingSameDomain: false; urlOrigin: string; urlPathname: string | undefined; }; } } { const buildOptions = Reflect(); assert(); } export type PageId = typeof pageIds[number]; export function generateFtlFilesCodeFactory(params: { indexHtmlCode: string; //NOTE: Expected to be an empty object if external assets mode is enabled. cssGlobalsToDefine: Record; buildOptions: BuildOptionsLike; }) { const { cssGlobalsToDefine, indexHtmlCode, buildOptions } = params; const $ = cheerio.load(indexHtmlCode); fix_imports_statements: { if (!buildOptions.isStandalone && buildOptions.isAppAndKeycloakServerSharingSameDomain) { break fix_imports_statements; } $("script:not([src])").each((...[, element]) => { const { fixedJsCode } = replaceImportsFromStaticInJsCode({ "jsCode": $(element).html()!, buildOptions }); $(element).text(fixedJsCode); }); $("style").each((...[, element]) => { const { fixedCssCode } = replaceImportsInInlineCssCode({ "cssCode": $(element).html()!, buildOptions }); $(element).text(fixedCssCode); }); ( [ ["link", "href"], ["script", "src"] ] as const ).forEach(([selector, attrName]) => $(selector).each((...[, element]) => { const href = $(element).attr(attrName); if (href === undefined) { return; } $(element).attr( attrName, buildOptions.isStandalone ? href.replace(new RegExp(`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`), "${url.resourcesPath}/build/") : href.replace(/^\//, `${buildOptions.urlOrigin}/`) ); }) ); if (Object.keys(cssGlobalsToDefine).length !== 0) { $("head").prepend( [ "", "", "" ].join("\n") ); } } //FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later. const replaceValueBySearchValue = { '{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': fs .readFileSync(pathJoin(__dirname, "ftl_object_to_js_code_declaring_an_object.ftl")) .toString("utf8") .match(/^', " ", "" ].join("\n") }; $("head").prepend( [ "", "", objectKeys(replaceValueBySearchValue)[1] ].join("\n") ); const partiallyFixedIndexHtmlCode = $.html(); function generateFtlFilesCode(params: { pageId: string }): { ftlCode: string; } { const { pageId } = params; const $ = cheerio.load(partiallyFixedIndexHtmlCode); let ftlCode = $.html(); Object.entries({ ...replaceValueBySearchValue, //If updated, don't forget to change in the ftl script as well. "PAGE_ID_xIgLsPgGId9D8e": pageId }).map(([searchValue, replaceValue]) => (ftlCode = ftlCode.replace(searchValue, replaceValue))); return { ftlCode }; } return { generateFtlFilesCode }; }