diff --git a/src/bin/constants.ts b/src/bin/constants.ts index a3ab98e3..279e773a 100644 --- a/src/bin/constants.ts +++ b/src/bin/constants.ts @@ -6,6 +6,7 @@ export const resolvedViteConfigJsonBasename = ".keycloakifyViteConfig.json"; export const basenameOfTheKeycloakifyResourcesDir = "build"; export const themeTypes = ["login", "account"] as const; +export const retrocompatPostfix = "_retrocompat"; export const accountV1ThemeName = "account-v1"; export type ThemeType = (typeof themeTypes)[number]; diff --git a/src/bin/keycloakify/buildOptions/buildOptions.ts b/src/bin/keycloakify/buildOptions/buildOptions.ts index 4dab5750..bb462d59 100644 --- a/src/bin/keycloakify/buildOptions/buildOptions.ts +++ b/src/bin/keycloakify/buildOptions/buildOptions.ts @@ -27,6 +27,7 @@ export type BuildOptions = { * In this case the urlPathname will be "/my-app/" */ urlPathname: string | undefined; assetsDirPath: string; + doBuildRetrocompatAccountTheme: boolean; }; export function readBuildOptions(params: { reactAppRootDirPath: string; processArgv: string[] }): BuildOptions { @@ -178,6 +179,7 @@ export function readBuildOptions(params: { reactAppRootDirPath: string; processA } return pathJoin(reactAppBuildDirPath, resolvedViteConfig.assetsDir); - })() + })(), + "doBuildRetrocompatAccountTheme": parsedPackageJson.keycloakify?.doBuildRetrocompatAccountTheme ?? true }; } diff --git a/src/bin/keycloakify/buildOptions/parsedPackageJson.ts b/src/bin/keycloakify/buildOptions/parsedPackageJson.ts index e21baf2a..4b2aafff 100644 --- a/src/bin/keycloakify/buildOptions/parsedPackageJson.ts +++ b/src/bin/keycloakify/buildOptions/parsedPackageJson.ts @@ -17,6 +17,7 @@ export type ParsedPackageJson = { reactAppBuildDirPath?: string; keycloakifyBuildDirPath?: string; themeName?: string | string[]; + doBuildRetrocompatAccountTheme?: boolean; }; }; @@ -33,7 +34,8 @@ 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() }); diff --git a/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts b/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts index 845f2c3f..b6d93f29 100644 --- a/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts +++ b/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.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 { type ThemeType, accountV1ThemeName } from "../../constants"; +import { type ThemeType, retrocompatPostfix, accountV1ThemeName } from "../../constants"; import { bringInAccountV1 } from "./bringInAccountV1"; type BuildOptionsLike = { @@ -13,6 +13,7 @@ type BuildOptionsLike = { cacheDirPath: string; keycloakifyBuildDirPath: string; themeNames: string[]; + doBuildRetrocompatAccountTheme: boolean; }; { @@ -113,7 +114,15 @@ export async function generateJavaStackFiles(params: { "types": Object.entries(implementedThemeTypes) .filter(([, isImplemented]) => isImplemented) .map(([themeType]) => themeType) - } + }, + ...(!implementedThemeTypes.account || !buildOptions.doBuildRetrocompatAccountTheme + ? [] + : [ + { + "name": `${themeName}${retrocompatPostfix}`, + "types": ["account"] + } + ]) ]) .flat() ] diff --git a/src/bin/keycloakify/generateTheme/generateTheme.ts b/src/bin/keycloakify/generateTheme/generateTheme.ts index 72ddae7e..b9b95a74 100644 --- a/src/bin/keycloakify/generateTheme/generateTheme.ts +++ b/src/bin/keycloakify/generateTheme/generateTheme.ts @@ -1,6 +1,6 @@ import { transformCodebase } from "../../tools/transformCodebase"; import * as fs from "fs"; -import { join as pathJoin, resolve as pathResolve } from "path"; +import { join as pathJoin, basename as pathBasename, resolve as pathResolve } from "path"; import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode"; import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode"; import { generateFtlFilesCodeFactory, loginThemePageIds, accountThemePageIds } from "../generateFtl"; @@ -9,6 +9,7 @@ import { type ThemeType, lastKeycloakVersionWithAccountV1, keycloak_resources, + retrocompatPostfix, accountV1ThemeName, basenameOfTheKeycloakifyResourcesDir } from "../../constants"; @@ -31,6 +32,7 @@ export type BuildOptionsLike = { cacheDirPath: string; assetsDirPath: string; urlPathname: string | undefined; + doBuildRetrocompatAccountTheme: boolean; }; assert(); @@ -44,9 +46,17 @@ export async function generateTheme(params: { }): Promise { const { themeName, themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion } = params; - const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => { - const { themeType } = params; - return pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme", themeName, themeType); + const getThemeTypeDirPath = (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 = {}; @@ -197,6 +207,25 @@ export async function generateTheme(params: { "utf8" ) ); + + if (themeType === "account" && buildOptions.doBuildRetrocompatAccountTheme) { + transformCodebase({ + "srcDirPath": themeTypeDirPath, + "destDirPath": getThemeTypeDirPath({ themeType, "isRetrocompat": true }), + "transformSourceCode": ({ filePath, sourceCode }) => { + if (pathBasename(filePath) === "theme.properties") { + return { + "modifiedSourceCode": Buffer.from( + sourceCode.toString("utf8").replace(`parent=${accountV1ThemeName}`, "parent=keycloak"), + "utf8" + ) + }; + } + + return { "modifiedSourceCode": sourceCode }; + } + }); + } } email: { diff --git a/src/bin/promptKeycloakVersion.ts b/src/bin/promptKeycloakVersion.ts index 276dc90f..63e7b25c 100644 --- a/src/bin/promptKeycloakVersion.ts +++ b/src/bin/promptKeycloakVersion.ts @@ -1,6 +1,7 @@ import { getLatestsSemVersionedTagFactory } from "./tools/octokit-addons/getLatestsSemVersionedTag"; import { Octokit } from "@octokit/rest"; import cliSelect from "cli-select"; +import { lastKeycloakVersionWithAccountV1 } from "./constants"; export async function promptKeycloakVersion() { const { getLatestsSemVersionedTag } = (() => { @@ -26,6 +27,7 @@ export async function promptKeycloakVersion() { "owner": "keycloak", "repo": "keycloak" }).then(arr => arr.map(({ tag }) => tag))), + lastKeycloakVersionWithAccountV1, "11.0.3" ];