diff --git a/src/bin/keycloakify/buildJars/buildJars.ts b/src/bin/keycloakify/buildJars/buildJars.ts index 47639e6c..19fef589 100644 --- a/src/bin/keycloakify/buildJars/buildJars.ts +++ b/src/bin/keycloakify/buildJars/buildJars.ts @@ -4,6 +4,7 @@ import { keycloakAccountV1Versions, keycloakThemeAdditionalInfoExtensionVersions import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar"; import { buildJar, BuildOptionsLike as BuildOptionsLike_buildJar } from "./buildJar"; import type { BuildOptions } from "../../shared/buildOptions"; +import { getJarFileBasename } from "./getJarFileBasename"; export type BuildOptionsLike = BuildOptionsLike_buildJar & { keycloakifyBuildDirPath: string; @@ -11,14 +12,9 @@ export type BuildOptionsLike = BuildOptionsLike_buildJar & { assert(); -export async function buildJars(params: { - doesImplementAccountTheme: boolean; - buildOptions: BuildOptionsLike; -}): Promise<{ lastJarFileBasename: string }> { +export async function buildJars(params: { doesImplementAccountTheme: boolean; buildOptions: BuildOptionsLike }): Promise { const { doesImplementAccountTheme, buildOptions } = params; - let lastJarFileBasename: string | undefined = undefined; - await Promise.all( keycloakAccountV1Versions .map(keycloakAccountV1Version => @@ -38,11 +34,12 @@ export async function buildJars(params: { }) .filter(exclude(undefined)) .map(({ keycloakThemeAdditionalInfoExtensionVersion, keycloakVersionRange }) => { - const jarFileBasename = `keycloak-theme-for-kc-${keycloakVersionRange}.jar`; + const { jarFileBasename } = getJarFileBasename({ keycloakVersionRange }); - lastJarFileBasename = jarFileBasename; - - return { keycloakThemeAdditionalInfoExtensionVersion, jarFileBasename }; + return { + keycloakThemeAdditionalInfoExtensionVersion, + jarFileBasename + }; }) .map(({ keycloakThemeAdditionalInfoExtensionVersion, jarFileBasename }) => buildJar({ @@ -55,8 +52,4 @@ export async function buildJars(params: { ) .flat() ); - - assert(lastJarFileBasename !== undefined); - - return { lastJarFileBasename }; } diff --git a/src/bin/keycloakify/buildJars/getJarFileBasename.ts b/src/bin/keycloakify/buildJars/getJarFileBasename.ts new file mode 100644 index 00000000..ab59605a --- /dev/null +++ b/src/bin/keycloakify/buildJars/getJarFileBasename.ts @@ -0,0 +1,9 @@ +import type { KeycloakVersionRange } from "./getKeycloakVersionRangeForJar"; + +export function getJarFileBasename(params: { keycloakVersionRange: KeycloakVersionRange }) { + const { keycloakVersionRange } = params; + + const jarFileBasename = `keycloak-theme-for-kc-${keycloakVersionRange}.jar`; + + return { jarFileBasename }; +} diff --git a/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts b/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts index 5984315c..6462fdb9 100644 --- a/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts +++ b/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts @@ -1,37 +1,83 @@ import { assert, type Equals } from "tsafe/assert"; import type { KeycloakAccountV1Version, KeycloakThemeAdditionalInfoExtensionVersion } from "./extensionVersions"; +import { id } from "tsafe/id"; + +export type KeycloakVersionRange = KeycloakVersionRange.WithAccountTheme | KeycloakVersionRange.WithoutAccountTheme; + +export namespace KeycloakVersionRange { + export type WithoutAccountTheme = "21-and-below" | "22-and-above"; + + export type WithAccountTheme = "21-and-below" | "23" | "24-and-above"; +} export function getKeycloakVersionRangeForJar(params: { doesImplementAccountTheme: boolean; keycloakAccountV1Version: KeycloakAccountV1Version; keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion; -}): string | undefined { +}): KeycloakVersionRange | undefined { const { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, doesImplementAccountTheme } = params; - switch (keycloakAccountV1Version) { - case null: - switch (keycloakThemeAdditionalInfoExtensionVersion) { - case null: - return doesImplementAccountTheme ? "21-and-below" : "21-and-below"; - case "1.1.5": - return doesImplementAccountTheme ? undefined : "22-and-above"; - } - assert>(false); - case "0.3": - switch (keycloakThemeAdditionalInfoExtensionVersion) { - case null: - return doesImplementAccountTheme ? undefined : undefined; - case "1.1.5": - return doesImplementAccountTheme ? "23" : undefined; - } - assert>(false); - case "0.4": - switch (keycloakThemeAdditionalInfoExtensionVersion) { - case null: - return doesImplementAccountTheme ? undefined : undefined; - case "1.1.5": - return doesImplementAccountTheme ? "24-and-above" : undefined; - } - assert>(false); + if (doesImplementAccountTheme) { + return id( + (() => { + switch (keycloakAccountV1Version) { + case null: + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return "21-and-below" as const; + case "1.1.5": + return undefined; + } + assert>(false); + case "0.3": + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return undefined; + case "1.1.5": + return "23" as const; + } + assert>(false); + case "0.4": + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return undefined; + case "1.1.5": + return "24-and-above" as const; + } + assert>(false); + } + })() + ); + } else { + return id( + (() => { + switch (keycloakAccountV1Version) { + case null: + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return "21-and-below"; + case "1.1.5": + return "22-and-above"; + } + assert>(false); + case "0.3": + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return undefined; + case "1.1.5": + return undefined; + } + assert>(false); + case "0.4": + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return undefined; + case "1.1.5": + return undefined; + } + assert>(false); + } + })() + ); } } diff --git a/src/bin/keycloakify/generateTheme/generateSrcMainResources.ts b/src/bin/keycloakify/generateTheme/generateSrcMainResources.ts index f69958fc..927abd3e 100644 --- a/src/bin/keycloakify/generateTheme/generateSrcMainResources.ts +++ b/src/bin/keycloakify/generateTheme/generateSrcMainResources.ts @@ -43,7 +43,7 @@ export async function generateSrcMainResources(params: { buildOptions: BuildOptionsLike; keycloakifyVersion: string; srcMainResourcesDirPath: string; -}): Promise<{ doesImplementAccountTheme: boolean }> { +}): Promise { const { themeName, themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion, srcMainResourcesDirPath } = params; const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => { @@ -75,7 +75,7 @@ export async function generateSrcMainResources(params: { rmSync(destDirPath, { "recursive": true, "force": true }); if (themeType === "account" && implementedThemeTypes.login) { - // NOTE: We prevend doing it twice, it has been done for the login theme. + // NOTE: We prevent doing it twice, it has been done for the login theme. transformCodebase({ "srcDirPath": pathJoin( @@ -263,6 +263,4 @@ export async function generateSrcMainResources(params: { fs.writeFileSync(keycloakThemeJsonFilePath, Buffer.from(JSON.stringify(parsedKeycloakThemeJson, null, 2), "utf8")); } - - return { "doesImplementAccountTheme": implementedThemeTypes.account }; } diff --git a/src/bin/keycloakify/generateTheme/generateTheme.ts b/src/bin/keycloakify/generateTheme/generateTheme.ts index c6d4411e..a41bf802 100644 --- a/src/bin/keycloakify/generateTheme/generateTheme.ts +++ b/src/bin/keycloakify/generateTheme/generateTheme.ts @@ -16,14 +16,14 @@ export async function generateTheme(params: { keycloakifySrcDirPath: string; buildOptions: BuildOptionsLike; keycloakifyVersion: string; -}): Promise<{ doesImplementAccountTheme: boolean }> { +}): Promise { const { themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion } = params; const [themeName, ...themeVariantNames] = buildOptions.themeNames; const srcMainResourcesDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources"); - const { doesImplementAccountTheme } = await generateSrcMainResources({ + await generateSrcMainResources({ themeName, srcMainResourcesDirPath, themeSrcDirPath, @@ -39,6 +39,4 @@ export async function generateTheme(params: { srcMainResourcesDirPath }); } - - return { doesImplementAccountTheme }; } diff --git a/src/bin/keycloakify/keycloakify.ts b/src/bin/keycloakify/keycloakify.ts index 2867595b..de6459fb 100644 --- a/src/bin/keycloakify/keycloakify.ts +++ b/src/bin/keycloakify/keycloakify.ts @@ -1,7 +1,6 @@ import { generateTheme } from "./generateTheme"; import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path"; import * as child_process from "child_process"; -import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer"; import * as fs from "fs"; import { readBuildOptions } from "../shared/buildOptions"; import { getLogger } from "../tools/logger"; @@ -18,6 +17,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions }) const buildOptions = readBuildOptions({ cliCommandOptions }); const logger = getLogger({ "isSilent": buildOptions.isSilent }); + logger.log("🔏 Building the keycloak theme...⌚"); const { themeSrcDirPath } = getThemeSrcDirPath({ "reactAppRootDirPath": buildOptions.reactAppRootDirPath }); @@ -30,7 +30,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions }) fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8")); } - const { doesImplementAccountTheme } = await generateTheme({ + await generateTheme({ themeSrcDirPath, "keycloakifySrcDirPath": pathJoin(getThisCodebaseRootDirPath(), "src"), "keycloakifyVersion": readThisNpmProjectVersion(), @@ -51,51 +51,15 @@ export async function command(params: { cliCommandOptions: CliCommandOptions }) }); } - const { lastJarFileBasename } = await buildJars({ - doesImplementAccountTheme, - buildOptions - }); - - generateStartKeycloakTestingContainer({ - "jarFilePath": pathJoin(buildOptions.keycloakifyBuildDirPath, lastJarFileBasename), + await buildJars({ doesImplementAccountTheme, buildOptions }); logger.log( - [ - `✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathJoin( - pathRelative(buildOptions.reactAppRootDirPath, buildOptions.keycloakifyBuildDirPath), - "keycloak-theme-for-kc-*.jar" - )}`, - "", - `To test your theme locally you can spin up a Keycloak container image with the theme pre loaded by running:`, - "", - `👉 $ .${pathSep}${pathRelative( - buildOptions.reactAppRootDirPath, - pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename) - )} 👈`, - ``, - `Once your container is up and running: `, - "- Log into the admin console 👉 http://localhost:8080/admin username: admin, password: admin 👈", - `- Create a realm: Master -> AddRealm -> Name: myrealm`, - `- Enable registration: Realm settings -> Login tab -> User registration: on`, - `- Enable the Account theme (optional): Realm settings -> Themes tab -> Account theme: ${buildOptions.themeNames[0]}`, - ` Clients -> account -> Login theme: ${buildOptions.themeNames[0]}`, - `- Enable the email theme (optional): Realm settings -> Themes tab -> Email theme: ${buildOptions.themeNames[0]} (option will appear only if you have ran npx initialize-email-theme)`, - `- Create a client Clients -> Create -> Client ID: myclient`, - ` Root URL: https://www.keycloak.org/app/`, - ` Valid redirect URIs: https://www.keycloak.org/app* http://localhost* (localhost is optional)`, - ` Valid post logout redirect URIs: https://www.keycloak.org/app* http://localhost*`, - ` Web origins: *`, - ` Login Theme: ${buildOptions.themeNames[0]}`, - ` Save (button at the bottom of the page)`, - ``, - `- Go to 👉 https://www.keycloak.org/app/ 👈 Click "Save" then "Sign in". You should see your login page`, - `- Got to 👉 http://localhost:8080/realms/myrealm/account 👈 to see your account theme`, - ``, - `Video tutorial: https://youtu.be/WMyGZNHQkjU`, - `` - ].join("\n") + `✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathJoin( + pathRelative(process.cwd(), buildOptions.keycloakifyBuildDirPath), + "keycloak-theme-for-kc-*.jar" + )}` ); } diff --git a/src/bin/main.ts b/src/bin/main.ts index fe720082..a5c0b9cc 100644 --- a/src/bin/main.ts +++ b/src/bin/main.ts @@ -87,6 +87,18 @@ program } }); +program + .command({ + "name": "start-keycloak-container", + "description": "Spin up a Keycloak container with the theme preloaded and the realm pre configured." + }) + .task({ + "handler": async cliCommandOptions => { + const { command } = await import("./start-keycloak-container"); + return command({ cliCommandOptions }); + } + }); + // Fallback to build command if no command is provided { const [, , ...rest] = process.argv; diff --git a/src/bin/start-keycloak-container.ts b/src/bin/start-keycloak-container.ts new file mode 100644 index 00000000..535f2bb3 --- /dev/null +++ b/src/bin/start-keycloak-container.ts @@ -0,0 +1,10 @@ +import { readBuildOptions } from "./shared/buildOptions"; +import type { CliCommandOptions } from "./main"; + +export async function command(params: { cliCommandOptions: CliCommandOptions }) { + const { cliCommandOptions } = params; + + const buildOptions = readBuildOptions({ cliCommandOptions }); + + console.log("TODO", buildOptions); +}