diff --git a/.gitignore b/.gitignore index 687df547..dc5f3c97 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,8 @@ jspm_packages /src/login/i18n/messages_defaultSet/ /src/account/i18n/messages_defaultSet/ +/resources/ +/account-v1/ # VS Code devcontainers .devcontainer diff --git a/package.json b/package.json index 6af71d74..3c5b32fa 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "git://github.com/keycloakify/keycloakify.git" }, "scripts": { - "prepare": "tsx scripts/generate-i18n-messages.ts", + "prepare": "tsx scripts/prepare/main.ts", "build": "tsx scripts/build.ts", "storybook": "tsx scripts/start-storybook.ts", "link-in-starter": "tsx scripts/link-in-starter.ts", diff --git a/scripts/build.ts b/scripts/build.ts index c32f2087..583d135f 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -4,6 +4,7 @@ import { join } from "path"; import { assert } from "tsafe/assert"; import { transformCodebase } from "../src/bin/tools/transformCodebase"; import chalk from "chalk"; +import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../src/bin/shared/constants"; console.log(chalk.cyan("Building Keycloakify...")); @@ -136,9 +137,17 @@ fs.rmSync(join("dist", "ncc_out"), { recursive: true }); assert(hasBeenPatched); } -fs.rmSync(join("dist", "src"), { recursive: true, force: true }); +for (const dirBasename of [ + "src", + WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES, + WELL_KNOWN_DIRECTORY_BASE_NAME.ACCOUNT_V1 +]) { + const destDirPath = join("dist", dirBasename); -fs.cpSync("src", join("dist", "src"), { recursive: true }); + fs.rmSync(destDirPath, { recursive: true, force: true }); + + fs.cpSync(dirBasename, destDirPath, { recursive: true }); +} transformCodebase({ srcDirPath: join("stories"), diff --git a/scripts/prepare/constants.ts b/scripts/prepare/constants.ts new file mode 100644 index 00000000..fffddb47 --- /dev/null +++ b/scripts/prepare/constants.ts @@ -0,0 +1,4 @@ +export const KEYCLOAK_VERSION = { + FOR_LOGIN_THEME: "25.0.4", + FOR_ACCOUNT_MULTI_PAGE: "21.1.2" +}; diff --git a/scripts/prepare/createAccountV1Dir.ts b/scripts/prepare/createAccountV1Dir.ts new file mode 100644 index 00000000..19a7e91f --- /dev/null +++ b/scripts/prepare/createAccountV1Dir.ts @@ -0,0 +1,77 @@ +import * as fs from "fs"; +import { join as pathJoin } from "path"; +import { KEYCLOAK_VERSION } from "./constants"; +import { transformCodebase } from "../../src/bin/tools/transformCodebase"; +import { downloadKeycloakDefaultTheme } from "./downloadKeycloakDefaultTheme"; +import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../src/bin/shared/constants"; +import { getThisCodebaseRootDirPath } from "../../src/bin/tools/getThisCodebaseRootDirPath"; +import { accountMultiPageSupportedLanguages } from "./generateI18nMessages"; + +export async function createAccountV1Dir() { + const { extractedDirPath } = await downloadKeycloakDefaultTheme({ + keycloakVersion: KEYCLOAK_VERSION.FOR_ACCOUNT_MULTI_PAGE + }); + + // TODO: Exclude unused resources. + + const destDirPath = pathJoin( + getThisCodebaseRootDirPath(), + WELL_KNOWN_DIRECTORY_BASE_NAME.ACCOUNT_V1 + ); + + transformCodebase({ + srcDirPath: pathJoin(extractedDirPath, "base", "account"), + destDirPath + }); + + transformCodebase({ + srcDirPath: pathJoin(extractedDirPath, "keycloak", "account", "resources"), + destDirPath: pathJoin(destDirPath, "resources") + }); + + transformCodebase({ + srcDirPath: pathJoin(extractedDirPath, "keycloak", "common", "resources"), + destDirPath: pathJoin( + destDirPath, + "resources", + WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON + ) + }); + + fs.writeFileSync( + pathJoin(destDirPath, "theme.properties"), + Buffer.from( + [ + "accountResourceProvider=account-v1", + "", + `locales=${accountMultiPageSupportedLanguages.join(",")}`, + "", + "styles=" + + [ + "css/account.css", + "img/icon-sidebar-active.png", + "img/logo.png", + ...[ + "patternfly.min.css", + "patternfly-additions.min.css", + "patternfly-additions.min.css" + ].map( + fileBasename => + `${WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON}/node_modules/patternfly/dist/css/${fileBasename}` + ) + ].join(" "), + "", + "##### css classes for form buttons", + "# main class used for all buttons", + "kcButtonClass=btn", + "# classes defining priority of the button - primary or default (there is typically only one priority button for the form)", + "kcButtonPrimaryClass=btn-primary", + "kcButtonDefaultClass=btn-default", + "# classes defining size of the button", + "kcButtonLargeClass=btn-lg", + "" + ].join("\n"), + "utf8" + ) + ); +} diff --git a/scripts/prepare/createResourcesDir.ts b/scripts/prepare/createResourcesDir.ts new file mode 100644 index 00000000..1fb13f54 --- /dev/null +++ b/scripts/prepare/createResourcesDir.ts @@ -0,0 +1,70 @@ +import { join as pathJoin } from "path"; +import { downloadKeycloakDefaultTheme } from "./downloadKeycloakDefaultTheme"; +import { KEYCLOAK_VERSION } from "./constants"; +import { transformCodebase } from "../../src/bin/tools/transformCodebase"; +import { existsAsync } from "../../src/bin/tools/fs.existsAsync"; +import { getThisCodebaseRootDirPath } from "../../src/bin/tools/getThisCodebaseRootDirPath"; +import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../src/bin/shared/constants"; +import { assert, type Equals } from "tsafe/assert"; + +export async function createResourcesDir() { + await Promise.all( + (["login", "account"] as const).map(async themeType => { + const keycloakVersion = (() => { + switch (themeType) { + case "login": + return KEYCLOAK_VERSION.FOR_LOGIN_THEME; + case "account": + return KEYCLOAK_VERSION.FOR_ACCOUNT_MULTI_PAGE; + } + assert>(); + })(); + + const { extractedDirPath } = await downloadKeycloakDefaultTheme({ + keycloakVersion + }); + + const destDirPath = pathJoin( + getThisCodebaseRootDirPath(), + WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES, + themeType + ); + + base_resources: { + const srcDirPath = pathJoin( + extractedDirPath, + "base", + themeType, + "resources" + ); + + if (!(await existsAsync(srcDirPath))) { + break base_resources; + } + + transformCodebase({ + srcDirPath, + destDirPath + }); + } + + transformCodebase({ + srcDirPath: pathJoin( + extractedDirPath, + "keycloak", + themeType, + "resources" + ), + destDirPath + }); + + transformCodebase({ + srcDirPath: pathJoin(extractedDirPath, "keycloak", "common", "resources"), + destDirPath: pathJoin( + destDirPath, + WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON + ) + }); + }) + ); +} diff --git a/scripts/prepare/downloadKeycloakDefaultTheme.ts b/scripts/prepare/downloadKeycloakDefaultTheme.ts new file mode 100644 index 00000000..7848e969 --- /dev/null +++ b/scripts/prepare/downloadKeycloakDefaultTheme.ts @@ -0,0 +1,34 @@ +import { relative as pathRelative } from "path"; +import { downloadAndExtractArchive } from "../../src/bin/tools/downloadAndExtractArchive"; +import { getProxyFetchOptions } from "../../src/bin/tools/fetchProxyOptions"; +import { join as pathJoin } from "path"; +import { getThisCodebaseRootDirPath } from "../../src/bin/tools/getThisCodebaseRootDirPath"; + +export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: string }) { + const { keycloakVersion } = params; + + const { extractedDirPath } = await downloadAndExtractArchive({ + url: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`, + cacheDirPath: pathJoin( + getThisCodebaseRootDirPath(), + "node_modules", + ".cache", + "scripts" + ), + fetchOptions: getProxyFetchOptions({ + npmConfigGetCwd: getThisCodebaseRootDirPath() + }), + uniqueIdOfOnArchiveFile: "downloadKeycloakDefaultTheme", + onArchiveFile: async ({ fileRelativePath, writeFile }) => { + const fileRelativePath_target = pathRelative("theme", fileRelativePath); + + if (fileRelativePath_target.startsWith("..")) { + return; + } + + await writeFile({ fileRelativePath: fileRelativePath_target }); + } + }); + + return { extractedDirPath }; +} diff --git a/scripts/generate-i18n-messages.ts b/scripts/prepare/generateI18nMessages.ts similarity index 94% rename from scripts/generate-i18n-messages.ts rename to scripts/prepare/generateI18nMessages.ts index 8aa3cc9c..a127002e 100644 --- a/scripts/generate-i18n-messages.ts +++ b/scripts/prepare/generateI18nMessages.ts @@ -8,15 +8,12 @@ import { } from "path"; import { assert } from "tsafe/assert"; import { same } from "evt/tools/inDepth"; -import { crawl } from "../src/bin/tools/crawl"; -import { downloadKeycloakDefaultTheme } from "../src/bin/shared/downloadKeycloakDefaultTheme"; -import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath"; -import { deepAssign } from "../src/tools/deepAssign"; -import { getProxyFetchOptions } from "../src/bin/tools/fetchProxyOptions"; -import { - THEME_TYPES, - LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1 -} from "../src/bin/shared/constants"; +import { crawl } from "../../src/bin/tools/crawl"; +import { downloadKeycloakDefaultTheme } from "./downloadKeycloakDefaultTheme"; +import { getThisCodebaseRootDirPath } from "../../src/bin/tools/getThisCodebaseRootDirPath"; +import { deepAssign } from "../../src/tools/deepAssign"; +import { THEME_TYPES } from "../../src/bin/shared/constants"; +import { KEYCLOAK_VERSION } from "./constants"; // NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files, // update the version array for generating for newer version. @@ -24,7 +21,7 @@ import { //@ts-ignore const propertiesParser = require("properties-parser"); -async function main() { +export async function generateI18nMessages() { const thisCodebaseRootDirPath = getThisCodebaseRootDirPath(); type Dictionary = { [idiomId: string]: string }; @@ -32,30 +29,19 @@ async function main() { const record: { [themeType: string]: { [language: string]: Dictionary } } = {}; for (const themeType of THEME_TYPES) { - const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({ + const { extractedDirPath } = await downloadKeycloakDefaultTheme({ keycloakVersion: (() => { switch (themeType) { case "login": - return "25.0.4"; + return KEYCLOAK_VERSION.FOR_LOGIN_THEME; case "account": - return LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1; + return KEYCLOAK_VERSION.FOR_ACCOUNT_MULTI_PAGE; } - })(), - buildContext: { - cacheDirPath: pathJoin( - thisCodebaseRootDirPath, - "node_modules", - ".cache", - "keycloakify" - ), - fetchOptions: getProxyFetchOptions({ - npmConfigGetCwd: thisCodebaseRootDirPath - }) - } + })() }); { - const baseThemeDirPath = pathJoin(defaultThemeDirPath, "base"); + const baseThemeDirPath = pathJoin(extractedDirPath, "base"); const re = new RegExp( `^([^\\${pathSep}]+)\\${pathSep}messages\\${pathSep}messages_([^.]+).properties$` ); @@ -597,30 +583,34 @@ const keycloakifyExtraMessages_login: Record< /* spell-checker: enable */ }; +export const accountMultiPageSupportedLanguages = [ + "en", + "ar", + "ca", + "cs", + "da", + "de", + "es", + "fi", + "fr", + "hu", + "it", + "ja", + "lt", + "lv", + "nl", + "no", + "pl", + "pt-BR", + "ru", + "sk", + "sv", + "tr", + "zh-CN" +] as const; + const keycloakifyExtraMessages_account: Record< - | "en" - | "ar" - | "ca" - | "cs" - | "da" - | "de" - | "es" - | "fi" - | "fr" - | "hu" - | "it" - | "ja" - | "lt" - | "lv" - | "nl" - | "no" - | "pl" - | "pt-BR" - | "ru" - | "sk" - | "sv" - | "tr" - | "zh-CN", + (typeof accountMultiPageSupportedLanguages)[number], Record<"newPasswordSameAsOld" | "passwordConfirmNotMatch", string> > = { en: { @@ -719,7 +709,3 @@ const keycloakifyExtraMessages_account: Record< } /* spell-checker: enable */ }; - -if (require.main === module) { - main(); -} diff --git a/scripts/prepare/main.ts b/scripts/prepare/main.ts new file mode 100644 index 00000000..4b35dcf5 --- /dev/null +++ b/scripts/prepare/main.ts @@ -0,0 +1,13 @@ +import { generateI18nMessages } from "./generateI18nMessages"; +import { createAccountV1Dir } from "./createAccountV1Dir"; +import { createResourcesDir } from "./createResourcesDir"; + +(async () => { + console.log("Pulling i18n messages..."); + await generateI18nMessages(); + console.log("Creating account-v1 dir..."); + await createAccountV1Dir(); + console.log("Creating resources dir..."); + await createResourcesDir(); + console.log("Done!"); +})(); diff --git a/src/account/KcContext/kcContextMocks.ts b/src/account/KcContext/kcContextMocks.ts index 3f79faef..a80ed058 100644 --- a/src/account/KcContext/kcContextMocks.ts +++ b/src/account/KcContext/kcContextMocks.ts @@ -1,10 +1,10 @@ import "keycloakify/tools/Object.fromEntries"; -import { RESOURCES_COMMON, KEYCLOAK_RESOURCES } from "keycloakify/bin/shared/constants"; +import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "keycloakify/bin/shared/constants"; import { id } from "tsafe/id"; import type { KcContext } from "./KcContext"; import { BASE_URL } from "keycloakify/lib/BASE_URL"; -const resourcesPath = `${BASE_URL}${KEYCLOAK_RESOURCES}/account/resources`; +const resourcesPath = `${BASE_URL}${WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY}/account`; export const kcContextCommonMock: KcContext.Common = { themeVersion: "0.0.0", @@ -13,7 +13,7 @@ export const kcContextCommonMock: KcContext.Common = { themeName: "my-theme-name", url: { resourcesPath, - resourcesCommonPath: `${resourcesPath}/${RESOURCES_COMMON}`, + resourcesCommonPath: `${resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON}`, resourceUrl: "#", accountUrl: "#", applicationsUrl: "#", diff --git a/src/bin/copy-keycloak-resources-to-public.ts b/src/bin/copy-keycloak-resources-to-public.ts index ff23cc61..b245076a 100644 --- a/src/bin/copy-keycloak-resources-to-public.ts +++ b/src/bin/copy-keycloak-resources-to-public.ts @@ -7,7 +7,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions }) const buildContext = getBuildContext({ cliCommandOptions }); - await copyKeycloakResourcesToPublic({ + copyKeycloakResourcesToPublic({ buildContext }); } diff --git a/src/bin/keycloakify/generateFtl/generateFtl.ts b/src/bin/keycloakify/generateFtl/generateFtl.ts index b47af42f..71331b7a 100644 --- a/src/bin/keycloakify/generateFtl/generateFtl.ts +++ b/src/bin/keycloakify/generateFtl/generateFtl.ts @@ -11,11 +11,7 @@ import * as fs from "fs"; import { join as pathJoin } from "path"; import type { BuildContext } from "../../shared/buildContext"; import { assert } from "tsafe/assert"; -import { - type ThemeType, - BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR, - RESOURCES_COMMON -} from "../../shared/constants"; +import { type ThemeType, WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../shared/constants"; import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath"; export type BuildContextLike = BuildContextLike_replaceImportsInJsCode & @@ -94,7 +90,7 @@ export function generateFtlFilesCodeFactory(params: { new RegExp( `^${(buildContext.urlPathname ?? "/").replace(/\//g, "\\/")}` ), - `\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/` + `\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/` ) ); }) @@ -119,7 +115,7 @@ export function generateFtlFilesCodeFactory(params: { .replace("{{keycloakifyVersion}}", keycloakifyVersion) .replace("{{themeVersion}}", buildContext.themeVersion) .replace("{{fieldNames}}", fieldNames.map(name => `"${name}"`).join(", ")) - .replace("{{RESOURCES_COMMON}}", RESOURCES_COMMON) + .replace("{{RESOURCES_COMMON}}", WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON) .replace( "{{userDefinedExclusions}}", buildContext.kcContextExclusionsFtlCode ?? "" diff --git a/src/bin/keycloakify/generateResources/bringInAccountV1.ts b/src/bin/keycloakify/generateResources/bringInAccountV1.ts deleted file mode 100644 index eb34dfc3..00000000 --- a/src/bin/keycloakify/generateResources/bringInAccountV1.ts +++ /dev/null @@ -1,89 +0,0 @@ -import * as fs from "fs"; -import { join as pathJoin } from "path"; -import { assert } from "tsafe/assert"; -import type { BuildContext } from "../../shared/buildContext"; -import { - RESOURCES_COMMON, - LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1, - ACCOUNT_V1_THEME_NAME -} from "../../shared/constants"; -import { - downloadKeycloakDefaultTheme, - BuildContextLike as BuildContextLike_downloadKeycloakDefaultTheme -} from "../../shared/downloadKeycloakDefaultTheme"; -import { transformCodebase } from "../../tools/transformCodebase"; - -export type BuildContextLike = BuildContextLike_downloadKeycloakDefaultTheme; - -assert(); - -export async function bringInAccountV1(params: { - resourcesDirPath: string; - buildContext: BuildContextLike; -}) { - const { resourcesDirPath, buildContext } = params; - - const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({ - keycloakVersion: LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1, - buildContext - }); - - const accountV1DirPath = pathJoin( - resourcesDirPath, - "theme", - ACCOUNT_V1_THEME_NAME, - "account" - ); - - transformCodebase({ - srcDirPath: pathJoin(defaultThemeDirPath, "base", "account"), - destDirPath: accountV1DirPath - }); - - transformCodebase({ - srcDirPath: pathJoin(defaultThemeDirPath, "keycloak", "account", "resources"), - destDirPath: pathJoin(accountV1DirPath, "resources") - }); - - transformCodebase({ - srcDirPath: pathJoin(defaultThemeDirPath, "keycloak", "common", "resources"), - destDirPath: pathJoin(accountV1DirPath, "resources", RESOURCES_COMMON) - }); - - fs.writeFileSync( - pathJoin(accountV1DirPath, "theme.properties"), - Buffer.from( - [ - "accountResourceProvider=account-v1", - "", - "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=" + - [ - "css/account.css", - "img/icon-sidebar-active.png", - "img/logo.png", - ...[ - "patternfly.min.css", - "patternfly-additions.min.css", - "patternfly-additions.min.css" - ].map( - fileBasename => - `${RESOURCES_COMMON}/node_modules/patternfly/dist/css/${fileBasename}` - ) - ].join(" "), - "", - "##### css classes for form buttons", - "# main class used for all buttons", - "kcButtonClass=btn", - "# classes defining priority of the button - primary or default (there is typically only one priority button for the form)", - "kcButtonPrimaryClass=btn-primary", - "kcButtonDefaultClass=btn-default", - "# classes defining size of the button", - "kcButtonLargeClass=btn-lg", - "" - ].join("\n"), - "utf8" - ) - ); -} diff --git a/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts b/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts index a9ca89d0..fc36cd57 100644 --- a/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts +++ b/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts @@ -1,11 +1,6 @@ import { transformCodebase } from "../../tools/transformCodebase"; import * as fs from "fs"; -import { - join as pathJoin, - resolve as pathResolve, - relative as pathRelative, - dirname as pathDirname -} from "path"; +import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path"; import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode"; import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode"; import { @@ -14,26 +9,15 @@ import { } from "../generateFtl"; import { type ThemeType, - LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1, - KEYCLOAK_RESOURCES, - ACCOUNT_V1_THEME_NAME, - BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR, LOGIN_THEME_PAGE_IDS, - ACCOUNT_THEME_PAGE_IDS + ACCOUNT_THEME_PAGE_IDS, + WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../shared/constants"; import type { BuildContext } from "../../shared/buildContext"; import { assert, type Equals } from "tsafe/assert"; -import { - downloadKeycloakStaticResources, - type BuildContextLike as BuildContextLike_downloadKeycloakStaticResources -} from "../../shared/downloadKeycloakStaticResources"; import { readFieldNameUsage } from "./readFieldNameUsage"; import { readExtraPagesNames } from "./readExtraPageNames"; import { generateMessageProperties } from "./generateMessageProperties"; -import { - bringInAccountV1, - type BuildContextLike as BuildContextLike_bringInAccountV1 -} from "./bringInAccountV1"; import { rmSync } from "../../tools/fs.rmSync"; import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion"; import { @@ -43,20 +27,18 @@ import { import { objectEntries } from "tsafe/objectEntries"; import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile"; import * as child_process from "child_process"; +import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath"; -export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode & - BuildContextLike_downloadKeycloakStaticResources & - BuildContextLike_bringInAccountV1 & { - extraThemeProperties: string[] | undefined; - loginThemeResourcesFromKeycloakVersion: string; - projectDirPath: string; - projectBuildDirPath: string; - environmentVariables: { name: string; default: string }[]; - implementedThemeTypes: BuildContext["implementedThemeTypes"]; - themeSrcDirPath: string; - bundler: "vite" | "webpack"; - packageJsonFilePath: string; - }; +export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode & { + extraThemeProperties: string[] | undefined; + projectDirPath: string; + projectBuildDirPath: string; + environmentVariables: { name: string; default: string }[]; + implementedThemeTypes: BuildContext["implementedThemeTypes"]; + themeSrcDirPath: string; + bundler: "vite" | "webpack"; + packageJsonFilePath: string; +}; assert(); @@ -88,7 +70,7 @@ export async function generateResourcesForMainTheme(params: { const destDirPath = pathJoin( themeTypeDirPath, "resources", - BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR + WELL_KNOWN_DIRECTORY_BASE_NAME.DIST ); // NOTE: Prevent accumulation of files in the assets dir, as names are hashed they pile up. @@ -106,7 +88,7 @@ export async function generateResourcesForMainTheme(params: { themeType: "login" }), "resources", - BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR + WELL_KNOWN_DIRECTORY_BASE_NAME.DIST ), destDirPath }); @@ -117,7 +99,7 @@ export async function generateResourcesForMainTheme(params: { { const dirPath = pathJoin( buildContext.projectBuildDirPath, - KEYCLOAK_RESOURCES + WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY ); if (fs.existsSync(dirPath)) { @@ -125,7 +107,7 @@ export async function generateResourcesForMainTheme(params: { throw new Error( [ - `Keycloakify build error: The ${KEYCLOAK_RESOURCES} directory shouldn't exist in your build directory.`, + `Keycloakify build error: The ${WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY} directory shouldn't exist in your build directory.`, `(${pathRelative(process.cwd(), dirPath)}).\n`, `Theses assets are only required for local development with Storybook.", "Please remove this directory as an additional step of your command.\n`, @@ -232,23 +214,50 @@ export async function generateResourcesForMainTheme(params: { }); } + bring_in_account_v3_i18n_messages: { + if (!buildContext.implementedThemeTypes.account.isImplemented) { + break bring_in_account_v3_i18n_messages; + } + if (buildContext.implementedThemeTypes.account.type !== "Single-Page") { + break bring_in_account_v3_i18n_messages; + } + + const accountUiDirPath = child_process + .execSync("npm list @keycloakify/keycloak-account-ui --parseable", { + cwd: pathDirname(buildContext.packageJsonFilePath) + }) + .toString("utf8") + .trim(); + + const messagesDirPath = pathJoin(accountUiDirPath, "messages"); + + if (!fs.existsSync(messagesDirPath)) { + throw new Error( + `Please update @keycloakify/keycloak-account-ui to 25.0.4-rc.5 or later.` + ); + } + + transformCodebase({ + srcDirPath: messagesDirPath, + destDirPath: pathJoin( + getThemeTypeDirPath({ themeType: "account" }), + "messages" + ) + }); + } + keycloak_static_resources: { if (isForAccountSpa) { break keycloak_static_resources; } - await downloadKeycloakStaticResources({ - keycloakVersion: (() => { - switch (themeType) { - case "account": - return LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1; - case "login": - return buildContext.loginThemeResourcesFromKeycloakVersion; - } - })(), - themeDirPath: pathResolve(pathJoin(themeTypeDirPath, "..")), - themeType, - buildContext + transformCodebase({ + srcDirPath: pathJoin( + getThisCodebaseRootDirPath(), + WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES, + themeType + ), + destDirPath: pathJoin(themeTypeDirPath, "resources") }); } @@ -259,7 +268,7 @@ export async function generateResourcesForMainTheme(params: { `parent=${(() => { switch (themeType) { case "account": - return isForAccountSpa ? "base" : ACCOUNT_V1_THEME_NAME; + return isForAccountSpa ? "base" : "account-v1"; case "login": return "keycloak"; } @@ -299,41 +308,12 @@ export async function generateResourcesForMainTheme(params: { break bring_in_account_v1; } - await bringInAccountV1({ - resourcesDirPath, - buildContext - }); - } - - bring_in_account_v3_i18n_messages: { - if (!buildContext.implementedThemeTypes.account.isImplemented) { - break bring_in_account_v3_i18n_messages; - } - if (buildContext.implementedThemeTypes.account.type !== "Single-Page") { - break bring_in_account_v3_i18n_messages; - } - - const accountUiDirPath = child_process - .execSync("npm list @keycloakify/keycloak-account-ui --parseable", { - cwd: pathDirname(buildContext.packageJsonFilePath) - }) - .toString("utf8") - .trim(); - - const messagesDirPath = pathJoin(accountUiDirPath, "messages"); - - if (!fs.existsSync(messagesDirPath)) { - throw new Error( - `Please update @keycloakify/keycloak-account-ui to 25.0.4-rc.5 or later.` - ); - } - transformCodebase({ - srcDirPath: messagesDirPath, - destDirPath: pathJoin( - getThemeTypeDirPath({ themeType: "account" }), - "messages" - ) + srcDirPath: pathJoin( + getThisCodebaseRootDirPath(), + WELL_KNOWN_DIRECTORY_BASE_NAME.ACCOUNT_V1 + ), + destDirPath: pathJoin(resourcesDirPath, "theme", "account-v1", "account") }); } @@ -349,7 +329,7 @@ export async function generateResourcesForMainTheme(params: { if (buildContext.implementedThemeTypes.account.isImplemented) { metaInfKeycloakThemes.themes.push({ - name: ACCOUNT_V1_THEME_NAME, + name: "account-v1", types: ["account"] }); } diff --git a/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts b/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts index 707f6e5a..f15b078b 100644 --- a/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts +++ b/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts @@ -1,5 +1,5 @@ import type { BuildContext } from "../../shared/buildContext"; -import { BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR } from "../../shared/constants"; +import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../shared/constants"; import { assert } from "tsafe/assert"; import { posix } from "path"; @@ -50,7 +50,7 @@ export function replaceImportsInCssCode(params: { break inline_style_in_html; } - return `url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}${assetFileAbsoluteUrlPathname}")`; + return `url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}${assetFileAbsoluteUrlPathname}")`; } const assetFileRelativeUrlPathname = posix.relative( diff --git a/src/bin/keycloakify/replacers/replaceImportsInJsCode/vite.ts b/src/bin/keycloakify/replacers/replaceImportsInJsCode/vite.ts index 20048cf3..3830eb72 100644 --- a/src/bin/keycloakify/replacers/replaceImportsInJsCode/vite.ts +++ b/src/bin/keycloakify/replacers/replaceImportsInJsCode/vite.ts @@ -1,4 +1,4 @@ -import { BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR } from "../../../shared/constants"; +import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../../shared/constants"; import { assert } from "tsafe/assert"; import type { BuildContext } from "../../../shared/buildContext"; import * as nodePath from "path"; @@ -85,13 +85,13 @@ export function replaceImportsInJsCode_vite(params: { fixedJsCode = replaceAll( fixedJsCode, `"${relativePathOfAssetFile}"`, - `(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/${relativePathOfAssetFile}")` + `(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/${relativePathOfAssetFile}")` ); fixedJsCode = replaceAll( fixedJsCode, `"${buildContext.urlPathname ?? "/"}${relativePathOfAssetFile}"`, - `(window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/${relativePathOfAssetFile}")` + `(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/${relativePathOfAssetFile}")` ); }); } diff --git a/src/bin/keycloakify/replacers/replaceImportsInJsCode/webpack.ts b/src/bin/keycloakify/replacers/replaceImportsInJsCode/webpack.ts index 7c03125f..466822f0 100644 --- a/src/bin/keycloakify/replacers/replaceImportsInJsCode/webpack.ts +++ b/src/bin/keycloakify/replacers/replaceImportsInJsCode/webpack.ts @@ -1,4 +1,4 @@ -import { BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR } from "../../../shared/constants"; +import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../../shared/constants"; import { assert } from "tsafe/assert"; import type { BuildContext } from "../../../shared/buildContext"; import * as nodePath from "path"; @@ -90,7 +90,7 @@ export function replaceImportsInJsCode_webpack(params: { return "${u}"; })()] = ${ isArrowFunction ? `${e} =>` : `function(${e}) { return ` - } "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/${staticDir}${language}/"` + } "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/${staticDir}${language}/"` .replace(/\s+/g, " ") .trim(); } @@ -104,7 +104,7 @@ export function replaceImportsInJsCode_webpack(params: { `[a-zA-Z]+\\.[a-zA-Z]+\\+"${staticDir.replace(/\//g, "\\/")}`, "g" ), - `window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/${staticDir}` + `window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/${staticDir}` ); return { fixedJsCode }; diff --git a/src/bin/shared/buildContext.ts b/src/bin/shared/buildContext.ts index 4c45d449..15d9773a 100644 --- a/src/bin/shared/buildContext.ts +++ b/src/bin/shared/buildContext.ts @@ -14,8 +14,7 @@ import { assert, type Equals } from "tsafe/assert"; import * as child_process from "child_process"; import { VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES, - BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME, - LOGIN_THEME_RESOURCES_FROM_KEYCLOAK_VERSION_DEFAULT + BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME } from "./constants"; import type { KeycloakVersionRange } from "./KeycloakVersionRange"; import { exclude } from "tsafe"; @@ -33,7 +32,6 @@ export type BuildContext = { extraThemeProperties: string[] | undefined; groupId: string; artifactId: string; - loginThemeResourcesFromKeycloakVersion: string; projectDirPath: string; projectBuildDirPath: string; /** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */ @@ -85,7 +83,6 @@ export type BuildOptions = { extraThemeProperties?: string[]; artifactId?: string; groupId?: string; - loginThemeResourcesFromKeycloakVersion?: string; keycloakifyBuildDirPath?: string; kcContextExclusionsFtl?: string; startKeycloakOptions?: { @@ -357,7 +354,6 @@ export function getBuildContext(params: { extraThemeProperties: z.array(z.string()).optional(), artifactId: z.string().optional(), groupId: z.string().optional(), - loginThemeResourcesFromKeycloakVersion: z.string().optional(), keycloakifyBuildDirPath: z.string().optional(), kcContextExclusionsFtl: z.string().optional(), startKeycloakOptions: zStartKeycloakOptions.optional() @@ -545,9 +541,6 @@ export function getBuildContext(params: { process.env.KEYCLOAKIFY_ARTIFACT_ID ?? buildOptions.artifactId ?? `${themeNames[0]}-keycloak-theme`, - loginThemeResourcesFromKeycloakVersion: - buildOptions.loginThemeResourcesFromKeycloakVersion ?? - LOGIN_THEME_RESOURCES_FROM_KEYCLOAK_VERSION_DEFAULT, projectDirPath, projectBuildDirPath, keycloakifyBuildDirPath: (() => { diff --git a/src/bin/shared/constants.ts b/src/bin/shared/constants.ts index ad015654..1f3f2f84 100644 --- a/src/bin/shared/constants.ts +++ b/src/bin/shared/constants.ts @@ -1,10 +1,12 @@ -export const KEYCLOAK_RESOURCES = "keycloak-resources"; -export const RESOURCES_COMMON = "resources-common"; -export const LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1 = "21.1.2"; -export const BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR = "dist"; +export const WELL_KNOWN_DIRECTORY_BASE_NAME = { + DOT_KEYCLOAKIFY: ".keycloakify", + RESOURCES_COMMON: "resources-common", + DIST: "dist", + RESOURCES: "resources", + ACCOUNT_V1: "account-v1" +}; export const THEME_TYPES = ["login", "account"] as const; -export const ACCOUNT_V1_THEME_NAME = "account-v1"; export type ThemeType = (typeof THEME_TYPES)[number]; @@ -71,5 +73,3 @@ export type AccountThemePageId = (typeof ACCOUNT_THEME_PAGE_IDS)[number]; export const CONTAINER_NAME = "keycloak-keycloakify"; export const FALLBACK_LANGUAGE_TAG = "en"; - -export const LOGIN_THEME_RESOURCES_FROM_KEYCLOAK_VERSION_DEFAULT = "24.0.4"; diff --git a/src/bin/shared/copyKeycloakResourcesToPublic.ts b/src/bin/shared/copyKeycloakResourcesToPublic.ts index 5f4778ab..ff6b6241 100644 --- a/src/bin/shared/copyKeycloakResourcesToPublic.ts +++ b/src/bin/shared/copyKeycloakResourcesToPublic.ts @@ -1,44 +1,34 @@ -import { - downloadKeycloakStaticResources, - type BuildContextLike as BuildContextLike_downloadKeycloakStaticResources -} from "./downloadKeycloakStaticResources"; -import { join as pathJoin, relative as pathRelative } from "path"; -import { - THEME_TYPES, - KEYCLOAK_RESOURCES, - LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1 -} from "../shared/constants"; +import { join as pathJoin, dirname as pathDirname } from "path"; +import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../shared/constants"; import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion"; import { assert } from "tsafe/assert"; import * as fs from "fs"; import { rmSync } from "../tools/fs.rmSync"; import type { BuildContext } from "./buildContext"; +import { transformCodebase } from "../tools/transformCodebase"; +import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath"; -export type BuildContextLike = BuildContextLike_downloadKeycloakStaticResources & { - loginThemeResourcesFromKeycloakVersion: string; +export type BuildContextLike = { publicDirPath: string; }; assert(); -export async function copyKeycloakResourcesToPublic(params: { +export function copyKeycloakResourcesToPublic(params: { buildContext: BuildContextLike; }) { const { buildContext } = params; - const destDirPath = pathJoin(buildContext.publicDirPath, KEYCLOAK_RESOURCES); + const destDirPath = pathJoin( + buildContext.publicDirPath, + WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY + ); const keycloakifyBuildinfoFilePath = pathJoin(destDirPath, "keycloakify.buildinfo"); const keycloakifyBuildinfoRaw = JSON.stringify( { - destDirPath, - keycloakifyVersion: readThisNpmPackageVersion(), - buildContext: { - loginThemeResourcesFromKeycloakVersion: readThisNpmPackageVersion(), - cacheDirPath: pathRelative(destDirPath, buildContext.cacheDirPath), - fetchOptions: buildContext.fetchOptions - } + keycloakifyVersion: readThisNpmPackageVersion() }, null, 2 @@ -62,35 +52,33 @@ export async function copyKeycloakResourcesToPublic(params: { rmSync(destDirPath, { force: true, recursive: true }); + // NOTE: To remove in a while, remove the legacy keycloak-resources directory + rmSync(pathJoin(pathDirname(destDirPath), "keycloak-resources"), { + force: true, + recursive: true + }); + fs.mkdirSync(destDirPath, { recursive: true }); fs.writeFileSync(pathJoin(destDirPath, ".gitignore"), Buffer.from("*", "utf8")); - for (const themeType of THEME_TYPES) { - await downloadKeycloakStaticResources({ - keycloakVersion: (() => { - switch (themeType) { - case "login": - return buildContext.loginThemeResourcesFromKeycloakVersion; - case "account": - return LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1; - } - })(), - themeType, - themeDirPath: destDirPath, - buildContext - }); - } + transformCodebase({ + srcDirPath: pathJoin( + getThisCodebaseRootDirPath(), + WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES + ), + destDirPath + }); fs.writeFileSync( pathJoin(destDirPath, "README.txt"), Buffer.from( // prettier-ignore [ - "This is just a test folder that helps develop", - "the login and register page without having to run a Keycloak container\n", - "This directory will be automatically excluded from the final build." - ].join(" ") + "This directory is only used in dev mode by Keycloakify", + "It won't be included in your final build.", + "Do not modify anything in this directory.", + ].join("\n") ) ); diff --git a/src/bin/shared/downloadKeycloakDefaultTheme/downloadKeycloakDefaultTheme.ts b/src/bin/shared/downloadKeycloakDefaultTheme/downloadKeycloakDefaultTheme.ts deleted file mode 100644 index 6988f121..00000000 --- a/src/bin/shared/downloadKeycloakDefaultTheme/downloadKeycloakDefaultTheme.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { join as pathJoin, relative as pathRelative } from "path"; -import { type BuildContext } from "../buildContext"; -import { assert } from "tsafe/assert"; -import { LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1 } from "../constants"; -import { downloadAndExtractArchive } from "../../tools/downloadAndExtractArchive"; -import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath"; -import * as fsPr from "fs/promises"; - -export type BuildContextLike = { - cacheDirPath: string; - fetchOptions: BuildContext["fetchOptions"]; -}; - -assert(); - -export async function downloadKeycloakDefaultTheme(params: { - keycloakVersion: string; - buildContext: BuildContextLike; -}): Promise<{ defaultThemeDirPath: string }> { - const { keycloakVersion, buildContext } = params; - - let kcNodeModulesKeepFilePaths: Set | undefined = undefined; - let kcNodeModulesKeepFilePaths_lastAccountV1: Set | undefined = undefined; - - let areExtraAssetsFor24Copied = false; - - const { extractedDirPath } = await downloadAndExtractArchive({ - url: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`, - cacheDirPath: buildContext.cacheDirPath, - fetchOptions: buildContext.fetchOptions, - uniqueIdOfOnArchiveFile: "downloadKeycloakDefaultTheme", - onArchiveFile: async params => { - const fileRelativePath = pathRelative("theme", params.fileRelativePath); - - if (fileRelativePath.startsWith("..")) { - return; - } - - skip_keycloak_v2: { - if (!fileRelativePath.startsWith(pathJoin("keycloak.v2"))) { - break skip_keycloak_v2; - } - - return; - } - - const { readFile, writeFile } = params; - - last_account_v1_transformations: { - if (LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1 !== keycloakVersion) { - break last_account_v1_transformations; - } - - skip_web_modules: { - if ( - !fileRelativePath.startsWith( - pathJoin("keycloak", "common", "resources", "web_modules") - ) - ) { - break skip_web_modules; - } - - return; - } - - skip_lib: { - if ( - !fileRelativePath.startsWith( - pathJoin("keycloak", "common", "resources", "lib") - ) - ) { - break skip_lib; - } - - return; - } - - skip_node_modules: { - const nodeModulesRelativeDirPath = pathJoin( - "keycloak", - "common", - "resources", - "node_modules" - ); - - if (!fileRelativePath.startsWith(nodeModulesRelativeDirPath)) { - break skip_node_modules; - } - - if (kcNodeModulesKeepFilePaths_lastAccountV1 === undefined) { - kcNodeModulesKeepFilePaths_lastAccountV1 = new Set([ - pathJoin("patternfly", "dist", "css", "patternfly.min.css"), - pathJoin( - "patternfly", - "dist", - "css", - "patternfly-additions.min.css" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "OpenSans-Regular-webfont.woff2" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "OpenSans-Bold-webfont.woff2" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "OpenSans-Light-webfont.woff2" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "OpenSans-Semibold-webfont.woff2" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "PatternFlyIcons-webfont.ttf" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "PatternFlyIcons-webfont.woff" - ) - ]); - } - - const fileRelativeToNodeModulesPath = fileRelativePath.substring( - nodeModulesRelativeDirPath.length + 1 - ); - - if ( - kcNodeModulesKeepFilePaths_lastAccountV1.has( - fileRelativeToNodeModulesPath - ) - ) { - break skip_node_modules; - } - - return; - } - - patch_account_css: { - if ( - fileRelativePath !== - pathJoin("keycloak", "account", "resources", "css", "account.css") - ) { - break patch_account_css; - } - - await writeFile({ - fileRelativePath, - modifiedData: Buffer.from( - (await readFile()) - .toString("utf8") - .replace("top: -34px;", "top: -34px !important;"), - "utf8" - ) - }); - - return; - } - } - - copy_extra_assets: { - if (keycloakVersion !== "24.0.4") { - break copy_extra_assets; - } - - if (areExtraAssetsFor24Copied) { - break copy_extra_assets; - } - - const extraAssetsDirPath = pathJoin( - getThisCodebaseRootDirPath(), - "src", - "bin", - "shared", - "downloadKeycloakDefaultTheme", - "extra-assets" - ); - - await Promise.all( - ["webauthnAuthenticate.js", "passkeysConditionalAuth.js"].map( - async fileBasename => - writeFile({ - fileRelativePath: pathJoin( - "base", - "login", - "resources", - "js", - fileBasename - ), - modifiedData: await fsPr.readFile( - pathJoin(extraAssetsDirPath, fileBasename) - ) - }) - ) - ); - } - - skip_unused_resources: { - if (keycloakVersion !== "24.0.4") { - break skip_unused_resources; - } - - skip_node_modules: { - const nodeModulesRelativeDirPath = pathJoin( - "keycloak", - "common", - "resources", - "node_modules" - ); - - if (!fileRelativePath.startsWith(nodeModulesRelativeDirPath)) { - break skip_node_modules; - } - - if (kcNodeModulesKeepFilePaths === undefined) { - kcNodeModulesKeepFilePaths = new Set([ - pathJoin("@patternfly", "patternfly", "patternfly.min.css"), - pathJoin("patternfly", "dist", "css", "patternfly.min.css"), - pathJoin( - "patternfly", - "dist", - "css", - "patternfly-additions.min.css" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "OpenSans-Regular-webfont.woff2" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "OpenSans-Light-webfont.woff2" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "OpenSans-Bold-webfont.woff2" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "OpenSans-Bold-webfont.woff" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "OpenSans-Bold-webfont.ttf" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "fontawesome-webfont.woff2" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "PatternFlyIcons-webfont.ttf" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "PatternFlyIcons-webfont.woff" - ), - pathJoin( - "patternfly", - "dist", - "fonts", - "OpenSans-Semibold-webfont.woff2" - ), - pathJoin("patternfly", "dist", "img", "bg-login.jpg"), - pathJoin("jquery", "dist", "jquery.min.js") - ]); - } - - const fileRelativeToNodeModulesPath = fileRelativePath.substring( - nodeModulesRelativeDirPath.length + 1 - ); - - if (kcNodeModulesKeepFilePaths.has(fileRelativeToNodeModulesPath)) { - break skip_node_modules; - } - - return; - } - - skip_vendor: { - if ( - !fileRelativePath.startsWith( - pathJoin("keycloak", "common", "resources", "vendor") - ) - ) { - break skip_vendor; - } - - return; - } - - skip_rollup_config: { - if ( - fileRelativePath !== - pathJoin("keycloak", "common", "resources", "rollup.config.js") - ) { - break skip_rollup_config; - } - - return; - } - } - - await writeFile({ fileRelativePath }); - } - }); - - return { defaultThemeDirPath: extractedDirPath }; -} diff --git a/src/bin/shared/downloadKeycloakDefaultTheme/extra-assets/passkeysConditionalAuth.js b/src/bin/shared/downloadKeycloakDefaultTheme/extra-assets/passkeysConditionalAuth.js deleted file mode 100644 index 5fcbb4ed..00000000 --- a/src/bin/shared/downloadKeycloakDefaultTheme/extra-assets/passkeysConditionalAuth.js +++ /dev/null @@ -1,79 +0,0 @@ -import { base64url } from "rfc4648"; -import { returnSuccess, returnFailure } from "./webauthnAuthenticate.js"; - -export function initAuthenticate(input) { - // Check if WebAuthn is supported by this browser - if (!window.PublicKeyCredential) { - returnFailure(input.errmsg); - return; - } - if (input.isUserIdentified || typeof PublicKeyCredential.isConditionalMediationAvailable === "undefined") { - document.getElementById("kc-form-passkey-button").style.display = 'block'; - } else { - tryAutoFillUI(input); - } -} - -function doAuthenticate(input) { - // Check if WebAuthn is supported by this browser - if (!window.PublicKeyCredential) { - returnFailure(input.errmsg); - return; - } - - const publicKey = { - rpId : input.rpId, - challenge: base64url.parse(input.challenge, { loose: true }) - }; - - publicKey.allowCredentials = !input.isUserIdentified ? [] : getAllowCredentials(); - - if (input.createTimeout !== 0) { - publicKey.timeout = input.createTimeout * 1000; - } - - if (input.userVerification !== 'not specified') { - publicKey.userVerification = input.userVerification; - } - - return navigator.credentials.get({ - publicKey: publicKey, - ...input.additionalOptions - }); -} - -async function tryAutoFillUI(input) { - const isConditionalMediationAvailable = await PublicKeyCredential.isConditionalMediationAvailable(); - if (isConditionalMediationAvailable) { - document.getElementById("kc-form-login").style.display = "block"; - input.additionalOptions = { mediation: 'conditional'}; - try { - const result = await doAuthenticate(input); - returnSuccess(result); - } catch (error) { - returnFailure(error); - } - } else { - document.getElementById("kc-form-passkey-button").style.display = 'block'; - } -} - -function getAllowCredentials() { - const allowCredentials = []; - const authnUse = document.forms['authn_select'].authn_use_chk; - if (authnUse !== undefined) { - if (authnUse.length === undefined) { - allowCredentials.push({ - id: base64url.parse(authnUse.value, {loose: true}), - type: 'public-key', - }); - } else { - authnUse.forEach((entry) => - allowCredentials.push({ - id: base64url.parse(entry.value, {loose: true}), - type: 'public-key', - })); - } - } - return allowCredentials; -} \ No newline at end of file diff --git a/src/bin/shared/downloadKeycloakDefaultTheme/extra-assets/webauthnAuthenticate.js b/src/bin/shared/downloadKeycloakDefaultTheme/extra-assets/webauthnAuthenticate.js deleted file mode 100644 index eaa05b69..00000000 --- a/src/bin/shared/downloadKeycloakDefaultTheme/extra-assets/webauthnAuthenticate.js +++ /dev/null @@ -1,82 +0,0 @@ -import { base64url } from "rfc4648"; - -export async function authenticateByWebAuthn(input) { - if (!input.isUserIdentified) { - try { - const result = await doAuthenticate([], input.challenge, input.userVerification, input.rpId, input.createTimeout, input.errmsg); - returnSuccess(result); - } catch (error) { - returnFailure(error); - } - return; - } - checkAllowCredentials(input.challenge, input.userVerification, input.rpId, input.createTimeout, input.errmsg); -} - -async function checkAllowCredentials(challenge, userVerification, rpId, createTimeout, errmsg) { - const allowCredentials = []; - const authnUse = document.forms['authn_select'].authn_use_chk; - if (authnUse !== undefined) { - if (authnUse.length === undefined) { - allowCredentials.push({ - id: base64url.parse(authnUse.value, {loose: true}), - type: 'public-key', - }); - } else { - authnUse.forEach((entry) => - allowCredentials.push({ - id: base64url.parse(entry.value, {loose: true}), - type: 'public-key', - })); - } - } - try { - const result = await doAuthenticate(allowCredentials, challenge, userVerification, rpId, createTimeout, errmsg); - returnSuccess(result); - } catch (error) { - returnFailure(error); - } -} - -function doAuthenticate(allowCredentials, challenge, userVerification, rpId, createTimeout, errmsg) { - // Check if WebAuthn is supported by this browser - if (!window.PublicKeyCredential) { - returnFailure(errmsg); - return; - } - - const publicKey = { - rpId : rpId, - challenge: base64url.parse(challenge, { loose: true }) - }; - - if (createTimeout !== 0) { - publicKey.timeout = createTimeout * 1000; - } - - if (allowCredentials.length) { - publicKey.allowCredentials = allowCredentials; - } - - if (userVerification !== 'not specified') { - publicKey.userVerification = userVerification; - } - - return navigator.credentials.get({publicKey}); -} - -export function returnSuccess(result) { - document.getElementById("clientDataJSON").value = base64url.stringify(new Uint8Array(result.response.clientDataJSON), { pad: false }); - document.getElementById("authenticatorData").value = base64url.stringify(new Uint8Array(result.response.authenticatorData), { pad: false }); - document.getElementById("signature").value = base64url.stringify(new Uint8Array(result.response.signature), { pad: false }); - document.getElementById("credentialId").value = result.id; - if (result.response.userHandle) { - document.getElementById("userHandle").value = base64url.stringify(new Uint8Array(result.response.userHandle), { pad: false }); - } - document.getElementById("webauth").submit(); -} - -export function returnFailure(err) { - document.getElementById("error").value = err; - document.getElementById("webauth").submit(); -} \ No newline at end of file diff --git a/src/bin/shared/downloadKeycloakDefaultTheme/index.ts b/src/bin/shared/downloadKeycloakDefaultTheme/index.ts deleted file mode 100644 index c8acd724..00000000 --- a/src/bin/shared/downloadKeycloakDefaultTheme/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./downloadKeycloakDefaultTheme"; diff --git a/src/bin/shared/downloadKeycloakStaticResources.ts b/src/bin/shared/downloadKeycloakStaticResources.ts deleted file mode 100644 index a66b6272..00000000 --- a/src/bin/shared/downloadKeycloakStaticResources.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { transformCodebase } from "../tools/transformCodebase"; -import { join as pathJoin } from "path"; -import { - downloadKeycloakDefaultTheme, - type BuildContextLike as BuildContextLike_downloadKeycloakDefaultTheme -} from "./downloadKeycloakDefaultTheme"; -import { RESOURCES_COMMON, type ThemeType } from "./constants"; -import type { BuildContext } from "./buildContext"; -import { assert } from "tsafe/assert"; -import { existsAsync } from "../tools/fs.existsAsync"; - -export type BuildContextLike = BuildContextLike_downloadKeycloakDefaultTheme & {}; - -assert(); - -export async function downloadKeycloakStaticResources(params: { - themeType: ThemeType; - themeDirPath: string; - keycloakVersion: string; - buildContext: BuildContextLike; -}) { - const { themeType, themeDirPath, keycloakVersion, buildContext } = params; - - const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({ - keycloakVersion, - buildContext - }); - - const resourcesDirPath = pathJoin(themeDirPath, themeType, "resources"); - - repatriate_base_resources: { - const srcDirPath = pathJoin(defaultThemeDirPath, "base", themeType, "resources"); - - if (!(await existsAsync(srcDirPath))) { - break repatriate_base_resources; - } - - transformCodebase({ - srcDirPath, - destDirPath: resourcesDirPath - }); - } - - transformCodebase({ - srcDirPath: pathJoin(defaultThemeDirPath, "keycloak", themeType, "resources"), - destDirPath: resourcesDirPath - }); - - transformCodebase({ - srcDirPath: pathJoin(defaultThemeDirPath, "keycloak", "common", "resources"), - destDirPath: pathJoin(resourcesDirPath, RESOURCES_COMMON) - }); -} diff --git a/src/login/KcContext/kcContextMocks.ts b/src/login/KcContext/kcContextMocks.ts index 99343466..52122cf8 100644 --- a/src/login/KcContext/kcContextMocks.ts +++ b/src/login/KcContext/kcContextMocks.ts @@ -1,8 +1,7 @@ import "keycloakify/tools/Object.fromEntries"; import type { KcContext, Attribute } from "./KcContext"; import { - RESOURCES_COMMON, - KEYCLOAK_RESOURCES, + WELL_KNOWN_DIRECTORY_BASE_NAME, type LoginThemePageId } from "keycloakify/bin/shared/constants"; import { id } from "tsafe/id"; @@ -76,7 +75,7 @@ const attributesByName = Object.fromEntries( ]).map(attribute => [attribute.name, attribute]) ); -const resourcesPath = `${BASE_URL}${KEYCLOAK_RESOURCES}/login/resources`; +const resourcesPath = `${BASE_URL}${WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY}/login`; export const kcContextCommonMock: KcContext.Common = { themeVersion: "0.0.0", @@ -86,7 +85,7 @@ export const kcContextCommonMock: KcContext.Common = { url: { loginAction: "#", resourcesPath, - resourcesCommonPath: `${resourcesPath}/${RESOURCES_COMMON}`, + resourcesCommonPath: `${resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON}`, loginRestartFlowUrl: "#", loginUrl: "#", ssoLoginInOtherTabsUrl: "#" diff --git a/src/vite-plugin/vite-plugin.ts b/src/vite-plugin/vite-plugin.ts index 3c5214b2..d773be8b 100644 --- a/src/vite-plugin/vite-plugin.ts +++ b/src/vite-plugin/vite-plugin.ts @@ -1,8 +1,7 @@ import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path"; import type { Plugin } from "vite"; import { - BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR, - KEYCLOAK_RESOURCES, + WELL_KNOWN_DIRECTORY_BASE_NAME, VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES } from "../bin/shared/constants"; import { id } from "tsafe/id"; @@ -128,14 +127,8 @@ export function keycloakify(params: keycloakify.Params) { } }); - await Promise.all([ - copyKeycloakResourcesToPublic({ - buildContext - }), - generateKcGenTs({ - buildContext - }) - ]); + copyKeycloakResourcesToPublic({ buildContext }), + await generateKcGenTs({ buildContext }); }, transform: (code, id) => { assert(command !== undefined); @@ -174,7 +167,7 @@ export function keycloakify(params: keycloakify.Params) { `(`, `(window.kcContext === undefined || import.meta.env.MODE === "development")?`, `"${urlPathname ?? "/"}":`, - `(window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/")`, + `(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/")`, `)` ].join("") ); @@ -207,10 +200,13 @@ export function keycloakify(params: keycloakify.Params) { assert(buildDirPath !== undefined); - await rm(pathJoin(buildDirPath, KEYCLOAK_RESOURCES), { - recursive: true, - force: true - }); + await rm( + pathJoin(buildDirPath, WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY), + { + recursive: true, + force: true + } + ); } } satisfies Plugin; diff --git a/test/bin/replacers.spec.ts b/test/bin/replacers.spec.ts index 7732074e..cb36d686 100644 --- a/test/bin/replacers.spec.ts +++ b/test/bin/replacers.spec.ts @@ -2,7 +2,7 @@ import { replaceImportsInJsCode_vite } from "keycloakify/bin/keycloakify/replace import { replaceImportsInJsCode_webpack } from "keycloakify/bin/keycloakify/replacers/replaceImportsInJsCode/webpack"; import { replaceImportsInCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInCssCode"; import { expect, it, describe } from "vitest"; -import { BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR } from "keycloakify/bin/shared/constants"; +import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "keycloakify/bin/shared/constants"; describe("js replacer - vite", () => { it("replaceImportsInJsCode_vite - 1", () => { @@ -87,13 +87,13 @@ describe("js replacer - vite", () => { }); const fixedJsCodeExpected = ` - S=(window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/keycloakify-logo-mqjydaoZ.png"),H=(()=>{ + S=(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/keycloakify-logo-mqjydaoZ.png"),H=(()=>{ function __vite__mapDeps(indexes) { if (!__vite__mapDeps.viteFileDeps) { __vite__mapDeps.viteFileDeps = [ - (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/Login-dJpPRzM4.js"), - (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/index-XwzrZ5Gu.js") + (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/Login-dJpPRzM4.js"), + (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/index-XwzrZ5Gu.js") ] } return indexes.map((i) => __vite__mapDeps.viteFileDeps[i]) @@ -146,13 +146,13 @@ describe("js replacer - vite", () => { }); const fixedJsCodeExpected = ` - S=(window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/foo/bar/keycloakify-logo-mqjydaoZ.png"),H=(()=>{ + S=(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/foo/bar/keycloakify-logo-mqjydaoZ.png"),H=(()=>{ function __vite__mapDeps(indexes) { if (!__vite__mapDeps.viteFileDeps) { __vite__mapDeps.viteFileDeps = [ - (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/foo/bar/Login-dJpPRzM4.js"), - (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/foo/bar/index-XwzrZ5Gu.js") + (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/foo/bar/Login-dJpPRzM4.js"), + (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/foo/bar/index-XwzrZ5Gu.js") ] } return indexes.map((i) => __vite__mapDeps.viteFileDeps[i]) @@ -205,13 +205,13 @@ describe("js replacer - vite", () => { }); const fixedJsCodeExpected = ` - S=(window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/keycloakify-logo-mqjydaoZ.png"),H=(()=>{ + S=(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/keycloakify-logo-mqjydaoZ.png"),H=(()=>{ function __vite__mapDeps(indexes) { if (!__vite__mapDeps.viteFileDeps) { __vite__mapDeps.viteFileDeps = [ - (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/Login-dJpPRzM4.js"), - (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/index-XwzrZ5Gu.js") + (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/Login-dJpPRzM4.js"), + (window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/index-XwzrZ5Gu.js") ] } return indexes.map((i) => __vite__mapDeps.viteFileDeps[i]) @@ -267,13 +267,13 @@ describe("js replacer - webpack", () => { const fixedJsCodeExpected = ` function f() { - return window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/js/" + ({}[e] || e) + "." + { + return window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/js/" + ({}[e] || e) + "." + { 3: "0664cdc0" }[e] + ".chunk.js" } function sameAsF() { - return window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/js/" + ({}[e] || e) + "." + { + return window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/js/" + ({}[e] || e) + "." + { 3: "0664cdc0" }[e] + ".chunk.js" } @@ -288,7 +288,7 @@ describe("js replacer - webpack", () => { } return "u"; })()] = function(e) { - return "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/js/" + e + "." + { + return "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/js/" + e + "." + { 147: "6c5cee76", 787: "8da10fcf", 922: "be170a73" @@ -305,7 +305,7 @@ describe("js replacer - webpack", () => { } return "miniCssF"; })()] = function(e) { - return "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/css/" + e + "." + { + return "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/css/" + e + "." + { 164:"dcfd7749", 908:"67c9ed2c" } [e] + ".chunk.css" @@ -320,7 +320,7 @@ describe("js replacer - webpack", () => { }); } return "u"; - })()] = e => "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js" + })()] = e => "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js" t[(function(){ var pd = Object.getOwnPropertyDescriptor(t, "p"); @@ -331,7 +331,7 @@ describe("js replacer - webpack", () => { }); } return "miniCssF"; - })()] = e => "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css" + })()] = e => "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css" `; expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true); @@ -495,15 +495,15 @@ describe("css replacer", () => { const fixedCssCodeExpected = ` .my-div { - background: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/background.png") no-repeat center center; + background: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/background.png") no-repeat center center; } .my-div2 { - background: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/background.png") repeat center center; + background: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/background.png") repeat center center; } .my-div3 { - background-image: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/media/something.svg"); + background-image: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/media/something.svg"); } `; @@ -533,15 +533,15 @@ describe("css replacer", () => { const fixedCssCodeExpected = ` .my-div { - background: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/background.png") no-repeat center center; + background: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/background.png") no-repeat center center; } .my-div2 { - background: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/background.png") repeat center center; + background: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/background.png") repeat center center; } .my-div3 { - background-image: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/media/something.svg"); + background-image: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/media/something.svg"); } `;