diff --git a/README.md b/README.md index 0acfb895..54c3a984 100644 --- a/README.md +++ b/README.md @@ -125,17 +125,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d # Changelog highlights -## 9.2 - -Remove the `_retrocompat` option in the account theme dropdown and the `doBuildRetrocompatAccountTheme` build option. -`_retrocompat` was useless because `` works fine even on older Keycloak version. - -What you have to remember is that: - -- If you have no account theme: You can use the `retrocompat_*.jar` it will work on any Keycloak version. -- If you have an Account theme: Use `retrocompat_*.jar` only if your Keycloak is in a version prior to 23. - Keycloak 22 is not supported (Only login themes works in Keycloak 22). - ## 9.0 Bring back support for account themes in Keycloak v23 and up! [See issue](https://github.com/keycloakify/keycloakify/issues/389). diff --git a/src/bin/constants.ts b/src/bin/constants.ts index 6cb76d97..fa8c2a49 100644 --- a/src/bin/constants.ts +++ b/src/bin/constants.ts @@ -3,6 +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/download-builtin-keycloak-theme.ts b/src/bin/download-builtin-keycloak-theme.ts index ab257e92..47801229 100644 --- a/src/bin/download-builtin-keycloak-theme.ts +++ b/src/bin/download-builtin-keycloak-theme.ts @@ -27,6 +27,51 @@ export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: st "preCacheTransform": { "actionCacheId": "npm install and build", "action": async ({ destDirPath }) => { + fix_account_css: { + const accountCssFilePath = pathJoin(destDirPath, "keycloak", "account", "resources", "css", "account.css"); + + if (!fs.existsSync(accountCssFilePath)) { + break fix_account_css; + } + + fs.writeFileSync( + accountCssFilePath, + Buffer.from(fs.readFileSync(accountCssFilePath).toString("utf8").replace("top: -34px;", "top: -34px !important;"), "utf8") + ); + } + + fix_account_topt: { + const totpFtlFilePath = pathJoin(destDirPath, "base", "account", "totp.ftl"); + + if (!fs.existsSync(totpFtlFilePath)) { + break fix_account_topt; + } + + fs.writeFileSync( + totpFtlFilePath, + Buffer.from( + fs + .readFileSync(totpFtlFilePath) + .toString("utf8") + .replace( + [ + " <#list totp.policy.supportedApplications as app>", + "
  • ${app}
  • ", + " " + ].join("\n"), + [ + " <#if totp.policy.supportedApplications?has_content>", + " <#list totp.policy.supportedApplications as app>", + "
  • ${app}
  • ", + " ", + " " + ].join("\n") + ), + "utf8" + ) + ); + } + install_common_node_modules: { const commonResourcesDirPath = pathJoin(destDirPath, "keycloak", "common", "resources"); 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 3ca94b27..c41c618f 100644 --- a/src/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.ts +++ b/src/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.ts @@ -38,7 +38,21 @@ export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike const commonResourceFilePaths = [ "node_modules/patternfly/dist/css/patternfly.min.css", - "node_modules/patternfly/dist/css/patternfly-additions.min.css" + "node_modules/patternfly/dist/css/patternfly-additions.min.css", + ...[ + "OpenSans-Light-webfont.woff2", + "OpenSans-Regular-webfont.woff2", + "OpenSans-Bold-webfont.woff2", + "OpenSans-Semibold-webfont.woff2", + "OpenSans-Bold-webfont.woff", + "OpenSans-Light-webfont.woff", + "OpenSans-Regular-webfont.woff", + "OpenSans-Semibold-webfont.woff", + "OpenSans-Regular-webfont.ttf", + "OpenSans-Light-webfont.ttf", + "OpenSans-Semibold-webfont.ttf", + "OpenSans-Bold-webfont.ttf" + ].map(path => `node_modules/patternfly/dist/fonts/${path}`) ]; for (const relativeFilePath of commonResourceFilePaths.map(path => pathJoin(...path.split("/")))) { @@ -49,7 +63,7 @@ export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike fs.cpSync(pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "common", "resources", relativeFilePath), destFilePath); } - const resourceFilePaths = ["css/account.css"]; + const resourceFilePaths = ["css/account.css", "img/icon-sidebar-active.png", "img/logo.png"]; for (const relativeFilePath of resourceFilePaths.map(path => pathJoin(...path.split("/")))) { const destFilePath = pathJoin(accountV1DirPath, "resources", relativeFilePath); @@ -69,7 +83,7 @@ export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike "", "locales=ar,ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN", "", - "styles=" + [...resourceFilePaths, ...commonResourceFilePaths.map(path => `resources_common/${path}`)].join(" "), + "styles=" + [...resourceFilePaths, ...commonResourceFilePaths.map(path => `resources-common/${path}`)].join(" "), "", "##### css classes for form buttons", "# main class used for all buttons", diff --git a/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts b/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts index 732c2007..df7b92ac 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, accountV1 } from "../../constants"; +import { type ThemeType, retrocompatPostfix, accountV1 } from "../../constants"; import { bringInAccountV1 } from "./bringInAccountV1"; export type BuildOptionsLike = { @@ -13,6 +13,7 @@ export 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 1cffad27..b0e9ef84 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, resolve as pathResolve } from "path"; +import { join as pathJoin, basename as pathBasename, resolve as pathResolve } 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, accountV1 } 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,9 +36,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 = {}; @@ -192,6 +201,22 @@ 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=${accountV1}`, "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" ];