diff --git a/src/bin/keycloakify/buildJars/buildJar.ts b/src/bin/keycloakify/buildJars/buildJar.ts index f56a40bc..f370f1d2 100644 --- a/src/bin/keycloakify/buildJars/buildJar.ts +++ b/src/bin/keycloakify/buildJars/buildJar.ts @@ -33,6 +33,7 @@ export async function buildJar(params: { keycloakAccountV1Version: KeycloakAccountV1Version; keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion; resourcesDirPath: string; + doesImplementAccountV1Theme: boolean; buildContext: BuildContextLike; }): Promise { const { @@ -40,6 +41,7 @@ export async function buildJar(params: { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, resourcesDirPath, + doesImplementAccountV1Theme, buildContext } = params; @@ -61,7 +63,7 @@ export async function buildJar(params: { srcDirPath: resourcesDirPath, destDirPath: tmpResourcesDirPath, transformSourceCode: - keycloakAccountV1Version !== null + !doesImplementAccountV1Theme || keycloakAccountV1Version !== null ? undefined : (params: { fileRelativePath: string; @@ -105,7 +107,17 @@ export async function buildJar(params: { } }); - if (keycloakAccountV1Version === null) { + remove_account_v1_in_meta_inf: { + if (!doesImplementAccountV1Theme) { + // NOTE: We do not have account v1 anyway + break remove_account_v1_in_meta_inf; + } + + if (keycloakAccountV1Version !== null) { + // NOTE: No, we need to keep account-v1 in meta-inf + break remove_account_v1_in_meta_inf; + } + writeMetaInfKeycloakThemes({ resourcesDirPath: tmpResourcesDirPath, getNewMetaInfKeycloakTheme: ({ metaInfKeycloakTheme }) => { @@ -135,6 +147,7 @@ export async function buildJar(params: { } })(); + // TODO: Remove this optimization, it's a bit hacky. if (doBreak) { break route_legacy_pages; } diff --git a/src/bin/keycloakify/buildJars/buildJars.ts b/src/bin/keycloakify/buildJars/buildJars.ts index 4fb2be9a..2a3c891b 100644 --- a/src/bin/keycloakify/buildJars/buildJars.ts +++ b/src/bin/keycloakify/buildJars/buildJars.ts @@ -12,6 +12,7 @@ export type BuildContextLike = BuildContextLike_buildJar & { keycloakifyBuildDirPath: string; recordIsImplementedByThemeType: BuildContext["recordIsImplementedByThemeType"]; jarTargets: BuildContext["jarTargets"]; + doUseAccountV3: boolean; }; assert(); @@ -22,7 +23,9 @@ export async function buildJars(params: { }): Promise { const { resourcesDirPath, buildContext } = params; - const doesImplementAccountTheme = buildContext.recordIsImplementedByThemeType.account; + const doesImplementAccountV1Theme = + buildContext.recordIsImplementedByThemeType.account && + !buildContext.doUseAccountV3; await Promise.all( keycloakAccountV1Versions @@ -30,7 +33,7 @@ export async function buildJars(params: { keycloakThemeAdditionalInfoExtensionVersions.map( keycloakThemeAdditionalInfoExtensionVersion => { const keycloakVersionRange = getKeycloakVersionRangeForJar({ - doesImplementAccountTheme, + doesImplementAccountV1Theme, keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion }); @@ -55,6 +58,7 @@ export async function buildJars(params: { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, resourcesDirPath, + doesImplementAccountV1Theme, buildContext }); } diff --git a/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts b/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts index 90a3171f..3999ce8f 100644 --- a/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts +++ b/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts @@ -6,17 +6,17 @@ import type { import type { KeycloakVersionRange } from "../../shared/KeycloakVersionRange"; export function getKeycloakVersionRangeForJar(params: { - doesImplementAccountTheme: boolean; + doesImplementAccountV1Theme: boolean; keycloakAccountV1Version: KeycloakAccountV1Version; keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion; }): KeycloakVersionRange | undefined { const { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, - doesImplementAccountTheme + doesImplementAccountV1Theme } = params; - if (doesImplementAccountTheme) { + if (doesImplementAccountV1Theme) { const keycloakVersionRange = (() => { switch (keycloakAccountV1Version) { case null: @@ -63,7 +63,7 @@ export function getKeycloakVersionRangeForJar(params: { assert< Equals< typeof keycloakVersionRange, - KeycloakVersionRange.WithAccountTheme | undefined + KeycloakVersionRange.WithAccountV1Theme | undefined > >(); @@ -87,7 +87,7 @@ export function getKeycloakVersionRangeForJar(params: { assert< Equals< typeof keycloakVersionRange, - KeycloakVersionRange.WithoutAccountTheme | undefined + KeycloakVersionRange.WithoutAccountV1Theme | undefined > >(); diff --git a/src/bin/keycloakify/generateFtl/generateFtl.ts b/src/bin/keycloakify/generateFtl/generateFtl.ts index e3af9b68..18e81527 100644 --- a/src/bin/keycloakify/generateFtl/generateFtl.ts +++ b/src/bin/keycloakify/generateFtl/generateFtl.ts @@ -34,6 +34,7 @@ export function generateFtlFilesCodeFactory(params: { keycloakifyVersion: string; themeType: ThemeType; fieldNames: string[]; + isAccountV3: boolean; }) { const { themeName, @@ -41,7 +42,8 @@ export function generateFtlFilesCodeFactory(params: { buildContext, keycloakifyVersion, themeType, - fieldNames + fieldNames, + isAccountV3 } = params; const $ = cheerio.load(indexHtmlCode); @@ -68,7 +70,8 @@ export function generateFtlFilesCodeFactory(params: { const { fixedCssCode } = replaceImportsInCssCode({ cssCode, cssFileRelativeDirPath: undefined, - buildContext + buildContext, + isAccountV3 }); $(element).text(fixedCssCode); @@ -93,7 +96,7 @@ export function generateFtlFilesCodeFactory(params: { new RegExp( `^${(buildContext.urlPathname ?? "/").replace(/\//g, "\\/")}` ), - `\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/` + `\${${!isAccountV3 ? "url.resourcesPath" : "resourceUrl"}}/${basenameOfTheKeycloakifyResourcesDir}/` ) ); }) diff --git a/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl b/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl index 774483e0..418e9932 100644 --- a/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl +++ b/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl @@ -1,4 +1,5 @@ <#assign pageId="PAGE_ID_xIgLsPgGId9D8e"> +<#assign themeType="KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr"> const kcContext = ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc}; if( kcContext.messagesPerField ){ var existsError_singleFieldName = kcContext.messagesPerField.existsError; @@ -27,7 +28,7 @@ if( kcContext.messagesPerField ){ } kcContext.keycloakifyVersion = "KEYCLOAKIFY_VERSION_xEdKd3xEdr"; kcContext.themeVersion = "KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx"; -kcContext.themeType = "KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr"; +kcContext.themeType = "${themeType}"; kcContext.themeName = "KEYCLOAKIFY_THEME_NAME_cXxKd3xEer"; kcContext.pageId = "${pageId}"; if( kcContext.url && kcContext.url.resourcesPath ){ diff --git a/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts b/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts index a748a9ba..33d86bf0 100644 --- a/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts +++ b/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts @@ -54,6 +54,7 @@ export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode & recordIsImplementedByThemeType: BuildContext["recordIsImplementedByThemeType"]; themeSrcDirPath: string; bundler: { type: "vite" } | { type: "webpack" }; + doUseAccountV3: boolean; }; assert(); @@ -71,6 +72,7 @@ export async function generateResourcesForMainTheme(params: { }; for (const themeType of ["login", "account"] as const) { + const isAccountV3 = themeType === "account" && buildContext.doUseAccountV3; if (!buildContext.recordIsImplementedByThemeType[themeType]) { continue; } @@ -136,7 +138,8 @@ export async function generateResourcesForMainTheme(params: { const { fixedCssCode } = replaceImportsInCssCode({ cssCode: sourceCode.toString("utf8"), cssFileRelativeDirPath: pathDirname(fileRelativePath), - buildContext + buildContext, + isAccountV3 }); return { @@ -171,7 +174,8 @@ export async function generateResourcesForMainTheme(params: { fieldNames: readFieldNameUsage({ themeSrcDirPath: buildContext.themeSrcDirPath, themeType - }) + }), + isAccountV3 }); [ @@ -180,13 +184,15 @@ export async function generateResourcesForMainTheme(params: { case "login": return loginThemePageIds; case "account": - return accountThemePageIds; + return isAccountV3 ? ["index.ftl"] : accountThemePageIds; } })(), - ...readExtraPagesNames({ - themeType, - themeSrcDirPath: buildContext.themeSrcDirPath - }) + ...(isAccountV3 + ? [] + : readExtraPagesNames({ + themeType, + themeSrcDirPath: buildContext.themeSrcDirPath + })) ].forEach(pageId => { const { ftlCode } = generateFtlFilesCode({ pageId }); @@ -196,40 +202,52 @@ export async function generateResourcesForMainTheme(params: { ); }); - generateMessageProperties({ - themeSrcDirPath: buildContext.themeSrcDirPath, - themeType - }).forEach(({ languageTag, propertiesFileSource }) => { - const messagesDirPath = pathJoin(themeTypeDirPath, "messages"); + i18n_messages_generation: { + if (isAccountV3) { + break i18n_messages_generation; + } - fs.mkdirSync(pathJoin(themeTypeDirPath, "messages"), { - recursive: true + generateMessageProperties({ + themeSrcDirPath: buildContext.themeSrcDirPath, + themeType + }).forEach(({ languageTag, propertiesFileSource }) => { + const messagesDirPath = pathJoin(themeTypeDirPath, "messages"); + + fs.mkdirSync(pathJoin(themeTypeDirPath, "messages"), { + recursive: true + }); + + const propertiesFilePath = pathJoin( + messagesDirPath, + `messages_${languageTag}.properties` + ); + + fs.writeFileSync( + propertiesFilePath, + Buffer.from(propertiesFileSource, "utf8") + ); }); + } - const propertiesFilePath = pathJoin( - messagesDirPath, - `messages_${languageTag}.properties` - ); + keycloak_static_resources: { + if (isAccountV3) { + break keycloak_static_resources; + } - fs.writeFileSync( - propertiesFilePath, - Buffer.from(propertiesFileSource, "utf8") - ); - }); - - await downloadKeycloakStaticResources({ - keycloakVersion: (() => { - switch (themeType) { - case "account": - return lastKeycloakVersionWithAccountV1; - case "login": - return buildContext.loginThemeResourcesFromKeycloakVersion; - } - })(), - themeDirPath: pathResolve(pathJoin(themeTypeDirPath, "..")), - themeType, - buildContext - }); + await downloadKeycloakStaticResources({ + keycloakVersion: (() => { + switch (themeType) { + case "account": + return lastKeycloakVersionWithAccountV1; + case "login": + return buildContext.loginThemeResourcesFromKeycloakVersion; + } + })(), + themeDirPath: pathResolve(pathJoin(themeTypeDirPath, "..")), + themeType, + buildContext + }); + } fs.writeFileSync( pathJoin(themeTypeDirPath, "theme.properties"), @@ -238,12 +256,13 @@ export async function generateResourcesForMainTheme(params: { `parent=${(() => { switch (themeType) { case "account": - return accountV1ThemeName; + return isAccountV3 ? "base" : accountV1ThemeName; case "login": return "keycloak"; } assert>(false); })()}`, + ...(isAccountV3 ? ["deprecatedMode=false"] : []), ...(buildContext.extraThemeProperties ?? []), ...buildContext.environmentVariables.map( ({ name, default: defaultValue }) => @@ -268,7 +287,15 @@ export async function generateResourcesForMainTheme(params: { }); } - if (buildContext.recordIsImplementedByThemeType.account) { + bring_in_account_v1: { + if (buildContext.doUseAccountV3) { + break bring_in_account_v1; + } + + if (!buildContext.recordIsImplementedByThemeType.account) { + break bring_in_account_v1; + } + await bringInAccountV1({ resourcesDirPath, buildContext diff --git a/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts b/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts index c48dc348..7794c36b 100644 --- a/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts +++ b/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts @@ -12,11 +12,12 @@ assert(); export function replaceImportsInCssCode(params: { cssCode: string; cssFileRelativeDirPath: string | undefined; + isAccountV3: boolean; buildContext: BuildContextLike; }): { fixedCssCode: string; } { - const { cssCode, cssFileRelativeDirPath, buildContext } = params; + const { cssCode, cssFileRelativeDirPath, buildContext, isAccountV3 } = params; const fixedCssCode = cssCode.replace( /url\(["']?(\/[^/][^)"']+)["']?\)/g, @@ -37,7 +38,7 @@ export function replaceImportsInCssCode(params: { break inline_style_in_html; } - return `url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}${assetFileAbsoluteUrlPathname})`; + return `url(\${${!isAccountV3 ? "url.resourcesPath" : "resourceUrl"}}/${basenameOfTheKeycloakifyResourcesDir}${assetFileAbsoluteUrlPathname})`; } const assetFileRelativeUrlPathname = posix.relative( diff --git a/src/bin/shared/KeycloakVersionRange.ts b/src/bin/shared/KeycloakVersionRange.ts index fedc63b4..2827d34e 100644 --- a/src/bin/shared/KeycloakVersionRange.ts +++ b/src/bin/shared/KeycloakVersionRange.ts @@ -1,9 +1,9 @@ export type KeycloakVersionRange = - | KeycloakVersionRange.WithAccountTheme - | KeycloakVersionRange.WithoutAccountTheme; + | KeycloakVersionRange.WithAccountV1Theme + | KeycloakVersionRange.WithoutAccountV1Theme; export namespace KeycloakVersionRange { - export type WithoutAccountTheme = "21-and-below" | "22-and-above"; + export type WithoutAccountV1Theme = "21-and-below" | "22-and-above"; - export type WithAccountTheme = "21-and-below" | "23" | "24" | "25-and-above"; + export type WithAccountV1Theme = "21-and-below" | "23" | "24" | "25-and-above"; } diff --git a/src/bin/shared/buildContext.ts b/src/bin/shared/buildContext.ts index 48b23d35..f0356abb 100644 --- a/src/bin/shared/buildContext.ts +++ b/src/bin/shared/buildContext.ts @@ -63,6 +63,7 @@ export type BuildContext = { packageJsonDirPath: string; packageJsonScripts: Record; }; + doUseAccountV3: boolean; }; export type BuildOptions = { @@ -77,16 +78,17 @@ export type BuildOptions = { kcContextExclusionsFtl?: string; /** https://docs.keycloakify.dev/v/v10/targetting-specific-keycloak-versions */ keycloakVersionTargets?: BuildOptions.KeycloakVersionTargets; + doUseAccountV3?: boolean; }; export namespace BuildOptions { export type KeycloakVersionTargets = | ({ hasAccountTheme: true } & Record< - KeycloakVersionRange.WithAccountTheme, + KeycloakVersionRange.WithAccountV1Theme, string | boolean >) | ({ hasAccountTheme: false } & Record< - KeycloakVersionRange.WithoutAccountTheme, + KeycloakVersionRange.WithoutAccountV1Theme, string | boolean >); } @@ -229,6 +231,7 @@ export function getBuildContext(params: { projectBuildDirPath?: string; staticDirPathInProjectBuildDirPath?: string; publicDirPath?: string; + doUseAccountV3?: boolean; }; type ParsedPackageJson = { @@ -297,7 +300,8 @@ export function getBuildContext(params: { return zKeycloakVersionTargets; })() - ).optional() + ).optional(), + doUseAccountV3: z.boolean().optional() }); { @@ -386,6 +390,8 @@ export function getBuildContext(params: { const bundler = resolvedViteConfig !== undefined ? "vite" : "webpack"; + const doUseAccountV3 = buildOptions.doUseAccountV3 ?? false; + return { bundler: resolvedViteConfig !== undefined @@ -606,10 +612,10 @@ export function getBuildContext(params: { } const keycloakVersionRange: KeycloakVersionRange = (() => { - const doesImplementAccountTheme = - recordIsImplementedByThemeType.account; + const doesImplementAccountV1Theme = + !doUseAccountV3 && recordIsImplementedByThemeType.account; - if (doesImplementAccountTheme) { + if (doesImplementAccountV1Theme) { const keycloakVersionRange = (() => { if (buildForKeycloakMajorVersionNumber <= 21) { return "21-and-below" as const; @@ -631,7 +637,7 @@ export function getBuildContext(params: { assert< Equals< typeof keycloakVersionRange, - KeycloakVersionRange.WithAccountTheme + KeycloakVersionRange.WithAccountV1Theme > >(); @@ -648,7 +654,7 @@ export function getBuildContext(params: { assert< Equals< typeof keycloakVersionRange, - KeycloakVersionRange.WithoutAccountTheme + KeycloakVersionRange.WithoutAccountV1Theme > >(); @@ -696,7 +702,7 @@ export function getBuildContext(params: { const jarTargets_default = (() => { const jarTargets: BuildContext["jarTargets"] = []; - if (recordIsImplementedByThemeType.account) { + if (!doUseAccountV3 && recordIsImplementedByThemeType.account) { for (const keycloakVersionRange of [ "21-and-below", "23", @@ -706,7 +712,7 @@ export function getBuildContext(params: { assert< Equals< typeof keycloakVersionRange, - KeycloakVersionRange.WithAccountTheme + KeycloakVersionRange.WithAccountV1Theme > >(true); jarTargets.push({ @@ -723,7 +729,7 @@ export function getBuildContext(params: { assert< Equals< typeof keycloakVersionRange, - KeycloakVersionRange.WithoutAccountTheme + KeycloakVersionRange.WithoutAccountV1Theme > >(true); jarTargets.push({ @@ -742,8 +748,9 @@ export function getBuildContext(params: { } if ( - buildOptions.keycloakVersionTargets.hasAccountTheme !== - recordIsImplementedByThemeType.account + buildOptions.keycloakVersionTargets.hasAccountTheme !== doUseAccountV3 + ? false + : recordIsImplementedByThemeType.account ) { console.log( chalk.red( @@ -863,6 +870,7 @@ export function getBuildContext(params: { } return jarTargets; - })() + })(), + doUseAccountV3 }; } diff --git a/test/bin/replacers.spec.ts b/test/bin/replacers.spec.ts index 313ba397..87a8bffc 100644 --- a/test/bin/replacers.spec.ts +++ b/test/bin/replacers.spec.ts @@ -396,6 +396,7 @@ describe("css replacer", () => { } `, cssFileRelativeDirPath: "assets/", + isAccountV3: false, buildContext: { urlPathname: undefined } @@ -434,6 +435,7 @@ describe("css replacer", () => { } `, cssFileRelativeDirPath: "assets/", + isAccountV3: false, buildContext: { urlPathname: "/a/b/" } @@ -472,6 +474,7 @@ describe("css replacer", () => { } `, cssFileRelativeDirPath: undefined, + isAccountV3: false, buildContext: { urlPathname: "/a/b/" } @@ -510,6 +513,7 @@ describe("css replacer", () => { } `, cssFileRelativeDirPath: undefined, + isAccountV3: false, buildContext: { urlPathname: undefined }