diff --git a/src/bin/keycloakify/buildJars/buildJar.ts b/src/bin/keycloakify/buildJars/buildJar.ts index eb57adea..f56a40bc 100644 --- a/src/bin/keycloakify/buildJars/buildJar.ts +++ b/src/bin/keycloakify/buildJars/buildJar.ts @@ -16,7 +16,7 @@ import { readFileSync } from "fs"; import { isInside } from "../../tools/isInside"; import child_process from "child_process"; import { rmSync } from "../../tools/fs.rmSync"; -import { getMetaInfKeycloakThemesJsonFilePath } from "../../shared/metaInfKeycloakThemes"; +import { writeMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes"; export type BuildContextLike = BuildContextLike_generatePom & { keycloakifyBuildDirPath: string; @@ -50,9 +50,16 @@ export async function buildJar(params: { rmSync(keycloakifyBuildTmpDirPath, { recursive: true, force: true }); + const tmpResourcesDirPath = pathJoin( + keycloakifyBuildTmpDirPath, + "src", + "main", + "resources" + ); + transformCodebase({ srcDirPath: resourcesDirPath, - destDirPath: pathJoin(keycloakifyBuildTmpDirPath, "src", "main", "resources"), + destDirPath: tmpResourcesDirPath, transformSourceCode: keycloakAccountV1Version !== null ? undefined @@ -71,31 +78,6 @@ export async function buildJar(params: { return undefined; } - if ( - fileRelativePath === - getMetaInfKeycloakThemesJsonFilePath({ - resourcesDirPath: "." - }) - ) { - const keycloakThemesJsonParsed = JSON.parse( - sourceCode.toString("utf8") - ) as { - themes: { name: string; types: string[] }[]; - }; - - keycloakThemesJsonParsed.themes = - keycloakThemesJsonParsed.themes.filter( - ({ name }) => name !== accountV1ThemeName - ); - - return { - modifiedSourceCode: Buffer.from( - JSON.stringify(keycloakThemesJsonParsed, null, 2), - "utf8" - ) - }; - } - for (const themeName of buildContext.themeNames) { if ( fileRelativePath === @@ -123,6 +105,21 @@ export async function buildJar(params: { } }); + if (keycloakAccountV1Version === null) { + writeMetaInfKeycloakThemes({ + resourcesDirPath: tmpResourcesDirPath, + getNewMetaInfKeycloakTheme: ({ metaInfKeycloakTheme }) => { + assert(metaInfKeycloakTheme !== undefined); + + metaInfKeycloakTheme.themes = metaInfKeycloakTheme.themes.filter( + ({ name }) => name !== accountV1ThemeName + ); + + return metaInfKeycloakTheme; + } + }); + } + route_legacy_pages: { // NOTE: If there's no account theme there is no special target for keycloak 24 and up so we create // the pages anyway. If there is an account pages, since we know that account-v1 is only support keycloak diff --git a/src/bin/keycloakify/buildJars/buildJars.ts b/src/bin/keycloakify/buildJars/buildJars.ts index 6c04b72a..7ac24d0e 100644 --- a/src/bin/keycloakify/buildJars/buildJars.ts +++ b/src/bin/keycloakify/buildJars/buildJars.ts @@ -8,10 +8,10 @@ import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar"; import { buildJar, BuildContextLike as BuildContextLike_buildJar } from "./buildJar"; import type { BuildContext } from "../../shared/buildContext"; import { getJarFileBasename } from "../../shared/getJarFileBasename"; -import { readMetaInfKeycloakThemes_fromResourcesDirPath } from "../../shared/metaInfKeycloakThemes"; -import { accountV1ThemeName } from "../../shared/constants"; +import { getImplementedThemeTypes } from "../../shared/getImplementedThemeTypes"; export type BuildContextLike = BuildContextLike_buildJar & { + projectDirPath: string; keycloakifyBuildDirPath: string; }; @@ -24,9 +24,9 @@ export async function buildJars(params: { }): Promise { const { onlyBuildJarFileBasename, resourcesDirPath, buildContext } = params; - const doesImplementAccountTheme = readMetaInfKeycloakThemes_fromResourcesDirPath({ - resourcesDirPath - }).themes.some(({ name }) => name === accountV1ThemeName); + const doesImplementAccountTheme = getImplementedThemeTypes({ + projectDirPath: buildContext.projectDirPath + }).implementedThemeTypes.account; await Promise.all( keycloakAccountV1Versions diff --git a/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts b/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts index 0be2dd2f..654fe288 100644 --- a/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts +++ b/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.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, resolve as pathResolve, relative as pathRelative } from "path"; import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode"; import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode"; import { @@ -16,7 +16,6 @@ import { loginThemePageIds, accountThemePageIds } from "../../shared/constants"; -import { isInside } from "../../tools/isInside"; import type { BuildContext } from "../../shared/buildContext"; import { assert, type Equals } from "tsafe/assert"; import { @@ -39,6 +38,7 @@ import { } from "../../shared/metaInfKeycloakThemes"; import { objectEntries } from "tsafe/objectEntries"; import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile"; +import { getImplementedThemeTypes } from "../../shared/getImplementedThemeTypes"; export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode & BuildContextLike_downloadKeycloakStaticResources & @@ -63,6 +63,10 @@ export async function generateResourcesForMainTheme(params: { projectDirPath: buildContext.projectDirPath }); + const { implementedThemeTypes } = getImplementedThemeTypes({ + projectDirPath: buildContext.projectDirPath + }); + const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => { const { themeType } = params; return pathJoin(resourcesDirPath, "theme", themeName, themeType); @@ -70,19 +74,11 @@ export async function generateResourcesForMainTheme(params: { const cssGlobalsToDefine: Record = {}; - const implementedThemeTypes: Record = { - login: false, - account: false, - email: false - }; - for (const themeType of ["login", "account"] as const) { - if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) { + if (!implementedThemeTypes[themeType]) { continue; } - implementedThemeTypes[themeType] = true; - const themeTypeDirPath = getThemeTypeDirPath({ themeType }); apply_replacers_and_move_to_theme_resources: { @@ -112,25 +108,32 @@ export async function generateResourcesForMainTheme(params: { break apply_replacers_and_move_to_theme_resources; } + { + const dirPath = pathJoin( + buildContext.projectBuildDirPath, + keycloak_resources + ); + + if (fs.existsSync(dirPath)) { + assert(buildContext.bundler === "webpack"); + + throw new Error( + [ + `Keycloakify build error: The ${keycloak_resources} 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`, + `For example: \`"build": "... && rimraf ${pathRelative(buildContext.projectDirPath, dirPath)}"\`` + ].join(" ") + ); + } + } + transformCodebase({ srcDirPath: buildContext.projectBuildDirPath, destDirPath, transformSourceCode: ({ filePath, sourceCode }) => { - //NOTE: Prevent cycles, excludes the folder we generated for debug in public/ - // This should not happen if users follow the new instruction setup but we keep it for retrocompatibility. - if ( - isInside({ - dirPath: pathJoin( - buildContext.projectBuildDirPath, - keycloak_resources - ), - filePath - }) - ) { - return undefined; - } - - if (/\.css?$/i.test(filePath)) { + if (filePath.endsWith(".css")) { const { cssGlobalsToDefine: cssGlobalsToDefineForThisFile, fixedCssCode @@ -149,7 +152,7 @@ export async function generateResourcesForMainTheme(params: { }; } - if (/\.js?$/i.test(filePath)) { + if (filePath.endsWith(".js")) { const { fixedJsCode } = replaceImportsInJsCode({ jsCode: sourceCode.toString("utf8"), buildContext @@ -262,13 +265,11 @@ export async function generateResourcesForMainTheme(params: { } email: { - const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email"); - - if (!fs.existsSync(emailThemeSrcDirPath)) { + if (!implementedThemeTypes.email) { break email; } - implementedThemeTypes.email = true; + const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email"); transformCodebase({ srcDirPath: emailThemeSrcDirPath, @@ -302,7 +303,7 @@ export async function generateResourcesForMainTheme(params: { writeMetaInfKeycloakThemes({ resourcesDirPath, - metaInfKeycloakThemes + getNewMetaInfKeycloakTheme: () => metaInfKeycloakThemes }); } } diff --git a/src/bin/keycloakify/generateResources/generateResourcesForThemeVariant.ts b/src/bin/keycloakify/generateResources/generateResourcesForThemeVariant.ts index 70e0e2d2..0aab4fe7 100644 --- a/src/bin/keycloakify/generateResources/generateResourcesForThemeVariant.ts +++ b/src/bin/keycloakify/generateResources/generateResourcesForThemeVariant.ts @@ -1,10 +1,7 @@ import { join as pathJoin, extname as pathExtname, sep as pathSep } from "path"; import { transformCodebase } from "../../tools/transformCodebase"; import type { BuildContext } from "../../shared/buildContext"; -import { - readMetaInfKeycloakThemes_fromResourcesDirPath, - writeMetaInfKeycloakThemes -} from "../../shared/metaInfKeycloakThemes"; +import { writeMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes"; import { assert } from "tsafe/assert"; export type BuildContextLike = { @@ -34,8 +31,8 @@ export function generateResourcesForThemeVariant(params: { Buffer.from(sourceCode) .toString("utf-8") .replace( - `out["themeName"] = "${themeName}";`, - `out["themeName"] = "${themeVariantName}";` + `kcContext.themeName = "${themeName}";`, + `kcContext.themeName = "${themeVariantName}";` ), "utf8" ); @@ -49,26 +46,25 @@ export function generateResourcesForThemeVariant(params: { } }); - { - const updatedMetaInfKeycloakThemes = - readMetaInfKeycloakThemes_fromResourcesDirPath({ - resourcesDirPath + writeMetaInfKeycloakThemes({ + resourcesDirPath, + getNewMetaInfKeycloakTheme: ({ metaInfKeycloakTheme }) => { + assert(metaInfKeycloakTheme !== undefined); + + const newMetaInfKeycloakTheme = metaInfKeycloakTheme; + + newMetaInfKeycloakTheme.themes.push({ + name: themeVariantName, + types: (() => { + const theme = newMetaInfKeycloakTheme.themes.find( + ({ name }) => name === themeName + ); + assert(theme !== undefined); + return theme.types; + })() }); - updatedMetaInfKeycloakThemes.themes.push({ - name: themeVariantName, - types: (() => { - const theme = updatedMetaInfKeycloakThemes.themes.find( - ({ name }) => name === themeName - ); - assert(theme !== undefined); - return theme.types; - })() - }); - - writeMetaInfKeycloakThemes({ - resourcesDirPath, - metaInfKeycloakThemes: updatedMetaInfKeycloakThemes - }); - } + return newMetaInfKeycloakTheme; + } + }); } diff --git a/src/bin/shared/getImplementedThemeTypes.ts b/src/bin/shared/getImplementedThemeTypes.ts new file mode 100644 index 00000000..14af6de1 --- /dev/null +++ b/src/bin/shared/getImplementedThemeTypes.ts @@ -0,0 +1,23 @@ +import { join as pathJoin } from "path"; +import { objectFromEntries } from "tsafe/objectFromEntries"; +import * as fs from "fs"; +import { type ThemeType } from "./constants"; +import { getThemeSrcDirPath } from "./getThemeSrcDirPath"; + +export function getImplementedThemeTypes(params: { projectDirPath: string }) { + const { projectDirPath } = params; + + const { themeSrcDirPath } = getThemeSrcDirPath({ + projectDirPath + }); + + const implementedThemeTypes: Readonly> = + objectFromEntries( + (["login", "account", "email"] as const).map(themeType => [ + themeType, + fs.existsSync(pathJoin(themeSrcDirPath, themeType)) + ]) + ); + + return { implementedThemeTypes }; +} diff --git a/src/bin/shared/getThemeSrcDirPath.ts b/src/bin/shared/getThemeSrcDirPath.ts index a692bb60..0e415619 100644 --- a/src/bin/shared/getThemeSrcDirPath.ts +++ b/src/bin/shared/getThemeSrcDirPath.ts @@ -3,48 +3,60 @@ import { exclude } from "tsafe"; import { crawl } from "../tools/crawl"; import { join as pathJoin } from "path"; import { themeTypes } from "./constants"; +import chalk from "chalk"; -const themeSrcDirBasenames = ["keycloak-theme", "keycloak_theme"]; +let cache: { projectDirPath: string; themeSrcDirPath: string } | undefined = undefined; /** Can't catch error, if the directory isn't found, this function will just exit the process with an error message. */ export function getThemeSrcDirPath(params: { projectDirPath: string }) { const { projectDirPath } = params; - const srcDirPath = pathJoin(projectDirPath, "src"); - - const themeSrcDirPath: string | undefined = crawl({ - dirPath: srcDirPath, - returnedPathsType: "relative to dirPath" - }) - .map(fileRelativePath => { - for (const themeSrcDirBasename of themeSrcDirBasenames) { - const split = fileRelativePath.split(themeSrcDirBasename); - if (split.length === 2) { - return pathJoin(srcDirPath, split[0] + themeSrcDirBasename); - } - } - return undefined; - }) - .filter(exclude(undefined))[0]; - - if (themeSrcDirPath !== undefined) { + if (cache !== undefined && cache.projectDirPath === projectDirPath) { + const { themeSrcDirPath } = cache; return { themeSrcDirPath }; } - for (const themeType of [...themeTypes, "email"]) { - if (!fs.existsSync(pathJoin(srcDirPath, themeType))) { - continue; + cache = undefined; + + const { themeSrcDirPath } = (() => { + const srcDirPath = pathJoin(projectDirPath, "src"); + + const themeSrcDirPath: string | undefined = crawl({ + dirPath: srcDirPath, + returnedPathsType: "relative to dirPath" + }) + .map(fileRelativePath => { + for (const themeSrcDirBasename of themeSrcDirBasenames) { + const split = fileRelativePath.split(themeSrcDirBasename); + if (split.length === 2) { + return pathJoin(srcDirPath, split[0] + themeSrcDirBasename); + } + } + return undefined; + }) + .filter(exclude(undefined))[0]; + + if (themeSrcDirPath !== undefined) { + return { themeSrcDirPath }; } - return { themeSrcDirPath: srcDirPath }; - } - console.error( - [ - "Can't locate your theme source directory. It should be either: ", - "src/ or src/keycloak-theme or src/keycloak_theme.", - "Example in the starter: https://github.com/keycloakify/keycloakify-starter/tree/main/src/keycloak-theme" - ].join("\n") - ); + for (const themeType of [...themeTypes, "email"]) { + if (!fs.existsSync(pathJoin(srcDirPath, themeType))) { + continue; + } + return { themeSrcDirPath: srcDirPath }; + } - process.exit(-1); + console.log( + chalk.red("Can't locate your theme source directory. It should be either: ") + ); + + process.exit(-1); + })(); + + cache = { projectDirPath, themeSrcDirPath }; + + return { themeSrcDirPath }; } + +const themeSrcDirBasenames = ["keycloak-theme", "keycloak_theme"]; diff --git a/src/bin/shared/metaInfKeycloakThemes.ts b/src/bin/shared/metaInfKeycloakThemes.ts index 61b62fc3..ef55b078 100644 --- a/src/bin/shared/metaInfKeycloakThemes.ts +++ b/src/bin/shared/metaInfKeycloakThemes.ts @@ -1,84 +1,40 @@ import { join as pathJoin, dirname as pathDirname } from "path"; import type { ThemeType } from "./constants"; import * as fs from "fs"; -import { assert } from "tsafe/assert"; -import { extractArchive } from "../tools/extractArchive"; export type MetaInfKeycloakTheme = { themes: { name: string; types: (ThemeType | "email")[] }[]; }; -export function getMetaInfKeycloakThemesJsonFilePath(params: { - resourcesDirPath: string; -}) { - const { resourcesDirPath } = params; - - return pathJoin( - resourcesDirPath === "." ? "" : resourcesDirPath, - "META-INF", - "keycloak-themes.json" - ); -} - -export function readMetaInfKeycloakThemes_fromResourcesDirPath(params: { - resourcesDirPath: string; -}) { - const { resourcesDirPath } = params; - - return JSON.parse( - fs - .readFileSync( - getMetaInfKeycloakThemesJsonFilePath({ - resourcesDirPath - }) - ) - .toString("utf8") - ) as MetaInfKeycloakTheme; -} - -export async function readMetaInfKeycloakThemes_fromJar(params: { - jarFilePath: string; -}): Promise { - const { jarFilePath } = params; - let metaInfKeycloakThemes: MetaInfKeycloakTheme | undefined = undefined; - - await extractArchive({ - archiveFilePath: jarFilePath, - onArchiveFile: async ({ relativeFilePathInArchive, readFile, earlyExit }) => { - if ( - relativeFilePathInArchive === - getMetaInfKeycloakThemesJsonFilePath({ resourcesDirPath: "." }) - ) { - metaInfKeycloakThemes = JSON.parse((await readFile()).toString("utf8")); - earlyExit(); - } - } - }); - - assert(metaInfKeycloakThemes !== undefined); - - return metaInfKeycloakThemes; -} - export function writeMetaInfKeycloakThemes(params: { resourcesDirPath: string; - metaInfKeycloakThemes: MetaInfKeycloakTheme; + getNewMetaInfKeycloakTheme: (params: { + metaInfKeycloakTheme: MetaInfKeycloakTheme | undefined; + }) => MetaInfKeycloakTheme; }) { - const { resourcesDirPath, metaInfKeycloakThemes } = params; + const { resourcesDirPath, getNewMetaInfKeycloakTheme } = params; - const metaInfKeycloakThemesJsonPath = getMetaInfKeycloakThemesJsonFilePath({ - resourcesDirPath + const filePath = pathJoin(resourcesDirPath, "META-INF", "keycloak-themes.json"); + + const currentMetaInfKeycloakTheme = !fs.existsSync(filePath) + ? undefined + : (JSON.parse( + fs.readFileSync(filePath).toString("utf8") + ) as MetaInfKeycloakTheme); + + const newMetaInfKeycloakThemes = getNewMetaInfKeycloakTheme({ + metaInfKeycloakTheme: currentMetaInfKeycloakTheme }); { - const dirPath = pathDirname(metaInfKeycloakThemesJsonPath); + const dirPath = pathDirname(filePath); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } fs.writeFileSync( - metaInfKeycloakThemesJsonPath, - Buffer.from(JSON.stringify(metaInfKeycloakThemes, null, 2), "utf8") + filePath, + Buffer.from(JSON.stringify(newMetaInfKeycloakThemes, null, 2), "utf8") ); } diff --git a/src/bin/start-keycloak/start-keycloak.ts b/src/bin/start-keycloak/start-keycloak.ts index 43029820..74b98302 100644 --- a/src/bin/start-keycloak/start-keycloak.ts +++ b/src/bin/start-keycloak/start-keycloak.ts @@ -2,7 +2,7 @@ import { getBuildContext } from "../shared/buildContext"; import { exclude } from "tsafe/exclude"; import type { CliCommandOptions as CliCommandOptions_common } from "../main"; import { promptKeycloakVersion } from "../shared/promptKeycloakVersion"; -import { readMetaInfKeycloakThemes_fromJar } from "../shared/metaInfKeycloakThemes"; +import { getImplementedThemeTypes } from "../shared/getImplementedThemeTypes"; import { accountV1ThemeName, containerName } from "../shared/constants"; import { SemVer } from "../tools/SemVer"; import type { KeycloakVersionRange } from "../shared/KeycloakVersionRange"; @@ -120,37 +120,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions }) } } - const { doesImplementAccountTheme } = await (async () => { - const latestJarFilePath = fs - .readdirSync(buildContext.keycloakifyBuildDirPath) - .filter(fileBasename => fileBasename.endsWith(".jar")) - .map(fileBasename => - pathJoin(buildContext.keycloakifyBuildDirPath, fileBasename) - ) - .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0]; - - assert(latestJarFilePath !== undefined); - - const metaInfKeycloakThemes = await readMetaInfKeycloakThemes_fromJar({ - jarFilePath: latestJarFilePath - }); - - const mainThemeEntry = metaInfKeycloakThemes.themes.find( - ({ name }) => name === buildContext.themeNames[0] - ); - - assert(mainThemeEntry !== undefined); - - const doesImplementAccountTheme = mainThemeEntry.types.includes("account"); - - return { doesImplementAccountTheme }; - })(); + const doesImplementAccountTheme = getImplementedThemeTypes({ + projectDirPath: buildContext.projectDirPath + }).implementedThemeTypes.account; const { keycloakVersion, keycloakMajorNumber: keycloakMajorVersionNumber } = - await (async function getKeycloakMajor(): Promise<{ - keycloakVersion: string; - keycloakMajorNumber: number; - }> { + await (async () => { if (cliCommandOptions.keycloakVersion !== undefined) { return { keycloakVersion: cliCommandOptions.keycloakVersion, @@ -173,16 +148,6 @@ export async function command(params: { cliCommandOptions: CliCommandOptions }) const keycloakMajorNumber = SemVer.parse(keycloakVersion).major; - if (doesImplementAccountTheme && keycloakMajorNumber === 22) { - console.log( - [ - "Unfortunately, Keycloakify themes that implements an account theme do not work on Keycloak 22", - "Please select any other Keycloak version" - ].join(" ") - ); - return getKeycloakMajor(); - } - return { keycloakVersion, keycloakMajorNumber }; })();