diff --git a/src/bin/keycloakify/buildJars/buildJar.ts b/src/bin/keycloakify/buildJars/buildJar.ts new file mode 100644 index 00000000..89c5987e --- /dev/null +++ b/src/bin/keycloakify/buildJars/buildJar.ts @@ -0,0 +1,15 @@ +import { assert, type Equals } from "tsafe/assert"; +import { exclude } from "tsafe/exclude"; +import type { KeycloakAccountV1Versions, KeycloakThemeAdditionalInfoExtensionVersions } from "./extensionVersions"; + +export async function buildJar(params: { + jarFileBasename: string; + keycloakAccountV1Version: KeycloakAccountV1Versions; + keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersions; + buildOptions: { + keycloakifyBuildDirPath: string; + }; +}): Promise { + child_process.execSync("mvn clean install", { "cwd": buildOptions.keycloakifyBuildDirPath }); + // TODO: Implement +} diff --git a/src/bin/keycloakify/buildJars/buildJars.ts b/src/bin/keycloakify/buildJars/buildJars.ts new file mode 100644 index 00000000..aadf9288 --- /dev/null +++ b/src/bin/keycloakify/buildJars/buildJars.ts @@ -0,0 +1,57 @@ +import { assert } from "tsafe/assert"; +import { exclude } from "tsafe/exclude"; +import { keycloakAccountV1Versions, keycloakThemeAdditionalInfoExtensionVersions } from "./extensionVersions"; +import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar"; +import { buildJar } from "./buildJar"; + +export async function buildJars(params: { + doImplementAccountTheme: boolean; + buildOptions: { + keycloakifyBuildDirPath: string; + }; +}): Promise<{ lastJarFileBasename: string }> { + const { doImplementAccountTheme, buildOptions } = params; + + let lastJarFileBasename: string | undefined = undefined; + + await Promise.all( + keycloakAccountV1Versions + .map(keycloakAccountV1Version => + keycloakThemeAdditionalInfoExtensionVersions + .map(keycloakThemeAdditionalInfoExtensionVersion => { + const keycloakVersionRange = getKeycloakVersionRangeForJar({ + doImplementAccountTheme, + keycloakAccountV1Version, + keycloakThemeAdditionalInfoExtensionVersion + }); + + if (keycloakVersionRange === undefined) { + return undefined; + } + + return { keycloakThemeAdditionalInfoExtensionVersion, keycloakVersionRange }; + }) + .filter(exclude(undefined)) + .map(({ keycloakThemeAdditionalInfoExtensionVersion, keycloakVersionRange }) => { + const jarFileBasename = `keycloak-theme-for-kc-${keycloakVersionRange}.jar`; + + lastJarFileBasename = jarFileBasename; + + return { keycloakThemeAdditionalInfoExtensionVersion, jarFileBasename }; + }) + .map(({ keycloakThemeAdditionalInfoExtensionVersion, jarFileBasename }) => + buildJar({ + jarFileBasename, + keycloakAccountV1Version, + keycloakThemeAdditionalInfoExtensionVersion, + buildOptions + }) + ) + ) + .flat() + ); + + assert(lastJarFileBasename !== undefined); + + return { lastJarFileBasename }; +} diff --git a/src/bin/keycloakify/buildJars/extensionVersions.ts b/src/bin/keycloakify/buildJars/extensionVersions.ts new file mode 100644 index 00000000..79fac401 --- /dev/null +++ b/src/bin/keycloakify/buildJars/extensionVersions.ts @@ -0,0 +1,7 @@ +export const keycloakAccountV1Versions = [null, "0.3", "0.4"] as const; + +export type KeycloakAccountV1Versions = (typeof keycloakAccountV1Versions)[number]; + +export const keycloakThemeAdditionalInfoExtensionVersions = [null, "0.1"] as const; + +export type KeycloakThemeAdditionalInfoExtensionVersions = (typeof keycloakThemeAdditionalInfoExtensionVersions)[number]; diff --git a/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts b/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts new file mode 100644 index 00000000..cd774fa3 --- /dev/null +++ b/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts @@ -0,0 +1,37 @@ +import { assert, type Equals } from "tsafe/assert"; +import type { KeycloakAccountV1Versions, KeycloakThemeAdditionalInfoExtensionVersions } from "./extensionVersions"; + +export function getKeycloakVersionRangeForJar(params: { + doImplementAccountTheme: boolean; + keycloakAccountV1Version: KeycloakAccountV1Versions; + keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersions; +}): string | undefined { + const { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, doImplementAccountTheme } = params; + + switch (keycloakAccountV1Version) { + case null: + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return doImplementAccountTheme ? "21-and-below" : "21-and-below"; + case "0.1": + return doImplementAccountTheme ? undefined : "22-and-above"; + } + assert>(false); + case "0.3": + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return doImplementAccountTheme ? undefined : undefined; + case "0.1": + return doImplementAccountTheme ? "23" : undefined; + } + assert>(false); + case "0.4": + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return doImplementAccountTheme ? undefined : undefined; + case "0.1": + return doImplementAccountTheme ? "24-and-above" : undefined; + } + assert>(false); + } +} diff --git a/src/bin/keycloakify/buildJars/index.ts b/src/bin/keycloakify/buildJars/index.ts new file mode 100644 index 00000000..fc6227c3 --- /dev/null +++ b/src/bin/keycloakify/buildJars/index.ts @@ -0,0 +1 @@ +export * from "./buildJars"; diff --git a/src/bin/keycloakify/buildOptions/UserProvidedBuildOptions.ts b/src/bin/keycloakify/buildOptions/UserProvidedBuildOptions.ts index ae555752..167e7d0a 100644 --- a/src/bin/keycloakify/buildOptions/UserProvidedBuildOptions.ts +++ b/src/bin/keycloakify/buildOptions/UserProvidedBuildOptions.ts @@ -4,7 +4,6 @@ export type UserProvidedBuildOptions = { extraThemeProperties?: string[]; artifactId?: string; groupId?: string; - doCreateJar?: boolean; loginThemeResourcesFromKeycloakVersion?: string; reactAppBuildDirPath?: string; keycloakifyBuildDirPath?: string; @@ -15,7 +14,6 @@ export const zUserProvidedBuildOptions = z.object({ "extraThemeProperties": z.array(z.string()).optional(), "artifactId": z.string().optional(), "groupId": z.string().optional(), - "doCreateJar": z.boolean().optional(), "loginThemeResourcesFromKeycloakVersion": z.string().optional(), "reactAppBuildDirPath": z.string().optional(), "keycloakifyBuildDirPath": z.string().optional(), diff --git a/src/bin/keycloakify/buildOptions/buildOptions.ts b/src/bin/keycloakify/buildOptions/buildOptions.ts index 68e13ffe..3e51c7f4 100644 --- a/src/bin/keycloakify/buildOptions/buildOptions.ts +++ b/src/bin/keycloakify/buildOptions/buildOptions.ts @@ -18,7 +18,6 @@ export type BuildOptions = { extraThemeProperties: string[] | undefined; groupId: string; artifactId: string; - doCreateJar: boolean; loginThemeResourcesFromKeycloakVersion: string; reactAppRootDirPath: string; reactAppBuildDirPath: string; @@ -115,7 +114,6 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption ); })(), "artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? userProvidedBuildOptions.artifactId ?? `${themeNames[0]}-keycloak-theme`, - "doCreateJar": userProvidedBuildOptions.doCreateJar ?? true, "loginThemeResourcesFromKeycloakVersion": userProvidedBuildOptions.loginThemeResourcesFromKeycloakVersion ?? "24.0.4", reactAppRootDirPath, reactAppBuildDirPath, diff --git a/src/bin/keycloakify/generateStartKeycloakTestingContainer.ts b/src/bin/keycloakify/generateStartKeycloakTestingContainer.ts index 045ce29a..90f73c08 100644 --- a/src/bin/keycloakify/generateStartKeycloakTestingContainer.ts +++ b/src/bin/keycloakify/generateStartKeycloakTestingContainer.ts @@ -17,10 +17,11 @@ export type BuildOptionsLike = { generateStartKeycloakTestingContainer.basename = "start_keycloak_testing_container.sh"; const containerName = "keycloak-testing-container"; +const keycloakVersion = "24.0.4"; /** Files for being able to run a hot reload keycloak container */ -export function generateStartKeycloakTestingContainer(params: { jarFilePath: string; keycloakVersion: string; buildOptions: BuildOptionsLike }) { - const { jarFilePath, keycloakVersion, buildOptions } = params; +export function generateStartKeycloakTestingContainer(params: { jarFilePath: string; buildOptions: BuildOptionsLike }) { + const { jarFilePath, buildOptions } = params; const themeRelativeDirPath = pathJoin("src", "main", "resources", "theme"); const themeDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, themeRelativeDirPath); diff --git a/src/bin/keycloakify/generateTheme/generateTheme.ts b/src/bin/keycloakify/generateTheme/generateTheme.ts index db6f9532..9ad5ff35 100644 --- a/src/bin/keycloakify/generateTheme/generateTheme.ts +++ b/src/bin/keycloakify/generateTheme/generateTheme.ts @@ -31,7 +31,6 @@ export type BuildOptionsLike = { cacheDirPath: string; assetsDirPath: string; urlPathname: string | undefined; - themeNames: string[]; npmWorkspaceRootDirPath: string; }; @@ -43,7 +42,7 @@ export async function generateTheme(params: { keycloakifySrcDirPath: string; buildOptions: BuildOptionsLike; keycloakifyVersion: string; -}): Promise { +}): Promise<{ implementedThemeTypes: Record }> { const { themeName, themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion } = params; const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => { @@ -231,14 +230,12 @@ export async function generateTheme(params: { const parsedKeycloakThemeJson: { themes: { name: string; types: string[] }[] } = { "themes": [] }; - buildOptions.themeNames.forEach(themeName => - parsedKeycloakThemeJson.themes.push({ - "name": themeName, - "types": Object.entries(implementedThemeTypes) - .filter(([, isImplemented]) => isImplemented) - .map(([themeType]) => themeType) - }) - ); + parsedKeycloakThemeJson.themes.push({ + "name": themeName, + "types": Object.entries(implementedThemeTypes) + .filter(([, isImplemented]) => isImplemented) + .map(([themeType]) => themeType) + }); account_specific_extra_work: { if (!implementedThemeTypes.account) { @@ -269,4 +266,6 @@ export async function generateTheme(params: { fs.writeFileSync(keycloakThemeJsonFilePath, Buffer.from(JSON.stringify(parsedKeycloakThemeJson, null, 2), "utf8")); } + + return { implementedThemeTypes }; } diff --git a/src/bin/keycloakify/generateThemeVariants.ts b/src/bin/keycloakify/generateThemeVariants.ts new file mode 100644 index 00000000..6ec2b86f --- /dev/null +++ b/src/bin/keycloakify/generateThemeVariants.ts @@ -0,0 +1,12 @@ +import type { ThemeType } from "../constants"; + +export function generateThemeVariations(params: { + themeName: string; + themeVariantName: string; + implementedThemeTypes: Record; + buildOptions: { + keycloakifyBuildDirPath: string; + }; +}) { + //TODO: Implement +} diff --git a/src/bin/keycloakify/keycloakify.ts b/src/bin/keycloakify/keycloakify.ts index eb2be7e1..8e2c05da 100644 --- a/src/bin/keycloakify/keycloakify.ts +++ b/src/bin/keycloakify/keycloakify.ts @@ -10,6 +10,8 @@ import { getThemeSrcDirPath } from "../getThemeSrcDirPath"; import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath"; import { readThisNpmProjectVersion } from "../tools/readThisNpmProjectVersion"; import { keycloakifyBuildOptionsForPostPostBuildScriptEnvName } from "../constants"; +import { buildJars } from "./buildJars"; +import { generateThemeVariations } from "./generateThemeVariants"; export async function main() { const buildOptions = readBuildOptions({ @@ -21,12 +23,21 @@ export async function main() { const { themeSrcDirPath } = getThemeSrcDirPath({ "reactAppRootDirPath": buildOptions.reactAppRootDirPath }); - for (const themeName of buildOptions.themeNames) { - await generateTheme({ + const [themeName, ...themeVariantNames] = buildOptions.themeNames; + + const { implementedThemeTypes } = await generateTheme({ + themeName, + themeSrcDirPath, + "keycloakifySrcDirPath": pathJoin(getThisCodebaseRootDirPath(), "src"), + "keycloakifyVersion": readThisNpmProjectVersion(), + buildOptions + }); + + for (const themeVariantName of themeVariantNames) { + generateThemeVariations({ themeName, - themeSrcDirPath, - "keycloakifySrcDirPath": pathJoin(getThisCodebaseRootDirPath(), "src"), - "keycloakifyVersion": readThisNpmProjectVersion(), + themeVariantName, + implementedThemeTypes, buildOptions }); } @@ -37,16 +48,6 @@ export async function main() { fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8")); } - const containerKeycloakVersion = "24.0.4"; - - const jarFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`); - - generateStartKeycloakTestingContainer({ - "keycloakVersion": containerKeycloakVersion, - jarFilePath, - buildOptions - }); - fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8")); run_post_build_script: { @@ -63,27 +64,24 @@ export async function main() { }); } - create_jar: { - if (!buildOptions.doCreateJar) { - break create_jar; - } + const { lastJarFileBasename } = await buildJars({ + "doImplementAccountTheme": implementedThemeTypes.account, + buildOptions + }); - child_process.execSync("mvn clean install", { "cwd": buildOptions.keycloakifyBuildDirPath }); - } + generateStartKeycloakTestingContainer({ + "jarFilePath": pathJoin(buildOptions.keycloakifyBuildDirPath, lastJarFileBasename), + buildOptions + }); logger.log( [ + `✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathJoin( + pathRelative(buildOptions.reactAppRootDirPath, buildOptions.keycloakifyBuildDirPath), + "keycloak-theme-for-kc-*.jar" + )}`, "", - ...(!buildOptions.doCreateJar - ? [] - : [ - `✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathRelative( - buildOptions.reactAppRootDirPath, - jarFilePath - )} 🚀` - ]), - "", - `To test your theme locally you can spin up a Keycloak ${containerKeycloakVersion} container image with the theme pre loaded by running:`, + `To test your theme locally you can spin up a Keycloak container image with the theme pre loaded by running:`, "", `👉 $ .${pathSep}${pathRelative( buildOptions.reactAppRootDirPath,