diff --git a/src/bin/constants.ts b/src/bin/constants.ts index 88527c77..fa8c2a49 100644 --- a/src/bin/constants.ts +++ b/src/bin/constants.ts @@ -3,5 +3,7 @@ export const resources_common = "resources-common"; export const lastKeycloakVersionWithAccountV1 = "21.1.2"; export const themeTypes = ["login", "account"] as const; +export const retrocompatPostfix = "_retrocompat"; +export const accountV1 = "account-v1"; export type ThemeType = (typeof themeTypes)[number]; diff --git a/src/bin/keycloakify/BuildOptions.ts b/src/bin/keycloakify/BuildOptions.ts index 67b30f31..119dd557 100644 --- a/src/bin/keycloakify/BuildOptions.ts +++ b/src/bin/keycloakify/BuildOptions.ts @@ -24,6 +24,7 @@ export type BuildOptions = { /** If your app is hosted under a subpath, it's the case in CRA if you have "homepage": "https://example.com/my-app" in your package.json * In this case the urlPathname will be "/my-app/" */ urlPathname: string | undefined; + doBuildRetrocompatAccountTheme: boolean; }; export function readBuildOptions(params: { reactAppRootDirPath: string; processArgv: string[] }): BuildOptions { @@ -150,6 +151,7 @@ export function readBuildOptions(params: { reactAppRootDirPath: string; processA const out = url.pathname.replace(/([^/])$/, "$1/"); return out === "/" ? undefined : out; - })() + })(), + "doBuildRetrocompatAccountTheme": parsedPackageJson.keycloakify?.doBuildRetrocompatAccountTheme ?? true }; } diff --git a/src/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.ts b/src/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.ts index 6a6db6a9..25a1b5c0 100644 --- a/src/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.ts +++ b/src/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.ts @@ -3,7 +3,7 @@ import { join as pathJoin, dirname as pathDirname } from "path"; import { assert } from "tsafe/assert"; import { Reflect } from "tsafe/Reflect"; import type { BuildOptions } from "../BuildOptions"; -import { resources_common, lastKeycloakVersionWithAccountV1 } from "../../constants"; +import { resources_common, lastKeycloakVersionWithAccountV1, accountV1 } from "../../constants"; import { downloadBuiltinKeycloakTheme } from "../../download-builtin-keycloak-theme"; import { transformCodebase } from "../../tools/transformCodebase"; @@ -18,8 +18,6 @@ export type BuildOptionsLike = { assert(); } -export const accountV1 = "account-v1"; - export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike }) { const { buildOptions } = params; diff --git a/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts b/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts index 7e843d9b..960deafd 100644 --- a/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts +++ b/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts @@ -3,8 +3,8 @@ import { join as pathJoin, dirname as pathDirname } from "path"; import { assert } from "tsafe/assert"; import { Reflect } from "tsafe/Reflect"; import type { BuildOptions } from "../BuildOptions"; -import { type ThemeType } from "../../constants"; -import { bringInAccountV1, accountV1 } from "./bringInAccountV1"; +import { type ThemeType, retrocompatPostfix, accountV1 } from "../../constants"; +import { bringInAccountV1 } from "./bringInAccountV1"; export type BuildOptionsLike = { groupId: string; @@ -189,7 +189,7 @@ export async function generateJavaStackFiles(params: { ? [] : [ { - "name": `${themeName}_retrocompat`, + "name": `${themeName}${retrocompatPostfix}`, "types": ["account"] } ]) diff --git a/src/bin/keycloakify/generateTheme/generateTheme.ts b/src/bin/keycloakify/generateTheme/generateTheme.ts index d96ef376..b49b0b87 100644 --- a/src/bin/keycloakify/generateTheme/generateTheme.ts +++ b/src/bin/keycloakify/generateTheme/generateTheme.ts @@ -1,10 +1,10 @@ import { transformCodebase } from "../../tools/transformCodebase"; import * as fs from "fs"; -import { join as pathJoin } from "path"; +import { join as pathJoin, basename as pathBasename } from "path"; import { replaceImportsFromStaticInJsCode } from "../replacers/replaceImportsFromStaticInJsCode"; import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode"; import { generateFtlFilesCodeFactory, loginThemePageIds, accountThemePageIds } from "../generateFtl"; -import { themeTypes, type ThemeType, lastKeycloakVersionWithAccountV1, keycloak_resources } from "../../constants"; +import { themeTypes, type ThemeType, lastKeycloakVersionWithAccountV1, keycloak_resources, retrocompatPostfix, accountV1 } from "../../constants"; import { isInside } from "../../tools/isInside"; import type { BuildOptions } from "../BuildOptions"; import { assert, type Equals } from "tsafe/assert"; @@ -22,6 +22,7 @@ export type BuildOptionsLike = { keycloakifyBuildDirPath: string; reactAppBuildDirPath: string; cacheDirPath: string; + doBuildRetrocompatAccountTheme: boolean; }; assert(); @@ -35,8 +36,18 @@ export async function generateTheme(params: { }): Promise { const { themeName, themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion } = params; - const getThemeDirPath = (themeType: ThemeType | "email") => - pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme", themeName, themeType); + const getThemeDirPath = (params: { themeType: ThemeType | "email"; isRetrocompat?: true }) => { + const { themeType, isRetrocompat = false } = params; + return pathJoin( + buildOptions.keycloakifyBuildDirPath, + "src", + "main", + "resources", + "theme", + `${themeName}${isRetrocompat ? retrocompatPostfix : ""}`, + themeType + ); + }; let allCssGlobalsToDefine: Record = {}; @@ -47,7 +58,7 @@ export async function generateTheme(params: { continue; } - const themeDirPath = getThemeDirPath(themeType); + const themeDirPath = getThemeDirPath({ themeType }); copy_app_resources_to_theme_path: { const isFirstPass = themeType.indexOf(themeType) === 0; @@ -179,7 +190,7 @@ export async function generateTheme(params: { `parent=${(() => { switch (themeType) { case "account": - return "account-v1"; + return accountV1; case "login": return "keycloak"; } @@ -190,6 +201,22 @@ export async function generateTheme(params: { "utf8" ) ); + + if (themeType === "account" && buildOptions.doBuildRetrocompatAccountTheme) { + transformCodebase({ + "srcDirPath": themeDirPath, + "destDirPath": getThemeDirPath({ themeType, "isRetrocompat": true }), + "transformSourceCode": ({ filePath, sourceCode }) => { + if (pathBasename(filePath) === "theme.properties") { + return { + "modifiedSourceCode": Buffer.from(sourceCode.toString("utf8").replace(`parent=${accountV1}`, "parent=keycloak"), "utf8") + }; + } + + return { "modifiedSourceCode": sourceCode }; + } + }); + } } email: { @@ -201,7 +228,7 @@ export async function generateTheme(params: { transformCodebase({ "srcDirPath": emailThemeSrcDirPath, - "destDirPath": getThemeDirPath("email") + "destDirPath": getThemeDirPath({ "themeType": "email" }) }); } } diff --git a/src/bin/keycloakify/parsedPackageJson.ts b/src/bin/keycloakify/parsedPackageJson.ts index 6e869a5c..43478f14 100644 --- a/src/bin/keycloakify/parsedPackageJson.ts +++ b/src/bin/keycloakify/parsedPackageJson.ts @@ -18,6 +18,7 @@ export type ParsedPackageJson = { reactAppBuildDirPath?: string; keycloakifyBuildDirPath?: string; themeName?: string | string[]; + doBuildRetrocompatAccountTheme?: boolean; }; }; @@ -35,7 +36,8 @@ export const zParsedPackageJson = z.object({ "loginThemeResourcesFromKeycloakVersion": z.string().optional(), "reactAppBuildDirPath": z.string().optional(), "keycloakifyBuildDirPath": z.string().optional(), - "themeName": z.union([z.string(), z.array(z.string())]).optional() + "themeName": z.union([z.string(), z.array(z.string())]).optional(), + "doBuildRetrocompatAccountTheme": z.boolean().optional() }) .optional() });